From 456118f4416e53f17ee2117d4cdef9bb893c8c2e Mon Sep 17 00:00:00 2001 From: smartcontracts Date: Tue, 25 Feb 2025 09:14:26 -0600 Subject: [PATCH 001/130] feat: add IRI security review by OffbeatLabs (#14319) Adds the Incident Response Improvements security review by offbeat labs. --- .../2025_01-IRI-OffbeatLabs.pdf | Bin 0 -> 397118 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/security-reviews/2025_01-IRI-OffbeatLabs.pdf diff --git a/docs/security-reviews/2025_01-IRI-OffbeatLabs.pdf b/docs/security-reviews/2025_01-IRI-OffbeatLabs.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e791d4ce84f30f1ab3f8af9311589762f48bcb9f GIT binary patch literal 397118 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

Gq`2b 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�iER97~INf|(0YC+6?D<`g6WYI=?k`j|$kR~H- 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 R8JXy?fQ4ZyWnqvUc=-_DmGlu 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

  • c1`3QzhqTf)4B%G-DsW0XM8ndaRD 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:`
    ${i}
    `).join("").replace(Q7,(i,a)=>r.renderToString(a,{throwOnError:!0,displayMode:!0,output:n}).replace(/\n/g," ").replace(//g,""))},"renderKatex"),We={getRows:Cbe,sanitizeText:qr,sanitizeTextOrArray:_be,hasBreaks:Lbe,splitBreaks:Dbe,lineBreakRegex:Qf,removeScript:A$,getUrl:Nbe,evaluate:yr,getMax:Mbe,getMin:Ibe}});var Bbe,Fbe,Sr,Lo,Yn=R(()=>{"use strict";ut();Bbe=o(function(t,e){for(let r of e)t.attr(r[0],r[1])},"d3Attrs"),Fbe=o(function(t,e,r){let n=new Map;return r?(n.set("width","100%"),n.set("style",`max-width: ${e}px;`)):(n.set("height",t),n.set("width",e)),n},"calculateSvgSizeAttrs"),Sr=o(function(t,e,r,n){let i=Fbe(e,r,n);Bbe(t,i)},"configureSvgSize"),Lo=o(function(t,e,r,n){let i=e.node().getBBox(),a=i.width,s=i.height;V.info(`SVG bounds: ${a}x${s}`,i);let l=0,u=0;V.info(`Graph bounds: ${l}x${u}`,t),l=a+r*2,u=s+r*2,V.info(`Calculated bounds: ${l}x${u}`),Sr(e,u,l,n);let h=`${i.x-r} ${i.y-r} ${i.width+2*r} ${i.height+2*r}`;e.attr("viewBox",h)},"setupGraphViewbox")});var S4,zbe,L$,D$,Z7=R(()=>{"use strict";ut();S4={},zbe=o((t,e,r)=>{let n="";return t in S4&&S4[t]?n=S4[t](r):V.warn(`No theme found for ${t}`),` & { + font-family: ${r.fontFamily}; + font-size: ${r.fontSize}; + fill: ${r.textColor} + } + + /* Classes common for multiple diagrams */ + + & .error-icon { + fill: ${r.errorBkgColor}; + } + & .error-text { + fill: ${r.errorTextColor}; + stroke: ${r.errorTextColor}; + } + + & .edge-thickness-normal { + stroke-width: 1px; + } + & .edge-thickness-thick { + stroke-width: 3.5px + } + & .edge-pattern-solid { + stroke-dasharray: 0; + } + & .edge-thickness-invisible { + stroke-width: 0; + fill: none; + } + & .edge-pattern-dashed{ + stroke-dasharray: 3; + } + .edge-pattern-dotted { + stroke-dasharray: 2; + } + + & .marker { + fill: ${r.lineColor}; + stroke: ${r.lineColor}; + } + & .marker.cross { + stroke: ${r.lineColor}; + } + + & svg { + font-family: ${r.fontFamily}; + font-size: ${r.fontSize}; + } + & p { + margin: 0 + } + + ${n} + + ${e} +`},"getStyles"),L$=o((t,e)=>{e!==void 0&&(S4[t]=e)},"addStylesForDiagram"),D$=zbe});var ly={};hr(ly,{clear:()=>vr,getAccDescription:()=>Lr,getAccTitle:()=>Ar,getDiagramTitle:()=>Xr,setAccDescription:()=>_r,setAccTitle:()=>kr,setDiagramTitle:()=>nn});var J7,eS,tS,rS,vr,kr,Ar,_r,Lr,nn,Xr,bi=R(()=>{"use strict";rr();qs();J7="",eS="",tS="",rS=o(t=>qr(t,Or()),"sanitizeText"),vr=o(()=>{J7="",tS="",eS=""},"clear"),kr=o(t=>{J7=rS(t).replace(/^\s+/g,"")},"setAccTitle"),Ar=o(()=>J7,"getAccTitle"),_r=o(t=>{tS=rS(t).replace(/\n\s+/g,` +`)},"setAccDescription"),Lr=o(()=>tS,"getAccDescription"),nn=o(t=>{eS=rS(t)},"setDiagramTitle"),Xr=o(()=>eS,"getDiagramTitle")});var R$,Gbe,de,iS,_4,$be,aS,Vbe,A4,Jf,cy,nS,_t=R(()=>{"use strict";Hf();ut();qs();rr();Yn();Z7();bi();R$=V,Gbe=$1,de=Or,iS=Zb,_4=uh,$be=o(t=>qr(t,de()),"sanitizeText"),aS=Lo,Vbe=o(()=>ly,"getCommonDb"),A4={},Jf=o((t,e,r)=>{A4[t]&&R$.warn(`Diagram with id ${t} already registered. Overwriting.`),A4[t]=e,r&&$C(t,r),L$(t,e.styles),e.injectUtils?.(R$,Gbe,de,$be,aS,Vbe(),()=>{})},"registerDiagram"),cy=o(t=>{if(t in A4)return A4[t];throw new nS(t)},"getDiagram"),nS=class extends Error{static{o(this,"DiagramNotFoundError")}constructor(e){super(`Diagram ${e} not found.`)}}});var ul,vh,ja,cl,nc,uy,sS,oS,L4,D4,N$,Ube,Hbe,Ybe,Wbe,qbe,Xbe,jbe,Kbe,Qbe,Zbe,Jbe,e4e,t4e,r4e,n4e,i4e,a4e,M$,s4e,o4e,I$,l4e,c4e,u4e,h4e,xh,f4e,d4e,p4e,m4e,g4e,hy,lS=R(()=>{"use strict";_t();rr();bi();ul=[],vh=[""],ja="global",cl="",nc=[{alias:"global",label:{text:"global"},type:{text:"global"},tags:null,link:null,parentBoundary:""}],uy=[],sS="",oS=!1,L4=4,D4=2,Ube=o(function(){return N$},"getC4Type"),Hbe=o(function(t){N$=qr(t,de())},"setC4Type"),Ybe=o(function(t,e,r,n,i,a,s,l,u){if(t==null||e===void 0||e===null||r===void 0||r===null||n===void 0||n===null)return;let h={},f=uy.find(d=>d.from===e&&d.to===r);if(f?h=f:uy.push(h),h.type=t,h.from=e,h.to=r,h.label={text:n},i==null)h.techn={text:""};else if(typeof i=="object"){let[d,p]=Object.entries(i)[0];h[d]={text:p}}else h.techn={text:i};if(a==null)h.descr={text:""};else if(typeof a=="object"){let[d,p]=Object.entries(a)[0];h[d]={text:p}}else h.descr={text:a};if(typeof s=="object"){let[d,p]=Object.entries(s)[0];h[d]=p}else h.sprite=s;if(typeof l=="object"){let[d,p]=Object.entries(l)[0];h[d]=p}else h.tags=l;if(typeof u=="object"){let[d,p]=Object.entries(u)[0];h[d]=p}else h.link=u;h.wrap=xh()},"addRel"),Wbe=o(function(t,e,r,n,i,a,s){if(e===null||r===null)return;let l={},u=ul.find(h=>h.alias===e);if(u&&e===u.alias?l=u:(l.alias=e,ul.push(l)),r==null?l.label={text:""}:l.label={text:r},n==null)l.descr={text:""};else if(typeof n=="object"){let[h,f]=Object.entries(n)[0];l[h]={text:f}}else l.descr={text:n};if(typeof i=="object"){let[h,f]=Object.entries(i)[0];l[h]=f}else l.sprite=i;if(typeof a=="object"){let[h,f]=Object.entries(a)[0];l[h]=f}else l.tags=a;if(typeof s=="object"){let[h,f]=Object.entries(s)[0];l[h]=f}else l.link=s;l.typeC4Shape={text:t},l.parentBoundary=ja,l.wrap=xh()},"addPersonOrSystem"),qbe=o(function(t,e,r,n,i,a,s,l){if(e===null||r===null)return;let u={},h=ul.find(f=>f.alias===e);if(h&&e===h.alias?u=h:(u.alias=e,ul.push(u)),r==null?u.label={text:""}:u.label={text:r},n==null)u.techn={text:""};else if(typeof n=="object"){let[f,d]=Object.entries(n)[0];u[f]={text:d}}else u.techn={text:n};if(i==null)u.descr={text:""};else if(typeof i=="object"){let[f,d]=Object.entries(i)[0];u[f]={text:d}}else u.descr={text:i};if(typeof a=="object"){let[f,d]=Object.entries(a)[0];u[f]=d}else u.sprite=a;if(typeof s=="object"){let[f,d]=Object.entries(s)[0];u[f]=d}else u.tags=s;if(typeof l=="object"){let[f,d]=Object.entries(l)[0];u[f]=d}else u.link=l;u.wrap=xh(),u.typeC4Shape={text:t},u.parentBoundary=ja},"addContainer"),Xbe=o(function(t,e,r,n,i,a,s,l){if(e===null||r===null)return;let u={},h=ul.find(f=>f.alias===e);if(h&&e===h.alias?u=h:(u.alias=e,ul.push(u)),r==null?u.label={text:""}:u.label={text:r},n==null)u.techn={text:""};else if(typeof n=="object"){let[f,d]=Object.entries(n)[0];u[f]={text:d}}else u.techn={text:n};if(i==null)u.descr={text:""};else if(typeof i=="object"){let[f,d]=Object.entries(i)[0];u[f]={text:d}}else u.descr={text:i};if(typeof a=="object"){let[f,d]=Object.entries(a)[0];u[f]=d}else u.sprite=a;if(typeof s=="object"){let[f,d]=Object.entries(s)[0];u[f]=d}else u.tags=s;if(typeof l=="object"){let[f,d]=Object.entries(l)[0];u[f]=d}else u.link=l;u.wrap=xh(),u.typeC4Shape={text:t},u.parentBoundary=ja},"addComponent"),jbe=o(function(t,e,r,n,i){if(t===null||e===null)return;let a={},s=nc.find(l=>l.alias===t);if(s&&t===s.alias?a=s:(a.alias=t,nc.push(a)),e==null?a.label={text:""}:a.label={text:e},r==null)a.type={text:"system"};else if(typeof r=="object"){let[l,u]=Object.entries(r)[0];a[l]={text:u}}else a.type={text:r};if(typeof n=="object"){let[l,u]=Object.entries(n)[0];a[l]=u}else a.tags=n;if(typeof i=="object"){let[l,u]=Object.entries(i)[0];a[l]=u}else a.link=i;a.parentBoundary=ja,a.wrap=xh(),cl=ja,ja=t,vh.push(cl)},"addPersonOrSystemBoundary"),Kbe=o(function(t,e,r,n,i){if(t===null||e===null)return;let a={},s=nc.find(l=>l.alias===t);if(s&&t===s.alias?a=s:(a.alias=t,nc.push(a)),e==null?a.label={text:""}:a.label={text:e},r==null)a.type={text:"container"};else if(typeof r=="object"){let[l,u]=Object.entries(r)[0];a[l]={text:u}}else a.type={text:r};if(typeof n=="object"){let[l,u]=Object.entries(n)[0];a[l]=u}else a.tags=n;if(typeof i=="object"){let[l,u]=Object.entries(i)[0];a[l]=u}else a.link=i;a.parentBoundary=ja,a.wrap=xh(),cl=ja,ja=t,vh.push(cl)},"addContainerBoundary"),Qbe=o(function(t,e,r,n,i,a,s,l){if(e===null||r===null)return;let u={},h=nc.find(f=>f.alias===e);if(h&&e===h.alias?u=h:(u.alias=e,nc.push(u)),r==null?u.label={text:""}:u.label={text:r},n==null)u.type={text:"node"};else if(typeof n=="object"){let[f,d]=Object.entries(n)[0];u[f]={text:d}}else u.type={text:n};if(i==null)u.descr={text:""};else if(typeof i=="object"){let[f,d]=Object.entries(i)[0];u[f]={text:d}}else u.descr={text:i};if(typeof s=="object"){let[f,d]=Object.entries(s)[0];u[f]=d}else u.tags=s;if(typeof l=="object"){let[f,d]=Object.entries(l)[0];u[f]=d}else u.link=l;u.nodeType=t,u.parentBoundary=ja,u.wrap=xh(),cl=ja,ja=e,vh.push(cl)},"addDeploymentNode"),Zbe=o(function(){ja=cl,vh.pop(),cl=vh.pop(),vh.push(cl)},"popBoundaryParseStack"),Jbe=o(function(t,e,r,n,i,a,s,l,u,h,f){let d=ul.find(p=>p.alias===e);if(!(d===void 0&&(d=nc.find(p=>p.alias===e),d===void 0))){if(r!=null)if(typeof r=="object"){let[p,m]=Object.entries(r)[0];d[p]=m}else d.bgColor=r;if(n!=null)if(typeof n=="object"){let[p,m]=Object.entries(n)[0];d[p]=m}else d.fontColor=n;if(i!=null)if(typeof i=="object"){let[p,m]=Object.entries(i)[0];d[p]=m}else d.borderColor=i;if(a!=null)if(typeof a=="object"){let[p,m]=Object.entries(a)[0];d[p]=m}else d.shadowing=a;if(s!=null)if(typeof s=="object"){let[p,m]=Object.entries(s)[0];d[p]=m}else d.shape=s;if(l!=null)if(typeof l=="object"){let[p,m]=Object.entries(l)[0];d[p]=m}else d.sprite=l;if(u!=null)if(typeof u=="object"){let[p,m]=Object.entries(u)[0];d[p]=m}else d.techn=u;if(h!=null)if(typeof h=="object"){let[p,m]=Object.entries(h)[0];d[p]=m}else d.legendText=h;if(f!=null)if(typeof f=="object"){let[p,m]=Object.entries(f)[0];d[p]=m}else d.legendSprite=f}},"updateElStyle"),e4e=o(function(t,e,r,n,i,a,s){let l=uy.find(u=>u.from===e&&u.to===r);if(l!==void 0){if(n!=null)if(typeof n=="object"){let[u,h]=Object.entries(n)[0];l[u]=h}else l.textColor=n;if(i!=null)if(typeof i=="object"){let[u,h]=Object.entries(i)[0];l[u]=h}else l.lineColor=i;if(a!=null)if(typeof a=="object"){let[u,h]=Object.entries(a)[0];l[u]=parseInt(h)}else l.offsetX=parseInt(a);if(s!=null)if(typeof s=="object"){let[u,h]=Object.entries(s)[0];l[u]=parseInt(h)}else l.offsetY=parseInt(s)}},"updateRelStyle"),t4e=o(function(t,e,r){let n=L4,i=D4;if(typeof e=="object"){let a=Object.values(e)[0];n=parseInt(a)}else n=parseInt(e);if(typeof r=="object"){let a=Object.values(r)[0];i=parseInt(a)}else i=parseInt(r);n>=1&&(L4=n),i>=1&&(D4=i)},"updateLayoutConfig"),r4e=o(function(){return L4},"getC4ShapeInRow"),n4e=o(function(){return D4},"getC4BoundaryInRow"),i4e=o(function(){return ja},"getCurrentBoundaryParse"),a4e=o(function(){return cl},"getParentBoundaryParse"),M$=o(function(t){return t==null?ul:ul.filter(e=>e.parentBoundary===t)},"getC4ShapeArray"),s4e=o(function(t){return ul.find(e=>e.alias===t)},"getC4Shape"),o4e=o(function(t){return Object.keys(M$(t))},"getC4ShapeKeys"),I$=o(function(t){return t==null?nc:nc.filter(e=>e.parentBoundary===t)},"getBoundaries"),l4e=I$,c4e=o(function(){return uy},"getRels"),u4e=o(function(){return sS},"getTitle"),h4e=o(function(t){oS=t},"setWrap"),xh=o(function(){return oS},"autoWrap"),f4e=o(function(){ul=[],nc=[{alias:"global",label:{text:"global"},type:{text:"global"},tags:null,link:null,parentBoundary:""}],cl="",ja="global",vh=[""],uy=[],vh=[""],sS="",oS=!1,L4=4,D4=2},"clear"),d4e={SOLID:0,DOTTED:1,NOTE:2,SOLID_CROSS:3,DOTTED_CROSS:4,SOLID_OPEN:5,DOTTED_OPEN:6,LOOP_START:10,LOOP_END:11,ALT_START:12,ALT_ELSE:13,ALT_END:14,OPT_START:15,OPT_END:16,ACTIVE_START:17,ACTIVE_END:18,PAR_START:19,PAR_AND:20,PAR_END:21,RECT_START:22,RECT_END:23,SOLID_POINT:24,DOTTED_POINT:25},p4e={FILLED:0,OPEN:1},m4e={LEFTOF:0,RIGHTOF:1,OVER:2},g4e=o(function(t){sS=qr(t,de())},"setTitle"),hy={addPersonOrSystem:Wbe,addPersonOrSystemBoundary:jbe,addContainer:qbe,addContainerBoundary:Kbe,addComponent:Xbe,addDeploymentNode:Qbe,popBoundaryParseStack:Zbe,addRel:Ybe,updateElStyle:Jbe,updateRelStyle:e4e,updateLayoutConfig:t4e,autoWrap:xh,setWrap:h4e,getC4ShapeArray:M$,getC4Shape:s4e,getC4ShapeKeys:o4e,getBoundaries:I$,getBoundarys:l4e,getCurrentBoundaryParse:i4e,getParentBoundaryParse:a4e,getRels:c4e,getTitle:u4e,getC4Type:Ube,getC4ShapeInRow:r4e,getC4BoundaryInRow:n4e,setAccTitle:kr,getAccTitle:Ar,getAccDescription:Lr,setAccDescription:_r,getConfig:o(()=>de().c4,"getConfig"),clear:f4e,LINETYPE:d4e,ARROWTYPE:p4e,PLACEMENT:m4e,setTitle:g4e,setC4Type:Hbe}});function ed(t,e){return t==null||e==null?NaN:te?1:t>=e?0:NaN}var cS=R(()=>{"use strict";o(ed,"ascending")});function uS(t,e){return t==null||e==null?NaN:et?1:e>=t?0:NaN}var O$=R(()=>{"use strict";o(uS,"descending")});function td(t){let e,r,n;t.length!==2?(e=ed,r=o((l,u)=>ed(t(l),u),"compare2"),n=o((l,u)=>t(l)-u,"delta")):(e=t===ed||t===uS?t:y4e,r=t,n=t);function i(l,u,h=0,f=l.length){if(h>>1;r(l[d],u)<0?h=d+1:f=d}while(h>>1;r(l[d],u)<=0?h=d+1:f=d}while(hh&&n(l[d-1],u)>-n(l[d],u)?d-1:d}return o(s,"center"),{left:i,center:s,right:a}}function y4e(){return 0}var hS=R(()=>{"use strict";cS();O$();o(td,"bisector");o(y4e,"zero")});function fS(t){return t===null?NaN:+t}var P$=R(()=>{"use strict";o(fS,"number")});var B$,F$,v4e,x4e,dS,z$=R(()=>{"use strict";cS();hS();P$();B$=td(ed),F$=B$.right,v4e=B$.left,x4e=td(fS).center,dS=F$});function G$({_intern:t,_key:e},r){let n=e(r);return t.has(n)?t.get(n):r}function b4e({_intern:t,_key:e},r){let n=e(r);return t.has(n)?t.get(n):(t.set(n,r),r)}function w4e({_intern:t,_key:e},r){let n=e(r);return t.has(n)&&(r=t.get(n),t.delete(n)),r}function T4e(t){return t!==null&&typeof t=="object"?t.valueOf():t}var wp,$$=R(()=>{"use strict";wp=class extends Map{static{o(this,"InternMap")}constructor(e,r=T4e){if(super(),Object.defineProperties(this,{_intern:{value:new Map},_key:{value:r}}),e!=null)for(let[n,i]of e)this.set(n,i)}get(e){return super.get(G$(this,e))}has(e){return super.has(G$(this,e))}set(e,r){return super.set(b4e(this,e),r)}delete(e){return super.delete(w4e(this,e))}};o(G$,"intern_get");o(b4e,"intern_set");o(w4e,"intern_delete");o(T4e,"keyof")});function R4(t,e,r){let n=(e-t)/Math.max(0,r),i=Math.floor(Math.log10(n)),a=n/Math.pow(10,i),s=a>=k4e?10:a>=E4e?5:a>=C4e?2:1,l,u,h;return i<0?(h=Math.pow(10,-i)/s,l=Math.round(t*h),u=Math.round(e*h),l/he&&--u,h=-h):(h=Math.pow(10,i)*s,l=Math.round(t/h),u=Math.round(e/h),l*he&&--u),u0))return[];if(t===e)return[t];let n=e=i))return[];let l=a-i+1,u=new Array(l);if(n)if(s<0)for(let h=0;h{"use strict";k4e=Math.sqrt(50),E4e=Math.sqrt(10),C4e=Math.sqrt(2);o(R4,"tickSpec");o(N4,"ticks");o(fy,"tickIncrement");o(Tp,"tickStep")});function M4(t,e){let r;if(e===void 0)for(let n of t)n!=null&&(r=n)&&(r=n);else{let n=-1;for(let i of t)(i=e(i,++n,t))!=null&&(r=i)&&(r=i)}return r}var U$=R(()=>{"use strict";o(M4,"max")});function I4(t,e){let r;if(e===void 0)for(let n of t)n!=null&&(r>n||r===void 0&&n>=n)&&(r=n);else{let n=-1;for(let i of t)(i=e(i,++n,t))!=null&&(r>i||r===void 0&&i>=i)&&(r=i)}return r}var H$=R(()=>{"use strict";o(I4,"min")});function O4(t,e,r){t=+t,e=+e,r=(i=arguments.length)<2?(e=t,t=0,1):i<3?1:+r;for(var n=-1,i=Math.max(0,Math.ceil((e-t)/r))|0,a=new Array(i);++n{"use strict";o(O4,"range")});var bh=R(()=>{"use strict";z$();hS();U$();H$();Y$();V$();$$()});function pS(t){return t}var W$=R(()=>{"use strict";o(pS,"default")});function S4e(t){return"translate("+t+",0)"}function A4e(t){return"translate(0,"+t+")"}function _4e(t){return e=>+t(e)}function L4e(t,e){return e=Math.max(0,t.bandwidth()-e*2)/2,t.round()&&(e=Math.round(e)),r=>+t(r)+e}function D4e(){return!this.__axis}function X$(t,e){var r=[],n=null,i=null,a=6,s=6,l=3,u=typeof window<"u"&&window.devicePixelRatio>1?0:.5,h=t===B4||t===P4?-1:1,f=t===P4||t===mS?"x":"y",d=t===B4||t===gS?S4e:A4e;function p(m){var g=n??(e.ticks?e.ticks.apply(e,r):e.domain()),y=i??(e.tickFormat?e.tickFormat.apply(e,r):pS),v=Math.max(a,0)+l,x=e.range(),b=+x[0]+u,w=+x[x.length-1]+u,S=(e.bandwidth?L4e:_4e)(e.copy(),u),T=m.selection?m.selection():m,E=T.selectAll(".domain").data([null]),_=T.selectAll(".tick").data(g,e).order(),A=_.exit(),L=_.enter().append("g").attr("class","tick"),M=_.select("line"),N=_.select("text");E=E.merge(E.enter().insert("path",".tick").attr("class","domain").attr("stroke","currentColor")),_=_.merge(L),M=M.merge(L.append("line").attr("stroke","currentColor").attr(f+"2",h*a)),N=N.merge(L.append("text").attr("fill","currentColor").attr(f,h*v).attr("dy",t===B4?"0em":t===gS?"0.71em":"0.32em")),m!==T&&(E=E.transition(m),_=_.transition(m),M=M.transition(m),N=N.transition(m),A=A.transition(m).attr("opacity",q$).attr("transform",function(k){return isFinite(k=S(k))?d(k+u):this.getAttribute("transform")}),L.attr("opacity",q$).attr("transform",function(k){var I=this.parentNode.__axis;return d((I&&isFinite(I=I(k))?I:S(k))+u)})),A.remove(),E.attr("d",t===P4||t===mS?s?"M"+h*s+","+b+"H"+u+"V"+w+"H"+h*s:"M"+u+","+b+"V"+w:s?"M"+b+","+h*s+"V"+u+"H"+w+"V"+h*s:"M"+b+","+u+"H"+w),_.attr("opacity",1).attr("transform",function(k){return d(S(k)+u)}),M.attr(f+"2",h*a),N.attr(f,h*v).text(y),T.filter(D4e).attr("fill","none").attr("font-size",10).attr("font-family","sans-serif").attr("text-anchor",t===mS?"start":t===P4?"end":"middle"),T.each(function(){this.__axis=S})}return o(p,"axis"),p.scale=function(m){return arguments.length?(e=m,p):e},p.ticks=function(){return r=Array.from(arguments),p},p.tickArguments=function(m){return arguments.length?(r=m==null?[]:Array.from(m),p):r.slice()},p.tickValues=function(m){return arguments.length?(n=m==null?null:Array.from(m),p):n&&n.slice()},p.tickFormat=function(m){return arguments.length?(i=m,p):i},p.tickSize=function(m){return arguments.length?(a=s=+m,p):a},p.tickSizeInner=function(m){return arguments.length?(a=+m,p):a},p.tickSizeOuter=function(m){return arguments.length?(s=+m,p):s},p.tickPadding=function(m){return arguments.length?(l=+m,p):l},p.offset=function(m){return arguments.length?(u=+m,p):u},p}function yS(t){return X$(B4,t)}function vS(t){return X$(gS,t)}var B4,mS,gS,P4,q$,j$=R(()=>{"use strict";W$();B4=1,mS=2,gS=3,P4=4,q$=1e-6;o(S4e,"translateX");o(A4e,"translateY");o(_4e,"number");o(L4e,"center");o(D4e,"entering");o(X$,"axis");o(yS,"axisTop");o(vS,"axisBottom")});var K$=R(()=>{"use strict";j$()});function Z$(){for(var t=0,e=arguments.length,r={},n;t=0&&(n=r.slice(i+1),r=r.slice(0,i)),r&&!e.hasOwnProperty(r))throw new Error("unknown type: "+r);return{type:r,name:n}})}function M4e(t,e){for(var r=0,n=t.length,i;r{"use strict";R4e={value:o(()=>{},"value")};o(Z$,"dispatch");o(F4,"Dispatch");o(N4e,"parseTypenames");F4.prototype=Z$.prototype={constructor:F4,on:o(function(t,e){var r=this._,n=N4e(t+"",r),i,a=-1,s=n.length;if(arguments.length<2){for(;++a0)for(var r=new Array(i),n=0,i,a;n{"use strict";J$()});var z4,wS,TS=R(()=>{"use strict";z4="http://www.w3.org/1999/xhtml",wS={svg:"http://www.w3.org/2000/svg",xhtml:z4,xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"}});function ic(t){var e=t+="",r=e.indexOf(":");return r>=0&&(e=t.slice(0,r))!=="xmlns"&&(t=t.slice(r+1)),wS.hasOwnProperty(e)?{space:wS[e],local:t}:t}var G4=R(()=>{"use strict";TS();o(ic,"default")});function I4e(t){return function(){var e=this.ownerDocument,r=this.namespaceURI;return r===z4&&e.documentElement.namespaceURI===z4?e.createElement(t):e.createElementNS(r,t)}}function O4e(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}function dy(t){var e=ic(t);return(e.local?O4e:I4e)(e)}var kS=R(()=>{"use strict";G4();TS();o(I4e,"creatorInherit");o(O4e,"creatorFixed");o(dy,"default")});function P4e(){}function wh(t){return t==null?P4e:function(){return this.querySelector(t)}}var $4=R(()=>{"use strict";o(P4e,"none");o(wh,"default")});function ES(t){typeof t!="function"&&(t=wh(t));for(var e=this._groups,r=e.length,n=new Array(r),i=0;i{"use strict";hl();$4();o(ES,"default")});function CS(t){return t==null?[]:Array.isArray(t)?t:Array.from(t)}var tV=R(()=>{"use strict";o(CS,"array")});function B4e(){return[]}function kp(t){return t==null?B4e:function(){return this.querySelectorAll(t)}}var SS=R(()=>{"use strict";o(B4e,"empty");o(kp,"default")});function F4e(t){return function(){return CS(t.apply(this,arguments))}}function AS(t){typeof t=="function"?t=F4e(t):t=kp(t);for(var e=this._groups,r=e.length,n=[],i=[],a=0;a{"use strict";hl();tV();SS();o(F4e,"arrayAll");o(AS,"default")});function Ep(t){return function(){return this.matches(t)}}function V4(t){return function(e){return e.matches(t)}}var py=R(()=>{"use strict";o(Ep,"default");o(V4,"childMatcher")});function G4e(t){return function(){return z4e.call(this.children,t)}}function $4e(){return this.firstElementChild}function _S(t){return this.select(t==null?$4e:G4e(typeof t=="function"?t:V4(t)))}var z4e,nV=R(()=>{"use strict";py();z4e=Array.prototype.find;o(G4e,"childFind");o($4e,"childFirst");o(_S,"default")});function U4e(){return Array.from(this.children)}function H4e(t){return function(){return V4e.call(this.children,t)}}function LS(t){return this.selectAll(t==null?U4e:H4e(typeof t=="function"?t:V4(t)))}var V4e,iV=R(()=>{"use strict";py();V4e=Array.prototype.filter;o(U4e,"children");o(H4e,"childrenFilter");o(LS,"default")});function DS(t){typeof t!="function"&&(t=Ep(t));for(var e=this._groups,r=e.length,n=new Array(r),i=0;i{"use strict";hl();py();o(DS,"default")});function my(t){return new Array(t.length)}var RS=R(()=>{"use strict";o(my,"default")});function NS(){return new Zn(this._enter||this._groups.map(my),this._parents)}function gy(t,e){this.ownerDocument=t.ownerDocument,this.namespaceURI=t.namespaceURI,this._next=null,this._parent=t,this.__data__=e}var MS=R(()=>{"use strict";RS();hl();o(NS,"default");o(gy,"EnterNode");gy.prototype={constructor:gy,appendChild:o(function(t){return this._parent.insertBefore(t,this._next)},"appendChild"),insertBefore:o(function(t,e){return this._parent.insertBefore(t,e)},"insertBefore"),querySelector:o(function(t){return this._parent.querySelector(t)},"querySelector"),querySelectorAll:o(function(t){return this._parent.querySelectorAll(t)},"querySelectorAll")}});function IS(t){return function(){return t}}var sV=R(()=>{"use strict";o(IS,"default")});function Y4e(t,e,r,n,i,a){for(var s=0,l,u=e.length,h=a.length;s=w&&(w=b+1);!(T=v[w])&&++w{"use strict";hl();MS();sV();o(Y4e,"bindIndex");o(W4e,"bindKey");o(q4e,"datum");o(OS,"default");o(X4e,"arraylike")});function PS(){return new Zn(this._exit||this._groups.map(my),this._parents)}var lV=R(()=>{"use strict";RS();hl();o(PS,"default")});function BS(t,e,r){var n=this.enter(),i=this,a=this.exit();return typeof t=="function"?(n=t(n),n&&(n=n.selection())):n=n.append(t+""),e!=null&&(i=e(i),i&&(i=i.selection())),r==null?a.remove():r(a),n&&i?n.merge(i).order():i}var cV=R(()=>{"use strict";o(BS,"default")});function FS(t){for(var e=t.selection?t.selection():t,r=this._groups,n=e._groups,i=r.length,a=n.length,s=Math.min(i,a),l=new Array(i),u=0;u{"use strict";hl();o(FS,"default")});function zS(){for(var t=this._groups,e=-1,r=t.length;++e=0;)(s=n[i])&&(a&&s.compareDocumentPosition(a)^4&&a.parentNode.insertBefore(s,a),a=s);return this}var hV=R(()=>{"use strict";o(zS,"default")});function GS(t){t||(t=j4e);function e(d,p){return d&&p?t(d.__data__,p.__data__):!d-!p}o(e,"compareNode");for(var r=this._groups,n=r.length,i=new Array(n),a=0;ae?1:t>=e?0:NaN}var fV=R(()=>{"use strict";hl();o(GS,"default");o(j4e,"ascending")});function $S(){var t=arguments[0];return arguments[0]=this,t.apply(null,arguments),this}var dV=R(()=>{"use strict";o($S,"default")});function VS(){return Array.from(this)}var pV=R(()=>{"use strict";o(VS,"default")});function US(){for(var t=this._groups,e=0,r=t.length;e{"use strict";o(US,"default")});function HS(){let t=0;for(let e of this)++t;return t}var gV=R(()=>{"use strict";o(HS,"default")});function YS(){return!this.node()}var yV=R(()=>{"use strict";o(YS,"default")});function WS(t){for(var e=this._groups,r=0,n=e.length;r{"use strict";o(WS,"default")});function K4e(t){return function(){this.removeAttribute(t)}}function Q4e(t){return function(){this.removeAttributeNS(t.space,t.local)}}function Z4e(t,e){return function(){this.setAttribute(t,e)}}function J4e(t,e){return function(){this.setAttributeNS(t.space,t.local,e)}}function e3e(t,e){return function(){var r=e.apply(this,arguments);r==null?this.removeAttribute(t):this.setAttribute(t,r)}}function t3e(t,e){return function(){var r=e.apply(this,arguments);r==null?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,r)}}function qS(t,e){var r=ic(t);if(arguments.length<2){var n=this.node();return r.local?n.getAttributeNS(r.space,r.local):n.getAttribute(r)}return this.each((e==null?r.local?Q4e:K4e:typeof e=="function"?r.local?t3e:e3e:r.local?J4e:Z4e)(r,e))}var xV=R(()=>{"use strict";G4();o(K4e,"attrRemove");o(Q4e,"attrRemoveNS");o(Z4e,"attrConstant");o(J4e,"attrConstantNS");o(e3e,"attrFunction");o(t3e,"attrFunctionNS");o(qS,"default")});function yy(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView}var XS=R(()=>{"use strict";o(yy,"default")});function r3e(t){return function(){this.style.removeProperty(t)}}function n3e(t,e,r){return function(){this.style.setProperty(t,e,r)}}function i3e(t,e,r){return function(){var n=e.apply(this,arguments);n==null?this.style.removeProperty(t):this.style.setProperty(t,n,r)}}function jS(t,e,r){return arguments.length>1?this.each((e==null?r3e:typeof e=="function"?i3e:n3e)(t,e,r??"")):Th(this.node(),t)}function Th(t,e){return t.style.getPropertyValue(e)||yy(t).getComputedStyle(t,null).getPropertyValue(e)}var KS=R(()=>{"use strict";XS();o(r3e,"styleRemove");o(n3e,"styleConstant");o(i3e,"styleFunction");o(jS,"default");o(Th,"styleValue")});function a3e(t){return function(){delete this[t]}}function s3e(t,e){return function(){this[t]=e}}function o3e(t,e){return function(){var r=e.apply(this,arguments);r==null?delete this[t]:this[t]=r}}function QS(t,e){return arguments.length>1?this.each((e==null?a3e:typeof e=="function"?o3e:s3e)(t,e)):this.node()[t]}var bV=R(()=>{"use strict";o(a3e,"propertyRemove");o(s3e,"propertyConstant");o(o3e,"propertyFunction");o(QS,"default")});function wV(t){return t.trim().split(/^|\s+/)}function ZS(t){return t.classList||new TV(t)}function TV(t){this._node=t,this._names=wV(t.getAttribute("class")||"")}function kV(t,e){for(var r=ZS(t),n=-1,i=e.length;++n{"use strict";o(wV,"classArray");o(ZS,"classList");o(TV,"ClassList");TV.prototype={add:o(function(t){var e=this._names.indexOf(t);e<0&&(this._names.push(t),this._node.setAttribute("class",this._names.join(" ")))},"add"),remove:o(function(t){var e=this._names.indexOf(t);e>=0&&(this._names.splice(e,1),this._node.setAttribute("class",this._names.join(" ")))},"remove"),contains:o(function(t){return this._names.indexOf(t)>=0},"contains")};o(kV,"classedAdd");o(EV,"classedRemove");o(l3e,"classedTrue");o(c3e,"classedFalse");o(u3e,"classedFunction");o(JS,"default")});function h3e(){this.textContent=""}function f3e(t){return function(){this.textContent=t}}function d3e(t){return function(){var e=t.apply(this,arguments);this.textContent=e??""}}function eA(t){return arguments.length?this.each(t==null?h3e:(typeof t=="function"?d3e:f3e)(t)):this.node().textContent}var SV=R(()=>{"use strict";o(h3e,"textRemove");o(f3e,"textConstant");o(d3e,"textFunction");o(eA,"default")});function p3e(){this.innerHTML=""}function m3e(t){return function(){this.innerHTML=t}}function g3e(t){return function(){var e=t.apply(this,arguments);this.innerHTML=e??""}}function tA(t){return arguments.length?this.each(t==null?p3e:(typeof t=="function"?g3e:m3e)(t)):this.node().innerHTML}var AV=R(()=>{"use strict";o(p3e,"htmlRemove");o(m3e,"htmlConstant");o(g3e,"htmlFunction");o(tA,"default")});function y3e(){this.nextSibling&&this.parentNode.appendChild(this)}function rA(){return this.each(y3e)}var _V=R(()=>{"use strict";o(y3e,"raise");o(rA,"default")});function v3e(){this.previousSibling&&this.parentNode.insertBefore(this,this.parentNode.firstChild)}function nA(){return this.each(v3e)}var LV=R(()=>{"use strict";o(v3e,"lower");o(nA,"default")});function iA(t){var e=typeof t=="function"?t:dy(t);return this.select(function(){return this.appendChild(e.apply(this,arguments))})}var DV=R(()=>{"use strict";kS();o(iA,"default")});function x3e(){return null}function aA(t,e){var r=typeof t=="function"?t:dy(t),n=e==null?x3e:typeof e=="function"?e:wh(e);return this.select(function(){return this.insertBefore(r.apply(this,arguments),n.apply(this,arguments)||null)})}var RV=R(()=>{"use strict";kS();$4();o(x3e,"constantNull");o(aA,"default")});function b3e(){var t=this.parentNode;t&&t.removeChild(this)}function sA(){return this.each(b3e)}var NV=R(()=>{"use strict";o(b3e,"remove");o(sA,"default")});function w3e(){var t=this.cloneNode(!1),e=this.parentNode;return e?e.insertBefore(t,this.nextSibling):t}function T3e(){var t=this.cloneNode(!0),e=this.parentNode;return e?e.insertBefore(t,this.nextSibling):t}function oA(t){return this.select(t?T3e:w3e)}var MV=R(()=>{"use strict";o(w3e,"selection_cloneShallow");o(T3e,"selection_cloneDeep");o(oA,"default")});function lA(t){return arguments.length?this.property("__data__",t):this.node().__data__}var IV=R(()=>{"use strict";o(lA,"default")});function k3e(t){return function(e){t.call(this,e,this.__data__)}}function E3e(t){return t.trim().split(/^|\s+/).map(function(e){var r="",n=e.indexOf(".");return n>=0&&(r=e.slice(n+1),e=e.slice(0,n)),{type:e,name:r}})}function C3e(t){return function(){var e=this.__on;if(e){for(var r=0,n=-1,i=e.length,a;r{"use strict";o(k3e,"contextListener");o(E3e,"parseTypenames");o(C3e,"onRemove");o(S3e,"onAdd");o(cA,"default")});function PV(t,e,r){var n=yy(t),i=n.CustomEvent;typeof i=="function"?i=new i(e,r):(i=n.document.createEvent("Event"),r?(i.initEvent(e,r.bubbles,r.cancelable),i.detail=r.detail):i.initEvent(e,!1,!1)),t.dispatchEvent(i)}function A3e(t,e){return function(){return PV(this,t,e)}}function _3e(t,e){return function(){return PV(this,t,e.apply(this,arguments))}}function uA(t,e){return this.each((typeof e=="function"?_3e:A3e)(t,e))}var BV=R(()=>{"use strict";XS();o(PV,"dispatchEvent");o(A3e,"dispatchConstant");o(_3e,"dispatchFunction");o(uA,"default")});function*hA(){for(var t=this._groups,e=0,r=t.length;e{"use strict";o(hA,"default")});function Zn(t,e){this._groups=t,this._parents=e}function zV(){return new Zn([[document.documentElement]],fA)}function L3e(){return this}var fA,cu,hl=R(()=>{"use strict";eV();rV();nV();iV();aV();oV();MS();lV();cV();uV();hV();fV();dV();pV();mV();gV();yV();vV();xV();KS();bV();CV();SV();AV();_V();LV();DV();RV();NV();MV();IV();OV();BV();FV();fA=[null];o(Zn,"Selection");o(zV,"selection");o(L3e,"selection_selection");Zn.prototype=zV.prototype={constructor:Zn,select:ES,selectAll:AS,selectChild:_S,selectChildren:LS,filter:DS,data:OS,enter:NS,exit:PS,join:BS,merge:FS,selection:L3e,order:zS,sort:GS,call:$S,nodes:VS,node:US,size:HS,empty:YS,each:WS,attr:qS,style:jS,property:QS,classed:JS,text:eA,html:tA,raise:rA,lower:nA,append:iA,insert:aA,remove:sA,clone:oA,datum:lA,on:cA,dispatch:uA,[Symbol.iterator]:hA};cu=zV});function $e(t){return typeof t=="string"?new Zn([[document.querySelector(t)]],[document.documentElement]):new Zn([[t]],fA)}var GV=R(()=>{"use strict";hl();o($e,"default")});var fl=R(()=>{"use strict";py();G4();GV();hl();$4();SS();KS()});var $V=R(()=>{"use strict"});function kh(t,e,r){t.prototype=e.prototype=r,r.constructor=t}function Cp(t,e){var r=Object.create(t.prototype);for(var n in e)r[n]=e[n];return r}var dA=R(()=>{"use strict";o(kh,"default");o(Cp,"extend")});function Eh(){}function UV(){return this.rgb().formatHex()}function B3e(){return this.rgb().formatHex8()}function F3e(){return KV(this).formatHsl()}function HV(){return this.rgb().formatRgb()}function pl(t){var e,r;return t=(t+"").trim().toLowerCase(),(e=D3e.exec(t))?(r=e[1].length,e=parseInt(e[1],16),r===6?YV(e):r===3?new la(e>>8&15|e>>4&240,e>>4&15|e&240,(e&15)<<4|e&15,1):r===8?U4(e>>24&255,e>>16&255,e>>8&255,(e&255)/255):r===4?U4(e>>12&15|e>>8&240,e>>8&15|e>>4&240,e>>4&15|e&240,((e&15)<<4|e&15)/255):null):(e=R3e.exec(t))?new la(e[1],e[2],e[3],1):(e=N3e.exec(t))?new la(e[1]*255/100,e[2]*255/100,e[3]*255/100,1):(e=M3e.exec(t))?U4(e[1],e[2],e[3],e[4]):(e=I3e.exec(t))?U4(e[1]*255/100,e[2]*255/100,e[3]*255/100,e[4]):(e=O3e.exec(t))?XV(e[1],e[2]/100,e[3]/100,1):(e=P3e.exec(t))?XV(e[1],e[2]/100,e[3]/100,e[4]):VV.hasOwnProperty(t)?YV(VV[t]):t==="transparent"?new la(NaN,NaN,NaN,0):null}function YV(t){return new la(t>>16&255,t>>8&255,t&255,1)}function U4(t,e,r,n){return n<=0&&(t=e=r=NaN),new la(t,e,r,n)}function mA(t){return t instanceof Eh||(t=pl(t)),t?(t=t.rgb(),new la(t.r,t.g,t.b,t.opacity)):new la}function Ap(t,e,r,n){return arguments.length===1?mA(t):new la(t,e,r,n??1)}function la(t,e,r,n){this.r=+t,this.g=+e,this.b=+r,this.opacity=+n}function WV(){return`#${rd(this.r)}${rd(this.g)}${rd(this.b)}`}function z3e(){return`#${rd(this.r)}${rd(this.g)}${rd(this.b)}${rd((isNaN(this.opacity)?1:this.opacity)*255)}`}function qV(){let t=W4(this.opacity);return`${t===1?"rgb(":"rgba("}${nd(this.r)}, ${nd(this.g)}, ${nd(this.b)}${t===1?")":`, ${t})`}`}function W4(t){return isNaN(t)?1:Math.max(0,Math.min(1,t))}function nd(t){return Math.max(0,Math.min(255,Math.round(t)||0))}function rd(t){return t=nd(t),(t<16?"0":"")+t.toString(16)}function XV(t,e,r,n){return n<=0?t=e=r=NaN:r<=0||r>=1?t=e=NaN:e<=0&&(t=NaN),new dl(t,e,r,n)}function KV(t){if(t instanceof dl)return new dl(t.h,t.s,t.l,t.opacity);if(t instanceof Eh||(t=pl(t)),!t)return new dl;if(t instanceof dl)return t;t=t.rgb();var e=t.r/255,r=t.g/255,n=t.b/255,i=Math.min(e,r,n),a=Math.max(e,r,n),s=NaN,l=a-i,u=(a+i)/2;return l?(e===a?s=(r-n)/l+(r0&&u<1?0:s,new dl(s,l,u,t.opacity)}function QV(t,e,r,n){return arguments.length===1?KV(t):new dl(t,e,r,n??1)}function dl(t,e,r,n){this.h=+t,this.s=+e,this.l=+r,this.opacity=+n}function jV(t){return t=(t||0)%360,t<0?t+360:t}function H4(t){return Math.max(0,Math.min(1,t||0))}function pA(t,e,r){return(t<60?e+(r-e)*t/60:t<180?r:t<240?e+(r-e)*(240-t)/60:e)*255}var vy,Y4,Sp,xy,ac,D3e,R3e,N3e,M3e,I3e,O3e,P3e,VV,gA=R(()=>{"use strict";dA();o(Eh,"Color");vy=.7,Y4=1/vy,Sp="\\s*([+-]?\\d+)\\s*",xy="\\s*([+-]?(?:\\d*\\.)?\\d+(?:[eE][+-]?\\d+)?)\\s*",ac="\\s*([+-]?(?:\\d*\\.)?\\d+(?:[eE][+-]?\\d+)?)%\\s*",D3e=/^#([0-9a-f]{3,8})$/,R3e=new RegExp(`^rgb\\(${Sp},${Sp},${Sp}\\)$`),N3e=new RegExp(`^rgb\\(${ac},${ac},${ac}\\)$`),M3e=new RegExp(`^rgba\\(${Sp},${Sp},${Sp},${xy}\\)$`),I3e=new RegExp(`^rgba\\(${ac},${ac},${ac},${xy}\\)$`),O3e=new RegExp(`^hsl\\(${xy},${ac},${ac}\\)$`),P3e=new RegExp(`^hsla\\(${xy},${ac},${ac},${xy}\\)$`),VV={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074};kh(Eh,pl,{copy(t){return Object.assign(new this.constructor,this,t)},displayable(){return this.rgb().displayable()},hex:UV,formatHex:UV,formatHex8:B3e,formatHsl:F3e,formatRgb:HV,toString:HV});o(UV,"color_formatHex");o(B3e,"color_formatHex8");o(F3e,"color_formatHsl");o(HV,"color_formatRgb");o(pl,"color");o(YV,"rgbn");o(U4,"rgba");o(mA,"rgbConvert");o(Ap,"rgb");o(la,"Rgb");kh(la,Ap,Cp(Eh,{brighter(t){return t=t==null?Y4:Math.pow(Y4,t),new la(this.r*t,this.g*t,this.b*t,this.opacity)},darker(t){return t=t==null?vy:Math.pow(vy,t),new la(this.r*t,this.g*t,this.b*t,this.opacity)},rgb(){return this},clamp(){return new la(nd(this.r),nd(this.g),nd(this.b),W4(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:WV,formatHex:WV,formatHex8:z3e,formatRgb:qV,toString:qV}));o(WV,"rgb_formatHex");o(z3e,"rgb_formatHex8");o(qV,"rgb_formatRgb");o(W4,"clampa");o(nd,"clampi");o(rd,"hex");o(XV,"hsla");o(KV,"hslConvert");o(QV,"hsl");o(dl,"Hsl");kh(dl,QV,Cp(Eh,{brighter(t){return t=t==null?Y4:Math.pow(Y4,t),new dl(this.h,this.s,this.l*t,this.opacity)},darker(t){return t=t==null?vy:Math.pow(vy,t),new dl(this.h,this.s,this.l*t,this.opacity)},rgb(){var t=this.h%360+(this.h<0)*360,e=isNaN(t)||isNaN(this.s)?0:this.s,r=this.l,n=r+(r<.5?r:1-r)*e,i=2*r-n;return new la(pA(t>=240?t-240:t+120,i,n),pA(t,i,n),pA(t<120?t+240:t-120,i,n),this.opacity)},clamp(){return new dl(jV(this.h),H4(this.s),H4(this.l),W4(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){let t=W4(this.opacity);return`${t===1?"hsl(":"hsla("}${jV(this.h)}, ${H4(this.s)*100}%, ${H4(this.l)*100}%${t===1?")":`, ${t})`}`}}));o(jV,"clamph");o(H4,"clampt");o(pA,"hsl2rgb")});var ZV,JV,eU=R(()=>{"use strict";ZV=Math.PI/180,JV=180/Math.PI});function sU(t){if(t instanceof sc)return new sc(t.l,t.a,t.b,t.opacity);if(t instanceof uu)return oU(t);t instanceof la||(t=mA(t));var e=bA(t.r),r=bA(t.g),n=bA(t.b),i=yA((.2225045*e+.7168786*r+.0606169*n)/rU),a,s;return e===r&&r===n?a=s=i:(a=yA((.4360747*e+.3850649*r+.1430804*n)/tU),s=yA((.0139322*e+.0971045*r+.7141733*n)/nU)),new sc(116*i-16,500*(a-i),200*(i-s),t.opacity)}function wA(t,e,r,n){return arguments.length===1?sU(t):new sc(t,e,r,n??1)}function sc(t,e,r,n){this.l=+t,this.a=+e,this.b=+r,this.opacity=+n}function yA(t){return t>G3e?Math.pow(t,1/3):t/aU+iU}function vA(t){return t>_p?t*t*t:aU*(t-iU)}function xA(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function bA(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function $3e(t){if(t instanceof uu)return new uu(t.h,t.c,t.l,t.opacity);if(t instanceof sc||(t=sU(t)),t.a===0&&t.b===0)return new uu(NaN,0{"use strict";dA();gA();eU();q4=18,tU=.96422,rU=1,nU=.82521,iU=4/29,_p=6/29,aU=3*_p*_p,G3e=_p*_p*_p;o(sU,"labConvert");o(wA,"lab");o(sc,"Lab");kh(sc,wA,Cp(Eh,{brighter(t){return new sc(this.l+q4*(t??1),this.a,this.b,this.opacity)},darker(t){return new sc(this.l-q4*(t??1),this.a,this.b,this.opacity)},rgb(){var t=(this.l+16)/116,e=isNaN(this.a)?t:t+this.a/500,r=isNaN(this.b)?t:t-this.b/200;return e=tU*vA(e),t=rU*vA(t),r=nU*vA(r),new la(xA(3.1338561*e-1.6168667*t-.4906146*r),xA(-.9787684*e+1.9161415*t+.033454*r),xA(.0719453*e-.2289914*t+1.4052427*r),this.opacity)}}));o(yA,"xyz2lab");o(vA,"lab2xyz");o(xA,"lrgb2rgb");o(bA,"rgb2lrgb");o($3e,"hclConvert");o(by,"hcl");o(uu,"Hcl");o(oU,"hcl2lab");kh(uu,by,Cp(Eh,{brighter(t){return new uu(this.h,this.c,this.l+q4*(t??1),this.opacity)},darker(t){return new uu(this.h,this.c,this.l-q4*(t??1),this.opacity)},rgb(){return oU(this).rgb()}}))});var Lp=R(()=>{"use strict";gA();lU()});function TA(t,e,r,n,i){var a=t*t,s=a*t;return((1-3*t+3*a-s)*e+(4-6*a+3*s)*r+(1+3*t+3*a-3*s)*n+s*i)/6}function kA(t){var e=t.length-1;return function(r){var n=r<=0?r=0:r>=1?(r=1,e-1):Math.floor(r*e),i=t[n],a=t[n+1],s=n>0?t[n-1]:2*i-a,l=n{"use strict";o(TA,"basis");o(kA,"default")});function CA(t){var e=t.length;return function(r){var n=Math.floor(((r%=1)<0?++r:r)*e),i=t[(n+e-1)%e],a=t[n%e],s=t[(n+1)%e],l=t[(n+2)%e];return TA((r-n/e)*e,i,a,s,l)}}var cU=R(()=>{"use strict";EA();o(CA,"default")});var Dp,SA=R(()=>{"use strict";Dp=o(t=>()=>t,"default")});function uU(t,e){return function(r){return t+r*e}}function V3e(t,e,r){return t=Math.pow(t,r),e=Math.pow(e,r)-t,r=1/r,function(n){return Math.pow(t+n*e,r)}}function hU(t,e){var r=e-t;return r?uU(t,r>180||r<-180?r-360*Math.round(r/360):r):Dp(isNaN(t)?e:t)}function fU(t){return(t=+t)==1?hu:function(e,r){return r-e?V3e(e,r,t):Dp(isNaN(e)?r:e)}}function hu(t,e){var r=e-t;return r?uU(t,r):Dp(isNaN(t)?e:t)}var AA=R(()=>{"use strict";SA();o(uU,"linear");o(V3e,"exponential");o(hU,"hue");o(fU,"gamma");o(hu,"nogamma")});function dU(t){return function(e){var r=e.length,n=new Array(r),i=new Array(r),a=new Array(r),s,l;for(s=0;s{"use strict";Lp();EA();cU();AA();id=o(function t(e){var r=fU(e);function n(i,a){var s=r((i=Ap(i)).r,(a=Ap(a)).r),l=r(i.g,a.g),u=r(i.b,a.b),h=hu(i.opacity,a.opacity);return function(f){return i.r=s(f),i.g=l(f),i.b=u(f),i.opacity=h(f),i+""}}return o(n,"rgb"),n.gamma=t,n},"rgbGamma")(1);o(dU,"rgbSpline");U3e=dU(kA),H3e=dU(CA)});function LA(t,e){e||(e=[]);var r=t?Math.min(e.length,t.length):0,n=e.slice(),i;return function(a){for(i=0;i{"use strict";o(LA,"default");o(pU,"isNumberArray")});function gU(t,e){var r=e?e.length:0,n=t?Math.min(r,t.length):0,i=new Array(n),a=new Array(r),s;for(s=0;s{"use strict";X4();o(gU,"genericArray")});function DA(t,e){var r=new Date;return t=+t,e=+e,function(n){return r.setTime(t*(1-n)+e*n),r}}var vU=R(()=>{"use strict";o(DA,"default")});function ji(t,e){return t=+t,e=+e,function(r){return t*(1-r)+e*r}}var wy=R(()=>{"use strict";o(ji,"default")});function RA(t,e){var r={},n={},i;(t===null||typeof t!="object")&&(t={}),(e===null||typeof e!="object")&&(e={});for(i in e)i in t?r[i]=Ch(t[i],e[i]):n[i]=e[i];return function(a){for(i in r)n[i]=r[i](a);return n}}var xU=R(()=>{"use strict";X4();o(RA,"default")});function Y3e(t){return function(){return t}}function W3e(t){return function(e){return t(e)+""}}function Rp(t,e){var r=MA.lastIndex=NA.lastIndex=0,n,i,a,s=-1,l=[],u=[];for(t=t+"",e=e+"";(n=MA.exec(t))&&(i=NA.exec(e));)(a=i.index)>r&&(a=e.slice(r,a),l[s]?l[s]+=a:l[++s]=a),(n=n[0])===(i=i[0])?l[s]?l[s]+=i:l[++s]=i:(l[++s]=null,u.push({i:s,x:ji(n,i)})),r=NA.lastIndex;return r{"use strict";wy();MA=/[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g,NA=new RegExp(MA.source,"g");o(Y3e,"zero");o(W3e,"one");o(Rp,"default")});function Ch(t,e){var r=typeof e,n;return e==null||r==="boolean"?Dp(e):(r==="number"?ji:r==="string"?(n=pl(e))?(e=n,id):Rp:e instanceof pl?id:e instanceof Date?DA:pU(e)?LA:Array.isArray(e)?gU:typeof e.valueOf!="function"&&typeof e.toString!="function"||isNaN(e)?RA:ji)(t,e)}var X4=R(()=>{"use strict";Lp();_A();yU();vU();wy();xU();IA();SA();mU();o(Ch,"default")});function j4(t,e){return t=+t,e=+e,function(r){return Math.round(t*(1-r)+e*r)}}var bU=R(()=>{"use strict";o(j4,"default")});function Q4(t,e,r,n,i,a){var s,l,u;return(s=Math.sqrt(t*t+e*e))&&(t/=s,e/=s),(u=t*r+e*n)&&(r-=t*u,n-=e*u),(l=Math.sqrt(r*r+n*n))&&(r/=l,n/=l,u/=l),t*n{"use strict";wU=180/Math.PI,K4={translateX:0,translateY:0,rotate:0,skewX:0,scaleX:1,scaleY:1};o(Q4,"default")});function kU(t){let e=new(typeof DOMMatrix=="function"?DOMMatrix:WebKitCSSMatrix)(t+"");return e.isIdentity?K4:Q4(e.a,e.b,e.c,e.d,e.e,e.f)}function EU(t){return t==null?K4:(Z4||(Z4=document.createElementNS("http://www.w3.org/2000/svg","g")),Z4.setAttribute("transform",t),(t=Z4.transform.baseVal.consolidate())?(t=t.matrix,Q4(t.a,t.b,t.c,t.d,t.e,t.f)):K4)}var Z4,CU=R(()=>{"use strict";TU();o(kU,"parseCss");o(EU,"parseSvg")});function SU(t,e,r,n){function i(h){return h.length?h.pop()+" ":""}o(i,"pop");function a(h,f,d,p,m,g){if(h!==d||f!==p){var y=m.push("translate(",null,e,null,r);g.push({i:y-4,x:ji(h,d)},{i:y-2,x:ji(f,p)})}else(d||p)&&m.push("translate("+d+e+p+r)}o(a,"translate");function s(h,f,d,p){h!==f?(h-f>180?f+=360:f-h>180&&(h+=360),p.push({i:d.push(i(d)+"rotate(",null,n)-2,x:ji(h,f)})):f&&d.push(i(d)+"rotate("+f+n)}o(s,"rotate");function l(h,f,d,p){h!==f?p.push({i:d.push(i(d)+"skewX(",null,n)-2,x:ji(h,f)}):f&&d.push(i(d)+"skewX("+f+n)}o(l,"skewX");function u(h,f,d,p,m,g){if(h!==d||f!==p){var y=m.push(i(m)+"scale(",null,",",null,")");g.push({i:y-4,x:ji(h,d)},{i:y-2,x:ji(f,p)})}else(d!==1||p!==1)&&m.push(i(m)+"scale("+d+","+p+")")}return o(u,"scale"),function(h,f){var d=[],p=[];return h=t(h),f=t(f),a(h.translateX,h.translateY,f.translateX,f.translateY,d,p),s(h.rotate,f.rotate,d,p),l(h.skewX,f.skewX,d,p),u(h.scaleX,h.scaleY,f.scaleX,f.scaleY,d,p),h=f=null,function(m){for(var g=-1,y=p.length,v;++g{"use strict";wy();CU();o(SU,"interpolateTransform");OA=SU(kU,"px, ","px)","deg)"),PA=SU(EU,", ",")",")")});function _U(t){return function(e,r){var n=t((e=by(e)).h,(r=by(r)).h),i=hu(e.c,r.c),a=hu(e.l,r.l),s=hu(e.opacity,r.opacity);return function(l){return e.h=n(l),e.c=i(l),e.l=a(l),e.opacity=s(l),e+""}}}var BA,q3e,LU=R(()=>{"use strict";Lp();AA();o(_U,"hcl");BA=_U(hU),q3e=_U(hu)});var Np=R(()=>{"use strict";X4();wy();bU();IA();AU();_A();LU()});function Ay(){return ad||(NU(X3e),ad=Cy.now()+t3)}function X3e(){ad=0}function Sy(){this._call=this._time=this._next=null}function r3(t,e,r){var n=new Sy;return n.restart(t,e,r),n}function MU(){Ay(),++Mp;for(var t=J4,e;t;)(e=ad-t._time)>=0&&t._call.call(void 0,e),t=t._next;--Mp}function DU(){ad=(e3=Cy.now())+t3,Mp=ky=0;try{MU()}finally{Mp=0,K3e(),ad=0}}function j3e(){var t=Cy.now(),e=t-e3;e>RU&&(t3-=e,e3=t)}function K3e(){for(var t,e=J4,r,n=1/0;e;)e._call?(n>e._time&&(n=e._time),t=e,e=e._next):(r=e._next,e._next=null,e=t?t._next=r:J4=r);Ey=t,FA(n)}function FA(t){if(!Mp){ky&&(ky=clearTimeout(ky));var e=t-ad;e>24?(t<1/0&&(ky=setTimeout(DU,t-Cy.now()-t3)),Ty&&(Ty=clearInterval(Ty))):(Ty||(e3=Cy.now(),Ty=setInterval(j3e,RU)),Mp=1,NU(DU))}}var Mp,ky,Ty,RU,J4,Ey,e3,ad,t3,Cy,NU,zA=R(()=>{"use strict";Mp=0,ky=0,Ty=0,RU=1e3,e3=0,ad=0,t3=0,Cy=typeof performance=="object"&&performance.now?performance:Date,NU=typeof window=="object"&&window.requestAnimationFrame?window.requestAnimationFrame.bind(window):function(t){setTimeout(t,17)};o(Ay,"now");o(X3e,"clearNow");o(Sy,"Timer");Sy.prototype=r3.prototype={constructor:Sy,restart:o(function(t,e,r){if(typeof t!="function")throw new TypeError("callback is not a function");r=(r==null?Ay():+r)+(e==null?0:+e),!this._next&&Ey!==this&&(Ey?Ey._next=this:J4=this,Ey=this),this._call=t,this._time=r,FA()},"restart"),stop:o(function(){this._call&&(this._call=null,this._time=1/0,FA())},"stop")};o(r3,"timer");o(MU,"timerFlush");o(DU,"wake");o(j3e,"poke");o(K3e,"nap");o(FA,"sleep")});function _y(t,e,r){var n=new Sy;return e=e==null?0:+e,n.restart(i=>{n.stop(),t(i+e)},e,r),n}var IU=R(()=>{"use strict";zA();o(_y,"default")});var n3=R(()=>{"use strict";zA();IU()});function fu(t,e,r,n,i,a){var s=t.__transition;if(!s)t.__transition={};else if(r in s)return;J3e(t,r,{name:e,index:n,group:i,on:Q3e,tween:Z3e,time:a.time,delay:a.delay,duration:a.duration,ease:a.ease,timer:null,state:BU})}function Dy(t,e){var r=Mi(t,e);if(r.state>BU)throw new Error("too late; already scheduled");return r}function ca(t,e){var r=Mi(t,e);if(r.state>i3)throw new Error("too late; already running");return r}function Mi(t,e){var r=t.__transition;if(!r||!(r=r[e]))throw new Error("transition not found");return r}function J3e(t,e,r){var n=t.__transition,i;n[e]=r,r.timer=r3(a,0,r.time);function a(h){r.state=OU,r.timer.restart(s,r.delay,r.time),r.delay<=h&&s(h-r.delay)}o(a,"schedule");function s(h){var f,d,p,m;if(r.state!==OU)return u();for(f in n)if(m=n[f],m.name===r.name){if(m.state===i3)return _y(s);m.state===PU?(m.state=Ly,m.timer.stop(),m.on.call("interrupt",t,t.__data__,m.index,m.group),delete n[f]):+f{"use strict";bS();n3();Q3e=xS("start","end","cancel","interrupt"),Z3e=[],BU=0,OU=1,a3=2,i3=3,PU=4,s3=5,Ly=6;o(fu,"default");o(Dy,"init");o(ca,"set");o(Mi,"get");o(J3e,"create")});function Ry(t,e){var r=t.__transition,n,i,a=!0,s;if(r){e=e==null?null:e+"";for(s in r){if((n=r[s]).name!==e){a=!1;continue}i=n.state>a3&&n.state{"use strict";ys();o(Ry,"default")});function GA(t){return this.each(function(){Ry(this,t)})}var zU=R(()=>{"use strict";FU();o(GA,"default")});function e5e(t,e){var r,n;return function(){var i=ca(this,t),a=i.tween;if(a!==r){n=r=a;for(var s=0,l=n.length;s{"use strict";ys();o(e5e,"tweenRemove");o(t5e,"tweenFunction");o($A,"default");o(Ip,"tweenValue")});function My(t,e){var r;return(typeof e=="number"?ji:e instanceof pl?id:(r=pl(e))?(e=r,id):Rp)(t,e)}var VA=R(()=>{"use strict";Lp();Np();o(My,"default")});function r5e(t){return function(){this.removeAttribute(t)}}function n5e(t){return function(){this.removeAttributeNS(t.space,t.local)}}function i5e(t,e,r){var n,i=r+"",a;return function(){var s=this.getAttribute(t);return s===i?null:s===n?a:a=e(n=s,r)}}function a5e(t,e,r){var n,i=r+"",a;return function(){var s=this.getAttributeNS(t.space,t.local);return s===i?null:s===n?a:a=e(n=s,r)}}function s5e(t,e,r){var n,i,a;return function(){var s,l=r(this),u;return l==null?void this.removeAttribute(t):(s=this.getAttribute(t),u=l+"",s===u?null:s===n&&u===i?a:(i=u,a=e(n=s,l)))}}function o5e(t,e,r){var n,i,a;return function(){var s,l=r(this),u;return l==null?void this.removeAttributeNS(t.space,t.local):(s=this.getAttributeNS(t.space,t.local),u=l+"",s===u?null:s===n&&u===i?a:(i=u,a=e(n=s,l)))}}function UA(t,e){var r=ic(t),n=r==="transform"?PA:My;return this.attrTween(t,typeof e=="function"?(r.local?o5e:s5e)(r,n,Ip(this,"attr."+t,e)):e==null?(r.local?n5e:r5e)(r):(r.local?a5e:i5e)(r,n,e))}var GU=R(()=>{"use strict";Np();fl();Ny();VA();o(r5e,"attrRemove");o(n5e,"attrRemoveNS");o(i5e,"attrConstant");o(a5e,"attrConstantNS");o(s5e,"attrFunction");o(o5e,"attrFunctionNS");o(UA,"default")});function l5e(t,e){return function(r){this.setAttribute(t,e.call(this,r))}}function c5e(t,e){return function(r){this.setAttributeNS(t.space,t.local,e.call(this,r))}}function u5e(t,e){var r,n;function i(){var a=e.apply(this,arguments);return a!==n&&(r=(n=a)&&c5e(t,a)),r}return o(i,"tween"),i._value=e,i}function h5e(t,e){var r,n;function i(){var a=e.apply(this,arguments);return a!==n&&(r=(n=a)&&l5e(t,a)),r}return o(i,"tween"),i._value=e,i}function HA(t,e){var r="attr."+t;if(arguments.length<2)return(r=this.tween(r))&&r._value;if(e==null)return this.tween(r,null);if(typeof e!="function")throw new Error;var n=ic(t);return this.tween(r,(n.local?u5e:h5e)(n,e))}var $U=R(()=>{"use strict";fl();o(l5e,"attrInterpolate");o(c5e,"attrInterpolateNS");o(u5e,"attrTweenNS");o(h5e,"attrTween");o(HA,"default")});function f5e(t,e){return function(){Dy(this,t).delay=+e.apply(this,arguments)}}function d5e(t,e){return e=+e,function(){Dy(this,t).delay=e}}function YA(t){var e=this._id;return arguments.length?this.each((typeof t=="function"?f5e:d5e)(e,t)):Mi(this.node(),e).delay}var VU=R(()=>{"use strict";ys();o(f5e,"delayFunction");o(d5e,"delayConstant");o(YA,"default")});function p5e(t,e){return function(){ca(this,t).duration=+e.apply(this,arguments)}}function m5e(t,e){return e=+e,function(){ca(this,t).duration=e}}function WA(t){var e=this._id;return arguments.length?this.each((typeof t=="function"?p5e:m5e)(e,t)):Mi(this.node(),e).duration}var UU=R(()=>{"use strict";ys();o(p5e,"durationFunction");o(m5e,"durationConstant");o(WA,"default")});function g5e(t,e){if(typeof e!="function")throw new Error;return function(){ca(this,t).ease=e}}function qA(t){var e=this._id;return arguments.length?this.each(g5e(e,t)):Mi(this.node(),e).ease}var HU=R(()=>{"use strict";ys();o(g5e,"easeConstant");o(qA,"default")});function y5e(t,e){return function(){var r=e.apply(this,arguments);if(typeof r!="function")throw new Error;ca(this,t).ease=r}}function XA(t){if(typeof t!="function")throw new Error;return this.each(y5e(this._id,t))}var YU=R(()=>{"use strict";ys();o(y5e,"easeVarying");o(XA,"default")});function jA(t){typeof t!="function"&&(t=Ep(t));for(var e=this._groups,r=e.length,n=new Array(r),i=0;i{"use strict";fl();sd();o(jA,"default")});function KA(t){if(t._id!==this._id)throw new Error;for(var e=this._groups,r=t._groups,n=e.length,i=r.length,a=Math.min(n,i),s=new Array(n),l=0;l{"use strict";sd();o(KA,"default")});function v5e(t){return(t+"").trim().split(/^|\s+/).every(function(e){var r=e.indexOf(".");return r>=0&&(e=e.slice(0,r)),!e||e==="start"})}function x5e(t,e,r){var n,i,a=v5e(e)?Dy:ca;return function(){var s=a(this,t),l=s.on;l!==n&&(i=(n=l).copy()).on(e,r),s.on=i}}function QA(t,e){var r=this._id;return arguments.length<2?Mi(this.node(),r).on.on(t):this.each(x5e(r,t,e))}var XU=R(()=>{"use strict";ys();o(v5e,"start");o(x5e,"onFunction");o(QA,"default")});function b5e(t){return function(){var e=this.parentNode;for(var r in this.__transition)if(+r!==t)return;e&&e.removeChild(this)}}function ZA(){return this.on("end.remove",b5e(this._id))}var jU=R(()=>{"use strict";o(b5e,"removeFunction");o(ZA,"default")});function JA(t){var e=this._name,r=this._id;typeof t!="function"&&(t=wh(t));for(var n=this._groups,i=n.length,a=new Array(i),s=0;s{"use strict";fl();sd();ys();o(JA,"default")});function e8(t){var e=this._name,r=this._id;typeof t!="function"&&(t=kp(t));for(var n=this._groups,i=n.length,a=[],s=[],l=0;l{"use strict";fl();sd();ys();o(e8,"default")});function t8(){return new w5e(this._groups,this._parents)}var w5e,ZU=R(()=>{"use strict";fl();w5e=cu.prototype.constructor;o(t8,"default")});function T5e(t,e){var r,n,i;return function(){var a=Th(this,t),s=(this.style.removeProperty(t),Th(this,t));return a===s?null:a===r&&s===n?i:i=e(r=a,n=s)}}function JU(t){return function(){this.style.removeProperty(t)}}function k5e(t,e,r){var n,i=r+"",a;return function(){var s=Th(this,t);return s===i?null:s===n?a:a=e(n=s,r)}}function E5e(t,e,r){var n,i,a;return function(){var s=Th(this,t),l=r(this),u=l+"";return l==null&&(u=l=(this.style.removeProperty(t),Th(this,t))),s===u?null:s===n&&u===i?a:(i=u,a=e(n=s,l))}}function C5e(t,e){var r,n,i,a="style."+e,s="end."+a,l;return function(){var u=ca(this,t),h=u.on,f=u.value[a]==null?l||(l=JU(e)):void 0;(h!==r||i!==f)&&(n=(r=h).copy()).on(s,i=f),u.on=n}}function r8(t,e,r){var n=(t+="")=="transform"?OA:My;return e==null?this.styleTween(t,T5e(t,n)).on("end.style."+t,JU(t)):typeof e=="function"?this.styleTween(t,E5e(t,n,Ip(this,"style."+t,e))).each(C5e(this._id,t)):this.styleTween(t,k5e(t,n,e),r).on("end.style."+t,null)}var eH=R(()=>{"use strict";Np();fl();ys();Ny();VA();o(T5e,"styleNull");o(JU,"styleRemove");o(k5e,"styleConstant");o(E5e,"styleFunction");o(C5e,"styleMaybeRemove");o(r8,"default")});function S5e(t,e,r){return function(n){this.style.setProperty(t,e.call(this,n),r)}}function A5e(t,e,r){var n,i;function a(){var s=e.apply(this,arguments);return s!==i&&(n=(i=s)&&S5e(t,s,r)),n}return o(a,"tween"),a._value=e,a}function n8(t,e,r){var n="style."+(t+="");if(arguments.length<2)return(n=this.tween(n))&&n._value;if(e==null)return this.tween(n,null);if(typeof e!="function")throw new Error;return this.tween(n,A5e(t,e,r??""))}var tH=R(()=>{"use strict";o(S5e,"styleInterpolate");o(A5e,"styleTween");o(n8,"default")});function _5e(t){return function(){this.textContent=t}}function L5e(t){return function(){var e=t(this);this.textContent=e??""}}function i8(t){return this.tween("text",typeof t=="function"?L5e(Ip(this,"text",t)):_5e(t==null?"":t+""))}var rH=R(()=>{"use strict";Ny();o(_5e,"textConstant");o(L5e,"textFunction");o(i8,"default")});function D5e(t){return function(e){this.textContent=t.call(this,e)}}function R5e(t){var e,r;function n(){var i=t.apply(this,arguments);return i!==r&&(e=(r=i)&&D5e(i)),e}return o(n,"tween"),n._value=t,n}function a8(t){var e="text";if(arguments.length<1)return(e=this.tween(e))&&e._value;if(t==null)return this.tween(e,null);if(typeof t!="function")throw new Error;return this.tween(e,R5e(t))}var nH=R(()=>{"use strict";o(D5e,"textInterpolate");o(R5e,"textTween");o(a8,"default")});function s8(){for(var t=this._name,e=this._id,r=o3(),n=this._groups,i=n.length,a=0;a{"use strict";sd();ys();o(s8,"default")});function o8(){var t,e,r=this,n=r._id,i=r.size();return new Promise(function(a,s){var l={value:s},u={value:o(function(){--i===0&&a()},"value")};r.each(function(){var h=ca(this,n),f=h.on;f!==t&&(e=(t=f).copy(),e._.cancel.push(l),e._.interrupt.push(l),e._.end.push(u)),h.on=e}),i===0&&a()})}var aH=R(()=>{"use strict";ys();o(o8,"default")});function Ka(t,e,r,n){this._groups=t,this._parents=e,this._name=r,this._id=n}function sH(t){return cu().transition(t)}function o3(){return++N5e}var N5e,du,sd=R(()=>{"use strict";fl();GU();$U();VU();UU();HU();YU();WU();qU();XU();jU();KU();QU();ZU();eH();tH();rH();nH();iH();Ny();aH();N5e=0;o(Ka,"Transition");o(sH,"transition");o(o3,"newId");du=cu.prototype;Ka.prototype=sH.prototype={constructor:Ka,select:JA,selectAll:e8,selectChild:du.selectChild,selectChildren:du.selectChildren,filter:jA,merge:KA,selection:t8,transition:s8,call:du.call,nodes:du.nodes,node:du.node,size:du.size,empty:du.empty,each:du.each,on:QA,attr:UA,attrTween:HA,style:r8,styleTween:n8,text:i8,textTween:a8,remove:ZA,tween:$A,delay:YA,duration:WA,ease:qA,easeVarying:XA,end:o8,[Symbol.iterator]:du[Symbol.iterator]}});function l3(t){return((t*=2)<=1?t*t*t:(t-=2)*t*t+2)/2}var oH=R(()=>{"use strict";o(l3,"cubicInOut")});var l8=R(()=>{"use strict";oH()});function I5e(t,e){for(var r;!(r=t.__transition)||!(r=r[e]);)if(!(t=t.parentNode))throw new Error(`transition ${e} not found`);return r}function c8(t){var e,r;t instanceof Ka?(e=t._id,t=t._name):(e=o3(),(r=M5e).time=Ay(),t=t==null?null:t+"");for(var n=this._groups,i=n.length,a=0;a{"use strict";sd();ys();l8();n3();M5e={time:null,delay:0,duration:250,ease:l3};o(I5e,"inherit");o(c8,"default")});var cH=R(()=>{"use strict";fl();zU();lH();cu.prototype.interrupt=GA;cu.prototype.transition=c8});var c3=R(()=>{"use strict";cH()});var uH=R(()=>{"use strict"});var hH=R(()=>{"use strict"});var fH=R(()=>{"use strict"});function dH(t){return[+t[0],+t[1]]}function O5e(t){return[dH(t[0]),dH(t[1])]}function u8(t){return{type:t}}var Kpt,Qpt,Zpt,Jpt,emt,tmt,pH=R(()=>{"use strict";c3();uH();hH();fH();({abs:Kpt,max:Qpt,min:Zpt}=Math);o(dH,"number1");o(O5e,"number2");Jpt={name:"x",handles:["w","e"].map(u8),input:o(function(t,e){return t==null?null:[[+t[0],e[0][1]],[+t[1],e[1][1]]]},"input"),output:o(function(t){return t&&[t[0][0],t[1][0]]},"output")},emt={name:"y",handles:["n","s"].map(u8),input:o(function(t,e){return t==null?null:[[e[0][0],+t[0]],[e[1][0],+t[1]]]},"input"),output:o(function(t){return t&&[t[0][1],t[1][1]]},"output")},tmt={name:"xy",handles:["n","w","e","s","nw","ne","sw","se"].map(u8),input:o(function(t){return t==null?null:O5e(t)},"input"),output:o(function(t){return t},"output")};o(u8,"type")});var mH=R(()=>{"use strict";pH()});function gH(t){this._+=t[0];for(let e=1,r=t.length;e=0))throw new Error(`invalid digits: ${t}`);if(e>15)return gH;let r=10**e;return function(n){this._+=n[0];for(let i=1,a=n.length;i{"use strict";h8=Math.PI,f8=2*h8,od=1e-6,P5e=f8-od;o(gH,"append");o(B5e,"appendRound");ld=class{static{o(this,"Path")}constructor(e){this._x0=this._y0=this._x1=this._y1=null,this._="",this._append=e==null?gH:B5e(e)}moveTo(e,r){this._append`M${this._x0=this._x1=+e},${this._y0=this._y1=+r}`}closePath(){this._x1!==null&&(this._x1=this._x0,this._y1=this._y0,this._append`Z`)}lineTo(e,r){this._append`L${this._x1=+e},${this._y1=+r}`}quadraticCurveTo(e,r,n,i){this._append`Q${+e},${+r},${this._x1=+n},${this._y1=+i}`}bezierCurveTo(e,r,n,i,a,s){this._append`C${+e},${+r},${+n},${+i},${this._x1=+a},${this._y1=+s}`}arcTo(e,r,n,i,a){if(e=+e,r=+r,n=+n,i=+i,a=+a,a<0)throw new Error(`negative radius: ${a}`);let s=this._x1,l=this._y1,u=n-e,h=i-r,f=s-e,d=l-r,p=f*f+d*d;if(this._x1===null)this._append`M${this._x1=e},${this._y1=r}`;else if(p>od)if(!(Math.abs(d*u-h*f)>od)||!a)this._append`L${this._x1=e},${this._y1=r}`;else{let m=n-s,g=i-l,y=u*u+h*h,v=m*m+g*g,x=Math.sqrt(y),b=Math.sqrt(p),w=a*Math.tan((h8-Math.acos((y+p-v)/(2*x*b)))/2),S=w/b,T=w/x;Math.abs(S-1)>od&&this._append`L${e+S*f},${r+S*d}`,this._append`A${a},${a},0,0,${+(d*m>f*g)},${this._x1=e+T*u},${this._y1=r+T*h}`}}arc(e,r,n,i,a,s){if(e=+e,r=+r,n=+n,s=!!s,n<0)throw new Error(`negative radius: ${n}`);let l=n*Math.cos(i),u=n*Math.sin(i),h=e+l,f=r+u,d=1^s,p=s?i-a:a-i;this._x1===null?this._append`M${h},${f}`:(Math.abs(this._x1-h)>od||Math.abs(this._y1-f)>od)&&this._append`L${h},${f}`,n&&(p<0&&(p=p%f8+f8),p>P5e?this._append`A${n},${n},0,1,${d},${e-l},${r-u}A${n},${n},0,1,${d},${this._x1=h},${this._y1=f}`:p>od&&this._append`A${n},${n},0,${+(p>=h8)},${d},${this._x1=e+n*Math.cos(a)},${this._y1=r+n*Math.sin(a)}`)}rect(e,r,n,i){this._append`M${this._x0=this._x1=+e},${this._y0=this._y1=+r}h${n=+n}v${+i}h${-n}Z`}toString(){return this._}};o(yH,"path");yH.prototype=ld.prototype});var d8=R(()=>{"use strict";vH()});var xH=R(()=>{"use strict"});var bH=R(()=>{"use strict"});var wH=R(()=>{"use strict"});var TH=R(()=>{"use strict"});var kH=R(()=>{"use strict"});var EH=R(()=>{"use strict"});var CH=R(()=>{"use strict"});function p8(t){return Math.abs(t=Math.round(t))>=1e21?t.toLocaleString("en").replace(/,/g,""):t.toString(10)}function cd(t,e){if((r=(t=e?t.toExponential(e-1):t.toExponential()).indexOf("e"))<0)return null;var r,n=t.slice(0,r);return[n.length>1?n[0]+n.slice(2):n,+t.slice(r+1)]}var Iy=R(()=>{"use strict";o(p8,"default");o(cd,"formatDecimalParts")});function ml(t){return t=cd(Math.abs(t)),t?t[1]:NaN}var Oy=R(()=>{"use strict";Iy();o(ml,"default")});function m8(t,e){return function(r,n){for(var i=r.length,a=[],s=0,l=t[0],u=0;i>0&&l>0&&(u+l+1>n&&(l=Math.max(1,n-u)),a.push(r.substring(i-=l,i+l)),!((u+=l+1)>n));)l=t[s=(s+1)%t.length];return a.reverse().join(e)}}var SH=R(()=>{"use strict";o(m8,"default")});function g8(t){return function(e){return e.replace(/[0-9]/g,function(r){return t[+r]})}}var AH=R(()=>{"use strict";o(g8,"default")});function Sh(t){if(!(e=F5e.exec(t)))throw new Error("invalid format: "+t);var e;return new u3({fill:e[1],align:e[2],sign:e[3],symbol:e[4],zero:e[5],width:e[6],comma:e[7],precision:e[8]&&e[8].slice(1),trim:e[9],type:e[10]})}function u3(t){this.fill=t.fill===void 0?" ":t.fill+"",this.align=t.align===void 0?">":t.align+"",this.sign=t.sign===void 0?"-":t.sign+"",this.symbol=t.symbol===void 0?"":t.symbol+"",this.zero=!!t.zero,this.width=t.width===void 0?void 0:+t.width,this.comma=!!t.comma,this.precision=t.precision===void 0?void 0:+t.precision,this.trim=!!t.trim,this.type=t.type===void 0?"":t.type+""}var F5e,y8=R(()=>{"use strict";F5e=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;o(Sh,"formatSpecifier");Sh.prototype=u3.prototype;o(u3,"FormatSpecifier");u3.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(this.width===void 0?"":Math.max(1,this.width|0))+(this.comma?",":"")+(this.precision===void 0?"":"."+Math.max(0,this.precision|0))+(this.trim?"~":"")+this.type}});function v8(t){e:for(var e=t.length,r=1,n=-1,i;r0&&(n=0);break}return n>0?t.slice(0,n)+t.slice(i+1):t}var _H=R(()=>{"use strict";o(v8,"default")});function b8(t,e){var r=cd(t,e);if(!r)return t+"";var n=r[0],i=r[1],a=i-(x8=Math.max(-8,Math.min(8,Math.floor(i/3)))*3)+1,s=n.length;return a===s?n:a>s?n+new Array(a-s+1).join("0"):a>0?n.slice(0,a)+"."+n.slice(a):"0."+new Array(1-a).join("0")+cd(t,Math.max(0,e+a-1))[0]}var x8,w8=R(()=>{"use strict";Iy();o(b8,"default")});function h3(t,e){var r=cd(t,e);if(!r)return t+"";var n=r[0],i=r[1];return i<0?"0."+new Array(-i).join("0")+n:n.length>i+1?n.slice(0,i+1)+"."+n.slice(i+1):n+new Array(i-n.length+2).join("0")}var LH=R(()=>{"use strict";Iy();o(h3,"default")});var T8,DH=R(()=>{"use strict";Iy();w8();LH();T8={"%":o((t,e)=>(t*100).toFixed(e),"%"),b:o(t=>Math.round(t).toString(2),"b"),c:o(t=>t+"","c"),d:p8,e:o((t,e)=>t.toExponential(e),"e"),f:o((t,e)=>t.toFixed(e),"f"),g:o((t,e)=>t.toPrecision(e),"g"),o:o(t=>Math.round(t).toString(8),"o"),p:o((t,e)=>h3(t*100,e),"p"),r:h3,s:b8,X:o(t=>Math.round(t).toString(16).toUpperCase(),"X"),x:o(t=>Math.round(t).toString(16),"x")}});function f3(t){return t}var RH=R(()=>{"use strict";o(f3,"default")});function k8(t){var e=t.grouping===void 0||t.thousands===void 0?f3:m8(NH.call(t.grouping,Number),t.thousands+""),r=t.currency===void 0?"":t.currency[0]+"",n=t.currency===void 0?"":t.currency[1]+"",i=t.decimal===void 0?".":t.decimal+"",a=t.numerals===void 0?f3:g8(NH.call(t.numerals,String)),s=t.percent===void 0?"%":t.percent+"",l=t.minus===void 0?"\u2212":t.minus+"",u=t.nan===void 0?"NaN":t.nan+"";function h(d){d=Sh(d);var p=d.fill,m=d.align,g=d.sign,y=d.symbol,v=d.zero,x=d.width,b=d.comma,w=d.precision,S=d.trim,T=d.type;T==="n"?(b=!0,T="g"):T8[T]||(w===void 0&&(w=12),S=!0,T="g"),(v||p==="0"&&m==="=")&&(v=!0,p="0",m="=");var E=y==="$"?r:y==="#"&&/[boxX]/.test(T)?"0"+T.toLowerCase():"",_=y==="$"?n:/[%p]/.test(T)?s:"",A=T8[T],L=/[defgprs%]/.test(T);w=w===void 0?6:/[gprs]/.test(T)?Math.max(1,Math.min(21,w)):Math.max(0,Math.min(20,w));function M(N){var k=E,I=_,C,O,D;if(T==="c")I=A(N)+I,N="";else{N=+N;var P=N<0||1/N<0;if(N=isNaN(N)?u:A(Math.abs(N),w),S&&(N=v8(N)),P&&+N==0&&g!=="+"&&(P=!1),k=(P?g==="("?g:l:g==="-"||g==="("?"":g)+k,I=(T==="s"?MH[8+x8/3]:"")+I+(P&&g==="("?")":""),L){for(C=-1,O=N.length;++CD||D>57){I=(D===46?i+N.slice(C+1):N.slice(C))+I,N=N.slice(0,C);break}}}b&&!v&&(N=e(N,1/0));var F=k.length+N.length+I.length,B=F>1)+k+N+I+B.slice(F);break;default:N=B+k+N+I;break}return a(N)}return o(M,"format"),M.toString=function(){return d+""},M}o(h,"newFormat");function f(d,p){var m=h((d=Sh(d),d.type="f",d)),g=Math.max(-8,Math.min(8,Math.floor(ml(p)/3)))*3,y=Math.pow(10,-g),v=MH[8+g/3];return function(x){return m(y*x)+v}}return o(f,"formatPrefix"),{format:h,formatPrefix:f}}var NH,MH,IH=R(()=>{"use strict";Oy();SH();AH();y8();_H();DH();w8();RH();NH=Array.prototype.map,MH=["y","z","a","f","p","n","\xB5","m","","k","M","G","T","P","E","Z","Y"];o(k8,"default")});function E8(t){return d3=k8(t),p3=d3.format,m3=d3.formatPrefix,d3}var d3,p3,m3,OH=R(()=>{"use strict";IH();E8({thousands:",",grouping:[3],currency:["$",""]});o(E8,"defaultLocale")});function g3(t){return Math.max(0,-ml(Math.abs(t)))}var PH=R(()=>{"use strict";Oy();o(g3,"default")});function y3(t,e){return Math.max(0,Math.max(-8,Math.min(8,Math.floor(ml(e)/3)))*3-ml(Math.abs(t)))}var BH=R(()=>{"use strict";Oy();o(y3,"default")});function v3(t,e){return t=Math.abs(t),e=Math.abs(e)-t,Math.max(0,ml(e)-ml(t))+1}var FH=R(()=>{"use strict";Oy();o(v3,"default")});var C8=R(()=>{"use strict";OH();y8();PH();BH();FH()});var zH=R(()=>{"use strict"});var GH=R(()=>{"use strict"});var $H=R(()=>{"use strict"});var VH=R(()=>{"use strict"});function Ah(t,e){switch(arguments.length){case 0:break;case 1:this.range(t);break;default:this.range(e).domain(t);break}return this}var Py=R(()=>{"use strict";o(Ah,"initRange")});function pu(){var t=new wp,e=[],r=[],n=S8;function i(a){let s=t.get(a);if(s===void 0){if(n!==S8)return n;t.set(a,s=e.push(a)-1)}return r[s%r.length]}return o(i,"scale"),i.domain=function(a){if(!arguments.length)return e.slice();e=[],t=new wp;for(let s of a)t.has(s)||t.set(s,e.push(s)-1);return i},i.range=function(a){return arguments.length?(r=Array.from(a),i):r.slice()},i.unknown=function(a){return arguments.length?(n=a,i):n},i.copy=function(){return pu(e,r).unknown(n)},Ah.apply(i,arguments),i}var S8,A8=R(()=>{"use strict";bh();Py();S8=Symbol("implicit");o(pu,"ordinal")});function Op(){var t=pu().unknown(void 0),e=t.domain,r=t.range,n=0,i=1,a,s,l=!1,u=0,h=0,f=.5;delete t.unknown;function d(){var p=e().length,m=i{"use strict";bh();Py();A8();o(Op,"band")});function _8(t){return function(){return t}}var HH=R(()=>{"use strict";o(_8,"constants")});function L8(t){return+t}var YH=R(()=>{"use strict";o(L8,"number")});function Pp(t){return t}function D8(t,e){return(e-=t=+t)?function(r){return(r-t)/e}:_8(isNaN(e)?NaN:.5)}function z5e(t,e){var r;return t>e&&(r=t,t=e,e=r),function(n){return Math.max(t,Math.min(e,n))}}function G5e(t,e,r){var n=t[0],i=t[1],a=e[0],s=e[1];return i2?$5e:G5e,u=h=null,d}o(f,"rescale");function d(p){return p==null||isNaN(p=+p)?a:(u||(u=l(t.map(n),e,r)))(n(s(p)))}return o(d,"scale"),d.invert=function(p){return s(i((h||(h=l(e,t.map(n),ji)))(p)))},d.domain=function(p){return arguments.length?(t=Array.from(p,L8),f()):t.slice()},d.range=function(p){return arguments.length?(e=Array.from(p),f()):e.slice()},d.rangeRound=function(p){return e=Array.from(p),r=j4,f()},d.clamp=function(p){return arguments.length?(s=p?!0:Pp,f()):s!==Pp},d.interpolate=function(p){return arguments.length?(r=p,f()):r},d.unknown=function(p){return arguments.length?(a=p,d):a},function(p,m){return n=p,i=m,f()}}function By(){return V5e()(Pp,Pp)}var WH,R8=R(()=>{"use strict";bh();Np();HH();YH();WH=[0,1];o(Pp,"identity");o(D8,"normalize");o(z5e,"clamper");o(G5e,"bimap");o($5e,"polymap");o(x3,"copy");o(V5e,"transformer");o(By,"continuous")});function N8(t,e,r,n){var i=Tp(t,e,r),a;switch(n=Sh(n??",f"),n.type){case"s":{var s=Math.max(Math.abs(t),Math.abs(e));return n.precision==null&&!isNaN(a=y3(i,s))&&(n.precision=a),m3(n,s)}case"":case"e":case"g":case"p":case"r":{n.precision==null&&!isNaN(a=v3(i,Math.max(Math.abs(t),Math.abs(e))))&&(n.precision=a-(n.type==="e"));break}case"f":case"%":{n.precision==null&&!isNaN(a=g3(i))&&(n.precision=a-(n.type==="%")*2);break}}return p3(n)}var qH=R(()=>{"use strict";bh();C8();o(N8,"tickFormat")});function U5e(t){var e=t.domain;return t.ticks=function(r){var n=e();return N4(n[0],n[n.length-1],r??10)},t.tickFormat=function(r,n){var i=e();return N8(i[0],i[i.length-1],r??10,n)},t.nice=function(r){r==null&&(r=10);var n=e(),i=0,a=n.length-1,s=n[i],l=n[a],u,h,f=10;for(l0;){if(h=fy(s,l,r),h===u)return n[i]=s,n[a]=l,e(n);if(h>0)s=Math.floor(s/h)*h,l=Math.ceil(l/h)*h;else if(h<0)s=Math.ceil(s*h)/h,l=Math.floor(l*h)/h;else break;u=h}return t},t}function gl(){var t=By();return t.copy=function(){return x3(t,gl())},Ah.apply(t,arguments),U5e(t)}var XH=R(()=>{"use strict";bh();R8();Py();qH();o(U5e,"linearish");o(gl,"linear")});function M8(t,e){t=t.slice();var r=0,n=t.length-1,i=t[r],a=t[n],s;return a{"use strict";o(M8,"nice")});function dn(t,e,r,n){function i(a){return t(a=arguments.length===0?new Date:new Date(+a)),a}return o(i,"interval"),i.floor=a=>(t(a=new Date(+a)),a),i.ceil=a=>(t(a=new Date(a-1)),e(a,1),t(a),a),i.round=a=>{let s=i(a),l=i.ceil(a);return a-s(e(a=new Date(+a),s==null?1:Math.floor(s)),a),i.range=(a,s,l)=>{let u=[];if(a=i.ceil(a),l=l==null?1:Math.floor(l),!(a0))return u;let h;do u.push(h=new Date(+a)),e(a,l),t(a);while(hdn(s=>{if(s>=s)for(;t(s),!a(s);)s.setTime(s-1)},(s,l)=>{if(s>=s)if(l<0)for(;++l<=0;)for(;e(s,-1),!a(s););else for(;--l>=0;)for(;e(s,1),!a(s););}),r&&(i.count=(a,s)=>(I8.setTime(+a),O8.setTime(+s),t(I8),t(O8),Math.floor(r(I8,O8))),i.every=a=>(a=Math.floor(a),!isFinite(a)||!(a>0)?null:a>1?i.filter(n?s=>n(s)%a===0:s=>i.count(0,s)%a===0):i)),i}var I8,O8,mu=R(()=>{"use strict";I8=new Date,O8=new Date;o(dn,"timeInterval")});var oc,KH,P8=R(()=>{"use strict";mu();oc=dn(()=>{},(t,e)=>{t.setTime(+t+e)},(t,e)=>e-t);oc.every=t=>(t=Math.floor(t),!isFinite(t)||!(t>0)?null:t>1?dn(e=>{e.setTime(Math.floor(e/t)*t)},(e,r)=>{e.setTime(+e+r*t)},(e,r)=>(r-e)/t):oc);KH=oc.range});var Ks,QH,B8=R(()=>{"use strict";mu();Ks=dn(t=>{t.setTime(t-t.getMilliseconds())},(t,e)=>{t.setTime(+t+e*1e3)},(t,e)=>(e-t)/1e3,t=>t.getUTCSeconds()),QH=Ks.range});var gu,H5e,b3,Y5e,F8=R(()=>{"use strict";mu();gu=dn(t=>{t.setTime(t-t.getMilliseconds()-t.getSeconds()*1e3)},(t,e)=>{t.setTime(+t+e*6e4)},(t,e)=>(e-t)/6e4,t=>t.getMinutes()),H5e=gu.range,b3=dn(t=>{t.setUTCSeconds(0,0)},(t,e)=>{t.setTime(+t+e*6e4)},(t,e)=>(e-t)/6e4,t=>t.getUTCMinutes()),Y5e=b3.range});var yu,W5e,w3,q5e,z8=R(()=>{"use strict";mu();yu=dn(t=>{t.setTime(t-t.getMilliseconds()-t.getSeconds()*1e3-t.getMinutes()*6e4)},(t,e)=>{t.setTime(+t+e*36e5)},(t,e)=>(e-t)/36e5,t=>t.getHours()),W5e=yu.range,w3=dn(t=>{t.setUTCMinutes(0,0,0)},(t,e)=>{t.setTime(+t+e*36e5)},(t,e)=>(e-t)/36e5,t=>t.getUTCHours()),q5e=w3.range});var Do,X5e,zy,j5e,T3,K5e,G8=R(()=>{"use strict";mu();Do=dn(t=>t.setHours(0,0,0,0),(t,e)=>t.setDate(t.getDate()+e),(t,e)=>(e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*6e4)/864e5,t=>t.getDate()-1),X5e=Do.range,zy=dn(t=>{t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCDate(t.getUTCDate()+e)},(t,e)=>(e-t)/864e5,t=>t.getUTCDate()-1),j5e=zy.range,T3=dn(t=>{t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCDate(t.getUTCDate()+e)},(t,e)=>(e-t)/864e5,t=>Math.floor(t/864e5)),K5e=T3.range});function fd(t){return dn(e=>{e.setDate(e.getDate()-(e.getDay()+7-t)%7),e.setHours(0,0,0,0)},(e,r)=>{e.setDate(e.getDate()+r*7)},(e,r)=>(r-e-(r.getTimezoneOffset()-e.getTimezoneOffset())*6e4)/6048e5)}function dd(t){return dn(e=>{e.setUTCDate(e.getUTCDate()-(e.getUTCDay()+7-t)%7),e.setUTCHours(0,0,0,0)},(e,r)=>{e.setUTCDate(e.getUTCDate()+r*7)},(e,r)=>(r-e)/6048e5)}var yl,_h,k3,E3,cc,C3,S3,JH,Q5e,Z5e,J5e,ewe,twe,rwe,pd,Bp,eY,tY,Lh,rY,nY,iY,nwe,iwe,awe,swe,owe,lwe,$8=R(()=>{"use strict";mu();o(fd,"timeWeekday");yl=fd(0),_h=fd(1),k3=fd(2),E3=fd(3),cc=fd(4),C3=fd(5),S3=fd(6),JH=yl.range,Q5e=_h.range,Z5e=k3.range,J5e=E3.range,ewe=cc.range,twe=C3.range,rwe=S3.range;o(dd,"utcWeekday");pd=dd(0),Bp=dd(1),eY=dd(2),tY=dd(3),Lh=dd(4),rY=dd(5),nY=dd(6),iY=pd.range,nwe=Bp.range,iwe=eY.range,awe=tY.range,swe=Lh.range,owe=rY.range,lwe=nY.range});var vu,cwe,A3,uwe,V8=R(()=>{"use strict";mu();vu=dn(t=>{t.setDate(1),t.setHours(0,0,0,0)},(t,e)=>{t.setMonth(t.getMonth()+e)},(t,e)=>e.getMonth()-t.getMonth()+(e.getFullYear()-t.getFullYear())*12,t=>t.getMonth()),cwe=vu.range,A3=dn(t=>{t.setUTCDate(1),t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCMonth(t.getUTCMonth()+e)},(t,e)=>e.getUTCMonth()-t.getUTCMonth()+(e.getUTCFullYear()-t.getUTCFullYear())*12,t=>t.getUTCMonth()),uwe=A3.range});var Qs,hwe,vl,fwe,U8=R(()=>{"use strict";mu();Qs=dn(t=>{t.setMonth(0,1),t.setHours(0,0,0,0)},(t,e)=>{t.setFullYear(t.getFullYear()+e)},(t,e)=>e.getFullYear()-t.getFullYear(),t=>t.getFullYear());Qs.every=t=>!isFinite(t=Math.floor(t))||!(t>0)?null:dn(e=>{e.setFullYear(Math.floor(e.getFullYear()/t)*t),e.setMonth(0,1),e.setHours(0,0,0,0)},(e,r)=>{e.setFullYear(e.getFullYear()+r*t)});hwe=Qs.range,vl=dn(t=>{t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCFullYear(t.getUTCFullYear()+e)},(t,e)=>e.getUTCFullYear()-t.getUTCFullYear(),t=>t.getUTCFullYear());vl.every=t=>!isFinite(t=Math.floor(t))||!(t>0)?null:dn(e=>{e.setUTCFullYear(Math.floor(e.getUTCFullYear()/t)*t),e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)},(e,r)=>{e.setUTCFullYear(e.getUTCFullYear()+r*t)});fwe=vl.range});function sY(t,e,r,n,i,a){let s=[[Ks,1,1e3],[Ks,5,5*1e3],[Ks,15,15*1e3],[Ks,30,30*1e3],[a,1,6e4],[a,5,5*6e4],[a,15,15*6e4],[a,30,30*6e4],[i,1,36e5],[i,3,3*36e5],[i,6,6*36e5],[i,12,12*36e5],[n,1,864e5],[n,2,2*864e5],[r,1,6048e5],[e,1,2592e6],[e,3,3*2592e6],[t,1,31536e6]];function l(h,f,d){let p=fv).right(s,p);if(m===s.length)return t.every(Tp(h/31536e6,f/31536e6,d));if(m===0)return oc.every(Math.max(Tp(h,f,d),1));let[g,y]=s[p/s[m-1][2]{"use strict";bh();P8();B8();F8();z8();G8();$8();V8();U8();o(sY,"ticker");[pwe,mwe]=sY(vl,A3,pd,T3,w3,b3),[H8,Y8]=sY(Qs,vu,yl,Do,yu,gu)});var _3=R(()=>{"use strict";P8();B8();F8();z8();G8();$8();V8();U8();oY()});function W8(t){if(0<=t.y&&t.y<100){var e=new Date(-1,t.m,t.d,t.H,t.M,t.S,t.L);return e.setFullYear(t.y),e}return new Date(t.y,t.m,t.d,t.H,t.M,t.S,t.L)}function q8(t){if(0<=t.y&&t.y<100){var e=new Date(Date.UTC(-1,t.m,t.d,t.H,t.M,t.S,t.L));return e.setUTCFullYear(t.y),e}return new Date(Date.UTC(t.y,t.m,t.d,t.H,t.M,t.S,t.L))}function Gy(t,e,r){return{y:t,m:e,d:r,H:0,M:0,S:0,L:0}}function X8(t){var e=t.dateTime,r=t.date,n=t.time,i=t.periods,a=t.days,s=t.shortDays,l=t.months,u=t.shortMonths,h=$y(i),f=Vy(i),d=$y(a),p=Vy(a),m=$y(s),g=Vy(s),y=$y(l),v=Vy(l),x=$y(u),b=Vy(u),w={a:P,A:F,b:B,B:$,c:null,d:dY,e:dY,f:Fwe,g:Xwe,G:Kwe,H:Owe,I:Pwe,j:Bwe,L:vY,m:zwe,M:Gwe,p:z,q:Y,Q:gY,s:yY,S:$we,u:Vwe,U:Uwe,V:Hwe,w:Ywe,W:Wwe,x:null,X:null,y:qwe,Y:jwe,Z:Qwe,"%":mY},S={a:Q,A:X,b:ie,B:j,c:null,d:pY,e:pY,f:tTe,g:hTe,G:dTe,H:Zwe,I:Jwe,j:eTe,L:bY,m:rTe,M:nTe,p:J,q:Z,Q:gY,s:yY,S:iTe,u:aTe,U:sTe,V:oTe,w:lTe,W:cTe,x:null,X:null,y:uTe,Y:fTe,Z:pTe,"%":mY},T={a:M,A:N,b:k,B:I,c:C,d:hY,e:hY,f:Rwe,g:uY,G:cY,H:fY,I:fY,j:Awe,L:Dwe,m:Swe,M:_we,p:L,q:Cwe,Q:Mwe,s:Iwe,S:Lwe,u:bwe,U:wwe,V:Twe,w:xwe,W:kwe,x:O,X:D,y:uY,Y:cY,Z:Ewe,"%":Nwe};w.x=E(r,w),w.X=E(n,w),w.c=E(e,w),S.x=E(r,S),S.X=E(n,S),S.c=E(e,S);function E(H,q){return function(K){var se=[],ce=-1,ue=0,te=H.length,De,oe,ke;for(K instanceof Date||(K=new Date(+K));++ce53)return null;"w"in se||(se.w=1),"Z"in se?(ue=q8(Gy(se.y,0,1)),te=ue.getUTCDay(),ue=te>4||te===0?Bp.ceil(ue):Bp(ue),ue=zy.offset(ue,(se.V-1)*7),se.y=ue.getUTCFullYear(),se.m=ue.getUTCMonth(),se.d=ue.getUTCDate()+(se.w+6)%7):(ue=W8(Gy(se.y,0,1)),te=ue.getDay(),ue=te>4||te===0?_h.ceil(ue):_h(ue),ue=Do.offset(ue,(se.V-1)*7),se.y=ue.getFullYear(),se.m=ue.getMonth(),se.d=ue.getDate()+(se.w+6)%7)}else("W"in se||"U"in se)&&("w"in se||(se.w="u"in se?se.u%7:"W"in se?1:0),te="Z"in se?q8(Gy(se.y,0,1)).getUTCDay():W8(Gy(se.y,0,1)).getDay(),se.m=0,se.d="W"in se?(se.w+6)%7+se.W*7-(te+5)%7:se.w+se.U*7-(te+6)%7);return"Z"in se?(se.H+=se.Z/100|0,se.M+=se.Z%100,q8(se)):W8(se)}}o(_,"newParse");function A(H,q,K,se){for(var ce=0,ue=q.length,te=K.length,De,oe;ce=te)return-1;if(De=q.charCodeAt(ce++),De===37){if(De=q.charAt(ce++),oe=T[De in lY?q.charAt(ce++):De],!oe||(se=oe(H,K,se))<0)return-1}else if(De!=K.charCodeAt(se++))return-1}return se}o(A,"parseSpecifier");function L(H,q,K){var se=h.exec(q.slice(K));return se?(H.p=f.get(se[0].toLowerCase()),K+se[0].length):-1}o(L,"parsePeriod");function M(H,q,K){var se=m.exec(q.slice(K));return se?(H.w=g.get(se[0].toLowerCase()),K+se[0].length):-1}o(M,"parseShortWeekday");function N(H,q,K){var se=d.exec(q.slice(K));return se?(H.w=p.get(se[0].toLowerCase()),K+se[0].length):-1}o(N,"parseWeekday");function k(H,q,K){var se=x.exec(q.slice(K));return se?(H.m=b.get(se[0].toLowerCase()),K+se[0].length):-1}o(k,"parseShortMonth");function I(H,q,K){var se=y.exec(q.slice(K));return se?(H.m=v.get(se[0].toLowerCase()),K+se[0].length):-1}o(I,"parseMonth");function C(H,q,K){return A(H,e,q,K)}o(C,"parseLocaleDateTime");function O(H,q,K){return A(H,r,q,K)}o(O,"parseLocaleDate");function D(H,q,K){return A(H,n,q,K)}o(D,"parseLocaleTime");function P(H){return s[H.getDay()]}o(P,"formatShortWeekday");function F(H){return a[H.getDay()]}o(F,"formatWeekday");function B(H){return u[H.getMonth()]}o(B,"formatShortMonth");function $(H){return l[H.getMonth()]}o($,"formatMonth");function z(H){return i[+(H.getHours()>=12)]}o(z,"formatPeriod");function Y(H){return 1+~~(H.getMonth()/3)}o(Y,"formatQuarter");function Q(H){return s[H.getUTCDay()]}o(Q,"formatUTCShortWeekday");function X(H){return a[H.getUTCDay()]}o(X,"formatUTCWeekday");function ie(H){return u[H.getUTCMonth()]}o(ie,"formatUTCShortMonth");function j(H){return l[H.getUTCMonth()]}o(j,"formatUTCMonth");function J(H){return i[+(H.getUTCHours()>=12)]}o(J,"formatUTCPeriod");function Z(H){return 1+~~(H.getUTCMonth()/3)}return o(Z,"formatUTCQuarter"),{format:o(function(H){var q=E(H+="",w);return q.toString=function(){return H},q},"format"),parse:o(function(H){var q=_(H+="",!1);return q.toString=function(){return H},q},"parse"),utcFormat:o(function(H){var q=E(H+="",S);return q.toString=function(){return H},q},"utcFormat"),utcParse:o(function(H){var q=_(H+="",!0);return q.toString=function(){return H},q},"utcParse")}}function Pr(t,e,r){var n=t<0?"-":"",i=(n?-t:t)+"",a=i.length;return n+(a[e.toLowerCase(),r]))}function xwe(t,e,r){var n=Ki.exec(e.slice(r,r+1));return n?(t.w=+n[0],r+n[0].length):-1}function bwe(t,e,r){var n=Ki.exec(e.slice(r,r+1));return n?(t.u=+n[0],r+n[0].length):-1}function wwe(t,e,r){var n=Ki.exec(e.slice(r,r+2));return n?(t.U=+n[0],r+n[0].length):-1}function Twe(t,e,r){var n=Ki.exec(e.slice(r,r+2));return n?(t.V=+n[0],r+n[0].length):-1}function kwe(t,e,r){var n=Ki.exec(e.slice(r,r+2));return n?(t.W=+n[0],r+n[0].length):-1}function cY(t,e,r){var n=Ki.exec(e.slice(r,r+4));return n?(t.y=+n[0],r+n[0].length):-1}function uY(t,e,r){var n=Ki.exec(e.slice(r,r+2));return n?(t.y=+n[0]+(+n[0]>68?1900:2e3),r+n[0].length):-1}function Ewe(t,e,r){var n=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(e.slice(r,r+6));return n?(t.Z=n[1]?0:-(n[2]+(n[3]||"00")),r+n[0].length):-1}function Cwe(t,e,r){var n=Ki.exec(e.slice(r,r+1));return n?(t.q=n[0]*3-3,r+n[0].length):-1}function Swe(t,e,r){var n=Ki.exec(e.slice(r,r+2));return n?(t.m=n[0]-1,r+n[0].length):-1}function hY(t,e,r){var n=Ki.exec(e.slice(r,r+2));return n?(t.d=+n[0],r+n[0].length):-1}function Awe(t,e,r){var n=Ki.exec(e.slice(r,r+3));return n?(t.m=0,t.d=+n[0],r+n[0].length):-1}function fY(t,e,r){var n=Ki.exec(e.slice(r,r+2));return n?(t.H=+n[0],r+n[0].length):-1}function _we(t,e,r){var n=Ki.exec(e.slice(r,r+2));return n?(t.M=+n[0],r+n[0].length):-1}function Lwe(t,e,r){var n=Ki.exec(e.slice(r,r+2));return n?(t.S=+n[0],r+n[0].length):-1}function Dwe(t,e,r){var n=Ki.exec(e.slice(r,r+3));return n?(t.L=+n[0],r+n[0].length):-1}function Rwe(t,e,r){var n=Ki.exec(e.slice(r,r+6));return n?(t.L=Math.floor(n[0]/1e3),r+n[0].length):-1}function Nwe(t,e,r){var n=gwe.exec(e.slice(r,r+1));return n?r+n[0].length:-1}function Mwe(t,e,r){var n=Ki.exec(e.slice(r));return n?(t.Q=+n[0],r+n[0].length):-1}function Iwe(t,e,r){var n=Ki.exec(e.slice(r));return n?(t.s=+n[0],r+n[0].length):-1}function dY(t,e){return Pr(t.getDate(),e,2)}function Owe(t,e){return Pr(t.getHours(),e,2)}function Pwe(t,e){return Pr(t.getHours()%12||12,e,2)}function Bwe(t,e){return Pr(1+Do.count(Qs(t),t),e,3)}function vY(t,e){return Pr(t.getMilliseconds(),e,3)}function Fwe(t,e){return vY(t,e)+"000"}function zwe(t,e){return Pr(t.getMonth()+1,e,2)}function Gwe(t,e){return Pr(t.getMinutes(),e,2)}function $we(t,e){return Pr(t.getSeconds(),e,2)}function Vwe(t){var e=t.getDay();return e===0?7:e}function Uwe(t,e){return Pr(yl.count(Qs(t)-1,t),e,2)}function xY(t){var e=t.getDay();return e>=4||e===0?cc(t):cc.ceil(t)}function Hwe(t,e){return t=xY(t),Pr(cc.count(Qs(t),t)+(Qs(t).getDay()===4),e,2)}function Ywe(t){return t.getDay()}function Wwe(t,e){return Pr(_h.count(Qs(t)-1,t),e,2)}function qwe(t,e){return Pr(t.getFullYear()%100,e,2)}function Xwe(t,e){return t=xY(t),Pr(t.getFullYear()%100,e,2)}function jwe(t,e){return Pr(t.getFullYear()%1e4,e,4)}function Kwe(t,e){var r=t.getDay();return t=r>=4||r===0?cc(t):cc.ceil(t),Pr(t.getFullYear()%1e4,e,4)}function Qwe(t){var e=t.getTimezoneOffset();return(e>0?"-":(e*=-1,"+"))+Pr(e/60|0,"0",2)+Pr(e%60,"0",2)}function pY(t,e){return Pr(t.getUTCDate(),e,2)}function Zwe(t,e){return Pr(t.getUTCHours(),e,2)}function Jwe(t,e){return Pr(t.getUTCHours()%12||12,e,2)}function eTe(t,e){return Pr(1+zy.count(vl(t),t),e,3)}function bY(t,e){return Pr(t.getUTCMilliseconds(),e,3)}function tTe(t,e){return bY(t,e)+"000"}function rTe(t,e){return Pr(t.getUTCMonth()+1,e,2)}function nTe(t,e){return Pr(t.getUTCMinutes(),e,2)}function iTe(t,e){return Pr(t.getUTCSeconds(),e,2)}function aTe(t){var e=t.getUTCDay();return e===0?7:e}function sTe(t,e){return Pr(pd.count(vl(t)-1,t),e,2)}function wY(t){var e=t.getUTCDay();return e>=4||e===0?Lh(t):Lh.ceil(t)}function oTe(t,e){return t=wY(t),Pr(Lh.count(vl(t),t)+(vl(t).getUTCDay()===4),e,2)}function lTe(t){return t.getUTCDay()}function cTe(t,e){return Pr(Bp.count(vl(t)-1,t),e,2)}function uTe(t,e){return Pr(t.getUTCFullYear()%100,e,2)}function hTe(t,e){return t=wY(t),Pr(t.getUTCFullYear()%100,e,2)}function fTe(t,e){return Pr(t.getUTCFullYear()%1e4,e,4)}function dTe(t,e){var r=t.getUTCDay();return t=r>=4||r===0?Lh(t):Lh.ceil(t),Pr(t.getUTCFullYear()%1e4,e,4)}function pTe(){return"+0000"}function mY(){return"%"}function gY(t){return+t}function yY(t){return Math.floor(+t/1e3)}var lY,Ki,gwe,ywe,TY=R(()=>{"use strict";_3();o(W8,"localDate");o(q8,"utcDate");o(Gy,"newDate");o(X8,"formatLocale");lY={"-":"",_:" ",0:"0"},Ki=/^\s*\d+/,gwe=/^%/,ywe=/[\\^$*+?|[\]().{}]/g;o(Pr,"pad");o(vwe,"requote");o($y,"formatRe");o(Vy,"formatLookup");o(xwe,"parseWeekdayNumberSunday");o(bwe,"parseWeekdayNumberMonday");o(wwe,"parseWeekNumberSunday");o(Twe,"parseWeekNumberISO");o(kwe,"parseWeekNumberMonday");o(cY,"parseFullYear");o(uY,"parseYear");o(Ewe,"parseZone");o(Cwe,"parseQuarter");o(Swe,"parseMonthNumber");o(hY,"parseDayOfMonth");o(Awe,"parseDayOfYear");o(fY,"parseHour24");o(_we,"parseMinutes");o(Lwe,"parseSeconds");o(Dwe,"parseMilliseconds");o(Rwe,"parseMicroseconds");o(Nwe,"parseLiteralPercent");o(Mwe,"parseUnixTimestamp");o(Iwe,"parseUnixTimestampSeconds");o(dY,"formatDayOfMonth");o(Owe,"formatHour24");o(Pwe,"formatHour12");o(Bwe,"formatDayOfYear");o(vY,"formatMilliseconds");o(Fwe,"formatMicroseconds");o(zwe,"formatMonthNumber");o(Gwe,"formatMinutes");o($we,"formatSeconds");o(Vwe,"formatWeekdayNumberMonday");o(Uwe,"formatWeekNumberSunday");o(xY,"dISO");o(Hwe,"formatWeekNumberISO");o(Ywe,"formatWeekdayNumberSunday");o(Wwe,"formatWeekNumberMonday");o(qwe,"formatYear");o(Xwe,"formatYearISO");o(jwe,"formatFullYear");o(Kwe,"formatFullYearISO");o(Qwe,"formatZone");o(pY,"formatUTCDayOfMonth");o(Zwe,"formatUTCHour24");o(Jwe,"formatUTCHour12");o(eTe,"formatUTCDayOfYear");o(bY,"formatUTCMilliseconds");o(tTe,"formatUTCMicroseconds");o(rTe,"formatUTCMonthNumber");o(nTe,"formatUTCMinutes");o(iTe,"formatUTCSeconds");o(aTe,"formatUTCWeekdayNumberMonday");o(sTe,"formatUTCWeekNumberSunday");o(wY,"UTCdISO");o(oTe,"formatUTCWeekNumberISO");o(lTe,"formatUTCWeekdayNumberSunday");o(cTe,"formatUTCWeekNumberMonday");o(uTe,"formatUTCYear");o(hTe,"formatUTCYearISO");o(fTe,"formatUTCFullYear");o(dTe,"formatUTCFullYearISO");o(pTe,"formatUTCZone");o(mY,"formatLiteralPercent");o(gY,"formatUnixTimestamp");o(yY,"formatUnixTimestampSeconds")});function j8(t){return Fp=X8(t),md=Fp.format,kY=Fp.parse,EY=Fp.utcFormat,CY=Fp.utcParse,Fp}var Fp,md,kY,EY,CY,SY=R(()=>{"use strict";TY();j8({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});o(j8,"defaultLocale")});var K8=R(()=>{"use strict";SY()});function mTe(t){return new Date(t)}function gTe(t){return t instanceof Date?+t:+new Date(+t)}function AY(t,e,r,n,i,a,s,l,u,h){var f=By(),d=f.invert,p=f.domain,m=h(".%L"),g=h(":%S"),y=h("%I:%M"),v=h("%I %p"),x=h("%a %d"),b=h("%b %d"),w=h("%B"),S=h("%Y");function T(E){return(u(E){"use strict";_3();K8();R8();Py();jH();o(mTe,"date");o(gTe,"number");o(AY,"calendar");o(L3,"time")});var LY=R(()=>{"use strict";UH();XH();A8();_Y()});function Q8(t){for(var e=t.length/6|0,r=new Array(e),n=0;n{"use strict";o(Q8,"default")});var Z8,RY=R(()=>{"use strict";DY();Z8=Q8("4e79a7f28e2ce1575976b7b259a14fedc949af7aa1ff9da79c755fbab0ab")});var NY=R(()=>{"use strict";RY()});function Nn(t){return o(function(){return t},"constant")}var D3=R(()=>{"use strict";o(Nn,"default")});function IY(t){return t>1?0:t<-1?zp:Math.acos(t)}function e_(t){return t>=1?Uy:t<=-1?-Uy:Math.asin(t)}var J8,ua,Dh,MY,R3,xl,gd,Qi,zp,Uy,Gp,N3=R(()=>{"use strict";J8=Math.abs,ua=Math.atan2,Dh=Math.cos,MY=Math.max,R3=Math.min,xl=Math.sin,gd=Math.sqrt,Qi=1e-12,zp=Math.PI,Uy=zp/2,Gp=2*zp;o(IY,"acos");o(e_,"asin")});function M3(t){let e=3;return t.digits=function(r){if(!arguments.length)return e;if(r==null)e=null;else{let n=Math.floor(r);if(!(n>=0))throw new RangeError(`invalid digits: ${r}`);e=n}return t},()=>new ld(e)}var t_=R(()=>{"use strict";d8();o(M3,"withPath")});function yTe(t){return t.innerRadius}function vTe(t){return t.outerRadius}function xTe(t){return t.startAngle}function bTe(t){return t.endAngle}function wTe(t){return t&&t.padAngle}function TTe(t,e,r,n,i,a,s,l){var u=r-t,h=n-e,f=s-i,d=l-a,p=d*u-f*h;if(!(p*pC*C+O*O&&(A=M,L=N),{cx:A,cy:L,x01:-f,y01:-d,x11:A*(i/T-1),y11:L*(i/T-1)}}function bl(){var t=yTe,e=vTe,r=Nn(0),n=null,i=xTe,a=bTe,s=wTe,l=null,u=M3(h);function h(){var f,d,p=+t.apply(this,arguments),m=+e.apply(this,arguments),g=i.apply(this,arguments)-Uy,y=a.apply(this,arguments)-Uy,v=J8(y-g),x=y>g;if(l||(l=f=u()),mQi))l.moveTo(0,0);else if(v>Gp-Qi)l.moveTo(m*Dh(g),m*xl(g)),l.arc(0,0,m,g,y,!x),p>Qi&&(l.moveTo(p*Dh(y),p*xl(y)),l.arc(0,0,p,y,g,x));else{var b=g,w=y,S=g,T=y,E=v,_=v,A=s.apply(this,arguments)/2,L=A>Qi&&(n?+n.apply(this,arguments):gd(p*p+m*m)),M=R3(J8(m-p)/2,+r.apply(this,arguments)),N=M,k=M,I,C;if(L>Qi){var O=e_(L/p*xl(A)),D=e_(L/m*xl(A));(E-=O*2)>Qi?(O*=x?1:-1,S+=O,T-=O):(E=0,S=T=(g+y)/2),(_-=D*2)>Qi?(D*=x?1:-1,b+=D,w-=D):(_=0,b=w=(g+y)/2)}var P=m*Dh(b),F=m*xl(b),B=p*Dh(T),$=p*xl(T);if(M>Qi){var z=m*Dh(w),Y=m*xl(w),Q=p*Dh(S),X=p*xl(S),ie;if(vQi?k>Qi?(I=I3(Q,X,P,F,m,k,x),C=I3(z,Y,B,$,m,k,x),l.moveTo(I.cx+I.x01,I.cy+I.y01),kQi)||!(E>Qi)?l.lineTo(B,$):N>Qi?(I=I3(B,$,z,Y,p,-N,x),C=I3(P,F,Q,X,p,-N,x),l.lineTo(I.cx+I.x01,I.cy+I.y01),N{"use strict";D3();N3();t_();o(yTe,"arcInnerRadius");o(vTe,"arcOuterRadius");o(xTe,"arcStartAngle");o(bTe,"arcEndAngle");o(wTe,"arcPadAngle");o(TTe,"intersect");o(I3,"cornerTangents");o(bl,"default")});function Hy(t){return typeof t=="object"&&"length"in t?t:Array.from(t)}var Dyt,r_=R(()=>{"use strict";Dyt=Array.prototype.slice;o(Hy,"default")});function PY(t){this._context=t}function xu(t){return new PY(t)}var n_=R(()=>{"use strict";o(PY,"Linear");PY.prototype={areaStart:o(function(){this._line=0},"areaStart"),areaEnd:o(function(){this._line=NaN},"areaEnd"),lineStart:o(function(){this._point=0},"lineStart"),lineEnd:o(function(){(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},"lineEnd"),point:o(function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;default:this._context.lineTo(t,e);break}},"point")};o(xu,"default")});function BY(t){return t[0]}function FY(t){return t[1]}var zY=R(()=>{"use strict";o(BY,"x");o(FY,"y")});function ha(t,e){var r=Nn(!0),n=null,i=xu,a=null,s=M3(l);t=typeof t=="function"?t:t===void 0?BY:Nn(t),e=typeof e=="function"?e:e===void 0?FY:Nn(e);function l(u){var h,f=(u=Hy(u)).length,d,p=!1,m;for(n==null&&(a=i(m=s())),h=0;h<=f;++h)!(h{"use strict";r_();D3();n_();t_();zY();o(ha,"default")});function i_(t,e){return et?1:e>=t?0:NaN}var $Y=R(()=>{"use strict";o(i_,"default")});function a_(t){return t}var VY=R(()=>{"use strict";o(a_,"default")});function O3(){var t=a_,e=i_,r=null,n=Nn(0),i=Nn(Gp),a=Nn(0);function s(l){var u,h=(l=Hy(l)).length,f,d,p=0,m=new Array(h),g=new Array(h),y=+n.apply(this,arguments),v=Math.min(Gp,Math.max(-Gp,i.apply(this,arguments)-y)),x,b=Math.min(Math.abs(v)/h,a.apply(this,arguments)),w=b*(v<0?-1:1),S;for(u=0;u0&&(p+=S);for(e!=null?m.sort(function(T,E){return e(g[T],g[E])}):r!=null&&m.sort(function(T,E){return r(l[T],l[E])}),u=0,d=p?(v-h*w)/p:0;u0?S*d:0)+w,g[f]={data:l[f],index:u,value:S,startAngle:y,endAngle:x,padAngle:b};return g}return o(s,"pie"),s.value=function(l){return arguments.length?(t=typeof l=="function"?l:Nn(+l),s):t},s.sortValues=function(l){return arguments.length?(e=l,r=null,s):e},s.sort=function(l){return arguments.length?(r=l,e=null,s):r},s.startAngle=function(l){return arguments.length?(n=typeof l=="function"?l:Nn(+l),s):n},s.endAngle=function(l){return arguments.length?(i=typeof l=="function"?l:Nn(+l),s):i},s.padAngle=function(l){return arguments.length?(a=typeof l=="function"?l:Nn(+l),s):a},s}var UY=R(()=>{"use strict";r_();D3();$Y();VY();N3();o(O3,"default")});function s_(t){return new P3(t,!0)}function o_(t){return new P3(t,!1)}var P3,HY=R(()=>{"use strict";P3=class{static{o(this,"Bump")}constructor(e,r){this._context=e,this._x=r}areaStart(){this._line=0}areaEnd(){this._line=NaN}lineStart(){this._point=0}lineEnd(){(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line}point(e,r){switch(e=+e,r=+r,this._point){case 0:{this._point=1,this._line?this._context.lineTo(e,r):this._context.moveTo(e,r);break}case 1:this._point=2;default:{this._x?this._context.bezierCurveTo(this._x0=(this._x0+e)/2,this._y0,this._x0,r,e,r):this._context.bezierCurveTo(this._x0,this._y0=(this._y0+r)/2,e,this._y0,e,r);break}}this._x0=e,this._y0=r}};o(s_,"bumpX");o(o_,"bumpY")});function Zs(){}var Yy=R(()=>{"use strict";o(Zs,"default")});function $p(t,e,r){t._context.bezierCurveTo((2*t._x0+t._x1)/3,(2*t._y0+t._y1)/3,(t._x0+2*t._x1)/3,(t._y0+2*t._y1)/3,(t._x0+4*t._x1+e)/6,(t._y0+4*t._y1+r)/6)}function Wy(t){this._context=t}function vs(t){return new Wy(t)}var qy=R(()=>{"use strict";o($p,"point");o(Wy,"Basis");Wy.prototype={areaStart:o(function(){this._line=0},"areaStart"),areaEnd:o(function(){this._line=NaN},"areaEnd"),lineStart:o(function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},"lineStart"),lineEnd:o(function(){switch(this._point){case 3:$p(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1);break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},"lineEnd"),point:o(function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:$p(this,t,e);break}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e},"point")};o(vs,"default")});function YY(t){this._context=t}function B3(t){return new YY(t)}var WY=R(()=>{"use strict";Yy();qy();o(YY,"BasisClosed");YY.prototype={areaStart:Zs,areaEnd:Zs,lineStart:o(function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},"lineStart"),lineEnd:o(function(){switch(this._point){case 1:{this._context.moveTo(this._x2,this._y2),this._context.closePath();break}case 2:{this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break}case 3:{this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4);break}}},"lineEnd"),point:o(function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x2=t,this._y2=e;break;case 1:this._point=2,this._x3=t,this._y3=e;break;case 2:this._point=3,this._x4=t,this._y4=e,this._context.moveTo((this._x0+4*this._x1+t)/6,(this._y0+4*this._y1+e)/6);break;default:$p(this,t,e);break}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e},"point")};o(B3,"default")});function qY(t){this._context=t}function F3(t){return new qY(t)}var XY=R(()=>{"use strict";qy();o(qY,"BasisOpen");qY.prototype={areaStart:o(function(){this._line=0},"areaStart"),areaEnd:o(function(){this._line=NaN},"areaEnd"),lineStart:o(function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},"lineStart"),lineEnd:o(function(){(this._line||this._line!==0&&this._point===3)&&this._context.closePath(),this._line=1-this._line},"lineEnd"),point:o(function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var r=(this._x0+4*this._x1+t)/6,n=(this._y0+4*this._y1+e)/6;this._line?this._context.lineTo(r,n):this._context.moveTo(r,n);break;case 3:this._point=4;default:$p(this,t,e);break}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e},"point")};o(F3,"default")});function jY(t,e){this._basis=new Wy(t),this._beta=e}var l_,KY=R(()=>{"use strict";qy();o(jY,"Bundle");jY.prototype={lineStart:o(function(){this._x=[],this._y=[],this._basis.lineStart()},"lineStart"),lineEnd:o(function(){var t=this._x,e=this._y,r=t.length-1;if(r>0)for(var n=t[0],i=e[0],a=t[r]-n,s=e[r]-i,l=-1,u;++l<=r;)u=l/r,this._basis.point(this._beta*t[l]+(1-this._beta)*(n+u*a),this._beta*e[l]+(1-this._beta)*(i+u*s));this._x=this._y=null,this._basis.lineEnd()},"lineEnd"),point:o(function(t,e){this._x.push(+t),this._y.push(+e)},"point")};l_=o(function t(e){function r(n){return e===1?new Wy(n):new jY(n,e)}return o(r,"bundle"),r.beta=function(n){return t(+n)},r},"custom")(.85)});function Vp(t,e,r){t._context.bezierCurveTo(t._x1+t._k*(t._x2-t._x0),t._y1+t._k*(t._y2-t._y0),t._x2+t._k*(t._x1-e),t._y2+t._k*(t._y1-r),t._x2,t._y2)}function z3(t,e){this._context=t,this._k=(1-e)/6}var c_,Xy=R(()=>{"use strict";o(Vp,"point");o(z3,"Cardinal");z3.prototype={areaStart:o(function(){this._line=0},"areaStart"),areaEnd:o(function(){this._line=NaN},"areaEnd"),lineStart:o(function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},"lineStart"),lineEnd:o(function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:Vp(this,this._x1,this._y1);break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},"lineEnd"),point:o(function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2,this._x1=t,this._y1=e;break;case 2:this._point=3;default:Vp(this,t,e);break}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e},"point")};c_=o(function t(e){function r(n){return new z3(n,e)}return o(r,"cardinal"),r.tension=function(n){return t(+n)},r},"custom")(0)});function G3(t,e){this._context=t,this._k=(1-e)/6}var u_,h_=R(()=>{"use strict";Yy();Xy();o(G3,"CardinalClosed");G3.prototype={areaStart:Zs,areaEnd:Zs,lineStart:o(function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},"lineStart"),lineEnd:o(function(){switch(this._point){case 1:{this._context.moveTo(this._x3,this._y3),this._context.closePath();break}case 2:{this._context.lineTo(this._x3,this._y3),this._context.closePath();break}case 3:{this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5);break}}},"lineEnd"),point:o(function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x3=t,this._y3=e;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=e);break;case 2:this._point=3,this._x5=t,this._y5=e;break;default:Vp(this,t,e);break}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e},"point")};u_=o(function t(e){function r(n){return new G3(n,e)}return o(r,"cardinal"),r.tension=function(n){return t(+n)},r},"custom")(0)});function $3(t,e){this._context=t,this._k=(1-e)/6}var f_,d_=R(()=>{"use strict";Xy();o($3,"CardinalOpen");$3.prototype={areaStart:o(function(){this._line=0},"areaStart"),areaEnd:o(function(){this._line=NaN},"areaEnd"),lineStart:o(function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},"lineStart"),lineEnd:o(function(){(this._line||this._line!==0&&this._point===3)&&this._context.closePath(),this._line=1-this._line},"lineEnd"),point:o(function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:Vp(this,t,e);break}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e},"point")};f_=o(function t(e){function r(n){return new $3(n,e)}return o(r,"cardinal"),r.tension=function(n){return t(+n)},r},"custom")(0)});function jy(t,e,r){var n=t._x1,i=t._y1,a=t._x2,s=t._y2;if(t._l01_a>Qi){var l=2*t._l01_2a+3*t._l01_a*t._l12_a+t._l12_2a,u=3*t._l01_a*(t._l01_a+t._l12_a);n=(n*l-t._x0*t._l12_2a+t._x2*t._l01_2a)/u,i=(i*l-t._y0*t._l12_2a+t._y2*t._l01_2a)/u}if(t._l23_a>Qi){var h=2*t._l23_2a+3*t._l23_a*t._l12_a+t._l12_2a,f=3*t._l23_a*(t._l23_a+t._l12_a);a=(a*h+t._x1*t._l23_2a-e*t._l12_2a)/f,s=(s*h+t._y1*t._l23_2a-r*t._l12_2a)/f}t._context.bezierCurveTo(n,i,a,s,t._x2,t._y2)}function QY(t,e){this._context=t,this._alpha=e}var p_,V3=R(()=>{"use strict";N3();Xy();o(jy,"point");o(QY,"CatmullRom");QY.prototype={areaStart:o(function(){this._line=0},"areaStart"),areaEnd:o(function(){this._line=NaN},"areaEnd"),lineStart:o(function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},"lineStart"),lineEnd:o(function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2);break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},"lineEnd"),point:o(function(t,e){if(t=+t,e=+e,this._point){var r=this._x2-t,n=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(r*r+n*n,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3;default:jy(this,t,e);break}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e},"point")};p_=o(function t(e){function r(n){return e?new QY(n,e):new z3(n,0)}return o(r,"catmullRom"),r.alpha=function(n){return t(+n)},r},"custom")(.5)});function ZY(t,e){this._context=t,this._alpha=e}var m_,JY=R(()=>{"use strict";h_();Yy();V3();o(ZY,"CatmullRomClosed");ZY.prototype={areaStart:Zs,areaEnd:Zs,lineStart:o(function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},"lineStart"),lineEnd:o(function(){switch(this._point){case 1:{this._context.moveTo(this._x3,this._y3),this._context.closePath();break}case 2:{this._context.lineTo(this._x3,this._y3),this._context.closePath();break}case 3:{this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5);break}}},"lineEnd"),point:o(function(t,e){if(t=+t,e=+e,this._point){var r=this._x2-t,n=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(r*r+n*n,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=t,this._y3=e;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=e);break;case 2:this._point=3,this._x5=t,this._y5=e;break;default:jy(this,t,e);break}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e},"point")};m_=o(function t(e){function r(n){return e?new ZY(n,e):new G3(n,0)}return o(r,"catmullRom"),r.alpha=function(n){return t(+n)},r},"custom")(.5)});function eW(t,e){this._context=t,this._alpha=e}var g_,tW=R(()=>{"use strict";d_();V3();o(eW,"CatmullRomOpen");eW.prototype={areaStart:o(function(){this._line=0},"areaStart"),areaEnd:o(function(){this._line=NaN},"areaEnd"),lineStart:o(function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},"lineStart"),lineEnd:o(function(){(this._line||this._line!==0&&this._point===3)&&this._context.closePath(),this._line=1-this._line},"lineEnd"),point:o(function(t,e){if(t=+t,e=+e,this._point){var r=this._x2-t,n=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(r*r+n*n,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:jy(this,t,e);break}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e},"point")};g_=o(function t(e){function r(n){return e?new eW(n,e):new $3(n,0)}return o(r,"catmullRom"),r.alpha=function(n){return t(+n)},r},"custom")(.5)});function rW(t){this._context=t}function U3(t){return new rW(t)}var nW=R(()=>{"use strict";Yy();o(rW,"LinearClosed");rW.prototype={areaStart:Zs,areaEnd:Zs,lineStart:o(function(){this._point=0},"lineStart"),lineEnd:o(function(){this._point&&this._context.closePath()},"lineEnd"),point:o(function(t,e){t=+t,e=+e,this._point?this._context.lineTo(t,e):(this._point=1,this._context.moveTo(t,e))},"point")};o(U3,"default")});function iW(t){return t<0?-1:1}function aW(t,e,r){var n=t._x1-t._x0,i=e-t._x1,a=(t._y1-t._y0)/(n||i<0&&-0),s=(r-t._y1)/(i||n<0&&-0),l=(a*i+s*n)/(n+i);return(iW(a)+iW(s))*Math.min(Math.abs(a),Math.abs(s),.5*Math.abs(l))||0}function sW(t,e){var r=t._x1-t._x0;return r?(3*(t._y1-t._y0)/r-e)/2:e}function y_(t,e,r){var n=t._x0,i=t._y0,a=t._x1,s=t._y1,l=(a-n)/3;t._context.bezierCurveTo(n+l,i+l*e,a-l,s-l*r,a,s)}function H3(t){this._context=t}function oW(t){this._context=new lW(t)}function lW(t){this._context=t}function v_(t){return new H3(t)}function x_(t){return new oW(t)}var cW=R(()=>{"use strict";o(iW,"sign");o(aW,"slope3");o(sW,"slope2");o(y_,"point");o(H3,"MonotoneX");H3.prototype={areaStart:o(function(){this._line=0},"areaStart"),areaEnd:o(function(){this._line=NaN},"areaEnd"),lineStart:o(function(){this._x0=this._x1=this._y0=this._y1=this._t0=NaN,this._point=0},"lineStart"),lineEnd:o(function(){switch(this._point){case 2:this._context.lineTo(this._x1,this._y1);break;case 3:y_(this,this._t0,sW(this,this._t0));break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},"lineEnd"),point:o(function(t,e){var r=NaN;if(t=+t,e=+e,!(t===this._x1&&e===this._y1)){switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3,y_(this,sW(this,r=aW(this,t,e)),r);break;default:y_(this,this._t0,r=aW(this,t,e));break}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e,this._t0=r}},"point")};o(oW,"MonotoneY");(oW.prototype=Object.create(H3.prototype)).point=function(t,e){H3.prototype.point.call(this,e,t)};o(lW,"ReflectContext");lW.prototype={moveTo:o(function(t,e){this._context.moveTo(e,t)},"moveTo"),closePath:o(function(){this._context.closePath()},"closePath"),lineTo:o(function(t,e){this._context.lineTo(e,t)},"lineTo"),bezierCurveTo:o(function(t,e,r,n,i,a){this._context.bezierCurveTo(e,t,n,r,a,i)},"bezierCurveTo")};o(v_,"monotoneX");o(x_,"monotoneY")});function hW(t){this._context=t}function uW(t){var e,r=t.length-1,n,i=new Array(r),a=new Array(r),s=new Array(r);for(i[0]=0,a[0]=2,s[0]=t[0]+2*t[1],e=1;e=0;--e)i[e]=(s[e]-i[e+1])/a[e];for(a[r-1]=(t[r]+i[r-1])/2,e=0;e{"use strict";o(hW,"Natural");hW.prototype={areaStart:o(function(){this._line=0},"areaStart"),areaEnd:o(function(){this._line=NaN},"areaEnd"),lineStart:o(function(){this._x=[],this._y=[]},"lineStart"),lineEnd:o(function(){var t=this._x,e=this._y,r=t.length;if(r)if(this._line?this._context.lineTo(t[0],e[0]):this._context.moveTo(t[0],e[0]),r===2)this._context.lineTo(t[1],e[1]);else for(var n=uW(t),i=uW(e),a=0,s=1;s{"use strict";o(W3,"Step");W3.prototype={areaStart:o(function(){this._line=0},"areaStart"),areaEnd:o(function(){this._line=NaN},"areaEnd"),lineStart:o(function(){this._x=this._y=NaN,this._point=0},"lineStart"),lineEnd:o(function(){0=0&&(this._t=1-this._t,this._line=1-this._line)},"lineEnd"),point:o(function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;default:{if(this._t<=0)this._context.lineTo(this._x,e),this._context.lineTo(t,e);else{var r=this._x*(1-this._t)+t*this._t;this._context.lineTo(r,this._y),this._context.lineTo(r,e)}break}}this._x=t,this._y=e},"point")};o(q3,"default");o(b_,"stepBefore");o(w_,"stepAfter")});var pW=R(()=>{"use strict";OY();GY();UY();WY();XY();qy();HY();KY();h_();d_();Xy();JY();tW();V3();nW();n_();cW();fW();dW()});var mW=R(()=>{"use strict"});var gW=R(()=>{"use strict"});function Rh(t,e,r){this.k=t,this.x=e,this.y=r}function k_(t){for(;!t.__zoom;)if(!(t=t.parentNode))return T_;return t.__zoom}var T_,E_=R(()=>{"use strict";o(Rh,"Transform");Rh.prototype={constructor:Rh,scale:o(function(t){return t===1?this:new Rh(this.k*t,this.x,this.y)},"scale"),translate:o(function(t,e){return t===0&e===0?this:new Rh(this.k,this.x+this.k*t,this.y+this.k*e)},"translate"),apply:o(function(t){return[t[0]*this.k+this.x,t[1]*this.k+this.y]},"apply"),applyX:o(function(t){return t*this.k+this.x},"applyX"),applyY:o(function(t){return t*this.k+this.y},"applyY"),invert:o(function(t){return[(t[0]-this.x)/this.k,(t[1]-this.y)/this.k]},"invert"),invertX:o(function(t){return(t-this.x)/this.k},"invertX"),invertY:o(function(t){return(t-this.y)/this.k},"invertY"),rescaleX:o(function(t){return t.copy().domain(t.range().map(this.invertX,this).map(t.invert,t))},"rescaleX"),rescaleY:o(function(t){return t.copy().domain(t.range().map(this.invertY,this).map(t.invert,t))},"rescaleY"),toString:o(function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"},"toString")};T_=new Rh(1,0,0);k_.prototype=Rh.prototype;o(k_,"transform")});var yW=R(()=>{"use strict"});var vW=R(()=>{"use strict";c3();mW();gW();E_();yW()});var xW=R(()=>{"use strict";vW();E_()});var Zt=R(()=>{"use strict";bh();K$();mH();xH();Lp();bH();wH();bS();$V();TH();l8();kH();CH();C8();zH();GH();Np();d8();$H();EH();VH();LY();NY();fl();pW();_3();K8();n3();c3();xW()});var bW=gi(Zi=>{"use strict";Object.defineProperty(Zi,"__esModule",{value:!0});Zi.BLANK_URL=Zi.relativeFirstCharacters=Zi.whitespaceEscapeCharsRegex=Zi.urlSchemeRegex=Zi.ctrlCharactersRegex=Zi.htmlCtrlEntityRegex=Zi.htmlEntitiesRegex=Zi.invalidProtocolRegex=void 0;Zi.invalidProtocolRegex=/^([^\w]*)(javascript|data|vbscript)/im;Zi.htmlEntitiesRegex=/&#(\w+)(^\w|;)?/g;Zi.htmlCtrlEntityRegex=/&(newline|tab);/gi;Zi.ctrlCharactersRegex=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim;Zi.urlSchemeRegex=/^.+(:|:)/gim;Zi.whitespaceEscapeCharsRegex=/(\\|%5[cC])((%(6[eE]|72|74))|[nrt])/g;Zi.relativeFirstCharacters=[".","/"];Zi.BLANK_URL="about:blank"});var Up=gi(X3=>{"use strict";Object.defineProperty(X3,"__esModule",{value:!0});X3.sanitizeUrl=void 0;var Na=bW();function kTe(t){return Na.relativeFirstCharacters.indexOf(t[0])>-1}o(kTe,"isRelativeUrlWithoutProtocol");function ETe(t){var e=t.replace(Na.ctrlCharactersRegex,"");return e.replace(Na.htmlEntitiesRegex,function(r,n){return String.fromCharCode(n)})}o(ETe,"decodeHtmlCharacters");function CTe(t){return URL.canParse(t)}o(CTe,"isValidUrl");function wW(t){try{return decodeURIComponent(t)}catch{return t}}o(wW,"decodeURI");function STe(t){if(!t)return Na.BLANK_URL;var e,r=wW(t.trim());do r=ETe(r).replace(Na.htmlCtrlEntityRegex,"").replace(Na.ctrlCharactersRegex,"").replace(Na.whitespaceEscapeCharsRegex,"").trim(),r=wW(r),e=r.match(Na.ctrlCharactersRegex)||r.match(Na.htmlEntitiesRegex)||r.match(Na.htmlCtrlEntityRegex)||r.match(Na.whitespaceEscapeCharsRegex);while(e&&e.length>0);var n=r;if(!n)return Na.BLANK_URL;if(kTe(n))return n;var i=n.trimStart(),a=i.match(Na.urlSchemeRegex);if(!a)return n;var s=a[0].toLowerCase().trim();if(Na.invalidProtocolRegex.test(s))return Na.BLANK_URL;var l=i.replace(/\\/g,"/");if(s==="mailto:"||s.includes("://"))return l;if(s==="http:"||s==="https:"){if(!CTe(l))return Na.BLANK_URL;var u=new URL(l);return u.protocol=u.protocol.toLowerCase(),u.hostname=u.hostname.toLowerCase(),u.toString()}return l}o(STe,"sanitizeUrl");X3.sanitizeUrl=STe});var C_,yd,j3,TW,kW,EW,wl,Ky,Qy=R(()=>{"use strict";C_=Xi(Up(),1);rr();yd=o((t,e)=>{let r=t.append("rect");if(r.attr("x",e.x),r.attr("y",e.y),r.attr("fill",e.fill),r.attr("stroke",e.stroke),r.attr("width",e.width),r.attr("height",e.height),e.name&&r.attr("name",e.name),e.rx&&r.attr("rx",e.rx),e.ry&&r.attr("ry",e.ry),e.attrs!==void 0)for(let n in e.attrs)r.attr(n,e.attrs[n]);return e.class&&r.attr("class",e.class),r},"drawRect"),j3=o((t,e)=>{let r={x:e.startx,y:e.starty,width:e.stopx-e.startx,height:e.stopy-e.starty,fill:e.fill,stroke:e.stroke,class:"rect"};yd(t,r).lower()},"drawBackgroundRect"),TW=o((t,e)=>{let r=e.text.replace(Qf," "),n=t.append("text");n.attr("x",e.x),n.attr("y",e.y),n.attr("class","legend"),n.style("text-anchor",e.anchor),e.class&&n.attr("class",e.class);let i=n.append("tspan");return i.attr("x",e.x+e.textMargin*2),i.text(r),n},"drawText"),kW=o((t,e,r,n)=>{let i=t.append("image");i.attr("x",e),i.attr("y",r);let a=(0,C_.sanitizeUrl)(n);i.attr("xlink:href",a)},"drawImage"),EW=o((t,e,r,n)=>{let i=t.append("use");i.attr("x",e),i.attr("y",r);let a=(0,C_.sanitizeUrl)(n);i.attr("xlink:href",`#${a}`)},"drawEmbeddedImage"),wl=o(()=>({x:0,y:0,width:100,height:100,fill:"#EDF2AE",stroke:"#666",anchor:"start",rx:0,ry:0}),"getNoteRect"),Ky=o(()=>({x:0,y:0,width:100,height:100,"text-anchor":"start",style:"#666",textMargin:0,rx:0,ry:0,tspan:!0}),"getTextObj")});var CW,S_,SW,ATe,_Te,LTe,DTe,RTe,NTe,MTe,ITe,OTe,PTe,BTe,FTe,bu,Tl,AW=R(()=>{"use strict";rr();Qy();CW=Xi(Up(),1),S_=o(function(t,e){return yd(t,e)},"drawRect"),SW=o(function(t,e,r,n,i,a){let s=t.append("image");s.attr("width",e),s.attr("height",r),s.attr("x",n),s.attr("y",i);let l=a.startsWith("data:image/png;base64")?a:(0,CW.sanitizeUrl)(a);s.attr("xlink:href",l)},"drawImage"),ATe=o((t,e,r)=>{let n=t.append("g"),i=0;for(let a of e){let s=a.textColor?a.textColor:"#444444",l=a.lineColor?a.lineColor:"#444444",u=a.offsetX?parseInt(a.offsetX):0,h=a.offsetY?parseInt(a.offsetY):0,f="";if(i===0){let p=n.append("line");p.attr("x1",a.startPoint.x),p.attr("y1",a.startPoint.y),p.attr("x2",a.endPoint.x),p.attr("y2",a.endPoint.y),p.attr("stroke-width","1"),p.attr("stroke",l),p.style("fill","none"),a.type!=="rel_b"&&p.attr("marker-end","url("+f+"#arrowhead)"),(a.type==="birel"||a.type==="rel_b")&&p.attr("marker-start","url("+f+"#arrowend)"),i=-1}else{let p=n.append("path");p.attr("fill","none").attr("stroke-width","1").attr("stroke",l).attr("d","Mstartx,starty Qcontrolx,controly stopx,stopy ".replaceAll("startx",a.startPoint.x).replaceAll("starty",a.startPoint.y).replaceAll("controlx",a.startPoint.x+(a.endPoint.x-a.startPoint.x)/2-(a.endPoint.x-a.startPoint.x)/4).replaceAll("controly",a.startPoint.y+(a.endPoint.y-a.startPoint.y)/2).replaceAll("stopx",a.endPoint.x).replaceAll("stopy",a.endPoint.y)),a.type!=="rel_b"&&p.attr("marker-end","url("+f+"#arrowhead)"),(a.type==="birel"||a.type==="rel_b")&&p.attr("marker-start","url("+f+"#arrowend)")}let d=r.messageFont();bu(r)(a.label.text,n,Math.min(a.startPoint.x,a.endPoint.x)+Math.abs(a.endPoint.x-a.startPoint.x)/2+u,Math.min(a.startPoint.y,a.endPoint.y)+Math.abs(a.endPoint.y-a.startPoint.y)/2+h,a.label.width,a.label.height,{fill:s},d),a.techn&&a.techn.text!==""&&(d=r.messageFont(),bu(r)("["+a.techn.text+"]",n,Math.min(a.startPoint.x,a.endPoint.x)+Math.abs(a.endPoint.x-a.startPoint.x)/2+u,Math.min(a.startPoint.y,a.endPoint.y)+Math.abs(a.endPoint.y-a.startPoint.y)/2+r.messageFontSize+5+h,Math.max(a.label.width,a.techn.width),a.techn.height,{fill:s,"font-style":"italic"},d))}},"drawRels"),_Te=o(function(t,e,r){let n=t.append("g"),i=e.bgColor?e.bgColor:"none",a=e.borderColor?e.borderColor:"#444444",s=e.fontColor?e.fontColor:"black",l={"stroke-width":1,"stroke-dasharray":"7.0,7.0"};e.nodeType&&(l={"stroke-width":1});let u={x:e.x,y:e.y,fill:i,stroke:a,width:e.width,height:e.height,rx:2.5,ry:2.5,attrs:l};S_(n,u);let h=r.boundaryFont();h.fontWeight="bold",h.fontSize=h.fontSize+2,h.fontColor=s,bu(r)(e.label.text,n,e.x,e.y+e.label.Y,e.width,e.height,{fill:"#444444"},h),e.type&&e.type.text!==""&&(h=r.boundaryFont(),h.fontColor=s,bu(r)(e.type.text,n,e.x,e.y+e.type.Y,e.width,e.height,{fill:"#444444"},h)),e.descr&&e.descr.text!==""&&(h=r.boundaryFont(),h.fontSize=h.fontSize-2,h.fontColor=s,bu(r)(e.descr.text,n,e.x,e.y+e.descr.Y,e.width,e.height,{fill:"#444444"},h))},"drawBoundary"),LTe=o(function(t,e,r){let n=e.bgColor?e.bgColor:r[e.typeC4Shape.text+"_bg_color"],i=e.borderColor?e.borderColor:r[e.typeC4Shape.text+"_border_color"],a=e.fontColor?e.fontColor:"#FFFFFF",s="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAIAAADYYG7QAAACD0lEQVR4Xu2YoU4EMRCGT+4j8Ai8AhaH4QHgAUjQuFMECUgMIUgwJAgMhgQsAYUiJCiQIBBY+EITsjfTdme6V24v4c8vyGbb+ZjOtN0bNcvjQXmkH83WvYBWto6PLm6v7p7uH1/w2fXD+PBycX1Pv2l3IdDm/vn7x+dXQiAubRzoURa7gRZWd0iGRIiJbOnhnfYBQZNJjNbuyY2eJG8fkDE3bbG4ep6MHUAsgYxmE3nVs6VsBWJSGccsOlFPmLIViMzLOB7pCVO2AtHJMohH7Fh6zqitQK7m0rJvAVYgGcEpe//PLdDz65sM4pF9N7ICcXDKIB5Nv6j7tD0NoSdM2QrU9Gg0ewE1LqBhHR3BBdvj2vapnidjHxD/q6vd7Pvhr31AwcY8eXMTXAKECZZJFXuEq27aLgQK5uLMohCenGGuGewOxSjBvYBqeG6B+Nqiblggdjnc+ZXDy+FNFpFzw76O3UBAROuXh6FoiAcf5g9eTvUgzy0nWg6I8cXHRUpg5bOVBCo+KDpFajOf23GgPme7RSQ+lacIENUgJ6gg1k6HjgOlqnLqip4tEuhv0hNEMXUD0clyXE3p6pZA0S2nnvTlXwLJEZWlb7cTQH1+USgTN4VhAenm/wea1OCAOmqo6fE1WCb9WSKBah+rbUWPWAmE2Rvk0ApiB45eOyNAzU8xcTvj8KvkKEoOaIYeHNA3ZuygAvFMUO0AAAAASUVORK5CYII=";switch(e.typeC4Shape.text){case"person":s="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAIAAADYYG7QAAACD0lEQVR4Xu2YoU4EMRCGT+4j8Ai8AhaH4QHgAUjQuFMECUgMIUgwJAgMhgQsAYUiJCiQIBBY+EITsjfTdme6V24v4c8vyGbb+ZjOtN0bNcvjQXmkH83WvYBWto6PLm6v7p7uH1/w2fXD+PBycX1Pv2l3IdDm/vn7x+dXQiAubRzoURa7gRZWd0iGRIiJbOnhnfYBQZNJjNbuyY2eJG8fkDE3bbG4ep6MHUAsgYxmE3nVs6VsBWJSGccsOlFPmLIViMzLOB7pCVO2AtHJMohH7Fh6zqitQK7m0rJvAVYgGcEpe//PLdDz65sM4pF9N7ICcXDKIB5Nv6j7tD0NoSdM2QrU9Gg0ewE1LqBhHR3BBdvj2vapnidjHxD/q6vd7Pvhr31AwcY8eXMTXAKECZZJFXuEq27aLgQK5uLMohCenGGuGewOxSjBvYBqeG6B+Nqiblggdjnc+ZXDy+FNFpFzw76O3UBAROuXh6FoiAcf5g9eTvUgzy0nWg6I8cXHRUpg5bOVBCo+KDpFajOf23GgPme7RSQ+lacIENUgJ6gg1k6HjgOlqnLqip4tEuhv0hNEMXUD0clyXE3p6pZA0S2nnvTlXwLJEZWlb7cTQH1+USgTN4VhAenm/wea1OCAOmqo6fE1WCb9WSKBah+rbUWPWAmE2Rvk0ApiB45eOyNAzU8xcTvj8KvkKEoOaIYeHNA3ZuygAvFMUO0AAAAASUVORK5CYII=";break;case"external_person":s="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAIAAADYYG7QAAAB6ElEQVR4Xu2YLY+EMBCG9+dWr0aj0Wg0Go1Go0+j8Xdv2uTCvv1gpt0ebHKPuhDaeW4605Z9mJvx4AdXUyTUdd08z+u6flmWZRnHsWkafk9DptAwDPu+f0eAYtu2PEaGWuj5fCIZrBAC2eLBAnRCsEkkxmeaJp7iDJ2QMDdHsLg8SxKFEJaAo8lAXnmuOFIhTMpxxKATebo4UiFknuNo4OniSIXQyRxEA3YsnjGCVEjVXD7yLUAqxBGUyPv/Y4W2beMgGuS7kVQIBycH0fD+oi5pezQETxdHKmQKGk1eQEYldK+jw5GxPfZ9z7Mk0Qnhf1W1m3w//EUn5BDmSZsbR44QQLBEqrBHqOrmSKaQAxdnLArCrxZcM7A7ZKs4ioRq8LFC+NpC3WCBJsvpVw5edm9iEXFuyNfxXAgSwfrFQ1c0iNda8AdejvUgnktOtJQQxmcfFzGglc5WVCj7oDgFqU18boeFSs52CUh8LE8BIVQDT1ABrB0HtgSEYlX5doJnCwv9TXocKCaKbnwhdDKPq4lf3SwU3HLq4V/+WYhHVMa/3b4IlfyikAduCkcBc7mQ3/z/Qq/cTuikhkzB12Ae/mcJC9U+Vo8Ej1gWAtgbeGgFsAMHr50BIWOLCbezvhpBFUdY6EJuJ/QDW0XoMX60zZ0AAAAASUVORK5CYII=";break}let l=t.append("g");l.attr("class","person-man");let u=wl();switch(e.typeC4Shape.text){case"person":case"external_person":case"system":case"external_system":case"container":case"external_container":case"component":case"external_component":u.x=e.x,u.y=e.y,u.fill=n,u.width=e.width,u.height=e.height,u.stroke=i,u.rx=2.5,u.ry=2.5,u.attrs={"stroke-width":.5},S_(l,u);break;case"system_db":case"external_system_db":case"container_db":case"external_container_db":case"component_db":case"external_component_db":l.append("path").attr("fill",n).attr("stroke-width","0.5").attr("stroke",i).attr("d","Mstartx,startyc0,-10 half,-10 half,-10c0,0 half,0 half,10l0,heightc0,10 -half,10 -half,10c0,0 -half,0 -half,-10l0,-height".replaceAll("startx",e.x).replaceAll("starty",e.y).replaceAll("half",e.width/2).replaceAll("height",e.height)),l.append("path").attr("fill","none").attr("stroke-width","0.5").attr("stroke",i).attr("d","Mstartx,startyc0,10 half,10 half,10c0,0 half,0 half,-10".replaceAll("startx",e.x).replaceAll("starty",e.y).replaceAll("half",e.width/2));break;case"system_queue":case"external_system_queue":case"container_queue":case"external_container_queue":case"component_queue":case"external_component_queue":l.append("path").attr("fill",n).attr("stroke-width","0.5").attr("stroke",i).attr("d","Mstartx,startylwidth,0c5,0 5,half 5,halfc0,0 0,half -5,halfl-width,0c-5,0 -5,-half -5,-halfc0,0 0,-half 5,-half".replaceAll("startx",e.x).replaceAll("starty",e.y).replaceAll("width",e.width).replaceAll("half",e.height/2)),l.append("path").attr("fill","none").attr("stroke-width","0.5").attr("stroke",i).attr("d","Mstartx,startyc-5,0 -5,half -5,halfc0,half 5,half 5,half".replaceAll("startx",e.x+e.width).replaceAll("starty",e.y).replaceAll("half",e.height/2));break}let h=FTe(r,e.typeC4Shape.text);switch(l.append("text").attr("fill",a).attr("font-family",h.fontFamily).attr("font-size",h.fontSize-2).attr("font-style","italic").attr("lengthAdjust","spacing").attr("textLength",e.typeC4Shape.width).attr("x",e.x+e.width/2-e.typeC4Shape.width/2).attr("y",e.y+e.typeC4Shape.Y).text("<<"+e.typeC4Shape.text+">>"),e.typeC4Shape.text){case"person":case"external_person":SW(l,48,48,e.x+e.width/2-24,e.y+e.image.Y,s);break}let f=r[e.typeC4Shape.text+"Font"]();return f.fontWeight="bold",f.fontSize=f.fontSize+2,f.fontColor=a,bu(r)(e.label.text,l,e.x,e.y+e.label.Y,e.width,e.height,{fill:a},f),f=r[e.typeC4Shape.text+"Font"](),f.fontColor=a,e.techn&&e.techn?.text!==""?bu(r)(e.techn.text,l,e.x,e.y+e.techn.Y,e.width,e.height,{fill:a,"font-style":"italic"},f):e.type&&e.type.text!==""&&bu(r)(e.type.text,l,e.x,e.y+e.type.Y,e.width,e.height,{fill:a,"font-style":"italic"},f),e.descr&&e.descr.text!==""&&(f=r.personFont(),f.fontColor=a,bu(r)(e.descr.text,l,e.x,e.y+e.descr.Y,e.width,e.height,{fill:a},f)),e.height},"drawC4Shape"),DTe=o(function(t){t.append("defs").append("symbol").attr("id","database").attr("fill-rule","evenodd").attr("clip-rule","evenodd").append("path").attr("transform","scale(.5)").attr("d","M12.258.001l.256.004.255.005.253.008.251.01.249.012.247.015.246.016.242.019.241.02.239.023.236.024.233.027.231.028.229.031.225.032.223.034.22.036.217.038.214.04.211.041.208.043.205.045.201.046.198.048.194.05.191.051.187.053.183.054.18.056.175.057.172.059.168.06.163.061.16.063.155.064.15.066.074.033.073.033.071.034.07.034.069.035.068.035.067.035.066.035.064.036.064.036.062.036.06.036.06.037.058.037.058.037.055.038.055.038.053.038.052.038.051.039.05.039.048.039.047.039.045.04.044.04.043.04.041.04.04.041.039.041.037.041.036.041.034.041.033.042.032.042.03.042.029.042.027.042.026.043.024.043.023.043.021.043.02.043.018.044.017.043.015.044.013.044.012.044.011.045.009.044.007.045.006.045.004.045.002.045.001.045v17l-.001.045-.002.045-.004.045-.006.045-.007.045-.009.044-.011.045-.012.044-.013.044-.015.044-.017.043-.018.044-.02.043-.021.043-.023.043-.024.043-.026.043-.027.042-.029.042-.03.042-.032.042-.033.042-.034.041-.036.041-.037.041-.039.041-.04.041-.041.04-.043.04-.044.04-.045.04-.047.039-.048.039-.05.039-.051.039-.052.038-.053.038-.055.038-.055.038-.058.037-.058.037-.06.037-.06.036-.062.036-.064.036-.064.036-.066.035-.067.035-.068.035-.069.035-.07.034-.071.034-.073.033-.074.033-.15.066-.155.064-.16.063-.163.061-.168.06-.172.059-.175.057-.18.056-.183.054-.187.053-.191.051-.194.05-.198.048-.201.046-.205.045-.208.043-.211.041-.214.04-.217.038-.22.036-.223.034-.225.032-.229.031-.231.028-.233.027-.236.024-.239.023-.241.02-.242.019-.246.016-.247.015-.249.012-.251.01-.253.008-.255.005-.256.004-.258.001-.258-.001-.256-.004-.255-.005-.253-.008-.251-.01-.249-.012-.247-.015-.245-.016-.243-.019-.241-.02-.238-.023-.236-.024-.234-.027-.231-.028-.228-.031-.226-.032-.223-.034-.22-.036-.217-.038-.214-.04-.211-.041-.208-.043-.204-.045-.201-.046-.198-.048-.195-.05-.19-.051-.187-.053-.184-.054-.179-.056-.176-.057-.172-.059-.167-.06-.164-.061-.159-.063-.155-.064-.151-.066-.074-.033-.072-.033-.072-.034-.07-.034-.069-.035-.068-.035-.067-.035-.066-.035-.064-.036-.063-.036-.062-.036-.061-.036-.06-.037-.058-.037-.057-.037-.056-.038-.055-.038-.053-.038-.052-.038-.051-.039-.049-.039-.049-.039-.046-.039-.046-.04-.044-.04-.043-.04-.041-.04-.04-.041-.039-.041-.037-.041-.036-.041-.034-.041-.033-.042-.032-.042-.03-.042-.029-.042-.027-.042-.026-.043-.024-.043-.023-.043-.021-.043-.02-.043-.018-.044-.017-.043-.015-.044-.013-.044-.012-.044-.011-.045-.009-.044-.007-.045-.006-.045-.004-.045-.002-.045-.001-.045v-17l.001-.045.002-.045.004-.045.006-.045.007-.045.009-.044.011-.045.012-.044.013-.044.015-.044.017-.043.018-.044.02-.043.021-.043.023-.043.024-.043.026-.043.027-.042.029-.042.03-.042.032-.042.033-.042.034-.041.036-.041.037-.041.039-.041.04-.041.041-.04.043-.04.044-.04.046-.04.046-.039.049-.039.049-.039.051-.039.052-.038.053-.038.055-.038.056-.038.057-.037.058-.037.06-.037.061-.036.062-.036.063-.036.064-.036.066-.035.067-.035.068-.035.069-.035.07-.034.072-.034.072-.033.074-.033.151-.066.155-.064.159-.063.164-.061.167-.06.172-.059.176-.057.179-.056.184-.054.187-.053.19-.051.195-.05.198-.048.201-.046.204-.045.208-.043.211-.041.214-.04.217-.038.22-.036.223-.034.226-.032.228-.031.231-.028.234-.027.236-.024.238-.023.241-.02.243-.019.245-.016.247-.015.249-.012.251-.01.253-.008.255-.005.256-.004.258-.001.258.001zm-9.258 20.499v.01l.001.021.003.021.004.022.005.021.006.022.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.023.018.024.019.024.021.024.022.025.023.024.024.025.052.049.056.05.061.051.066.051.07.051.075.051.079.052.084.052.088.052.092.052.097.052.102.051.105.052.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.048.144.049.147.047.152.047.155.047.16.045.163.045.167.043.171.043.176.041.178.041.183.039.187.039.19.037.194.035.197.035.202.033.204.031.209.03.212.029.216.027.219.025.222.024.226.021.23.02.233.018.236.016.24.015.243.012.246.01.249.008.253.005.256.004.259.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.021.224-.024.22-.026.216-.027.212-.028.21-.031.205-.031.202-.034.198-.034.194-.036.191-.037.187-.039.183-.04.179-.04.175-.042.172-.043.168-.044.163-.045.16-.046.155-.046.152-.047.148-.048.143-.049.139-.049.136-.05.131-.05.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.053.083-.051.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.05.023-.024.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.023.01-.022.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.127l-.077.055-.08.053-.083.054-.085.053-.087.052-.09.052-.093.051-.095.05-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.045-.118.044-.12.043-.122.042-.124.042-.126.041-.128.04-.13.04-.132.038-.134.038-.135.037-.138.037-.139.035-.142.035-.143.034-.144.033-.147.032-.148.031-.15.03-.151.03-.153.029-.154.027-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.01-.179.008-.179.008-.181.006-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.006-.179-.008-.179-.008-.178-.01-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.027-.153-.029-.151-.03-.15-.03-.148-.031-.146-.032-.145-.033-.143-.034-.141-.035-.14-.035-.137-.037-.136-.037-.134-.038-.132-.038-.13-.04-.128-.04-.126-.041-.124-.042-.122-.042-.12-.044-.117-.043-.116-.045-.113-.045-.112-.046-.109-.047-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.05-.093-.052-.09-.051-.087-.052-.085-.053-.083-.054-.08-.054-.077-.054v4.127zm0-5.654v.011l.001.021.003.021.004.021.005.022.006.022.007.022.009.022.01.022.011.023.012.023.013.023.015.024.016.023.017.024.018.024.019.024.021.024.022.024.023.025.024.024.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.052.11.051.114.051.119.052.123.05.127.051.131.05.135.049.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.044.171.042.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.022.23.02.233.018.236.016.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.012.241-.015.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.048.139-.05.136-.049.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.051.051-.049.023-.025.023-.024.021-.025.02-.024.019-.024.018-.024.017-.024.015-.023.014-.023.013-.024.012-.022.01-.023.01-.023.008-.022.006-.022.006-.022.004-.021.004-.022.001-.021.001-.021v-4.139l-.077.054-.08.054-.083.054-.085.052-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.044-.118.044-.12.044-.122.042-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.035-.143.033-.144.033-.147.033-.148.031-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.009-.179.009-.179.007-.181.007-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.007-.179-.007-.179-.009-.178-.009-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.031-.146-.033-.145-.033-.143-.033-.141-.035-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.04-.126-.041-.124-.042-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.051-.093-.051-.09-.051-.087-.053-.085-.052-.083-.054-.08-.054-.077-.054v4.139zm0-5.666v.011l.001.02.003.022.004.021.005.022.006.021.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.024.018.023.019.024.021.025.022.024.023.024.024.025.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.051.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.043.171.043.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.021.23.02.233.018.236.017.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.013.241-.014.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.049.139-.049.136-.049.131-.051.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.049.023-.025.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.022.01-.023.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.153l-.077.054-.08.054-.083.053-.085.053-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.048-.105.048-.106.048-.109.046-.111.046-.114.046-.115.044-.118.044-.12.043-.122.043-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.034-.143.034-.144.033-.147.032-.148.032-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.024-.161.024-.162.023-.163.023-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.01-.178.01-.179.009-.179.007-.181.006-.182.006-.182.004-.184.003-.184.001-.185.001-.185-.001-.184-.001-.184-.003-.182-.004-.182-.006-.181-.006-.179-.007-.179-.009-.178-.01-.176-.01-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.023-.162-.023-.161-.024-.159-.024-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.032-.146-.032-.145-.033-.143-.034-.141-.034-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.041-.126-.041-.124-.041-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.048-.105-.048-.102-.048-.1-.05-.097-.049-.095-.051-.093-.051-.09-.052-.087-.052-.085-.053-.083-.053-.08-.054-.077-.054v4.153zm8.74-8.179l-.257.004-.254.005-.25.008-.247.011-.244.012-.241.014-.237.016-.233.018-.231.021-.226.022-.224.023-.22.026-.216.027-.212.028-.21.031-.205.032-.202.033-.198.034-.194.036-.191.038-.187.038-.183.04-.179.041-.175.042-.172.043-.168.043-.163.045-.16.046-.155.046-.152.048-.148.048-.143.048-.139.049-.136.05-.131.05-.126.051-.123.051-.118.051-.114.052-.11.052-.106.052-.101.052-.096.052-.092.052-.088.052-.083.052-.079.052-.074.051-.07.052-.065.051-.06.05-.056.05-.051.05-.023.025-.023.024-.021.024-.02.025-.019.024-.018.024-.017.023-.015.024-.014.023-.013.023-.012.023-.01.023-.01.022-.008.022-.006.023-.006.021-.004.022-.004.021-.001.021-.001.021.001.021.001.021.004.021.004.022.006.021.006.023.008.022.01.022.01.023.012.023.013.023.014.023.015.024.017.023.018.024.019.024.02.025.021.024.023.024.023.025.051.05.056.05.06.05.065.051.07.052.074.051.079.052.083.052.088.052.092.052.096.052.101.052.106.052.11.052.114.052.118.051.123.051.126.051.131.05.136.05.139.049.143.048.148.048.152.048.155.046.16.046.163.045.168.043.172.043.175.042.179.041.183.04.187.038.191.038.194.036.198.034.202.033.205.032.21.031.212.028.216.027.22.026.224.023.226.022.231.021.233.018.237.016.241.014.244.012.247.011.25.008.254.005.257.004.26.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.022.224-.023.22-.026.216-.027.212-.028.21-.031.205-.032.202-.033.198-.034.194-.036.191-.038.187-.038.183-.04.179-.041.175-.042.172-.043.168-.043.163-.045.16-.046.155-.046.152-.048.148-.048.143-.048.139-.049.136-.05.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.05.051-.05.023-.025.023-.024.021-.024.02-.025.019-.024.018-.024.017-.023.015-.024.014-.023.013-.023.012-.023.01-.023.01-.022.008-.022.006-.023.006-.021.004-.022.004-.021.001-.021.001-.021-.001-.021-.001-.021-.004-.021-.004-.022-.006-.021-.006-.023-.008-.022-.01-.022-.01-.023-.012-.023-.013-.023-.014-.023-.015-.024-.017-.023-.018-.024-.019-.024-.02-.025-.021-.024-.023-.024-.023-.025-.051-.05-.056-.05-.06-.05-.065-.051-.07-.052-.074-.051-.079-.052-.083-.052-.088-.052-.092-.052-.096-.052-.101-.052-.106-.052-.11-.052-.114-.052-.118-.051-.123-.051-.126-.051-.131-.05-.136-.05-.139-.049-.143-.048-.148-.048-.152-.048-.155-.046-.16-.046-.163-.045-.168-.043-.172-.043-.175-.042-.179-.041-.183-.04-.187-.038-.191-.038-.194-.036-.198-.034-.202-.033-.205-.032-.21-.031-.212-.028-.216-.027-.22-.026-.224-.023-.226-.022-.231-.021-.233-.018-.237-.016-.241-.014-.244-.012-.247-.011-.25-.008-.254-.005-.257-.004-.26-.001-.26.001z")},"insertDatabaseIcon"),RTe=o(function(t){t.append("defs").append("symbol").attr("id","computer").attr("width","24").attr("height","24").append("path").attr("transform","scale(.5)").attr("d","M2 2v13h20v-13h-20zm18 11h-16v-9h16v9zm-10.228 6l.466-1h3.524l.467 1h-4.457zm14.228 3h-24l2-6h2.104l-1.33 4h18.45l-1.297-4h2.073l2 6zm-5-10h-14v-7h14v7z")},"insertComputerIcon"),NTe=o(function(t){t.append("defs").append("symbol").attr("id","clock").attr("width","24").attr("height","24").append("path").attr("transform","scale(.5)").attr("d","M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm5.848 12.459c.202.038.202.333.001.372-1.907.361-6.045 1.111-6.547 1.111-.719 0-1.301-.582-1.301-1.301 0-.512.77-5.447 1.125-7.445.034-.192.312-.181.343.014l.985 6.238 5.394 1.011z")},"insertClockIcon"),MTe=o(function(t){t.append("defs").append("marker").attr("id","arrowhead").attr("refX",9).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",12).attr("markerHeight",12).attr("orient","auto").append("path").attr("d","M 0 0 L 10 5 L 0 10 z")},"insertArrowHead"),ITe=o(function(t){t.append("defs").append("marker").attr("id","arrowend").attr("refX",1).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",12).attr("markerHeight",12).attr("orient","auto").append("path").attr("d","M 10 0 L 0 5 L 10 10 z")},"insertArrowEnd"),OTe=o(function(t){t.append("defs").append("marker").attr("id","filled-head").attr("refX",18).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L14,7 L9,1 Z")},"insertArrowFilledHead"),PTe=o(function(t){t.append("defs").append("marker").attr("id","sequencenumber").attr("refX",15).attr("refY",15).attr("markerWidth",60).attr("markerHeight",40).attr("orient","auto").append("circle").attr("cx",15).attr("cy",15).attr("r",6)},"insertDynamicNumber"),BTe=o(function(t){let r=t.append("defs").append("marker").attr("id","crosshead").attr("markerWidth",15).attr("markerHeight",8).attr("orient","auto").attr("refX",16).attr("refY",4);r.append("path").attr("fill","black").attr("stroke","#000000").style("stroke-dasharray","0, 0").attr("stroke-width","1px").attr("d","M 9,2 V 6 L16,4 Z"),r.append("path").attr("fill","none").attr("stroke","#000000").style("stroke-dasharray","0, 0").attr("stroke-width","1px").attr("d","M 0,1 L 6,7 M 6,1 L 0,7")},"insertArrowCrossHead"),FTe=o((t,e)=>({fontFamily:t[e+"FontFamily"],fontSize:t[e+"FontSize"],fontWeight:t[e+"FontWeight"]}),"getC4ShapeFont"),bu=function(){function t(i,a,s,l,u,h,f){let d=a.append("text").attr("x",s+u/2).attr("y",l+h/2+5).style("text-anchor","middle").text(i);n(d,f)}o(t,"byText");function e(i,a,s,l,u,h,f,d){let{fontSize:p,fontFamily:m,fontWeight:g}=d,y=i.split(We.lineBreakRegex);for(let v=0;v{"use strict";zTe=typeof global=="object"&&global&&global.Object===Object&&global,Q3=zTe});var GTe,$Te,Jn,Ro=R(()=>{"use strict";A_();GTe=typeof self=="object"&&self&&self.Object===Object&&self,$Te=Q3||GTe||Function("return this")(),Jn=$Te});var VTe,Ji,vd=R(()=>{"use strict";Ro();VTe=Jn.Symbol,Ji=VTe});function YTe(t){var e=UTe.call(t,Zy),r=t[Zy];try{t[Zy]=void 0;var n=!0}catch{}var i=HTe.call(t);return n&&(e?t[Zy]=r:delete t[Zy]),i}var _W,UTe,HTe,Zy,LW,DW=R(()=>{"use strict";vd();_W=Object.prototype,UTe=_W.hasOwnProperty,HTe=_W.toString,Zy=Ji?Ji.toStringTag:void 0;o(YTe,"getRawTag");LW=YTe});function XTe(t){return qTe.call(t)}var WTe,qTe,RW,NW=R(()=>{"use strict";WTe=Object.prototype,qTe=WTe.toString;o(XTe,"objectToString");RW=XTe});function QTe(t){return t==null?t===void 0?KTe:jTe:MW&&MW in Object(t)?LW(t):RW(t)}var jTe,KTe,MW,fa,wu=R(()=>{"use strict";vd();DW();NW();jTe="[object Null]",KTe="[object Undefined]",MW=Ji?Ji.toStringTag:void 0;o(QTe,"baseGetTag");fa=QTe});function ZTe(t){var e=typeof t;return t!=null&&(e=="object"||e=="function")}var pn,Js=R(()=>{"use strict";o(ZTe,"isObject");pn=ZTe});function nke(t){if(!pn(t))return!1;var e=fa(t);return e==eke||e==tke||e==JTe||e==rke}var JTe,eke,tke,rke,wi,Jy=R(()=>{"use strict";wu();Js();JTe="[object AsyncFunction]",eke="[object Function]",tke="[object GeneratorFunction]",rke="[object Proxy]";o(nke,"isFunction");wi=nke});var ike,Z3,IW=R(()=>{"use strict";Ro();ike=Jn["__core-js_shared__"],Z3=ike});function ake(t){return!!OW&&OW in t}var OW,PW,BW=R(()=>{"use strict";IW();OW=function(){var t=/[^.]+$/.exec(Z3&&Z3.keys&&Z3.keys.IE_PROTO||"");return t?"Symbol(src)_1."+t:""}();o(ake,"isMasked");PW=ake});function lke(t){if(t!=null){try{return oke.call(t)}catch{}try{return t+""}catch{}}return""}var ske,oke,Tu,__=R(()=>{"use strict";ske=Function.prototype,oke=ske.toString;o(lke,"toSource");Tu=lke});function gke(t){if(!pn(t)||PW(t))return!1;var e=wi(t)?mke:uke;return e.test(Tu(t))}var cke,uke,hke,fke,dke,pke,mke,FW,zW=R(()=>{"use strict";Jy();BW();Js();__();cke=/[\\^$.*+?()[\]{}|]/g,uke=/^\[object .+?Constructor\]$/,hke=Function.prototype,fke=Object.prototype,dke=hke.toString,pke=fke.hasOwnProperty,mke=RegExp("^"+dke.call(pke).replace(cke,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");o(gke,"baseIsNative");FW=gke});function yke(t,e){return t?.[e]}var GW,$W=R(()=>{"use strict";o(yke,"getValue");GW=yke});function vke(t,e){var r=GW(t,e);return FW(r)?r:void 0}var xs,Nh=R(()=>{"use strict";zW();$W();o(vke,"getNative");xs=vke});var xke,ku,ev=R(()=>{"use strict";Nh();xke=xs(Object,"create"),ku=xke});function bke(){this.__data__=ku?ku(null):{},this.size=0}var VW,UW=R(()=>{"use strict";ev();o(bke,"hashClear");VW=bke});function wke(t){var e=this.has(t)&&delete this.__data__[t];return this.size-=e?1:0,e}var HW,YW=R(()=>{"use strict";o(wke,"hashDelete");HW=wke});function Cke(t){var e=this.__data__;if(ku){var r=e[t];return r===Tke?void 0:r}return Eke.call(e,t)?e[t]:void 0}var Tke,kke,Eke,WW,qW=R(()=>{"use strict";ev();Tke="__lodash_hash_undefined__",kke=Object.prototype,Eke=kke.hasOwnProperty;o(Cke,"hashGet");WW=Cke});function _ke(t){var e=this.__data__;return ku?e[t]!==void 0:Ake.call(e,t)}var Ske,Ake,XW,jW=R(()=>{"use strict";ev();Ske=Object.prototype,Ake=Ske.hasOwnProperty;o(_ke,"hashHas");XW=_ke});function Dke(t,e){var r=this.__data__;return this.size+=this.has(t)?0:1,r[t]=ku&&e===void 0?Lke:e,this}var Lke,KW,QW=R(()=>{"use strict";ev();Lke="__lodash_hash_undefined__";o(Dke,"hashSet");KW=Dke});function Hp(t){var e=-1,r=t==null?0:t.length;for(this.clear();++e{"use strict";UW();YW();qW();jW();QW();o(Hp,"Hash");Hp.prototype.clear=VW;Hp.prototype.delete=HW;Hp.prototype.get=WW;Hp.prototype.has=XW;Hp.prototype.set=KW;L_=Hp});function Rke(){this.__data__=[],this.size=0}var JW,eq=R(()=>{"use strict";o(Rke,"listCacheClear");JW=Rke});function Nke(t,e){return t===e||t!==t&&e!==e}var No,xd=R(()=>{"use strict";o(Nke,"eq");No=Nke});function Mke(t,e){for(var r=t.length;r--;)if(No(t[r][0],e))return r;return-1}var Mh,tv=R(()=>{"use strict";xd();o(Mke,"assocIndexOf");Mh=Mke});function Pke(t){var e=this.__data__,r=Mh(e,t);if(r<0)return!1;var n=e.length-1;return r==n?e.pop():Oke.call(e,r,1),--this.size,!0}var Ike,Oke,tq,rq=R(()=>{"use strict";tv();Ike=Array.prototype,Oke=Ike.splice;o(Pke,"listCacheDelete");tq=Pke});function Bke(t){var e=this.__data__,r=Mh(e,t);return r<0?void 0:e[r][1]}var nq,iq=R(()=>{"use strict";tv();o(Bke,"listCacheGet");nq=Bke});function Fke(t){return Mh(this.__data__,t)>-1}var aq,sq=R(()=>{"use strict";tv();o(Fke,"listCacheHas");aq=Fke});function zke(t,e){var r=this.__data__,n=Mh(r,t);return n<0?(++this.size,r.push([t,e])):r[n][1]=e,this}var oq,lq=R(()=>{"use strict";tv();o(zke,"listCacheSet");oq=zke});function Yp(t){var e=-1,r=t==null?0:t.length;for(this.clear();++e{"use strict";eq();rq();iq();sq();lq();o(Yp,"ListCache");Yp.prototype.clear=JW;Yp.prototype.delete=tq;Yp.prototype.get=nq;Yp.prototype.has=aq;Yp.prototype.set=oq;Ih=Yp});var Gke,Oh,J3=R(()=>{"use strict";Nh();Ro();Gke=xs(Jn,"Map"),Oh=Gke});function $ke(){this.size=0,this.__data__={hash:new L_,map:new(Oh||Ih),string:new L_}}var cq,uq=R(()=>{"use strict";ZW();rv();J3();o($ke,"mapCacheClear");cq=$ke});function Vke(t){var e=typeof t;return e=="string"||e=="number"||e=="symbol"||e=="boolean"?t!=="__proto__":t===null}var hq,fq=R(()=>{"use strict";o(Vke,"isKeyable");hq=Vke});function Uke(t,e){var r=t.__data__;return hq(e)?r[typeof e=="string"?"string":"hash"]:r.map}var Ph,nv=R(()=>{"use strict";fq();o(Uke,"getMapData");Ph=Uke});function Hke(t){var e=Ph(this,t).delete(t);return this.size-=e?1:0,e}var dq,pq=R(()=>{"use strict";nv();o(Hke,"mapCacheDelete");dq=Hke});function Yke(t){return Ph(this,t).get(t)}var mq,gq=R(()=>{"use strict";nv();o(Yke,"mapCacheGet");mq=Yke});function Wke(t){return Ph(this,t).has(t)}var yq,vq=R(()=>{"use strict";nv();o(Wke,"mapCacheHas");yq=Wke});function qke(t,e){var r=Ph(this,t),n=r.size;return r.set(t,e),this.size+=r.size==n?0:1,this}var xq,bq=R(()=>{"use strict";nv();o(qke,"mapCacheSet");xq=qke});function Wp(t){var e=-1,r=t==null?0:t.length;for(this.clear();++e{"use strict";uq();pq();gq();vq();bq();o(Wp,"MapCache");Wp.prototype.clear=cq;Wp.prototype.delete=dq;Wp.prototype.get=mq;Wp.prototype.has=yq;Wp.prototype.set=xq;bd=Wp});function D_(t,e){if(typeof t!="function"||e!=null&&typeof e!="function")throw new TypeError(Xke);var r=o(function(){var n=arguments,i=e?e.apply(this,n):n[0],a=r.cache;if(a.has(i))return a.get(i);var s=t.apply(this,n);return r.cache=a.set(i,s)||a,s},"memoized");return r.cache=new(D_.Cache||bd),r}var Xke,qp,R_=R(()=>{"use strict";e5();Xke="Expected a function";o(D_,"memoize");D_.Cache=bd;qp=D_});function jke(){this.__data__=new Ih,this.size=0}var wq,Tq=R(()=>{"use strict";rv();o(jke,"stackClear");wq=jke});function Kke(t){var e=this.__data__,r=e.delete(t);return this.size=e.size,r}var kq,Eq=R(()=>{"use strict";o(Kke,"stackDelete");kq=Kke});function Qke(t){return this.__data__.get(t)}var Cq,Sq=R(()=>{"use strict";o(Qke,"stackGet");Cq=Qke});function Zke(t){return this.__data__.has(t)}var Aq,_q=R(()=>{"use strict";o(Zke,"stackHas");Aq=Zke});function eEe(t,e){var r=this.__data__;if(r instanceof Ih){var n=r.__data__;if(!Oh||n.length{"use strict";rv();J3();e5();Jke=200;o(eEe,"stackSet");Lq=eEe});function Xp(t){var e=this.__data__=new Ih(t);this.size=e.size}var uc,iv=R(()=>{"use strict";rv();Tq();Eq();Sq();_q();Dq();o(Xp,"Stack");Xp.prototype.clear=wq;Xp.prototype.delete=kq;Xp.prototype.get=Cq;Xp.prototype.has=Aq;Xp.prototype.set=Lq;uc=Xp});var tEe,jp,N_=R(()=>{"use strict";Nh();tEe=function(){try{var t=xs(Object,"defineProperty");return t({},"",{}),t}catch{}}(),jp=tEe});function rEe(t,e,r){e=="__proto__"&&jp?jp(t,e,{configurable:!0,enumerable:!0,value:r,writable:!0}):t[e]=r}var hc,Kp=R(()=>{"use strict";N_();o(rEe,"baseAssignValue");hc=rEe});function nEe(t,e,r){(r!==void 0&&!No(t[e],r)||r===void 0&&!(e in t))&&hc(t,e,r)}var av,M_=R(()=>{"use strict";Kp();xd();o(nEe,"assignMergeValue");av=nEe});function iEe(t){return function(e,r,n){for(var i=-1,a=Object(e),s=n(e),l=s.length;l--;){var u=s[t?l:++i];if(r(a[u],u,a)===!1)break}return e}}var Rq,Nq=R(()=>{"use strict";o(iEe,"createBaseFor");Rq=iEe});var aEe,Qp,t5=R(()=>{"use strict";Nq();aEe=Rq(),Qp=aEe});function oEe(t,e){if(e)return t.slice();var r=t.length,n=Oq?Oq(r):new t.constructor(r);return t.copy(n),n}var Pq,Mq,sEe,Iq,Oq,r5,I_=R(()=>{"use strict";Ro();Pq=typeof exports=="object"&&exports&&!exports.nodeType&&exports,Mq=Pq&&typeof module=="object"&&module&&!module.nodeType&&module,sEe=Mq&&Mq.exports===Pq,Iq=sEe?Jn.Buffer:void 0,Oq=Iq?Iq.allocUnsafe:void 0;o(oEe,"cloneBuffer");r5=oEe});var lEe,Zp,O_=R(()=>{"use strict";Ro();lEe=Jn.Uint8Array,Zp=lEe});function cEe(t){var e=new t.constructor(t.byteLength);return new Zp(e).set(new Zp(t)),e}var Jp,n5=R(()=>{"use strict";O_();o(cEe,"cloneArrayBuffer");Jp=cEe});function uEe(t,e){var r=e?Jp(t.buffer):t.buffer;return new t.constructor(r,t.byteOffset,t.length)}var i5,P_=R(()=>{"use strict";n5();o(uEe,"cloneTypedArray");i5=uEe});function hEe(t,e){var r=-1,n=t.length;for(e||(e=Array(n));++r{"use strict";o(hEe,"copyArray");a5=hEe});var Bq,fEe,Fq,zq=R(()=>{"use strict";Js();Bq=Object.create,fEe=function(){function t(){}return o(t,"object"),function(e){if(!pn(e))return{};if(Bq)return Bq(e);t.prototype=e;var r=new t;return t.prototype=void 0,r}}(),Fq=fEe});function dEe(t,e){return function(r){return t(e(r))}}var s5,F_=R(()=>{"use strict";o(dEe,"overArg");s5=dEe});var pEe,em,o5=R(()=>{"use strict";F_();pEe=s5(Object.getPrototypeOf,Object),em=pEe});function gEe(t){var e=t&&t.constructor,r=typeof e=="function"&&e.prototype||mEe;return t===r}var mEe,fc,tm=R(()=>{"use strict";mEe=Object.prototype;o(gEe,"isPrototype");fc=gEe});function yEe(t){return typeof t.constructor=="function"&&!fc(t)?Fq(em(t)):{}}var l5,z_=R(()=>{"use strict";zq();o5();tm();o(yEe,"initCloneObject");l5=yEe});function vEe(t){return t!=null&&typeof t=="object"}var Wn,Mo=R(()=>{"use strict";o(vEe,"isObjectLike");Wn=vEe});function bEe(t){return Wn(t)&&fa(t)==xEe}var xEe,G_,Gq=R(()=>{"use strict";wu();Mo();xEe="[object Arguments]";o(bEe,"baseIsArguments");G_=bEe});var $q,wEe,TEe,kEe,kl,rm=R(()=>{"use strict";Gq();Mo();$q=Object.prototype,wEe=$q.hasOwnProperty,TEe=$q.propertyIsEnumerable,kEe=G_(function(){return arguments}())?G_:function(t){return Wn(t)&&wEe.call(t,"callee")&&!TEe.call(t,"callee")},kl=kEe});var EEe,wt,Bn=R(()=>{"use strict";EEe=Array.isArray,wt=EEe});function SEe(t){return typeof t=="number"&&t>-1&&t%1==0&&t<=CEe}var CEe,nm,c5=R(()=>{"use strict";CEe=9007199254740991;o(SEe,"isLength");nm=SEe});function AEe(t){return t!=null&&nm(t.length)&&!wi(t)}var ei,Io=R(()=>{"use strict";Jy();c5();o(AEe,"isArrayLike");ei=AEe});function _Ee(t){return Wn(t)&&ei(t)}var wd,u5=R(()=>{"use strict";Io();Mo();o(_Ee,"isArrayLikeObject");wd=_Ee});function LEe(){return!1}var Vq,Uq=R(()=>{"use strict";o(LEe,"stubFalse");Vq=LEe});var Wq,Hq,DEe,Yq,REe,NEe,El,im=R(()=>{"use strict";Ro();Uq();Wq=typeof exports=="object"&&exports&&!exports.nodeType&&exports,Hq=Wq&&typeof module=="object"&&module&&!module.nodeType&&module,DEe=Hq&&Hq.exports===Wq,Yq=DEe?Jn.Buffer:void 0,REe=Yq?Yq.isBuffer:void 0,NEe=REe||Vq,El=NEe});function FEe(t){if(!Wn(t)||fa(t)!=MEe)return!1;var e=em(t);if(e===null)return!0;var r=PEe.call(e,"constructor")&&e.constructor;return typeof r=="function"&&r instanceof r&&qq.call(r)==BEe}var MEe,IEe,OEe,qq,PEe,BEe,Xq,jq=R(()=>{"use strict";wu();o5();Mo();MEe="[object Object]",IEe=Function.prototype,OEe=Object.prototype,qq=IEe.toString,PEe=OEe.hasOwnProperty,BEe=qq.call(Object);o(FEe,"isPlainObject");Xq=FEe});function c6e(t){return Wn(t)&&nm(t.length)&&!!Mn[fa(t)]}var zEe,GEe,$Ee,VEe,UEe,HEe,YEe,WEe,qEe,XEe,jEe,KEe,QEe,ZEe,JEe,e6e,t6e,r6e,n6e,i6e,a6e,s6e,o6e,l6e,Mn,Kq,Qq=R(()=>{"use strict";wu();c5();Mo();zEe="[object Arguments]",GEe="[object Array]",$Ee="[object Boolean]",VEe="[object Date]",UEe="[object Error]",HEe="[object Function]",YEe="[object Map]",WEe="[object Number]",qEe="[object Object]",XEe="[object RegExp]",jEe="[object Set]",KEe="[object String]",QEe="[object WeakMap]",ZEe="[object ArrayBuffer]",JEe="[object DataView]",e6e="[object Float32Array]",t6e="[object Float64Array]",r6e="[object Int8Array]",n6e="[object Int16Array]",i6e="[object Int32Array]",a6e="[object Uint8Array]",s6e="[object Uint8ClampedArray]",o6e="[object Uint16Array]",l6e="[object Uint32Array]",Mn={};Mn[e6e]=Mn[t6e]=Mn[r6e]=Mn[n6e]=Mn[i6e]=Mn[a6e]=Mn[s6e]=Mn[o6e]=Mn[l6e]=!0;Mn[zEe]=Mn[GEe]=Mn[ZEe]=Mn[$Ee]=Mn[JEe]=Mn[VEe]=Mn[UEe]=Mn[HEe]=Mn[YEe]=Mn[WEe]=Mn[qEe]=Mn[XEe]=Mn[jEe]=Mn[KEe]=Mn[QEe]=!1;o(c6e,"baseIsTypedArray");Kq=c6e});function u6e(t){return function(e){return t(e)}}var Oo,Td=R(()=>{"use strict";o(u6e,"baseUnary");Oo=u6e});var Zq,sv,h6e,$_,f6e,Po,ov=R(()=>{"use strict";A_();Zq=typeof exports=="object"&&exports&&!exports.nodeType&&exports,sv=Zq&&typeof module=="object"&&module&&!module.nodeType&&module,h6e=sv&&sv.exports===Zq,$_=h6e&&Q3.process,f6e=function(){try{var t=sv&&sv.require&&sv.require("util").types;return t||$_&&$_.binding&&$_.binding("util")}catch{}}(),Po=f6e});var Jq,d6e,Bh,lv=R(()=>{"use strict";Qq();Td();ov();Jq=Po&&Po.isTypedArray,d6e=Jq?Oo(Jq):Kq,Bh=d6e});function p6e(t,e){if(!(e==="constructor"&&typeof t[e]=="function")&&e!="__proto__")return t[e]}var cv,V_=R(()=>{"use strict";o(p6e,"safeGet");cv=p6e});function y6e(t,e,r){var n=t[e];(!(g6e.call(t,e)&&No(n,r))||r===void 0&&!(e in t))&&hc(t,e,r)}var m6e,g6e,dc,am=R(()=>{"use strict";Kp();xd();m6e=Object.prototype,g6e=m6e.hasOwnProperty;o(y6e,"assignValue");dc=y6e});function v6e(t,e,r,n){var i=!r;r||(r={});for(var a=-1,s=e.length;++a{"use strict";am();Kp();o(v6e,"copyObject");Bo=v6e});function x6e(t,e){for(var r=-1,n=Array(t);++r{"use strict";o(x6e,"baseTimes");eX=x6e});function T6e(t,e){var r=typeof t;return e=e??b6e,!!e&&(r=="number"||r!="symbol"&&w6e.test(t))&&t>-1&&t%1==0&&t{"use strict";b6e=9007199254740991,w6e=/^(?:0|[1-9]\d*)$/;o(T6e,"isIndex");Fh=T6e});function C6e(t,e){var r=wt(t),n=!r&&kl(t),i=!r&&!n&&El(t),a=!r&&!n&&!i&&Bh(t),s=r||n||i||a,l=s?eX(t.length,String):[],u=l.length;for(var h in t)(e||E6e.call(t,h))&&!(s&&(h=="length"||i&&(h=="offset"||h=="parent")||a&&(h=="buffer"||h=="byteLength"||h=="byteOffset")||Fh(h,u)))&&l.push(h);return l}var k6e,E6e,h5,U_=R(()=>{"use strict";tX();rm();Bn();im();uv();lv();k6e=Object.prototype,E6e=k6e.hasOwnProperty;o(C6e,"arrayLikeKeys");h5=C6e});function S6e(t){var e=[];if(t!=null)for(var r in Object(t))e.push(r);return e}var rX,nX=R(()=>{"use strict";o(S6e,"nativeKeysIn");rX=S6e});function L6e(t){if(!pn(t))return rX(t);var e=fc(t),r=[];for(var n in t)n=="constructor"&&(e||!_6e.call(t,n))||r.push(n);return r}var A6e,_6e,iX,aX=R(()=>{"use strict";Js();tm();nX();A6e=Object.prototype,_6e=A6e.hasOwnProperty;o(L6e,"baseKeysIn");iX=L6e});function D6e(t){return ei(t)?h5(t,!0):iX(t)}var bs,zh=R(()=>{"use strict";U_();aX();Io();o(D6e,"keysIn");bs=D6e});function R6e(t){return Bo(t,bs(t))}var sX,oX=R(()=>{"use strict";kd();zh();o(R6e,"toPlainObject");sX=R6e});function N6e(t,e,r,n,i,a,s){var l=cv(t,r),u=cv(e,r),h=s.get(u);if(h){av(t,r,h);return}var f=a?a(l,u,r+"",t,e,s):void 0,d=f===void 0;if(d){var p=wt(u),m=!p&&El(u),g=!p&&!m&&Bh(u);f=u,p||m||g?wt(l)?f=l:wd(l)?f=a5(l):m?(d=!1,f=r5(u,!0)):g?(d=!1,f=i5(u,!0)):f=[]:Xq(u)||kl(u)?(f=l,kl(l)?f=sX(l):(!pn(l)||wi(l))&&(f=l5(u))):d=!1}d&&(s.set(u,f),i(f,u,n,a,s),s.delete(u)),av(t,r,f)}var lX,cX=R(()=>{"use strict";M_();I_();P_();B_();z_();rm();Bn();u5();im();Jy();Js();jq();lv();V_();oX();o(N6e,"baseMergeDeep");lX=N6e});function uX(t,e,r,n,i){t!==e&&Qp(e,function(a,s){if(i||(i=new uc),pn(a))lX(t,e,s,r,uX,n,i);else{var l=n?n(cv(t,s),a,s+"",t,e,i):void 0;l===void 0&&(l=a),av(t,s,l)}},bs)}var hX,fX=R(()=>{"use strict";iv();M_();t5();cX();Js();zh();V_();o(uX,"baseMerge");hX=uX});function M6e(t){return t}var ea,Eu=R(()=>{"use strict";o(M6e,"identity");ea=M6e});function I6e(t,e,r){switch(r.length){case 0:return t.call(e);case 1:return t.call(e,r[0]);case 2:return t.call(e,r[0],r[1]);case 3:return t.call(e,r[0],r[1],r[2])}return t.apply(e,r)}var dX,pX=R(()=>{"use strict";o(I6e,"apply");dX=I6e});function O6e(t,e,r){return e=mX(e===void 0?t.length-1:e,0),function(){for(var n=arguments,i=-1,a=mX(n.length-e,0),s=Array(a);++i{"use strict";pX();mX=Math.max;o(O6e,"overRest");f5=O6e});function P6e(t){return function(){return t}}var ws,Y_=R(()=>{"use strict";o(P6e,"constant");ws=P6e});var B6e,gX,yX=R(()=>{"use strict";Y_();N_();Eu();B6e=jp?function(t,e){return jp(t,"toString",{configurable:!0,enumerable:!1,value:ws(e),writable:!0})}:ea,gX=B6e});function $6e(t){var e=0,r=0;return function(){var n=G6e(),i=z6e-(n-r);if(r=n,i>0){if(++e>=F6e)return arguments[0]}else e=0;return t.apply(void 0,arguments)}}var F6e,z6e,G6e,vX,xX=R(()=>{"use strict";F6e=800,z6e=16,G6e=Date.now;o($6e,"shortOut");vX=$6e});var V6e,d5,W_=R(()=>{"use strict";yX();xX();V6e=vX(gX),d5=V6e});function U6e(t,e){return d5(f5(t,e,ea),t+"")}var pc,sm=R(()=>{"use strict";Eu();H_();W_();o(U6e,"baseRest");pc=U6e});function H6e(t,e,r){if(!pn(r))return!1;var n=typeof e;return(n=="number"?ei(r)&&Fh(e,r.length):n=="string"&&e in r)?No(r[e],t):!1}var eo,Ed=R(()=>{"use strict";xd();Io();uv();Js();o(H6e,"isIterateeCall");eo=H6e});function Y6e(t){return pc(function(e,r){var n=-1,i=r.length,a=i>1?r[i-1]:void 0,s=i>2?r[2]:void 0;for(a=t.length>3&&typeof a=="function"?(i--,a):void 0,s&&eo(r[0],r[1],s)&&(a=i<3?void 0:a,i=1),e=Object(e);++n{"use strict";sm();Ed();o(Y6e,"createAssigner");p5=Y6e});var W6e,Gh,X_=R(()=>{"use strict";fX();q_();W6e=p5(function(t,e,r){hX(t,e,r)}),Gh=W6e});function om(t,e){if(!t)return e;let r=`curve${t.charAt(0).toUpperCase()+t.slice(1)}`;return q6e[r]??e}function Q6e(t,e){let r=t.trim();if(r)return e.securityLevel!=="loose"?(0,TX.sanitizeUrl)(r):r}function CX(t,e){return!t||!e?0:Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}function J6e(t){let e,r=0;t.forEach(i=>{r+=CX(i,e),e=i});let n=r/2;return Q_(t,n)}function eCe(t){return t.length===1?t[0]:J6e(t)}function rCe(t,e,r){let n=structuredClone(r);V.info("our points",n),e!=="start_left"&&e!=="start_right"&&n.reverse();let i=25+t,a=Q_(n,i),s=10+t*.5,l=Math.atan2(n[0].y-a.y,n[0].x-a.x),u={x:0,y:0};return e==="start_left"?(u.x=Math.sin(l+Math.PI)*s+(n[0].x+a.x)/2,u.y=-Math.cos(l+Math.PI)*s+(n[0].y+a.y)/2):e==="end_right"?(u.x=Math.sin(l-Math.PI)*s+(n[0].x+a.x)/2-5,u.y=-Math.cos(l-Math.PI)*s+(n[0].y+a.y)/2-5):e==="end_left"?(u.x=Math.sin(l)*s+(n[0].x+a.x)/2-5,u.y=-Math.cos(l)*s+(n[0].y+a.y)/2-5):(u.x=Math.sin(l)*s+(n[0].x+a.x)/2,u.y=-Math.cos(l)*s+(n[0].y+a.y)/2),u}function lm(t){let e="",r="";for(let n of t)n!==void 0&&(n.startsWith("color:")||n.startsWith("text-align:")?r=r+n+";":e=e+n+";");return{style:e,labelStyle:r}}function nCe(t){let e="",r="0123456789abcdef",n=r.length;for(let i=0;i{"use strict";TX=Xi(Up(),1);Zt();rr();r7();ut();Hf();cp();R_();X_();Vb();K_="\u200B",q6e={curveBasis:vs,curveBasisClosed:B3,curveBasisOpen:F3,curveBumpX:s_,curveBumpY:o_,curveBundle:l_,curveCardinalClosed:u_,curveCardinalOpen:f_,curveCardinal:c_,curveCatmullRomClosed:m_,curveCatmullRomOpen:g_,curveCatmullRom:p_,curveLinear:xu,curveLinearClosed:U3,curveMonotoneX:v_,curveMonotoneY:x_,curveNatural:Y3,curveStep:q3,curveStepAfter:w_,curveStepBefore:b_},X6e=/\s*(?:(\w+)(?=:):|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi,j6e=o(function(t,e){let r=kX(t,/(?:init\b)|(?:initialize\b)/),n={};if(Array.isArray(r)){let s=r.map(l=>l.args);fp(s),n=On(n,[...s])}else n=r.args;if(!n)return;let i=lp(t,e),a="config";return n[a]!==void 0&&(i==="flowchart-v2"&&(i="flowchart"),n[i]=n[a],delete n[a]),n},"detectInit"),kX=o(function(t,e=null){try{let r=new RegExp(`[%]{2}(?![{]${X6e.source})(?=[}][%]{2}).* +`,"ig");t=t.trim().replace(r,"").replace(/'/gm,'"'),V.debug(`Detecting diagram directive${e!==null?" type:"+e:""} based on the text:${t}`);let n,i=[];for(;(n=Vf.exec(t))!==null;)if(n.index===Vf.lastIndex&&Vf.lastIndex++,n&&!e||e&&n[1]?.match(e)||e&&n[2]?.match(e)){let a=n[1]?n[1]:n[2],s=n[3]?n[3].trim():n[4]?JSON.parse(n[4].trim()):null;i.push({type:a,args:s})}return i.length===0?{type:t,args:null}:i.length===1?i[0]:i}catch(r){return V.error(`ERROR: ${r.message} - Unable to parse directive type: '${e}' based on the text: '${t}'`),{type:void 0,args:null}}},"detectDirective"),EX=o(function(t){return t.replace(Vf,"")},"removeDirectives"),K6e=o(function(t,e){for(let[r,n]of e.entries())if(n.match(t))return r;return-1},"isSubstringInArray");o(om,"interpolateToCurve");o(Q6e,"formatUrl");Z6e=o((t,...e)=>{let r=t.split("."),n=r.length-1,i=r[n],a=window;for(let s=0;s{let r=Math.pow(10,e);return Math.round(t*r)/r},"roundNumber"),Q_=o((t,e)=>{let r,n=e;for(let i of t){if(r){let a=CX(i,r);if(a=1)return{x:i.x,y:i.y};if(s>0&&s<1)return{x:bX((1-s)*r.x+s*i.x,5),y:bX((1-s)*r.y+s*i.y,5)}}}r=i}throw new Error("Could not find a suitable point for the given distance")},"calculatePoint"),tCe=o((t,e,r)=>{V.info(`our points ${JSON.stringify(e)}`),e[0]!==r&&(e=e.reverse());let i=Q_(e,25),a=t?10:5,s=Math.atan2(e[0].y-i.y,e[0].x-i.x),l={x:0,y:0};return l.x=Math.sin(s)*a+(e[0].x+i.x)/2,l.y=-Math.cos(s)*a+(e[0].y+i.y)/2,l},"calcCardinalityPosition");o(rCe,"calcTerminalLabelPosition");o(lm,"getStylesFromArray");wX=0,Z_=o(()=>(wX++,"id-"+Math.random().toString(36).substr(2,12)+"-"+wX),"generateId");o(nCe,"makeRandomHex");J_=o(t=>nCe(t.length),"random"),iCe=o(function(){return{x:0,y:0,fill:void 0,anchor:"start",style:"#666",width:100,height:100,textMargin:0,rx:0,ry:0,valign:void 0,text:""}},"getTextObj"),aCe=o(function(t,e){let r=e.text.replace(We.lineBreakRegex," "),[,n]=mc(e.fontSize),i=t.append("text");i.attr("x",e.x),i.attr("y",e.y),i.style("text-anchor",e.anchor),i.style("font-family",e.fontFamily),i.style("font-size",n),i.style("font-weight",e.fontWeight),i.attr("fill",e.fill),e.class!==void 0&&i.attr("class",e.class);let a=i.append("tspan");return a.attr("x",e.x+e.textMargin*2),a.attr("fill",e.fill),a.text(r),i},"drawSimpleText"),e9=qp((t,e,r)=>{if(!t||(r=Object.assign({fontSize:12,fontWeight:400,fontFamily:"Arial",joinWith:"
    "},r),We.lineBreakRegex.test(t)))return t;let n=t.split(" ").filter(Boolean),i=[],a="";return n.forEach((s,l)=>{let u=Cl(`${s} `,r),h=Cl(a,r);if(u>e){let{hyphenatedStrings:p,remainingWord:m}=sCe(s,e,"-",r);i.push(a,...p),a=m}else h+u>=e?(i.push(a),a=s):a=[a,s].filter(Boolean).join(" ");l+1===n.length&&i.push(a)}),i.filter(s=>s!=="").join(r.joinWith)},(t,e,r)=>`${t}${e}${r.fontSize}${r.fontWeight}${r.fontFamily}${r.joinWith}`),sCe=qp((t,e,r="-",n)=>{n=Object.assign({fontSize:12,fontWeight:400,fontFamily:"Arial",margin:0},n);let i=[...t],a=[],s="";return i.forEach((l,u)=>{let h=`${s}${l}`;if(Cl(h,n)>=e){let d=u+1,p=i.length===d,m=`${h}${r}`;a.push(p?h:m),s=""}else s=h}),{hyphenatedStrings:a,remainingWord:s}},(t,e,r="-",n)=>`${t}${e}${r}${n.fontSize}${n.fontWeight}${n.fontFamily}`);o(g5,"calculateTextHeight");o(Cl,"calculateTextWidth");t9=qp((t,e)=>{let{fontSize:r=12,fontFamily:n="Arial",fontWeight:i=400}=e;if(!t)return{width:0,height:0};let[,a]=mc(r),s=["sans-serif",n],l=t.split(We.lineBreakRegex),u=[],h=$e("body");if(!h.remove)return{width:0,height:0,lineHeight:0};let f=h.append("svg");for(let p of s){let m=0,g={width:0,height:0,lineHeight:0};for(let y of l){let v=iCe();v.text=y||K_;let x=aCe(f,v).style("font-size",a).style("font-weight",i).style("font-family",p),b=(x._groups||x)[0][0].getBBox();if(b.width===0&&b.height===0)throw new Error("svg element not in render tree");g.width=Math.round(Math.max(g.width,b.width)),m=Math.round(b.height),g.height+=m,g.lineHeight=Math.round(Math.max(g.lineHeight,m))}u.push(g)}f.remove();let d=isNaN(u[1].height)||isNaN(u[1].width)||isNaN(u[1].lineHeight)||u[0].height>u[1].height&&u[0].width>u[1].width&&u[0].lineHeight>u[1].lineHeight?0:1;return u[d]},(t,e)=>`${t}${e.fontSize}${e.fontWeight}${e.fontFamily}`),j_=class{constructor(e=!1,r){this.count=0;this.count=r?r.length:0,this.next=e?()=>this.count++:()=>Date.now()}static{o(this,"InitIDGenerator")}},oCe=o(function(t){return m5=m5||document.createElement("div"),t=escape(t).replace(/%26/g,"&").replace(/%23/g,"#").replace(/%3B/g,";"),m5.innerHTML=t,unescape(m5.textContent)},"entityDecode");o(r9,"isDetailedError");lCe=o((t,e,r,n)=>{if(!n)return;let i=t.node()?.getBBox();i&&t.append("text").text(n).attr("x",i.x+i.width/2).attr("y",-r).attr("class",e)},"insertTitle"),mc=o(t=>{if(typeof t=="number")return[t,t+"px"];let e=parseInt(t??"",10);return Number.isNaN(e)?[void 0,void 0]:t===String(e)?[e,t+"px"]:[e,t]},"parseFontSize");o(Ts,"cleanAndMerge");Lt={assignWithDepth:On,wrapLabel:e9,calculateTextHeight:g5,calculateTextWidth:Cl,calculateTextDimensions:t9,cleanAndMerge:Ts,detectInit:j6e,detectDirective:kX,isSubstringInArray:K6e,interpolateToCurve:om,calcLabelPosition:eCe,calcCardinalityPosition:tCe,calcTerminalLabelPosition:rCe,formatUrl:Q6e,getStylesFromArray:lm,generateId:Z_,random:J_,runFunc:Z6e,entityDecode:oCe,insertTitle:lCe,parseFontSize:mc,InitIDGenerator:j_},SX=o(function(t){let e=t;return e=e.replace(/style.*:\S*#.*;/g,function(r){return r.substring(0,r.length-1)}),e=e.replace(/classDef.*:\S*#.*;/g,function(r){return r.substring(0,r.length-1)}),e=e.replace(/#\w+;/g,function(r){let n=r.substring(1,r.length-1);return/^\+?\d+$/.test(n)?"\uFB02\xB0\xB0"+n+"\xB6\xDF":"\uFB02\xB0"+n+"\xB6\xDF"}),e},"encodeEntities"),to=o(function(t){return t.replace(/fl°°/g,"&#").replace(/fl°/g,"&").replace(/¶ß/g,";")},"decodeEntities"),y5=o((t,e,{counter:r=0,prefix:n,suffix:i})=>`${n?`${n}_`:""}${t}_${e}_${r}${i?`_${i}`:""}`,"getEdgeId")});function Sl(t,e,r,n,i){if(!e[t].width)if(r)e[t].text=e9(e[t].text,i,n),e[t].textLines=e[t].text.split(We.lineBreakRegex).length,e[t].width=i,e[t].height=g5(e[t].text,n);else{let a=e[t].text.split(We.lineBreakRegex);e[t].textLines=a.length;let s=0;e[t].height=0,e[t].width=0;for(let l of a)e[t].width=Math.max(Cl(l,n),e[t].width),s=g5(l,n),e[t].height=e[t].height+s}}function RX(t,e,r,n,i){let a=new w5(i);a.data.widthLimit=r.data.widthLimit/Math.min(n9,n.length);for(let[s,l]of n.entries()){let u=0;l.image={width:0,height:0,Y:0},l.sprite&&(l.image.width=48,l.image.height=48,l.image.Y=u,u=l.image.Y+l.image.height);let h=l.wrap&&Nt.wrap,f=v5(Nt);if(f.fontSize=f.fontSize+2,f.fontWeight="bold",Sl("label",l,h,f,a.data.widthLimit),l.label.Y=u+8,u=l.label.Y+l.label.height,l.type&&l.type.text!==""){l.type.text="["+l.type.text+"]";let g=v5(Nt);Sl("type",l,h,g,a.data.widthLimit),l.type.Y=u+5,u=l.type.Y+l.type.height}if(l.descr&&l.descr.text!==""){let g=v5(Nt);g.fontSize=g.fontSize-2,Sl("descr",l,h,g,a.data.widthLimit),l.descr.Y=u+20,u=l.descr.Y+l.descr.height}if(s==0||s%n9===0){let g=r.data.startx+Nt.diagramMarginX,y=r.data.stopy+Nt.diagramMarginY+u;a.setData(g,g,y,y)}else{let g=a.data.stopx!==a.data.startx?a.data.stopx+Nt.diagramMarginX:a.data.startx,y=a.data.starty;a.setData(g,g,y,y)}a.name=l.alias;let d=i.db.getC4ShapeArray(l.alias),p=i.db.getC4ShapeKeys(l.alias);p.length>0&&DX(a,t,d,p),e=l.alias;let m=i.db.getBoundarys(e);m.length>0&&RX(t,e,a,m,i),l.alias!=="global"&&LX(t,l,a),r.data.stopy=Math.max(a.data.stopy+Nt.c4ShapeMargin,r.data.stopy),r.data.stopx=Math.max(a.data.stopx+Nt.c4ShapeMargin,r.data.stopx),x5=Math.max(x5,r.data.stopx),b5=Math.max(b5,r.data.stopy)}}var x5,b5,_X,n9,Nt,w5,i9,hv,v5,cCe,LX,DX,ks,AX,uCe,hCe,fCe,a9,NX=R(()=>{"use strict";Zt();AW();ut();VC();rr();lS();_t();cp();xr();Yn();x5=0,b5=0,_X=4,n9=2;U1.yy=hy;Nt={},w5=class{static{o(this,"Bounds")}constructor(e){this.name="",this.data={},this.data.startx=void 0,this.data.stopx=void 0,this.data.starty=void 0,this.data.stopy=void 0,this.data.widthLimit=void 0,this.nextData={},this.nextData.startx=void 0,this.nextData.stopx=void 0,this.nextData.starty=void 0,this.nextData.stopy=void 0,this.nextData.cnt=0,i9(e.db.getConfig())}setData(e,r,n,i){this.nextData.startx=this.data.startx=e,this.nextData.stopx=this.data.stopx=r,this.nextData.starty=this.data.starty=n,this.nextData.stopy=this.data.stopy=i}updateVal(e,r,n,i){e[r]===void 0?e[r]=n:e[r]=i(n,e[r])}insert(e){this.nextData.cnt=this.nextData.cnt+1;let r=this.nextData.startx===this.nextData.stopx?this.nextData.stopx+e.margin:this.nextData.stopx+e.margin*2,n=r+e.width,i=this.nextData.starty+e.margin*2,a=i+e.height;(r>=this.data.widthLimit||n>=this.data.widthLimit||this.nextData.cnt>_X)&&(r=this.nextData.startx+e.margin+Nt.nextLinePaddingX,i=this.nextData.stopy+e.margin*2,this.nextData.stopx=n=r+e.width,this.nextData.starty=this.nextData.stopy,this.nextData.stopy=a=i+e.height,this.nextData.cnt=1),e.x=r,e.y=i,this.updateVal(this.data,"startx",r,Math.min),this.updateVal(this.data,"starty",i,Math.min),this.updateVal(this.data,"stopx",n,Math.max),this.updateVal(this.data,"stopy",a,Math.max),this.updateVal(this.nextData,"startx",r,Math.min),this.updateVal(this.nextData,"starty",i,Math.min),this.updateVal(this.nextData,"stopx",n,Math.max),this.updateVal(this.nextData,"stopy",a,Math.max)}init(e){this.name="",this.data={startx:void 0,stopx:void 0,starty:void 0,stopy:void 0,widthLimit:void 0},this.nextData={startx:void 0,stopx:void 0,starty:void 0,stopy:void 0,cnt:0},i9(e.db.getConfig())}bumpLastMargin(e){this.data.stopx+=e,this.data.stopy+=e}},i9=o(function(t){On(Nt,t),t.fontFamily&&(Nt.personFontFamily=Nt.systemFontFamily=Nt.messageFontFamily=t.fontFamily),t.fontSize&&(Nt.personFontSize=Nt.systemFontSize=Nt.messageFontSize=t.fontSize),t.fontWeight&&(Nt.personFontWeight=Nt.systemFontWeight=Nt.messageFontWeight=t.fontWeight)},"setConf"),hv=o((t,e)=>({fontFamily:t[e+"FontFamily"],fontSize:t[e+"FontSize"],fontWeight:t[e+"FontWeight"]}),"c4ShapeFont"),v5=o(t=>({fontFamily:t.boundaryFontFamily,fontSize:t.boundaryFontSize,fontWeight:t.boundaryFontWeight}),"boundaryFont"),cCe=o(t=>({fontFamily:t.messageFontFamily,fontSize:t.messageFontSize,fontWeight:t.messageFontWeight}),"messageFont");o(Sl,"calcC4ShapeTextWH");LX=o(function(t,e,r){e.x=r.data.startx,e.y=r.data.starty,e.width=r.data.stopx-r.data.startx,e.height=r.data.stopy-r.data.starty,e.label.y=Nt.c4ShapeMargin-35;let n=e.wrap&&Nt.wrap,i=v5(Nt);i.fontSize=i.fontSize+2,i.fontWeight="bold";let a=Cl(e.label.text,i);Sl("label",e,n,i,a),Tl.drawBoundary(t,e,Nt)},"drawBoundary"),DX=o(function(t,e,r,n){let i=0;for(let a of n){i=0;let s=r[a],l=hv(Nt,s.typeC4Shape.text);switch(l.fontSize=l.fontSize-2,s.typeC4Shape.width=Cl("\xAB"+s.typeC4Shape.text+"\xBB",l),s.typeC4Shape.height=l.fontSize+2,s.typeC4Shape.Y=Nt.c4ShapePadding,i=s.typeC4Shape.Y+s.typeC4Shape.height-4,s.image={width:0,height:0,Y:0},s.typeC4Shape.text){case"person":case"external_person":s.image.width=48,s.image.height=48,s.image.Y=i,i=s.image.Y+s.image.height;break}s.sprite&&(s.image.width=48,s.image.height=48,s.image.Y=i,i=s.image.Y+s.image.height);let u=s.wrap&&Nt.wrap,h=Nt.width-Nt.c4ShapePadding*2,f=hv(Nt,s.typeC4Shape.text);if(f.fontSize=f.fontSize+2,f.fontWeight="bold",Sl("label",s,u,f,h),s.label.Y=i+8,i=s.label.Y+s.label.height,s.type&&s.type.text!==""){s.type.text="["+s.type.text+"]";let m=hv(Nt,s.typeC4Shape.text);Sl("type",s,u,m,h),s.type.Y=i+5,i=s.type.Y+s.type.height}else if(s.techn&&s.techn.text!==""){s.techn.text="["+s.techn.text+"]";let m=hv(Nt,s.techn.text);Sl("techn",s,u,m,h),s.techn.Y=i+5,i=s.techn.Y+s.techn.height}let d=i,p=s.label.width;if(s.descr&&s.descr.text!==""){let m=hv(Nt,s.typeC4Shape.text);Sl("descr",s,u,m,h),s.descr.Y=i+20,i=s.descr.Y+s.descr.height,p=Math.max(s.label.width,s.descr.width),d=i-s.descr.textLines*5}p=p+Nt.c4ShapePadding,s.width=Math.max(s.width||Nt.width,p,Nt.width),s.height=Math.max(s.height||Nt.height,d,Nt.height),s.margin=s.margin||Nt.c4ShapeMargin,t.insert(s),Tl.drawC4Shape(e,s,Nt)}t.bumpLastMargin(Nt.c4ShapeMargin)},"drawC4ShapeArray"),ks=class{static{o(this,"Point")}constructor(e,r){this.x=e,this.y=r}},AX=o(function(t,e){let r=t.x,n=t.y,i=e.x,a=e.y,s=r+t.width/2,l=n+t.height/2,u=Math.abs(r-i),h=Math.abs(n-a),f=h/u,d=t.height/t.width,p=null;return n==a&&ri?p=new ks(r,l):r==i&&na&&(p=new ks(s,n)),r>i&&n=f?p=new ks(r,l+f*t.width/2):p=new ks(s-u/h*t.height/2,n+t.height):r=f?p=new ks(r+t.width,l+f*t.width/2):p=new ks(s+u/h*t.height/2,n+t.height):ra?d>=f?p=new ks(r+t.width,l-f*t.width/2):p=new ks(s+t.height/2*u/h,n):r>i&&n>a&&(d>=f?p=new ks(r,l-t.width/2*f):p=new ks(s-t.height/2*u/h,n)),p},"getIntersectPoint"),uCe=o(function(t,e){let r={x:0,y:0};r.x=e.x+e.width/2,r.y=e.y+e.height/2;let n=AX(t,r);r.x=t.x+t.width/2,r.y=t.y+t.height/2;let i=AX(e,r);return{startPoint:n,endPoint:i}},"getIntersectPoints"),hCe=o(function(t,e,r,n){let i=0;for(let a of e){i=i+1;let s=a.wrap&&Nt.wrap,l=cCe(Nt);n.db.getC4Type()==="C4Dynamic"&&(a.label.text=i+": "+a.label.text);let h=Cl(a.label.text,l);Sl("label",a,s,l,h),a.techn&&a.techn.text!==""&&(h=Cl(a.techn.text,l),Sl("techn",a,s,l,h)),a.descr&&a.descr.text!==""&&(h=Cl(a.descr.text,l),Sl("descr",a,s,l,h));let f=r(a.from),d=r(a.to),p=uCe(f,d);a.startPoint=p.startPoint,a.endPoint=p.endPoint}Tl.drawRels(t,e,Nt)},"drawRels");o(RX,"drawInsideBoundary");fCe=o(function(t,e,r,n){Nt=de().c4;let i=de().securityLevel,a;i==="sandbox"&&(a=$e("#i"+e));let s=i==="sandbox"?$e(a.nodes()[0].contentDocument.body):$e("body"),l=n.db;n.db.setWrap(Nt.wrap),_X=l.getC4ShapeInRow(),n9=l.getC4BoundaryInRow(),V.debug(`C:${JSON.stringify(Nt,null,2)}`);let u=i==="sandbox"?s.select(`[id="${e}"]`):$e(`[id="${e}"]`);Tl.insertComputerIcon(u),Tl.insertDatabaseIcon(u),Tl.insertClockIcon(u);let h=new w5(n);h.setData(Nt.diagramMarginX,Nt.diagramMarginX,Nt.diagramMarginY,Nt.diagramMarginY),h.data.widthLimit=screen.availWidth,x5=Nt.diagramMarginX,b5=Nt.diagramMarginY;let f=n.db.getTitle(),d=n.db.getBoundarys("");RX(u,"",h,d,n),Tl.insertArrowHead(u),Tl.insertArrowEnd(u),Tl.insertArrowCrossHead(u),Tl.insertArrowFilledHead(u),hCe(u,n.db.getRels(),n.db.getC4Shape,n),h.data.stopx=x5,h.data.stopy=b5;let p=h.data,g=p.stopy-p.starty+2*Nt.diagramMarginY,v=p.stopx-p.startx+2*Nt.diagramMarginX;f&&u.append("text").text(f).attr("x",(p.stopx-p.startx)/2-4*Nt.diagramMarginX).attr("y",p.starty+Nt.diagramMarginY),Sr(u,g,v,Nt.useMaxWidth);let x=f?60:0;u.attr("viewBox",p.startx-Nt.diagramMarginX+" -"+(Nt.diagramMarginY+x)+" "+v+" "+(g+x)),V.debug("models:",p)},"draw"),a9={drawPersonOrSystemArray:DX,drawBoundary:LX,setConf:i9,draw:fCe}});var dCe,MX,IX=R(()=>{"use strict";dCe=o(t=>`.person { + stroke: ${t.personBorder}; + fill: ${t.personBkg}; + } +`,"getStyles"),MX=dCe});var OX={};hr(OX,{diagram:()=>pCe});var pCe,PX=R(()=>{"use strict";VC();lS();NX();IX();pCe={parser:rz,db:hy,renderer:a9,styles:MX,init:o(({c4:t,wrap:e})=>{a9.setConf(t),hy.setWrap(e)},"init")}});function o9(t){let e=[];for(let r of t){let n=dv.get(r);n?.styles&&(e=[...e,...n.styles??[]].map(i=>i.trim())),n?.textStyles&&(e=[...e,...n.textStyles??[]].map(i=>i.trim()))}return e}var vCe,zX,cm,$h,Es,dv,Cu,l9,c9,T5,s9,Fo,k5,E5,C5,S5,xCe,bCe,wCe,TCe,kCe,ECe,CCe,u9,SCe,ACe,_Ce,GX,LCe,DCe,h9,$X,VX,RCe,UX,NCe,MCe,ICe,OCe,PCe,fv,HX,YX,BCe,FCe,WX,zCe,GCe,$Ce,VCe,UCe,qX,XX,HCe,YCe,WCe,qCe,XCe,jCe,A5,f9=R(()=>{"use strict";Zt();xr();_t();rr();ut();bi();vCe="flowchart-",zX=0,cm=de(),$h=new Map,Es=[],dv=new Map,Cu=[],l9=new Map,c9=new Map,T5=0,s9=!0,E5=[],C5=o(t=>We.sanitizeText(t,cm),"sanitizeText"),S5=o(function(t){for(let e of $h.values())if(e.id===t)return e.domId;return t},"lookUpDomId"),xCe=o(function(t,e,r,n,i,a,s={}){if(!t||t.trim().length===0)return;let l,u=$h.get(t);u===void 0&&(u={id:t,labelType:"text",domId:vCe+t+"-"+zX,styles:[],classes:[]},$h.set(t,u)),zX++,e!==void 0?(cm=de(),l=C5(e.text.trim()),u.labelType=e.type,l.startsWith('"')&&l.endsWith('"')&&(l=l.substring(1,l.length-1)),u.text=l):u.text===void 0&&(u.text=t),r!==void 0&&(u.type=r),n?.forEach(function(h){u.styles.push(h)}),i?.forEach(function(h){u.classes.push(h)}),a!==void 0&&(u.dir=a),u.props===void 0?u.props=s:s!==void 0&&Object.assign(u.props,s)},"addVertex"),bCe=o(function(t,e,r){let a={start:t,end:e,type:void 0,text:"",labelType:"text"};V.info("abc78 Got edge...",a);let s=r.text;if(s!==void 0&&(a.text=C5(s.text.trim()),a.text.startsWith('"')&&a.text.endsWith('"')&&(a.text=a.text.substring(1,a.text.length-1)),a.labelType=s.type),r!==void 0&&(a.type=r.type,a.stroke=r.stroke,a.length=r.length>10?10:r.length),Es.length<(cm.maxEdges??500))V.info("Pushing edge..."),Es.push(a);else throw new Error(`Edge limit exceeded. ${Es.length} edges found, but the limit is ${cm.maxEdges}. + +Initialize mermaid with maxEdges set to a higher number to allow more edges. +You cannot set this config via configuration inside the diagram as it is a secure config. +You have to call mermaid.initialize.`)},"addSingleLink"),wCe=o(function(t,e,r){V.info("addLink",t,e,r);for(let n of t)for(let i of e)bCe(n,i,r)},"addLink"),TCe=o(function(t,e){t.forEach(function(r){r==="default"?Es.defaultInterpolate=e:Es[r].interpolate=e})},"updateLinkInterpolate"),kCe=o(function(t,e){t.forEach(function(r){if(typeof r=="number"&&r>=Es.length)throw new Error(`The index ${r} for linkStyle is out of bounds. Valid indices for linkStyle are between 0 and ${Es.length-1}. (Help: Ensure that the index is within the range of existing edges.)`);r==="default"?Es.defaultStyle=e:(Es[r].style=e,(Es[r]?.style?.length??0)>0&&!Es[r]?.style?.some(n=>n?.startsWith("fill"))&&Es[r]?.style?.push("fill:none"))})},"updateLink"),ECe=o(function(t,e){t.split(",").forEach(function(r){let n=dv.get(r);n===void 0&&(n={id:r,styles:[],textStyles:[]},dv.set(r,n)),e?.forEach(function(i){if(/color/.exec(i)){let a=i.replace("fill","bgFill");n.textStyles.push(a)}n.styles.push(i)})})},"addClass"),CCe=o(function(t){Fo=t,/.*/.exec(Fo)&&(Fo="LR"),/.*v/.exec(Fo)&&(Fo="TB"),Fo==="TD"&&(Fo="TB")},"setDirection"),u9=o(function(t,e){for(let r of t.split(",")){let n=$h.get(r);n&&n.classes.push(e);let i=l9.get(r);i&&i.classes.push(e)}},"setClass"),SCe=o(function(t,e){if(e!==void 0){e=C5(e);for(let r of t.split(","))c9.set(k5==="gen-1"?S5(r):r,e)}},"setTooltip"),ACe=o(function(t,e,r){let n=S5(t);if(de().securityLevel!=="loose"||e===void 0)return;let i=[];if(typeof r=="string"){i=r.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);for(let s=0;s")),i.classed("hover",!0)}).on("mouseout",function(){e.transition().duration(500).style("opacity",0),$e(this).classed("hover",!1)})},"setupToolTips");E5.push(UX);NCe=o(function(t="gen-1"){$h=new Map,dv=new Map,Es=[],E5=[UX],Cu=[],l9=new Map,T5=0,c9=new Map,s9=!0,k5=t,cm=de(),vr()},"clear"),MCe=o(t=>{k5=t||"gen-2"},"setGen"),ICe=o(function(){return"fill:#ffa;stroke: #f66; stroke-width: 3px; stroke-dasharray: 5, 5;fill:#ffa;stroke: #666;"},"defaultStyle"),OCe=o(function(t,e,r){let n=t.text.trim(),i=r.text;t===r&&/\s/.exec(r.text)&&(n=void 0);function a(h){let f={boolean:{},number:{},string:{}},d=[],p;return{nodeList:h.filter(function(g){let y=typeof g;return g.stmt&&g.stmt==="dir"?(p=g.value,!1):g.trim()===""?!1:y in f?f[y].hasOwnProperty(g)?!1:f[y][g]=!0:d.includes(g)?!1:d.push(g)}),dir:p}}o(a,"uniq");let{nodeList:s,dir:l}=a(e.flat());if(k5==="gen-1")for(let h=0;h2e3)return{result:!1,count:0};if(HX[fv]=e,Cu[e].id===t)return{result:!0,count:0};let n=0,i=1;for(;n=0){let s=YX(t,a);if(s.result)return{result:!0,count:i+s.count};i=i+s.count}n=n+1}return{result:!1,count:i}},"indexNodes2"),BCe=o(function(t){return HX[t]},"getDepthFirstPos"),FCe=o(function(){fv=-1,Cu.length>0&&YX("none",Cu.length-1)},"indexNodes"),WX=o(function(){return Cu},"getSubGraphs"),zCe=o(()=>s9?(s9=!1,!0):!1,"firstGraph"),GCe=o(t=>{let e=t.trim(),r="arrow_open";switch(e[0]){case"<":r="arrow_point",e=e.slice(1);break;case"x":r="arrow_cross",e=e.slice(1);break;case"o":r="arrow_circle",e=e.slice(1);break}let n="normal";return e.includes("=")&&(n="thick"),e.includes(".")&&(n="dotted"),{type:r,stroke:n}},"destructStartLink"),$Ce=o((t,e)=>{let r=e.length,n=0;for(let i=0;i{let e=t.trim(),r=e.slice(0,-1),n="arrow_open";switch(e.slice(-1)){case"x":n="arrow_cross",e.startsWith("x")&&(n="double_"+n,r=r.slice(1));break;case">":n="arrow_point",e.startsWith("<")&&(n="double_"+n,r=r.slice(1));break;case"o":n="arrow_circle",e.startsWith("o")&&(n="double_"+n,r=r.slice(1));break}let i="normal",a=r.length-1;r.startsWith("=")&&(i="thick"),r.startsWith("~")&&(i="invisible");let s=$Ce(".",r);return s&&(i="dotted",a=s),{type:n,stroke:i,length:a}},"destructEndLink"),UCe=o((t,e)=>{let r=VCe(t),n;if(e){if(n=GCe(e),n.stroke!==r.stroke)return{type:"INVALID",stroke:"INVALID"};if(n.type==="arrow_open")n.type=r.type;else{if(n.type!==r.type)return{type:"INVALID",stroke:"INVALID"};n.type="double_"+n.type}return n.type==="double_arrow"&&(n.type="double_arrow_point"),n.length=r.length,n}return r},"destructLink"),qX=o((t,e)=>{for(let r of t)if(r.nodes.includes(e))return!0;return!1},"exists"),XX=o((t,e)=>{let r=[];return t.nodes.forEach((n,i)=>{qX(e,n)||r.push(t.nodes[i])}),{nodes:r}},"makeUniq"),HCe={firstGraph:zCe},YCe=o(t=>t.type==="square"?"squareRect":t.type==="round"?"roundedRect":t.type??"squareRect","getTypeFromVertex"),WCe=o((t,e)=>t.find(r=>r.id===e),"findNode"),qCe=o(t=>{let e="none",r="arrow_point";switch(t){case"arrow_point":case"arrow_circle":case"arrow_cross":r=t;break;case"double_arrow_point":case"double_arrow_circle":case"double_arrow_cross":e=t.replace("double_",""),r=e;break}return{arrowTypeStart:e,arrowTypeEnd:r}},"destructEdgeType"),XCe=o((t,e,r,n,i,a)=>{let s=r.get(t.id),l=n.get(t.id)??!1,u=WCe(e,t.id);u?(u.cssStyles=t.styles,u.cssCompiledStyles=o9(t.classes),u.cssClasses=t.classes.join(" ")):e.push({id:t.id,label:t.text,labelStyle:"",parentId:s,padding:i.flowchart?.padding||8,cssStyles:t.styles,cssCompiledStyles:o9(["default","node",...t.classes]),cssClasses:"default "+t.classes.join(" "),shape:YCe(t),dir:t.dir,domId:t.domId,isGroup:l,look:a,link:t.link,linkTarget:t.linkTarget,tooltip:GX(t.id)})},"addNodeFromVertex");o(o9,"getCompiledStyles");jCe=o(()=>{let t=de(),e=[],r=[],n=WX(),i=new Map,a=new Map;for(let u=n.length-1;u>=0;u--){let h=n[u];h.nodes.length>0&&a.set(h.id,!0);for(let f of h.nodes)i.set(f,h.id)}for(let u=n.length-1;u>=0;u--){let h=n[u];e.push({id:h.id,label:h.title,labelStyle:"",parentId:i.get(h.id),padding:8,cssCompiledStyles:o9(h.classes),cssClasses:h.classes.join(" "),shape:"rect",dir:h.dir,isGroup:!0,look:t.look})}$X().forEach(u=>{XCe(u,e,i,a,t,t.look||"classic")});let l=VX();return l.forEach((u,h)=>{let{arrowTypeStart:f,arrowTypeEnd:d}=qCe(u.type),p=[...l.defaultStyle??[]];u.style&&p.push(...u.style);let m={id:y5(u.start,u.end,{counter:h,prefix:"L"}),start:u.start,end:u.end,type:u.type??"normal",label:u.text,labelpos:"c",thickness:u.stroke,minlen:u.length,classes:u?.stroke==="invisible"?"":"edge-thickness-normal edge-pattern-solid flowchart-link",arrowTypeStart:u?.stroke==="invisible"?"none":f,arrowTypeEnd:u?.stroke==="invisible"?"none":d,arrowheadStyle:"fill: #333",labelStyle:p,style:p,pattern:u.stroke,look:t.look};r.push(m)}),{nodes:e,edges:r,other:{},config:t}},"getData"),A5={defaultConfig:o(()=>_4.flowchart,"defaultConfig"),setAccTitle:kr,getAccTitle:Ar,getAccDescription:Lr,getData:jCe,setAccDescription:_r,addVertex:xCe,lookUpDomId:S5,addLink:wCe,updateLinkInterpolate:TCe,updateLink:kCe,addClass:ECe,setDirection:CCe,setClass:u9,setTooltip:SCe,getTooltip:GX,setClickEvent:LCe,setLink:_Ce,bindFunctions:DCe,getDirection:h9,getVertices:$X,getEdges:VX,getClasses:RCe,clear:NCe,setGen:MCe,defaultStyle:ICe,addSubGraph:OCe,getDepthFirstPos:BCe,indexNodes:FCe,getSubGraphs:WX,destructLink:UCe,lex:HCe,exists:qX,makeUniq:XX,setDiagramTitle:nn,getDiagramTitle:Xr}});var KCe,jX,KX=R(()=>{"use strict";KCe=o(t=>{let e=new Set;for(let r of t)switch(r){case"x":e.add("right"),e.add("left");break;case"y":e.add("up"),e.add("down");break;default:e.add(r);break}return e},"expandAndDeduplicateDirections"),jX=o((t,e,r)=>{let n=KCe(t),i=2,a=e.height+2*r.padding,s=a/i,l=e.width+2*s+r.padding,u=r.padding/2;return n.has("right")&&n.has("left")&&n.has("up")&&n.has("down")?[{x:0,y:0},{x:s,y:0},{x:l/2,y:2*u},{x:l-s,y:0},{x:l,y:0},{x:l,y:-a/3},{x:l+2*u,y:-a/2},{x:l,y:-2*a/3},{x:l,y:-a},{x:l-s,y:-a},{x:l/2,y:-a-2*u},{x:s,y:-a},{x:0,y:-a},{x:0,y:-2*a/3},{x:-2*u,y:-a/2},{x:0,y:-a/3}]:n.has("right")&&n.has("left")&&n.has("up")?[{x:s,y:0},{x:l-s,y:0},{x:l,y:-a/2},{x:l-s,y:-a},{x:s,y:-a},{x:0,y:-a/2}]:n.has("right")&&n.has("left")&&n.has("down")?[{x:0,y:0},{x:s,y:-a},{x:l-s,y:-a},{x:l,y:0}]:n.has("right")&&n.has("up")&&n.has("down")?[{x:0,y:0},{x:l,y:-s},{x:l,y:-a+s},{x:0,y:-a}]:n.has("left")&&n.has("up")&&n.has("down")?[{x:l,y:0},{x:0,y:-s},{x:0,y:-a+s},{x:l,y:-a}]:n.has("right")&&n.has("left")?[{x:s,y:0},{x:s,y:-u},{x:l-s,y:-u},{x:l-s,y:0},{x:l,y:-a/2},{x:l-s,y:-a},{x:l-s,y:-a+u},{x:s,y:-a+u},{x:s,y:-a},{x:0,y:-a/2}]:n.has("up")&&n.has("down")?[{x:l/2,y:0},{x:0,y:-u},{x:s,y:-u},{x:s,y:-a+u},{x:0,y:-a+u},{x:l/2,y:-a},{x:l,y:-a+u},{x:l-s,y:-a+u},{x:l-s,y:-u},{x:l,y:-u}]:n.has("right")&&n.has("up")?[{x:0,y:0},{x:l,y:-s},{x:0,y:-a}]:n.has("right")&&n.has("down")?[{x:0,y:0},{x:l,y:0},{x:0,y:-a}]:n.has("left")&&n.has("up")?[{x:l,y:0},{x:0,y:-s},{x:l,y:-a}]:n.has("left")&&n.has("down")?[{x:l,y:0},{x:0,y:0},{x:l,y:-a}]:n.has("right")?[{x:s,y:-u},{x:s,y:-u},{x:l-s,y:-u},{x:l-s,y:0},{x:l,y:-a/2},{x:l-s,y:-a},{x:l-s,y:-a+u},{x:s,y:-a+u},{x:s,y:-a+u}]:n.has("left")?[{x:s,y:0},{x:s,y:-u},{x:l-s,y:-u},{x:l-s,y:-a+u},{x:s,y:-a+u},{x:s,y:-a},{x:0,y:-a/2}]:n.has("up")?[{x:s,y:-u},{x:s,y:-a+u},{x:0,y:-a+u},{x:l/2,y:-a},{x:l,y:-a+u},{x:l-s,y:-a+u},{x:l-s,y:-u}]:n.has("down")?[{x:l/2,y:0},{x:0,y:-u},{x:s,y:-u},{x:s,y:-a+u},{x:l-s,y:-a+u},{x:l-s,y:-u},{x:l,y:-u}]:[{x:0,y:0}]},"getArrowPoints")});function m9(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}function rj(t){Sd=t}function ro(t,e){if(e){if(nj.test(t))return t.replace(QCe,QX)}else if(ij.test(t))return t.replace(ZCe,QX);return t}function t7e(t){return t.replace(e7e,(e,r)=>(r=r.toLowerCase(),r==="colon"?":":r.charAt(0)==="#"?r.charAt(1)==="x"?String.fromCharCode(parseInt(r.substring(2),16)):String.fromCharCode(+r.substring(1)):""))}function ln(t,e){let r=typeof t=="string"?t:t.source;e=e||"";let n={replace:o((i,a)=>{let s=typeof a=="string"?a:a.source;return s=s.replace(r7e,"$1"),r=r.replace(i,s),n},"replace"),getRegex:o(()=>new RegExp(r,e),"getRegex")};return n}function ZX(t){try{t=encodeURI(t).replace(/%25/g,"%")}catch{return null}return t}function JX(t,e){let r=t.replace(/\|/g,(a,s,l)=>{let u=!1,h=s;for(;--h>=0&&l[h]==="\\";)u=!u;return u?"|":" |"}),n=r.split(/ \|/),i=0;if(n[0].trim()||n.shift(),n.length>0&&!n[n.length-1].trim()&&n.pop(),e)if(n.length>e)n.splice(e);else for(;n.length{let a=i.match(/^\s+/);if(a===null)return i;let[s]=a;return s.length>=n.length?i.slice(n.length):i}).join(` +`)}function jr(t,e){return Cd.parse(t,e)}var Sd,nj,QCe,ij,ZCe,JCe,QX,e7e,r7e,gv,hm,a7e,s7e,o7e,vv,l7e,aj,sj,g9,c7e,y9,u7e,h7e,D5,v9,f7e,oj,d7e,x9,tj,p7e,m7e,lj,g7e,cj,y7e,xv,v7e,x7e,b7e,w7e,T7e,k7e,E7e,C7e,S7e,L5,A7e,uj,hj,_7e,b9,L7e,d9,D7e,_5,mv,Su,fm,yv,Au,um,p9,Cd,mkt,gkt,ykt,vkt,xkt,bkt,wkt,fj=R(()=>{"use strict";o(m9,"_getDefaults");Sd=m9();o(rj,"changeDefaults");nj=/[&<>"']/,QCe=new RegExp(nj.source,"g"),ij=/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,ZCe=new RegExp(ij.source,"g"),JCe={"&":"&","<":"<",">":">",'"':""","'":"'"},QX=o(t=>JCe[t],"getEscapeReplacement");o(ro,"escape$1");e7e=/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig;o(t7e,"unescape");r7e=/(^|[^\[])\^/g;o(ln,"edit");o(ZX,"cleanUrl");gv={exec:o(()=>null,"exec")};o(JX,"splitCells");o(pv,"rtrim");o(n7e,"findClosingBracket");o(ej,"outputLink");o(i7e,"indentCodeCompensation");hm=class{static{o(this,"_Tokenizer")}options;rules;lexer;constructor(e){this.options=e||Sd}space(e){let r=this.rules.block.newline.exec(e);if(r&&r[0].length>0)return{type:"space",raw:r[0]}}code(e){let r=this.rules.block.code.exec(e);if(r){let n=r[0].replace(/^ {1,4}/gm,"");return{type:"code",raw:r[0],codeBlockStyle:"indented",text:this.options.pedantic?n:pv(n,` +`)}}}fences(e){let r=this.rules.block.fences.exec(e);if(r){let n=r[0],i=i7e(n,r[3]||"");return{type:"code",raw:n,lang:r[2]?r[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):r[2],text:i}}}heading(e){let r=this.rules.block.heading.exec(e);if(r){let n=r[2].trim();if(/#$/.test(n)){let i=pv(n,"#");(this.options.pedantic||!i||/ $/.test(i))&&(n=i.trim())}return{type:"heading",raw:r[0],depth:r[1].length,text:n,tokens:this.lexer.inline(n)}}}hr(e){let r=this.rules.block.hr.exec(e);if(r)return{type:"hr",raw:pv(r[0],` +`)}}blockquote(e){let r=this.rules.block.blockquote.exec(e);if(r){let n=pv(r[0],` +`).split(` +`),i="",a="",s=[];for(;n.length>0;){let l=!1,u=[],h;for(h=0;h/.test(n[h]))u.push(n[h]),l=!0;else if(!l)u.push(n[h]);else break;n=n.slice(h);let f=u.join(` +`),d=f.replace(/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,` + $1`).replace(/^ {0,3}>[ \t]?/gm,"");i=i?`${i} +${f}`:f,a=a?`${a} +${d}`:d;let p=this.lexer.state.top;if(this.lexer.state.top=!0,this.lexer.blockTokens(d,s,!0),this.lexer.state.top=p,n.length===0)break;let m=s[s.length-1];if(m?.type==="code")break;if(m?.type==="blockquote"){let g=m,y=g.raw+` +`+n.join(` +`),v=this.blockquote(y);s[s.length-1]=v,i=i.substring(0,i.length-g.raw.length)+v.raw,a=a.substring(0,a.length-g.text.length)+v.text;break}else if(m?.type==="list"){let g=m,y=g.raw+` +`+n.join(` +`),v=this.list(y);s[s.length-1]=v,i=i.substring(0,i.length-m.raw.length)+v.raw,a=a.substring(0,a.length-g.raw.length)+v.raw,n=y.substring(s[s.length-1].raw.length).split(` +`);continue}}return{type:"blockquote",raw:i,tokens:s,text:a}}}list(e){let r=this.rules.block.list.exec(e);if(r){let n=r[1].trim(),i=n.length>1,a={type:"list",raw:"",ordered:i,start:i?+n.slice(0,-1):"",loose:!1,items:[]};n=i?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=i?n:"[*+-]");let s=new RegExp(`^( {0,3}${n})((?:[ ][^\\n]*)?(?:\\n|$))`),l=!1;for(;e;){let u=!1,h="",f="";if(!(r=s.exec(e))||this.rules.block.hr.test(e))break;h=r[0],e=e.substring(h.length);let d=r[2].split(` +`,1)[0].replace(/^\t+/,x=>" ".repeat(3*x.length)),p=e.split(` +`,1)[0],m=!d.trim(),g=0;if(this.options.pedantic?(g=2,f=d.trimStart()):m?g=r[1].length+1:(g=r[2].search(/[^ ]/),g=g>4?1:g,f=d.slice(g),g+=r[1].length),m&&/^ *$/.test(p)&&(h+=p+` +`,e=e.substring(p.length+1),u=!0),!u){let x=new RegExp(`^ {0,${Math.min(3,g-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`),b=new RegExp(`^ {0,${Math.min(3,g-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),w=new RegExp(`^ {0,${Math.min(3,g-1)}}(?:\`\`\`|~~~)`),S=new RegExp(`^ {0,${Math.min(3,g-1)}}#`);for(;e;){let T=e.split(` +`,1)[0];if(p=T,this.options.pedantic&&(p=p.replace(/^ {1,4}(?=( {4})*[^ ])/g," ")),w.test(p)||S.test(p)||x.test(p)||b.test(e))break;if(p.search(/[^ ]/)>=g||!p.trim())f+=` +`+p.slice(g);else{if(m||d.search(/[^ ]/)>=4||w.test(d)||S.test(d)||b.test(d))break;f+=` +`+p}!m&&!p.trim()&&(m=!0),h+=T+` +`,e=e.substring(T.length+1),d=p.slice(g)}}a.loose||(l?a.loose=!0:/\n *\n *$/.test(h)&&(l=!0));let y=null,v;this.options.gfm&&(y=/^\[[ xX]\] /.exec(f),y&&(v=y[0]!=="[ ] ",f=f.replace(/^\[[ xX]\] +/,""))),a.items.push({type:"list_item",raw:h,task:!!y,checked:v,loose:!1,text:f,tokens:[]}),a.raw+=h}a.items[a.items.length-1].raw=a.items[a.items.length-1].raw.trimEnd(),a.items[a.items.length-1].text=a.items[a.items.length-1].text.trimEnd(),a.raw=a.raw.trimEnd();for(let u=0;ud.type==="space"),f=h.length>0&&h.some(d=>/\n.*\n/.test(d.raw));a.loose=f}if(a.loose)for(let u=0;u$/,"$1").replace(this.rules.inline.anyPunctuation,"$1"):"",a=r[3]?r[3].substring(1,r[3].length-1).replace(this.rules.inline.anyPunctuation,"$1"):r[3];return{type:"def",tag:n,raw:r[0],href:i,title:a}}}table(e){let r=this.rules.block.table.exec(e);if(!r||!/[:|]/.test(r[2]))return;let n=JX(r[1]),i=r[2].replace(/^\||\| *$/g,"").split("|"),a=r[3]&&r[3].trim()?r[3].replace(/\n[ \t]*$/,"").split(` +`):[],s={type:"table",raw:r[0],header:[],align:[],rows:[]};if(n.length===i.length){for(let l of i)/^ *-+: *$/.test(l)?s.align.push("right"):/^ *:-+: *$/.test(l)?s.align.push("center"):/^ *:-+ *$/.test(l)?s.align.push("left"):s.align.push(null);for(let l=0;l({text:u,tokens:this.lexer.inline(u),header:!1,align:s.align[h]})));return s}}lheading(e){let r=this.rules.block.lheading.exec(e);if(r)return{type:"heading",raw:r[0],depth:r[2].charAt(0)==="="?1:2,text:r[1],tokens:this.lexer.inline(r[1])}}paragraph(e){let r=this.rules.block.paragraph.exec(e);if(r){let n=r[1].charAt(r[1].length-1)===` +`?r[1].slice(0,-1):r[1];return{type:"paragraph",raw:r[0],text:n,tokens:this.lexer.inline(n)}}}text(e){let r=this.rules.block.text.exec(e);if(r)return{type:"text",raw:r[0],text:r[0],tokens:this.lexer.inline(r[0])}}escape(e){let r=this.rules.inline.escape.exec(e);if(r)return{type:"escape",raw:r[0],text:ro(r[1])}}tag(e){let r=this.rules.inline.tag.exec(e);if(r)return!this.lexer.state.inLink&&/^
    /i.test(r[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&/^<(pre|code|kbd|script)(\s|>)/i.test(r[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(r[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:r[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:r[0]}}link(e){let r=this.rules.inline.link.exec(e);if(r){let n=r[2].trim();if(!this.options.pedantic&&/^$/.test(n))return;let s=pv(n.slice(0,-1),"\\");if((n.length-s.length)%2===0)return}else{let s=n7e(r[2],"()");if(s>-1){let u=(r[0].indexOf("!")===0?5:4)+r[1].length+s;r[2]=r[2].substring(0,s),r[0]=r[0].substring(0,u).trim(),r[3]=""}}let i=r[2],a="";if(this.options.pedantic){let s=/^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(i);s&&(i=s[1],a=s[3])}else a=r[3]?r[3].slice(1,-1):"";return i=i.trim(),/^$/.test(n)?i=i.slice(1):i=i.slice(1,-1)),ej(r,{href:i&&i.replace(this.rules.inline.anyPunctuation,"$1"),title:a&&a.replace(this.rules.inline.anyPunctuation,"$1")},r[0],this.lexer)}}reflink(e,r){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){let i=(n[2]||n[1]).replace(/\s+/g," "),a=r[i.toLowerCase()];if(!a){let s=n[0].charAt(0);return{type:"text",raw:s,text:s}}return ej(n,a,n[0],this.lexer)}}emStrong(e,r,n=""){let i=this.rules.inline.emStrongLDelim.exec(e);if(!i||i[3]&&n.match(/[\p{L}\p{N}]/u))return;if(!(i[1]||i[2]||"")||!n||this.rules.inline.punctuation.exec(n)){let s=[...i[0]].length-1,l,u,h=s,f=0,d=i[0][0]==="*"?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(d.lastIndex=0,r=r.slice(-1*e.length+s);(i=d.exec(r))!=null;){if(l=i[1]||i[2]||i[3]||i[4]||i[5]||i[6],!l)continue;if(u=[...l].length,i[3]||i[4]){h+=u;continue}else if((i[5]||i[6])&&s%3&&!((s+u)%3)){f+=u;continue}if(h-=u,h>0)continue;u=Math.min(u,u+h+f);let p=[...i[0]][0].length,m=e.slice(0,s+i.index+p+u);if(Math.min(s,u)%2){let y=m.slice(1,-1);return{type:"em",raw:m,text:y,tokens:this.lexer.inlineTokens(y)}}let g=m.slice(2,-2);return{type:"strong",raw:m,text:g,tokens:this.lexer.inlineTokens(g)}}}}codespan(e){let r=this.rules.inline.code.exec(e);if(r){let n=r[2].replace(/\n/g," "),i=/[^ ]/.test(n),a=/^ /.test(n)&&/ $/.test(n);return i&&a&&(n=n.substring(1,n.length-1)),n=ro(n,!0),{type:"codespan",raw:r[0],text:n}}}br(e){let r=this.rules.inline.br.exec(e);if(r)return{type:"br",raw:r[0]}}del(e){let r=this.rules.inline.del.exec(e);if(r)return{type:"del",raw:r[0],text:r[2],tokens:this.lexer.inlineTokens(r[2])}}autolink(e){let r=this.rules.inline.autolink.exec(e);if(r){let n,i;return r[2]==="@"?(n=ro(r[1]),i="mailto:"+n):(n=ro(r[1]),i=n),{type:"link",raw:r[0],text:n,href:i,tokens:[{type:"text",raw:n,text:n}]}}}url(e){let r;if(r=this.rules.inline.url.exec(e)){let n,i;if(r[2]==="@")n=ro(r[0]),i="mailto:"+n;else{let a;do a=r[0],r[0]=this.rules.inline._backpedal.exec(r[0])?.[0]??"";while(a!==r[0]);n=ro(r[0]),r[1]==="www."?i="http://"+r[0]:i=r[0]}return{type:"link",raw:r[0],text:n,href:i,tokens:[{type:"text",raw:n,text:n}]}}}inlineText(e){let r=this.rules.inline.text.exec(e);if(r){let n;return this.lexer.state.inRawBlock?n=r[0]:n=ro(r[0]),{type:"text",raw:r[0],text:n}}}},a7e=/^(?: *(?:\n|$))+/,s7e=/^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/,o7e=/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,vv=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,l7e=/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,aj=/(?:[*+-]|\d{1,9}[.)])/,sj=ln(/^(?!bull |blockCode|fences|blockquote|heading|html)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html))+?)\n {0,3}(=+|-+) *(?:\n+|$)/).replace(/bull/g,aj).replace(/blockCode/g,/ {4}/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).getRegex(),g9=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,c7e=/^[^\n]+/,y9=/(?!\s*\])(?:\\.|[^\[\]\\])+/,u7e=ln(/^ {0,3}\[(label)\]: *(?:\n *)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n *)?| *\n *)(title))? *(?:\n+|$)/).replace("label",y9).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),h7e=ln(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,aj).getRegex(),D5="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",v9=/|$))/,f7e=ln("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:(?:\\n *)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$))","i").replace("comment",v9).replace("tag",D5).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),oj=ln(g9).replace("hr",vv).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",D5).getRegex(),d7e=ln(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",oj).getRegex(),x9={blockquote:d7e,code:s7e,def:u7e,fences:o7e,heading:l7e,hr:vv,html:f7e,lheading:sj,list:h7e,newline:a7e,paragraph:oj,table:gv,text:c7e},tj=ln("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",vv).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",D5).getRegex(),p7e={...x9,table:tj,paragraph:ln(g9).replace("hr",vv).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",tj).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",D5).getRegex()},m7e={...x9,html:ln(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment",v9).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:gv,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:ln(g9).replace("hr",vv).replace("heading",` *#{1,6} *[^ +]`).replace("lheading",sj).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},lj=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,g7e=/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,cj=/^( {2,}|\\)\n(?!\s*$)/,y7e=/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\]*?>/g,b7e=ln(/^(?:\*+(?:((?!\*)[punct])|[^\s*]))|^_+(?:((?!_)[punct])|([^\s_]))/,"u").replace(/punct/g,xv).getRegex(),w7e=ln("^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)[punct](\\*+)(?=[\\s]|$)|[^punct\\s](\\*+)(?!\\*)(?=[punct\\s]|$)|(?!\\*)[punct\\s](\\*+)(?=[^punct\\s])|[\\s](\\*+)(?!\\*)(?=[punct])|(?!\\*)[punct](\\*+)(?!\\*)(?=[punct])|[^punct\\s](\\*+)(?=[^punct\\s])","gu").replace(/punct/g,xv).getRegex(),T7e=ln("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)[punct](_+)(?=[\\s]|$)|[^punct\\s](_+)(?!_)(?=[punct\\s]|$)|(?!_)[punct\\s](_+)(?=[^punct\\s])|[\\s](_+)(?!_)(?=[punct])|(?!_)[punct](_+)(?!_)(?=[punct])","gu").replace(/punct/g,xv).getRegex(),k7e=ln(/\\([punct])/,"gu").replace(/punct/g,xv).getRegex(),E7e=ln(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),C7e=ln(v9).replace("(?:-->|$)","-->").getRegex(),S7e=ln("^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^").replace("comment",C7e).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),L5=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,A7e=ln(/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/).replace("label",L5).replace("href",/<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),uj=ln(/^!?\[(label)\]\[(ref)\]/).replace("label",L5).replace("ref",y9).getRegex(),hj=ln(/^!?\[(ref)\](?:\[\])?/).replace("ref",y9).getRegex(),_7e=ln("reflink|nolink(?!\\()","g").replace("reflink",uj).replace("nolink",hj).getRegex(),b9={_backpedal:gv,anyPunctuation:k7e,autolink:E7e,blockSkip:x7e,br:cj,code:g7e,del:gv,emStrongLDelim:b7e,emStrongRDelimAst:w7e,emStrongRDelimUnd:T7e,escape:lj,link:A7e,nolink:hj,punctuation:v7e,reflink:uj,reflinkSearch:_7e,tag:S7e,text:y7e,url:gv},L7e={...b9,link:ln(/^!?\[(label)\]\((.*?)\)/).replace("label",L5).getRegex(),reflink:ln(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",L5).getRegex()},d9={...b9,escape:ln(lj).replace("])","~|])").getRegex(),url:ln(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,"i").replace("email",/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\u+" ".repeat(h.length));let i,a,s;for(;e;)if(!(this.options.extensions&&this.options.extensions.block&&this.options.extensions.block.some(l=>(i=l.call({lexer:this},e,r))?(e=e.substring(i.raw.length),r.push(i),!0):!1))){if(i=this.tokenizer.space(e)){e=e.substring(i.raw.length),i.raw.length===1&&r.length>0?r[r.length-1].raw+=` +`:r.push(i);continue}if(i=this.tokenizer.code(e)){e=e.substring(i.raw.length),a=r[r.length-1],a&&(a.type==="paragraph"||a.type==="text")?(a.raw+=` +`+i.raw,a.text+=` +`+i.text,this.inlineQueue[this.inlineQueue.length-1].src=a.text):r.push(i);continue}if(i=this.tokenizer.fences(e)){e=e.substring(i.raw.length),r.push(i);continue}if(i=this.tokenizer.heading(e)){e=e.substring(i.raw.length),r.push(i);continue}if(i=this.tokenizer.hr(e)){e=e.substring(i.raw.length),r.push(i);continue}if(i=this.tokenizer.blockquote(e)){e=e.substring(i.raw.length),r.push(i);continue}if(i=this.tokenizer.list(e)){e=e.substring(i.raw.length),r.push(i);continue}if(i=this.tokenizer.html(e)){e=e.substring(i.raw.length),r.push(i);continue}if(i=this.tokenizer.def(e)){e=e.substring(i.raw.length),a=r[r.length-1],a&&(a.type==="paragraph"||a.type==="text")?(a.raw+=` +`+i.raw,a.text+=` +`+i.raw,this.inlineQueue[this.inlineQueue.length-1].src=a.text):this.tokens.links[i.tag]||(this.tokens.links[i.tag]={href:i.href,title:i.title});continue}if(i=this.tokenizer.table(e)){e=e.substring(i.raw.length),r.push(i);continue}if(i=this.tokenizer.lheading(e)){e=e.substring(i.raw.length),r.push(i);continue}if(s=e,this.options.extensions&&this.options.extensions.startBlock){let l=1/0,u=e.slice(1),h;this.options.extensions.startBlock.forEach(f=>{h=f.call({lexer:this},u),typeof h=="number"&&h>=0&&(l=Math.min(l,h))}),l<1/0&&l>=0&&(s=e.substring(0,l+1))}if(this.state.top&&(i=this.tokenizer.paragraph(s))){a=r[r.length-1],n&&a?.type==="paragraph"?(a.raw+=` +`+i.raw,a.text+=` +`+i.text,this.inlineQueue.pop(),this.inlineQueue[this.inlineQueue.length-1].src=a.text):r.push(i),n=s.length!==e.length,e=e.substring(i.raw.length);continue}if(i=this.tokenizer.text(e)){e=e.substring(i.raw.length),a=r[r.length-1],a&&a.type==="text"?(a.raw+=` +`+i.raw,a.text+=` +`+i.text,this.inlineQueue.pop(),this.inlineQueue[this.inlineQueue.length-1].src=a.text):r.push(i);continue}if(e){let l="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(l);break}else throw new Error(l)}}return this.state.top=!0,r}inline(e,r=[]){return this.inlineQueue.push({src:e,tokens:r}),r}inlineTokens(e,r=[]){let n,i,a,s=e,l,u,h;if(this.tokens.links){let f=Object.keys(this.tokens.links);if(f.length>0)for(;(l=this.tokenizer.rules.inline.reflinkSearch.exec(s))!=null;)f.includes(l[0].slice(l[0].lastIndexOf("[")+1,-1))&&(s=s.slice(0,l.index)+"["+"a".repeat(l[0].length-2)+"]"+s.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;(l=this.tokenizer.rules.inline.blockSkip.exec(s))!=null;)s=s.slice(0,l.index)+"["+"a".repeat(l[0].length-2)+"]"+s.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);for(;(l=this.tokenizer.rules.inline.anyPunctuation.exec(s))!=null;)s=s.slice(0,l.index)+"++"+s.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);for(;e;)if(u||(h=""),u=!1,!(this.options.extensions&&this.options.extensions.inline&&this.options.extensions.inline.some(f=>(n=f.call({lexer:this},e,r))?(e=e.substring(n.raw.length),r.push(n),!0):!1))){if(n=this.tokenizer.escape(e)){e=e.substring(n.raw.length),r.push(n);continue}if(n=this.tokenizer.tag(e)){e=e.substring(n.raw.length),i=r[r.length-1],i&&n.type==="text"&&i.type==="text"?(i.raw+=n.raw,i.text+=n.text):r.push(n);continue}if(n=this.tokenizer.link(e)){e=e.substring(n.raw.length),r.push(n);continue}if(n=this.tokenizer.reflink(e,this.tokens.links)){e=e.substring(n.raw.length),i=r[r.length-1],i&&n.type==="text"&&i.type==="text"?(i.raw+=n.raw,i.text+=n.text):r.push(n);continue}if(n=this.tokenizer.emStrong(e,s,h)){e=e.substring(n.raw.length),r.push(n);continue}if(n=this.tokenizer.codespan(e)){e=e.substring(n.raw.length),r.push(n);continue}if(n=this.tokenizer.br(e)){e=e.substring(n.raw.length),r.push(n);continue}if(n=this.tokenizer.del(e)){e=e.substring(n.raw.length),r.push(n);continue}if(n=this.tokenizer.autolink(e)){e=e.substring(n.raw.length),r.push(n);continue}if(!this.state.inLink&&(n=this.tokenizer.url(e))){e=e.substring(n.raw.length),r.push(n);continue}if(a=e,this.options.extensions&&this.options.extensions.startInline){let f=1/0,d=e.slice(1),p;this.options.extensions.startInline.forEach(m=>{p=m.call({lexer:this},d),typeof p=="number"&&p>=0&&(f=Math.min(f,p))}),f<1/0&&f>=0&&(a=e.substring(0,f+1))}if(n=this.tokenizer.inlineText(a)){e=e.substring(n.raw.length),n.raw.slice(-1)!=="_"&&(h=n.raw.slice(-1)),u=!0,i=r[r.length-1],i&&i.type==="text"?(i.raw+=n.raw,i.text+=n.text):r.push(n);continue}if(e){let f="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(f);break}else throw new Error(f)}}return r}},fm=class{static{o(this,"_Renderer")}options;parser;constructor(e){this.options=e||Sd}space(e){return""}code({text:e,lang:r,escaped:n}){let i=(r||"").match(/^\S*/)?.[0],a=e.replace(/\n$/,"")+` +`;return i?'
    '+(n?a:ro(a,!0))+`
    +`:"
    "+(n?a:ro(a,!0))+`
    +`}blockquote({tokens:e}){return`
    +${this.parser.parse(e)}
    +`}html({text:e}){return e}heading({tokens:e,depth:r}){return`${this.parser.parseInline(e)} +`}hr(e){return`
    +`}list(e){let r=e.ordered,n=e.start,i="";for(let l=0;l +`+i+" +`}listitem(e){let r="";if(e.task){let n=this.checkbox({checked:!!e.checked});e.loose?e.tokens.length>0&&e.tokens[0].type==="paragraph"?(e.tokens[0].text=n+" "+e.tokens[0].text,e.tokens[0].tokens&&e.tokens[0].tokens.length>0&&e.tokens[0].tokens[0].type==="text"&&(e.tokens[0].tokens[0].text=n+" "+e.tokens[0].tokens[0].text)):e.tokens.unshift({type:"text",raw:n+" ",text:n+" "}):r+=n+" "}return r+=this.parser.parse(e.tokens,!!e.loose),`

    jRTi>F!( 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 Date: Tue, 25 Feb 2025 10:18:28 -0500 Subject: [PATCH 002/130] feat: op-deployer cleanupAfterExit, unit tests cleanup and clean cache subcmd (#14488) * feat: op-deployer clean cache command Signed-off-by: Yashvardhan Kukreja * lint Signed-off-by: Yashvardhan Kukreja * chore: cleaner dealing with test dir Signed-off-by: Yashvardhan Kukreja * chore: refactor the IsolatedTestDirWithAutoCleanup and break import cycle Signed-off-by: Yashvardhan Kukreja * chore: small cleanup Signed-off-by: Yashvardhan Kukreja --------- Signed-off-by: Yashvardhan Kukreja --- op-deployer/cmd/op-deployer/main.go | 6 ++ op-deployer/pkg/deployer/apply.go | 12 ++-- .../pkg/deployer/artifacts/download.go | 28 ++++----- .../pkg/deployer/artifacts/download_test.go | 30 +++++----- .../pkg/deployer/bootstrap/implementations.go | 3 +- .../bootstrap/implementations_test.go | 8 ++- op-deployer/pkg/deployer/bootstrap/proxy.go | 6 +- .../pkg/deployer/bootstrap/superchain.go | 7 ++- .../pkg/deployer/bootstrap/superchain_test.go | 4 ++ .../pkg/deployer/bootstrap/validator.go | 5 +- op-deployer/pkg/deployer/clean/cache.go | 35 ++++++++++++ op-deployer/pkg/deployer/clean/cache_test.go | 57 +++++++++++++++++++ op-deployer/pkg/deployer/clean/flags.go | 13 +++++ op-deployer/pkg/deployer/flags.go | 21 ++++++- op-deployer/pkg/deployer/inspect/flags.go | 14 +++-- op-deployer/pkg/deployer/inspect/semvers.go | 2 +- .../deployer/integration_test/apply_test.go | 10 ++++ op-deployer/pkg/deployer/testutil/env.go | 8 ++- op-deployer/pkg/deployer/upgrade/upgrader.go | 4 +- op-service/testutils/common.go | 22 +++++++ 20 files changed, 241 insertions(+), 54 deletions(-) create mode 100644 op-deployer/pkg/deployer/clean/cache.go create mode 100644 op-deployer/pkg/deployer/clean/cache_test.go create mode 100644 op-deployer/pkg/deployer/clean/flags.go create mode 100644 op-service/testutils/common.go diff --git a/op-deployer/cmd/op-deployer/main.go b/op-deployer/cmd/op-deployer/main.go index 89885c857b2..a3bd8363981 100644 --- a/op-deployer/cmd/op-deployer/main.go +++ b/op-deployer/cmd/op-deployer/main.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/clean" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/upgrade" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" @@ -60,6 +61,11 @@ func main() { Usage: "inspects the state of a deployment", Subcommands: inspect.Commands, }, + { + Name: "clean", + Usage: "cleans up various things", + Subcommands: clean.Commands, + }, } app.Writer = os.Stdout app.ErrWriter = os.Stderr diff --git a/op-deployer/pkg/deployer/apply.go b/op-deployer/pkg/deployer/apply.go index fb93daa3c28..033ba843021 100644 --- a/op-deployer/pkg/deployer/apply.go +++ b/op-deployer/pkg/deployer/apply.go @@ -37,8 +37,8 @@ type ApplyConfig struct { PrivateKey string DeploymentTarget DeploymentTarget Logger log.Logger - - privateKeyECDSA *ecdsa.PrivateKey + CacheDir string + privateKeyECDSA *ecdsa.PrivateKey } func (a *ApplyConfig) Check() error { @@ -80,6 +80,7 @@ func ApplyCLI() func(cliCtx *cli.Context) error { l1RPCUrl := cliCtx.String(L1RPCURLFlagName) workdir := cliCtx.String(WorkdirFlagName) privateKey := cliCtx.String(PrivateKeyFlagName) + cacheDir := cliCtx.String(CacheDirFlagName) depTarget, err := NewDeploymentTarget(cliCtx.String(DeploymentTargetFlag.Name)) if err != nil { return fmt.Errorf("failed to parse deployment target: %w", err) @@ -93,6 +94,7 @@ func ApplyCLI() func(cliCtx *cli.Context) error { PrivateKey: privateKey, DeploymentTarget: depTarget, Logger: l, + CacheDir: cacheDir, }) } } @@ -120,6 +122,7 @@ func Apply(ctx context.Context, cfg ApplyConfig) error { State: st, Logger: cfg.Logger, StateWriter: pipeline.WorkdirStateWriter(cfg.Workdir), + CacheDir: cfg.CacheDir, }); err != nil { return err } @@ -140,6 +143,7 @@ type ApplyPipelineOpts struct { State *state.State Logger log.Logger StateWriter pipeline.StateWriter + CacheDir string } func ApplyPipeline( @@ -152,7 +156,7 @@ func ApplyPipeline( } st := opts.State - l1ArtifactsFS, err := artifacts.Download(ctx, intent.L1ContractsLocator, artifacts.BarProgressor()) + l1ArtifactsFS, err := artifacts.Download(ctx, intent.L1ContractsLocator, artifacts.BarProgressor(), opts.CacheDir) if err != nil { return fmt.Errorf("failed to download L1 artifacts: %w", err) } @@ -161,7 +165,7 @@ func ApplyPipeline( if intent.L1ContractsLocator.Equal(intent.L2ContractsLocator) { l2ArtifactsFS = l1ArtifactsFS } else { - l2Afs, err := artifacts.Download(ctx, intent.L2ContractsLocator, artifacts.BarProgressor()) + l2Afs, err := artifacts.Download(ctx, intent.L2ContractsLocator, artifacts.BarProgressor(), opts.CacheDir) if err != nil { return fmt.Errorf("failed to download L2 artifacts: %w", err) } diff --git a/op-deployer/pkg/deployer/artifacts/download.go b/op-deployer/pkg/deployer/artifacts/download.go index 0e7b90d058c..3840a4c44e0 100644 --- a/op-deployer/pkg/deployer/artifacts/download.go +++ b/op-deployer/pkg/deployer/artifacts/download.go @@ -25,14 +25,14 @@ import ( var ErrUnsupportedArtifactsScheme = errors.New("unsupported artifacts URL scheme") type Downloader interface { - Download(ctx context.Context, url string, progress DownloadProgressor) (string, error) + Download(ctx context.Context, url string, progress DownloadProgressor, targetDir string) (string, error) } type Extractor interface { Extract(src string, dest string) (string, error) } -func Download(ctx context.Context, loc *Locator, progressor DownloadProgressor) (foundry.StatDirFs, error) { +func Download(ctx context.Context, loc *Locator, progressor DownloadProgressor, targetDir string) (foundry.StatDirFs, error) { if progressor == nil { progressor = NoopProgressor() } @@ -60,7 +60,7 @@ func Download(ctx context.Context, loc *Locator, progressor DownloadProgressor) var artifactsFS fs.FS switch u.Scheme { case "http", "https": - artifactsFS, err = downloadHTTP(ctx, u, progressor, checker) + artifactsFS, err = downloadHTTP(ctx, u, progressor, checker, targetDir) if err != nil { return nil, fmt.Errorf("failed to download artifacts: %w", err) } @@ -72,16 +72,16 @@ func Download(ctx context.Context, loc *Locator, progressor DownloadProgressor) return artifactsFS.(foundry.StatDirFs), nil } -func downloadHTTP(ctx context.Context, u *url.URL, progressor DownloadProgressor, checker integrityChecker) (fs.FS, error) { +func downloadHTTP(ctx context.Context, u *url.URL, progressor DownloadProgressor, checker integrityChecker, targetDir string) (fs.FS, error) { cacher := &CachingDownloader{ d: new(HTTPDownloader), } - tarballPath, err := cacher.Download(ctx, u.String(), progressor) + tarballPath, err := cacher.Download(ctx, u.String(), progressor, targetDir) if err != nil { return nil, fmt.Errorf("failed to download artifacts: %w", err) } - tmpDir, err := os.MkdirTemp("", "op-deployer-artifacts-*") + tmpDir, err := os.MkdirTemp(targetDir, "op-deployer-artifacts-*") if err != nil { return nil, fmt.Errorf("failed to create temp dir: %w", err) } @@ -96,7 +96,7 @@ func downloadHTTP(ctx context.Context, u *url.URL, progressor DownloadProgressor type HTTPDownloader struct{} -func (d *HTTPDownloader) Download(ctx context.Context, url string, progress DownloadProgressor) (string, error) { +func (d *HTTPDownloader) Download(ctx context.Context, url string, progress DownloadProgressor, targetDir string) (string, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return "", fmt.Errorf("failed to create request: %w", err) @@ -111,7 +111,10 @@ func (d *HTTPDownloader) Download(ctx context.Context, url string, progress Down } defer res.Body.Close() - tmpFile, err := os.CreateTemp("", "op-deployer-artifacts-*") + if err := os.MkdirAll(targetDir, 0755); err != nil { + return "", fmt.Errorf("failed to ensure cache directory: %w", err) + } + tmpFile, err := os.CreateTemp(targetDir, "op-deployer-artifacts-*") if err != nil { return "", fmt.Errorf("failed to create temporary file: %w", err) } @@ -133,21 +136,18 @@ type CachingDownloader struct { mtx sync.Mutex } -func (d *CachingDownloader) Download(ctx context.Context, url string, progress DownloadProgressor) (string, error) { +func (d *CachingDownloader) Download(ctx context.Context, url string, progress DownloadProgressor, targetDir string) (string, error) { d.mtx.Lock() defer d.mtx.Unlock() - cachePath := fmt.Sprintf("/tmp/op-deployer-cache/%x.tgz", sha256.Sum256([]byte(url))) + cachePath := path.Join(targetDir, fmt.Sprintf("%x.tgz", sha256.Sum256([]byte(url)))) if _, err := os.Stat(cachePath); err == nil { return cachePath, nil } - tmpPath, err := d.d.Download(ctx, url, progress) + tmpPath, err := d.d.Download(ctx, url, progress, targetDir) if err != nil { return "", fmt.Errorf("failed to download: %w", err) } - if err := os.MkdirAll("/tmp/op-deployer-cache", 0755); err != nil { - return "", fmt.Errorf("failed to create cache directory: %w", err) - } if err := os.Rename(tmpPath, cachePath); err != nil { return "", fmt.Errorf("failed to move downloaded file to cache: %w", err) } diff --git a/op-deployer/pkg/deployer/artifacts/download_test.go b/op-deployer/pkg/deployer/artifacts/download_test.go index 24dcaee09b5..e8349c1e846 100644 --- a/op-deployer/pkg/deployer/artifacts/download_test.go +++ b/op-deployer/pkg/deployer/artifacts/download_test.go @@ -14,8 +14,8 @@ import ( "github.com/minio/sha256-simd" + "github.com/ethereum-optimism/optimism/op-service/testutils" "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" ) @@ -44,8 +44,10 @@ func TestDownloadArtifacts_MockArtifacts(t *testing.T) { URL: artifactsURL, } + testCacheDir := testutils.IsolatedTestDirWithAutoCleanup(t) + t.Run("success", func(t *testing.T) { - fs, err := Download(ctx, loc, nil) + fs, err := Download(ctx, loc, nil, testCacheDir) require.NoError(t, err) require.NotNil(t, fs) @@ -57,7 +59,7 @@ func TestDownloadArtifacts_MockArtifacts(t *testing.T) { t.Run("bad integrity", func(t *testing.T) { _, err := downloadHTTP(ctx, loc.URL, nil, &hashIntegrityChecker{ hash: common.Hash{'B', 'A', 'D'}, - }) + }, testCacheDir) require.Error(t, err) require.ErrorContains(t, err, "integrity check failed") }) @@ -67,7 +69,7 @@ func TestDownloadArtifacts_MockArtifacts(t *testing.T) { } t.Run("ok integrity", func(t *testing.T) { - _, err := downloadHTTP(ctx, loc.URL, nil, correctIntegrity) + _, err := downloadHTTP(ctx, loc.URL, nil, correctIntegrity, testCacheDir) require.NoError(t, err) }) @@ -77,18 +79,12 @@ func TestDownloadArtifacts_MockArtifacts(t *testing.T) { u.Path = fmt.Sprintf("/different-path-%d", time.Now().UnixNano()) startCalls := atomic.LoadInt32(&callCount) - _, err = downloadHTTP(ctx, u, nil, correctIntegrity) + _, err = downloadHTTP(ctx, u, nil, correctIntegrity, testCacheDir) require.NoError(t, err) startCalls++ require.Equal(t, startCalls, atomic.LoadInt32(&callCount)) - t.Cleanup(func() { - require.NoError(t, os.Remove( - fmt.Sprintf("/tmp/op-deployer-cache/%x.tgz", sha256.Sum256([]byte(u.String()))), - )) - }) - - _, err = downloadHTTP(ctx, u, nil, correctIntegrity) + _, err = downloadHTTP(ctx, u, nil, correctIntegrity, testCacheDir) require.NoError(t, err) require.Equal(t, startCalls, atomic.LoadInt32(&callCount)) }) @@ -97,11 +93,10 @@ func TestDownloadArtifacts_MockArtifacts(t *testing.T) { u, err := url.Parse(loc.URL.String()) require.NoError(t, err) u.Path = fmt.Sprintf("/different-path-%d", time.Now().UnixNano()) - - _, err = downloadHTTP(ctx, u, nil, correctIntegrity) + _, err = downloadHTTP(ctx, u, nil, correctIntegrity, testCacheDir) require.NoError(t, err) - cacheFile := fmt.Sprintf("/tmp/op-deployer-cache/%x.tgz", sha256.Sum256([]byte(u.String()))) + cacheFile := fmt.Sprintf("%s/%x.tgz", testCacheDir, sha256.Sum256([]byte(u.String()))) t.Cleanup(func() { require.NoError(t, os.Remove(cacheFile)) }) @@ -112,7 +107,7 @@ func TestDownloadArtifacts_MockArtifacts(t *testing.T) { require.NoError(t, err) require.NoError(t, cacheF.Close()) - _, err = downloadHTTP(ctx, u, nil, correctIntegrity) + _, err = downloadHTTP(ctx, u, nil, correctIntegrity, testCacheDir) require.ErrorContains(t, err, "integrity check failed") }) } @@ -122,11 +117,12 @@ func TestDownloadArtifacts_TaggedVersions(t *testing.T) { "op-contracts/v1.6.0", "op-contracts/v1.7.0-beta.1+l2-contracts", } + testCacheDir := testutils.IsolatedTestDirWithAutoCleanup(t) for _, tag := range tags { t.Run(tag, func(t *testing.T) { t.Parallel() loc := MustNewLocatorFromTag(tag) - _, err := Download(context.Background(), loc, nil) + _, err := Download(context.Background(), loc, nil, testCacheDir) require.NoError(t, err) }) } diff --git a/op-deployer/pkg/deployer/bootstrap/implementations.go b/op-deployer/pkg/deployer/bootstrap/implementations.go index c868aeb60dc..30ee8498d0c 100644 --- a/op-deployer/pkg/deployer/bootstrap/implementations.go +++ b/op-deployer/pkg/deployer/bootstrap/implementations.go @@ -43,6 +43,7 @@ type ImplementationsConfig struct { ProtocolVersionsProxy common.Address `cli:"protocol-versions-proxy"` UpgradeController common.Address `cli:"upgrade-controller"` UseInterop bool `cli:"use-interop"` + CacheDir string `cli:"cache-dir"` Logger log.Logger @@ -133,7 +134,7 @@ func Implementations(ctx context.Context, cfg ImplementationsConfig) (opcm.Deplo lgr := cfg.Logger - artifactsFS, err := artifacts.Download(ctx, cfg.ArtifactsLocator, artifacts.BarProgressor()) + artifactsFS, err := artifacts.Download(ctx, cfg.ArtifactsLocator, artifacts.BarProgressor(), cfg.CacheDir) if err != nil { return dio, fmt.Errorf("failed to download artifacts: %w", err) } diff --git a/op-deployer/pkg/deployer/bootstrap/implementations_test.go b/op-deployer/pkg/deployer/bootstrap/implementations_test.go index 7ae57eb1cbe..d5a701d5729 100644 --- a/op-deployer/pkg/deployer/bootstrap/implementations_test.go +++ b/op-deployer/pkg/deployer/bootstrap/implementations_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/ethereum-optimism/optimism/op-service/testutils" "github.com/ethereum-optimism/optimism/op-service/testutils/devnet" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" @@ -19,17 +20,19 @@ import ( ) func TestImplementations(t *testing.T) { + testCacheDir := testutils.IsolatedTestDirWithAutoCleanup(t) + for _, network := range networks { t.Run(network, func(t *testing.T) { envVar := strings.ToUpper(network) + "_RPC_URL" rpcURL := os.Getenv(envVar) require.NotEmpty(t, rpcURL, "must specify RPC url via %s env var", envVar) - testImplementations(t, rpcURL) + testImplementations(t, rpcURL, testCacheDir) }) } } -func testImplementations(t *testing.T, forkRPCURL string) { +func testImplementations(t *testing.T, forkRPCURL string, cacheDir string) { t.Parallel() if forkRPCURL == "" { @@ -78,6 +81,7 @@ func testImplementations(t *testing.T, forkRPCURL string) { ProtocolVersionsProxy: superchain.ProtocolVersionsAddr, UpgradeController: proxyAdminOwner, UseInterop: false, + CacheDir: cacheDir, }) require.NoError(t, err) return out diff --git a/op-deployer/pkg/deployer/bootstrap/proxy.go b/op-deployer/pkg/deployer/bootstrap/proxy.go index 88844197490..06f4f762b5e 100644 --- a/op-deployer/pkg/deployer/bootstrap/proxy.go +++ b/op-deployer/pkg/deployer/bootstrap/proxy.go @@ -32,6 +32,7 @@ type ProxyConfig struct { PrivateKey string Logger log.Logger ArtifactsLocator *artifacts.Locator + CacheDir string privateKeyECDSA *ecdsa.PrivateKey @@ -77,6 +78,8 @@ func ProxyCLI(cliCtx *cli.Context) error { privateKey := cliCtx.String(deployer.PrivateKeyFlagName) outfile := cliCtx.String(OutfileFlagName) artifactsURLStr := cliCtx.String(ArtifactsLocatorFlagName) + cacheDir := cliCtx.String(deployer.CacheDirFlag.Name) + artifactsLocator := new(artifacts.Locator) if err := artifactsLocator.UnmarshalText([]byte(artifactsURLStr)); err != nil { return fmt.Errorf("failed to parse artifacts URL: %w", err) @@ -92,6 +95,7 @@ func ProxyCLI(cliCtx *cli.Context) error { Logger: l, ArtifactsLocator: artifactsLocator, Owner: owner, + CacheDir: cacheDir, }) if err != nil { return fmt.Errorf("failed to deploy Proxy: %w", err) @@ -110,7 +114,7 @@ func Proxy(ctx context.Context, cfg ProxyConfig) (opcm.DeployProxyOutput, error) } lgr := cfg.Logger - artifactsFS, err := artifacts.Download(ctx, cfg.ArtifactsLocator, artifacts.BarProgressor()) + artifactsFS, err := artifacts.Download(ctx, cfg.ArtifactsLocator, artifacts.BarProgressor(), cfg.CacheDir) if err != nil { return dpo, fmt.Errorf("failed to download artifacts: %w", err) } diff --git a/op-deployer/pkg/deployer/bootstrap/superchain.go b/op-deployer/pkg/deployer/bootstrap/superchain.go index 6bfec6c44c7..95055f2a2ae 100644 --- a/op-deployer/pkg/deployer/bootstrap/superchain.go +++ b/op-deployer/pkg/deployer/bootstrap/superchain.go @@ -30,6 +30,7 @@ type SuperchainConfig struct { PrivateKey string Logger log.Logger ArtifactsLocator *artifacts.Locator + CacheDir string privateKeyECDSA *ecdsa.PrivateKey @@ -99,12 +100,13 @@ func SuperchainCLI(cliCtx *cli.Context) error { requiredVersionStr := cliCtx.String(RequiredProtocolVersionFlagName) recommendedVersionStr := cliCtx.String(RecommendedProtocolVersionFlagName) outfile := cliCtx.String(OutfileFlagName) - + cacheDir := cliCtx.String(deployer.CacheDirFlag.Name) cfg := SuperchainConfig{ L1RPCUrl: l1RPCUrl, PrivateKey: privateKey, Logger: l, ArtifactsLocator: artifactsLocator, + CacheDir: cacheDir, SuperchainProxyAdminOwner: superchainProxyAdminOwner, ProtocolVersionsOwner: protocolVersionsOwner, Guardian: guardian, @@ -139,7 +141,8 @@ func Superchain(ctx context.Context, cfg SuperchainConfig) (opcm.DeploySuperchai } lgr := cfg.Logger - artifactsFS, err := artifacts.Download(ctx, cfg.ArtifactsLocator, artifacts.BarProgressor()) + cacheDir := cfg.CacheDir + artifactsFS, err := artifacts.Download(ctx, cfg.ArtifactsLocator, artifacts.BarProgressor(), cacheDir) if err != nil { return dso, fmt.Errorf("failed to download artifacts: %w", err) } diff --git a/op-deployer/pkg/deployer/bootstrap/superchain_test.go b/op-deployer/pkg/deployer/bootstrap/superchain_test.go index 398a06114c5..7e16db61b0b 100644 --- a/op-deployer/pkg/deployer/bootstrap/superchain_test.go +++ b/op-deployer/pkg/deployer/bootstrap/superchain_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/ethereum-optimism/optimism/op-service/testutils" "github.com/ethereum-optimism/optimism/op-service/testutils/devnet" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" @@ -54,6 +55,8 @@ func testSuperchain(t *testing.T, forkRPCURL string, version string) { }) l1RPC := forkedL1.RPCUrl() + testCacheDir := testutils.IsolatedTestDirWithAutoCleanup(t) + out, err := Superchain(ctx, SuperchainConfig{ L1RPCUrl: l1RPC, PrivateKey: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", @@ -66,6 +69,7 @@ func testSuperchain(t *testing.T, forkRPCURL string, version string) { Paused: false, RequiredProtocolVersion: params.ProtocolVersionV0{Major: 1}.Encode(), RecommendedProtocolVersion: params.ProtocolVersionV0{Major: 2}.Encode(), + CacheDir: testCacheDir, }) require.NoError(t, err) diff --git a/op-deployer/pkg/deployer/bootstrap/validator.go b/op-deployer/pkg/deployer/bootstrap/validator.go index f068fa1ec12..f32b00b13d4 100644 --- a/op-deployer/pkg/deployer/bootstrap/validator.go +++ b/op-deployer/pkg/deployer/bootstrap/validator.go @@ -32,6 +32,7 @@ type ValidatorConfig struct { Logger log.Logger ArtifactsLocator *artifacts.Locator Input ValidatorInput + CacheDir string privateKeyECDSA *ecdsa.PrivateKey } @@ -96,6 +97,7 @@ func ValidatorCLI(cliCtx *cli.Context) error { outfile := cliCtx.String(OutfileFlagName) artifactsURLStr := cliCtx.String(ArtifactsLocatorFlagName) configFile := cliCtx.String(ConfigFileFlag.Name) + cacheDir := cliCtx.String(deployer.CacheDirFlag.Name) artifactsLocator := new(artifacts.Locator) if err := artifactsLocator.UnmarshalText([]byte(artifactsURLStr)); err != nil { @@ -121,6 +123,7 @@ func ValidatorCLI(cliCtx *cli.Context) error { Logger: l, ArtifactsLocator: artifactsLocator, Input: input, + CacheDir: cacheDir, }) if err != nil { return fmt.Errorf("failed to deploy Validator: %w", err) @@ -140,7 +143,7 @@ func Validator(ctx context.Context, cfg ValidatorConfig) (ValidatorOutput, error lgr := cfg.Logger - artifactsFS, err := artifacts.Download(ctx, cfg.ArtifactsLocator, artifacts.BarProgressor()) + artifactsFS, err := artifacts.Download(ctx, cfg.ArtifactsLocator, artifacts.BarProgressor(), cfg.CacheDir) if err != nil { return output, fmt.Errorf("failed to download artifacts: %w", err) } diff --git a/op-deployer/pkg/deployer/clean/cache.go b/op-deployer/pkg/deployer/clean/cache.go new file mode 100644 index 00000000000..dc2c9b0803a --- /dev/null +++ b/op-deployer/pkg/deployer/clean/cache.go @@ -0,0 +1,35 @@ +package clean + +import ( + "fmt" + "os" + + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum/go-ethereum/log" + "github.com/urfave/cli/v2" +) + +func CacheCLI(cliCtx *cli.Context) error { + logCfg := oplog.ReadCLIConfig(cliCtx) + l := oplog.NewLogger(oplog.AppOut(cliCtx), logCfg) + oplog.SetGlobalLogHandler(l.Handler()) + + cacheDir := cliCtx.String(deployer.CacheDirFlag.Name) + if cacheDir == "" { + return fmt.Errorf("cache directory not set") + } + + return CleanCache(l, cacheDir) +} + +func CleanCache(l log.Logger, cacheDir string) error { + if err := os.RemoveAll(cacheDir); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to remove cache directory: %w", err) + } + if err := os.MkdirAll(cacheDir, 0o755); err != nil { + l.Warn("failed to recreate cache directory", "err", err) + } + + return nil +} diff --git a/op-deployer/pkg/deployer/clean/cache_test.go b/op-deployer/pkg/deployer/clean/cache_test.go new file mode 100644 index 00000000000..cb6d0d4c619 --- /dev/null +++ b/op-deployer/pkg/deployer/clean/cache_test.go @@ -0,0 +1,57 @@ +package clean + +import ( + "log/slog" + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/stretchr/testify/require" +) + +func TestCacheCLI(t *testing.T) { + tmpDir := t.TempDir() + + t.Cleanup(func() { + require.NoError(t, os.RemoveAll(tmpDir)) + }) + + require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "test.txt"), []byte("test"), 0644)) + + lgr := testlog.Logger(t, slog.LevelDebug) + + require.NoError(t, CleanCache(lgr, tmpDir)) + + require.DirExists(t, tmpDir) + files, err := os.ReadDir(tmpDir) + require.NoError(t, err) + require.Empty(t, files) +} + +func TestCacheCLIE2E(t *testing.T) { + tmpDirForCache := t.TempDir() + tmpDirForBinary := t.TempDir() + + t.Cleanup(func() { + require.NoError(t, os.RemoveAll(tmpDirForCache)) + require.NoError(t, os.RemoveAll(tmpDirForBinary)) + }) + + require.NoError(t, os.WriteFile(filepath.Join(tmpDirForCache, "test.txt"), []byte("test"), 0644)) + + binaryPath := filepath.Join(tmpDirForBinary, "op-deployer") + cmd := exec.Command("go", "build", "-o", binaryPath, "../../../cmd/op-deployer/main.go") + output, err := cmd.CombinedOutput() + require.NoError(t, err, "Failed to build binary: %s", output) + + cmd = exec.Command(binaryPath, "--cache-dir", tmpDirForCache, "clean", "cache") + output, err = cmd.CombinedOutput() + require.NoError(t, err, "Failed to run clean cache command: %s", output) + + require.DirExists(t, tmpDirForCache) + files, err := os.ReadDir(tmpDirForCache) + require.NoError(t, err) + require.Empty(t, files) +} diff --git a/op-deployer/pkg/deployer/clean/flags.go b/op-deployer/pkg/deployer/clean/flags.go new file mode 100644 index 00000000000..91375a009d8 --- /dev/null +++ b/op-deployer/pkg/deployer/clean/flags.go @@ -0,0 +1,13 @@ +package clean + +import ( + "github.com/urfave/cli/v2" +) + +var Commands = []*cli.Command{ + { + Name: "cache", + Usage: "Cleans the backing the backe of op-deployer for all the previous runs", + Action: CacheCLI, + }, +} diff --git a/op-deployer/pkg/deployer/flags.go b/op-deployer/pkg/deployer/flags.go index 11bf21a750c..8ef70b77b62 100644 --- a/op-deployer/pkg/deployer/flags.go +++ b/op-deployer/pkg/deployer/flags.go @@ -3,6 +3,7 @@ package deployer import ( "fmt" "os" + "path" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" @@ -14,6 +15,7 @@ import ( const ( EnvVarPrefix = "DEPLOYER" L1RPCURLFlagName = "l1-rpc-url" + CacheDirFlagName = "cache-dir" L1ChainIDFlagName = "l1-chain-id" L2ChainIDsFlagName = "l2-chain-ids" WorkdirFlagName = "workdir" @@ -46,6 +48,16 @@ func NewDeploymentTarget(s string) (DeploymentTarget, error) { } } +var homeDir string + +func init() { + var err error + homeDir, err = os.UserHomeDir() + if err != nil { + panic(fmt.Sprintf("failed to get home directory: %s", err)) + } +} + var ( L1RPCURLFlag = &cli.StringFlag{ Name: L1RPCURLFlagName, @@ -55,6 +67,13 @@ var ( "L1_RPC_URL", }, } + CacheDirFlag = &cli.StringFlag{ + Name: CacheDirFlagName, + Usage: "Cache directory. " + + "If set, the deployer will attempt to cache downloaded artifacts in the specified directory.", + EnvVars: PrefixEnvVar("CACHE_DIR"), + Value: path.Join(homeDir, ".op-deployer/cache"), + } L1ChainIDFlag = &cli.Uint64Flag{ Name: L1ChainIDFlagName, Usage: "Chain ID of the L1 chain.", @@ -100,7 +119,7 @@ var ( } ) -var GlobalFlags = append([]cli.Flag{}, oplog.CLIFlags(EnvVarPrefix)...) +var GlobalFlags = append([]cli.Flag{CacheDirFlag}, oplog.CLIFlags(EnvVarPrefix)...) var InitFlags = []cli.Flag{ L1ChainIDFlag, diff --git a/op-deployer/pkg/deployer/inspect/flags.go b/op-deployer/pkg/deployer/inspect/flags.go index 2f94edc088e..38e995785e9 100644 --- a/op-deployer/pkg/deployer/inspect/flags.go +++ b/op-deployer/pkg/deployer/inspect/flags.go @@ -72,9 +72,10 @@ var Commands = []*cli.Command{ } type cliConfig struct { - Workdir string - Outfile string - ChainID common.Hash + Workdir string + Outfile string + ChainID common.Hash + CacheDir string } func readConfig(cliCtx *cli.Context) (cliConfig, error) { @@ -101,8 +102,9 @@ func readConfig(cliCtx *cli.Context) (cliConfig, error) { } return cliConfig{ - Workdir: cliCtx.String(deployer.WorkdirFlagName), - Outfile: cliCtx.String(OutfileFlagName), - ChainID: chainID, + Workdir: cliCtx.String(deployer.WorkdirFlagName), + Outfile: cliCtx.String(OutfileFlagName), + ChainID: chainID, + CacheDir: cliCtx.String(deployer.CacheDirFlag.Name), }, nil } diff --git a/op-deployer/pkg/deployer/inspect/semvers.go b/op-deployer/pkg/deployer/inspect/semvers.go index f9658bf184c..43a5c4d6fbf 100644 --- a/op-deployer/pkg/deployer/inspect/semvers.go +++ b/op-deployer/pkg/deployer/inspect/semvers.go @@ -59,7 +59,7 @@ func L2SemversCLI(cliCtx *cli.Context) error { return fmt.Errorf("chain state does not have allocs") } - artifactsFS, err := artifacts.Download(ctx, intent.L2ContractsLocator, artifacts.BarProgressor()) + artifactsFS, err := artifacts.Download(ctx, intent.L2ContractsLocator, artifacts.BarProgressor(), cliCfg.CacheDir) if err != nil { return fmt.Errorf("failed to download L2 artifacts: %w", err) } diff --git a/op-deployer/pkg/deployer/integration_test/apply_test.go b/op-deployer/pkg/deployer/integration_test/apply_test.go index c41d133f62b..025b57f288e 100644 --- a/op-deployer/pkg/deployer/integration_test/apply_test.go +++ b/op-deployer/pkg/deployer/integration_test/apply_test.go @@ -10,6 +10,7 @@ import ( "strings" "testing" + "github.com/ethereum-optimism/optimism/op-service/testutils" "github.com/ethereum-optimism/optimism/op-service/testutils/devnet" altda "github.com/ethereum-optimism/optimism/op-alt-da" @@ -81,6 +82,8 @@ func TestEndToEndApply(t *testing.T) { loc, _ := testutil.LocalArtifacts(t) + testCacheDir := testutils.IsolatedTestDirWithAutoCleanup(t) + t.Run("two chains one after another", func(t *testing.T) { intent, st := newIntent(t, l1ChainID, dk, l2ChainID1, loc, loc) cg := ethClientCodeGetter(ctx, l1Client) @@ -95,6 +98,7 @@ func TestEndToEndApply(t *testing.T) { State: st, Logger: lgr, StateWriter: pipeline.NoopStateWriter(), + CacheDir: testCacheDir, }, )) @@ -112,6 +116,7 @@ func TestEndToEndApply(t *testing.T) { State: st, Logger: lgr, StateWriter: pipeline.NoopStateWriter(), + CacheDir: testCacheDir, }, )) @@ -134,6 +139,7 @@ func TestEndToEndApply(t *testing.T) { State: st, Logger: lgr, StateWriter: pipeline.NoopStateWriter(), + CacheDir: testCacheDir, }, ), pipeline.ErrRefusingToDeployTaggedReleaseWithoutOPCM) }) @@ -151,6 +157,7 @@ func TestEndToEndApply(t *testing.T) { State: st, Logger: lgr, StateWriter: pipeline.NoopStateWriter(), + CacheDir: testCacheDir, }, )) @@ -550,6 +557,8 @@ func setupGenesisChain(t *testing.T, l1ChainID uint64) (deployer.ApplyPipelineOp intent, st := newIntent(t, l1ChainIDBig, dk, l2ChainID1, loc, loc) + testCacheDir := testutils.IsolatedTestDirWithAutoCleanup(t) + opts := deployer.ApplyPipelineOpts{ DeploymentTarget: deployer.DeploymentTargetGenesis, DeployerPrivateKey: priv, @@ -557,6 +566,7 @@ func setupGenesisChain(t *testing.T, l1ChainID uint64) (deployer.ApplyPipelineOp State: st, Logger: lgr, StateWriter: pipeline.NoopStateWriter(), + CacheDir: testCacheDir, } return opts, intent, st diff --git a/op-deployer/pkg/deployer/testutil/env.go b/op-deployer/pkg/deployer/testutil/env.go index 29c57ea5d72..a8d9147c3e9 100644 --- a/op-deployer/pkg/deployer/testutil/env.go +++ b/op-deployer/pkg/deployer/testutil/env.go @@ -8,10 +8,10 @@ import ( "runtime" "testing" - "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" - "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" op_service "github.com/ethereum-optimism/optimism/op-service" + "github.com/ethereum-optimism/optimism/op-service/testutils" "github.com/stretchr/testify/require" ) @@ -27,7 +27,9 @@ func LocalArtifacts(t *testing.T) (*artifacts.Locator, foundry.StatDirFs) { URL: artifactsURL, } - artifactsFS, err := artifacts.Download(context.Background(), loc, artifacts.NoopProgressor()) + testCacheDir := testutils.IsolatedTestDirWithAutoCleanup(t) + + artifactsFS, err := artifacts.Download(context.Background(), loc, artifacts.NoopProgressor(), testCacheDir) require.NoError(t, err) return loc, artifactsFS diff --git a/op-deployer/pkg/deployer/upgrade/upgrader.go b/op-deployer/pkg/deployer/upgrade/upgrader.go index 1b7457c44e1..89c33202f87 100644 --- a/op-deployer/pkg/deployer/upgrade/upgrader.go +++ b/op-deployer/pkg/deployer/upgrade/upgrader.go @@ -101,7 +101,9 @@ func UpgradeCLI(upgrader Upgrader) func(*cli.Context) error { return fmt.Errorf("unknown deployment target: %s", deploymentTarget) } - artifactsFS, err := artifacts.Download(ctx, artifactsLocator, artifacts.BarProgressor()) + cacheDir := cliCtx.String(deployer.CacheDirFlag.Name) + + artifactsFS, err := artifacts.Download(ctx, artifactsLocator, artifacts.BarProgressor(), cacheDir) if err != nil { return fmt.Errorf("failed to download L1 artifacts: %w", err) } diff --git a/op-service/testutils/common.go b/op-service/testutils/common.go new file mode 100644 index 00000000000..55cb59ae98c --- /dev/null +++ b/op-service/testutils/common.go @@ -0,0 +1,22 @@ +package testutils + +import ( + "os" + "path" + "testing" + + "github.com/stretchr/testify/require" +) + +func IsolatedTestDirWithAutoCleanup(t *testing.T) string { + basePath := os.Getenv("TEST_ARTIFACTS_DIR") + if basePath == "" { + basePath = "./.tests" + } + dir := path.Join(basePath, t.Name()) + // the dir's existence should be handled by Download as well else it should be left to break + t.Cleanup(func() { + require.NoError(t, os.RemoveAll(dir)) + }) + return dir +} From 97f2d087caa3daf7c498a361bb26ff569ddea22b Mon Sep 17 00:00:00 2001 From: Yann Hodique Date: Tue, 25 Feb 2025 10:36:41 -0600 Subject: [PATCH 003/130] feat(op-program-svc): introduce new service to compute prestates (#14493) * feat(op-program-svc): introduce new service to compute prestates * test(op-program-svc): add tests * Update kurtosis-devnet/op-program-svc/README.md Co-authored-by: Inphi --------- Co-authored-by: Inphi --- kurtosis-devnet/op-program-svc/Dockerfile | 38 +++ kurtosis-devnet/op-program-svc/README.md | 11 + kurtosis-devnet/op-program-svc/build.go | 166 +++++++++++ kurtosis-devnet/op-program-svc/build_test.go | 201 +++++++++++++ kurtosis-devnet/op-program-svc/defaults.go | 66 +++++ kurtosis-devnet/op-program-svc/fs.go | 252 ++++++++++++++++ kurtosis-devnet/op-program-svc/fs_test.go | 104 +++++++ kurtosis-devnet/op-program-svc/interfaces.go | 47 +++ kurtosis-devnet/op-program-svc/justfile | 5 + kurtosis-devnet/op-program-svc/main.go | 38 +++ kurtosis-devnet/op-program-svc/mocks.go | 273 ++++++++++++++++++ kurtosis-devnet/op-program-svc/server.go | 158 ++++++++++ kurtosis-devnet/op-program-svc/server_test.go | 252 ++++++++++++++++ op-program/Dockerfile.repro | 42 +-- op-program/repro.justfile | 60 ++++ 15 files changed, 1686 insertions(+), 27 deletions(-) create mode 100644 kurtosis-devnet/op-program-svc/Dockerfile create mode 100644 kurtosis-devnet/op-program-svc/README.md create mode 100644 kurtosis-devnet/op-program-svc/build.go create mode 100644 kurtosis-devnet/op-program-svc/build_test.go create mode 100644 kurtosis-devnet/op-program-svc/defaults.go create mode 100644 kurtosis-devnet/op-program-svc/fs.go create mode 100644 kurtosis-devnet/op-program-svc/fs_test.go create mode 100644 kurtosis-devnet/op-program-svc/interfaces.go create mode 100644 kurtosis-devnet/op-program-svc/justfile create mode 100644 kurtosis-devnet/op-program-svc/main.go create mode 100644 kurtosis-devnet/op-program-svc/mocks.go create mode 100644 kurtosis-devnet/op-program-svc/server.go create mode 100644 kurtosis-devnet/op-program-svc/server_test.go create mode 100644 op-program/repro.justfile diff --git a/kurtosis-devnet/op-program-svc/Dockerfile b/kurtosis-devnet/op-program-svc/Dockerfile new file mode 100644 index 00000000000..f23e8ec51ce --- /dev/null +++ b/kurtosis-devnet/op-program-svc/Dockerfile @@ -0,0 +1,38 @@ +ARG BASE_VERSION=latest + +FROM golang:1.22.7-alpine3.20 AS builder + +COPY ./*.go /app/ +WORKDIR /app + +RUN go mod init op-program-svc +RUN go build -o op-program-svc . + + +FROM op-program-base:${BASE_VERSION} AS svc + +ARG GIT_COMMIT +ARG GIT_DATE + +ARG CANNON_VERSION=v0.0.0 +ARG OP_PROGRAM_VERSION=v0.0.0 + +ARG TARGETOS TARGETARCH + +WORKDIR /app + +# build cannon ahead of time +RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build just \ + -d /app/op-program \ + -f /app/op-program/repro.justfile \ + GOOS="$TARGETOS" \ + GOARCH="$TARGETARCH" \ + GIT_COMMIT="$GIT_COMMIT" \ + GIT_DATE="$GIT_DATE" \ + CANNON_VERSION="$CANNON_VERSION" \ + OP_PROGRAM_VERSION="$OP_PROGRAM_VERSION" \ + cannon + +COPY --from=builder /app/op-program-svc . +EXPOSE 8080 +CMD ["./op-program-svc"] diff --git a/kurtosis-devnet/op-program-svc/README.md b/kurtosis-devnet/op-program-svc/README.md new file mode 100644 index 00000000000..4e59ff1a191 --- /dev/null +++ b/kurtosis-devnet/op-program-svc/README.md @@ -0,0 +1,11 @@ +# Trigger new build: + +``` +$ curl -X POST -H "Content-Type: multipart/form-data" \ + -F "files[]=@rollup-2151908.json" \ + -F "files[]=@rollup-2151909.json" \ + -F "files[]=@genesis-2151908.json" \ + -F "files[]=@genesis-2151909.json" \ + -F "files[]=@depsets.json" \ + http://localhost:8080 +``` diff --git a/kurtosis-devnet/op-program-svc/build.go b/kurtosis-devnet/op-program-svc/build.go new file mode 100644 index 00000000000..f4e3582ab34 --- /dev/null +++ b/kurtosis-devnet/op-program-svc/build.go @@ -0,0 +1,166 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "log" + "mime/multipart" + "path/filepath" + "strconv" + "strings" + "sync" + + "bufio" +) + +// MultipartUploadedFile adapts multipart.FileHeader to UploadedFile +type MultipartUploadedFile struct { + header *multipart.FileHeader +} + +func NewMultipartUploadedFile(header *multipart.FileHeader) *MultipartUploadedFile { + return &MultipartUploadedFile{header: header} +} + +func (f *MultipartUploadedFile) Open() (io.ReadCloser, error) { + return f.header.Open() +} + +func (f *MultipartUploadedFile) GetFilename() string { + return f.header.Filename +} + +type Builder struct { + appRoot string + configsDir string + buildDir string + buildCmd string + fs FS + cmdFactory CommandFactory +} + +func NewBuilder(appRoot, configsDir, buildDir, buildCmd string) *Builder { + return &Builder{ + appRoot: appRoot, + configsDir: configsDir, + buildDir: buildDir, + buildCmd: buildCmd, + fs: &DefaultFileSystem{}, + cmdFactory: &DefaultCommandFactory{}, + } +} + +func (b *Builder) SaveUploadedFiles(files []UploadedFile) error { + // Create configs directory if it doesn't exist + fullConfigsDir := b.fs.Join(b.appRoot, b.buildDir, b.configsDir) + if err := b.fs.MkdirAll(fullConfigsDir, 0755); err != nil { + return fmt.Errorf("failed to create config directory: %w", err) + } + + // Save the files + for _, file := range files { + reader, err := file.Open() + if err != nil { + return fmt.Errorf("failed to open file: %w", err) + } + defer reader.Close() + + destPath := b.fs.Join(fullConfigsDir, b.normalizeFilename(file.GetFilename())) + dst, err := b.fs.Create(destPath) + if err != nil { + return fmt.Errorf("failed to create destination file: %w", err) + } + defer dst.Close() + + if _, err := io.Copy(dst, reader); err != nil { + return fmt.Errorf("failed to save file: %w", err) + } + log.Printf("Saved file: %s", destPath) + } + + return nil +} + +func (b *Builder) ExecuteBuild() ([]byte, error) { + log.Printf("Starting build...") + cmdParts := strings.Fields(b.buildCmd) + cmd := b.cmdFactory.CreateCommand(cmdParts[0], cmdParts[1:]...) + + // Set working directory + cmd.SetDir(b.fs.Join(b.appRoot, b.buildDir)) + + // Create pipes for stdout and stderr + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, fmt.Errorf("failed to create stdout pipe: %w", err) + } + stderr, err := cmd.StderrPipe() + if err != nil { + return nil, fmt.Errorf("failed to create stderr pipe: %w", err) + } + + // Buffer to store complete output for error reporting + var output bytes.Buffer + output.WriteString("Build output:\n") + + // Start the command + if err := cmd.Start(); err != nil { + return nil, fmt.Errorf("failed to start build: %w", err) + } + + // Create a WaitGroup to wait for both stdout and stderr to be processed + var wg sync.WaitGroup + wg.Add(2) + + // Stream stdout + go func() { + defer wg.Done() + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + line := scanner.Text() + log.Printf("[build] %s", line) + output.WriteString(line + "\n") + } + }() + + // Stream stderr + go func() { + defer wg.Done() + scanner := bufio.NewScanner(stderr) + for scanner.Scan() { + line := scanner.Text() + log.Printf("[build][stderr] %s", line) + output.WriteString(line + "\n") + } + }() + + // Wait for both streams to complete + wg.Wait() + + // Wait for the command to complete + if err := cmd.Wait(); err != nil { + return output.Bytes(), fmt.Errorf("build failed: %w", err) + } + + log.Printf("Build completed successfully") + return output.Bytes(), nil +} + +func (b *Builder) normalizeFilename(filename string) string { + // Get just the filename without directories + filename = filepath.Base(filename) + + // Check if filename matches PREFIX-NUMBER.json pattern + if parts := strings.Split(filename, "-"); len(parts) == 2 { + if numStr := strings.TrimSuffix(parts[1], ".json"); numStr != parts[1] { + // Check if the number part is actually numeric + if _, err := strconv.Atoi(numStr); err == nil { + // It matches the pattern and has a valid number, reorder to NUMBER-PREFIX.json + return numStr + "-" + parts[0] + ".json" + } + } + } + + return filename +} diff --git a/kurtosis-devnet/op-program-svc/build_test.go b/kurtosis-devnet/op-program-svc/build_test.go new file mode 100644 index 00000000000..df3b6f879f7 --- /dev/null +++ b/kurtosis-devnet/op-program-svc/build_test.go @@ -0,0 +1,201 @@ +package main + +import ( + "strings" + "testing" +) + +func TestSaveUploadedFiles(t *testing.T) { + tests := []struct { + name string + files []struct { + filename string + content []byte + } + shouldFail bool + }{ + { + name: "successful save", + files: []struct { + filename string + content []byte + }{ + { + filename: "test1.json", + content: []byte("test1 content"), + }, + { + filename: "test2.json", + content: []byte("test2 content"), + }, + }, + shouldFail: false, + }, + { + name: "filesystem error", + files: []struct { + filename string + content []byte + }{ + { + filename: "test1.json", + content: []byte("test1 content"), + }, + }, + shouldFail: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockFS := NewMockFS() + mockFS.ShouldFail = tt.shouldFail + + // Create mock uploaded files + files := make([]UploadedFile, len(tt.files)) + for i, f := range tt.files { + files[i] = NewMockUploadedFile(f.filename, f.content) + } + + b := &Builder{ + appRoot: "app", + configsDir: "configs", + buildDir: "build", + fs: mockFS, + } + + err := b.SaveUploadedFiles(files) + + if tt.shouldFail && err == nil { + t.Error("expected error but got none") + } + + if !tt.shouldFail { + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // Verify correct directory was created + if len(mockFS.MkdirCalls) != 1 { + t.Errorf("expected 1 mkdir call, got %d", len(mockFS.MkdirCalls)) + } + + // Verify files were created + expectedCreateCalls := len(tt.files) + if len(mockFS.CreateCalls) != expectedCreateCalls { + t.Errorf("expected %d create calls, got %d", expectedCreateCalls, len(mockFS.CreateCalls)) + } + } + }) + } +} + +func TestExecuteBuild(t *testing.T) { + tests := []struct { + name string + stdout string + stderr string + shouldFail bool + }{ + { + name: "successful build", + stdout: "build successful\n", + stderr: "", + shouldFail: false, + }, + { + name: "build failure", + stdout: "", + stderr: "build failed\n", + shouldFail: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockRunner := &MockCommandRunner{ + ShouldFail: tt.shouldFail, + Stdout: tt.stdout, + Stderr: tt.stderr, + } + + mockFactory := &MockCommandFactory{ + Runner: mockRunner, + } + + mockFS := NewMockFS() + + b := &Builder{ + appRoot: "app", + buildDir: "build", + buildCmd: "make build", + cmdFactory: mockFactory, + fs: mockFS, + } + + output, err := b.ExecuteBuild() + + if tt.shouldFail && err == nil { + t.Error("expected error but got none") + } + + if !tt.shouldFail { + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if !mockRunner.StartCalled { + t.Error("Start was not called") + } + + if !mockRunner.WaitCalled { + t.Error("Wait was not called") + } + + if !strings.Contains(string(output), tt.stdout) { + t.Errorf("expected output to contain %q, got %q", tt.stdout, string(output)) + } + } + }) + } +} + +func TestNormalizeFilename(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "standard format", + input: "prefix-123.json", + expected: "123-prefix.json", + }, + { + name: "no number", + input: "test.json", + expected: "test.json", + }, + { + name: "invalid number", + input: "prefix-abc.json", + expected: "prefix-abc.json", + }, + { + name: "no json extension", + input: "prefix-123.txt", + expected: "prefix-123.txt", + }, + } + + b := &Builder{} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := b.normalizeFilename(tt.input) + if result != tt.expected { + t.Errorf("expected %q, got %q", tt.expected, result) + } + }) + } +} diff --git a/kurtosis-devnet/op-program-svc/defaults.go b/kurtosis-devnet/op-program-svc/defaults.go new file mode 100644 index 00000000000..aba44060ae0 --- /dev/null +++ b/kurtosis-devnet/op-program-svc/defaults.go @@ -0,0 +1,66 @@ +package main + +import ( + "io" + "io/fs" + "os" + "os/exec" + "path/filepath" +) + +// osFile wraps os.File to implement File +type osFile struct { + *os.File +} + +func (f *osFile) Readdir(count int) ([]fs.FileInfo, error) { + return f.File.Readdir(count) +} + +// DefaultFileSystem implements testutils.FS using actual OS calls +type DefaultFileSystem struct{} + +func (fs *DefaultFileSystem) Open(name string) (File, error) { + file, err := os.Open(name) + if err != nil { + return nil, err + } + return &osFile{File: file}, nil +} + +func (fs *DefaultFileSystem) ReadDir(name string) ([]fs.DirEntry, error) { + return os.ReadDir(name) +} + +func (fs *DefaultFileSystem) ReadFile(name string) ([]byte, error) { + return os.ReadFile(name) +} + +func (fs *DefaultFileSystem) MkdirAll(path string, perm os.FileMode) error { + return os.MkdirAll(path, perm) +} + +func (fs *DefaultFileSystem) Create(name string) (io.WriteCloser, error) { + return os.Create(name) +} + +func (fs *DefaultFileSystem) Join(elem ...string) string { + return filepath.Join(elem...) +} + +// commandWrapper wraps exec.Cmd to implement CommandRunner +type commandWrapper struct { + *exec.Cmd +} + +func (c *commandWrapper) SetDir(dir string) { + c.Cmd.Dir = dir +} + +// DefaultCommandFactory implements testutils.CommandFactory using actual OS exec +type DefaultCommandFactory struct{} + +func (f *DefaultCommandFactory) CreateCommand(name string, args ...string) CommandRunner { + cmd := exec.Command(name, args...) + return &commandWrapper{Cmd: cmd} +} diff --git a/kurtosis-devnet/op-program-svc/fs.go b/kurtosis-devnet/op-program-svc/fs.go new file mode 100644 index 00000000000..ca7b04f132c --- /dev/null +++ b/kurtosis-devnet/op-program-svc/fs.go @@ -0,0 +1,252 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "log" + "net/http" + "os" + "path/filepath" + "regexp" + "strings" + "sync" + "time" +) + +// proofFileSystem implements http.FileSystem, mapping hash-based virtual paths to actual files +type proofFileSystem struct { + root string + fs FS // Use our consolidated FS interface + proofFiles map[string]string // hash -> variable part mapping + proofMutex sync.RWMutex +} + +// proofFile implements http.File, representing a virtual file in our proof filesystem +type proofFile struct { + file File +} + +func (f *proofFile) Close() error { + return f.file.Close() +} + +func (f *proofFile) Read(p []byte) (n int, err error) { + return f.file.Read(p) +} + +func (f *proofFile) Seek(offset int64, whence int) (int64, error) { + return f.file.(io.Seeker).Seek(offset, whence) +} + +func (f *proofFile) Readdir(count int) ([]fs.FileInfo, error) { + // For actual files, we don't support directory listing + return nil, fmt.Errorf("not a directory") +} + +func (f *proofFile) Stat() (fs.FileInfo, error) { + return f.file.(fs.File).Stat() +} + +// proofDir implements http.File for the root directory +type proofDir struct { + *proofFileSystem + pos int +} + +func (d *proofDir) Close() error { + return nil +} + +func (d *proofDir) Read(p []byte) (n int, err error) { + return 0, fmt.Errorf("cannot read a directory") +} + +func (d *proofDir) Seek(offset int64, whence int) (int64, error) { + return 0, fmt.Errorf("cannot seek a directory") +} + +func (d *proofDir) Readdir(count int) ([]fs.FileInfo, error) { + d.proofMutex.RLock() + defer d.proofMutex.RUnlock() + + // If we've already read all entries + if d.pos >= len(d.proofFiles)*2 { + if count <= 0 { + return nil, nil + } + return nil, io.EOF + } + + // Convert hashes to virtual file entries + var entries []fs.FileInfo + hashes := make([]string, 0, len(d.proofFiles)) + for hash := range d.proofFiles { + hashes = append(hashes, hash) + } + + start := d.pos + end := start + count + if count <= 0 || end > len(d.proofFiles)*2 { + end = len(d.proofFiles) * 2 + } + + for i := start; i < end; i++ { + hash := hashes[i/2] + isJSON := i%2 == 0 + + var name string + if isJSON { + name = hash + ".json" + } else { + name = hash + ".bin.gz" + } + + // Create a virtual file info + entries = append(entries, virtualFileInfo{ + name: name, + size: 0, // Size will be determined when actually opening the file + mode: 0644, + modTime: time.Now(), + isDir: false, + }) + } + + d.pos = end + return entries, nil +} + +func (d *proofDir) Stat() (fs.FileInfo, error) { + return virtualFileInfo{ + name: ".", + size: 0, + mode: 0755, + modTime: time.Now(), + isDir: true, + }, nil +} + +// virtualFileInfo implements fs.FileInfo for our virtual files +type virtualFileInfo struct { + name string + size int64 + mode fs.FileMode + modTime time.Time + isDir bool +} + +func (v virtualFileInfo) Name() string { return v.name } +func (v virtualFileInfo) Size() int64 { return v.size } +func (v virtualFileInfo) Mode() fs.FileMode { return v.mode } +func (v virtualFileInfo) ModTime() time.Time { return v.modTime } +func (v virtualFileInfo) IsDir() bool { return v.isDir } +func (v virtualFileInfo) Sys() interface{} { return nil } + +func newProofFileSystem(root string) *proofFileSystem { + return &proofFileSystem{ + root: root, + fs: &DefaultFileSystem{}, + proofFiles: make(map[string]string), + } +} + +// SetFS allows replacing the filesystem implementation, primarily for testing +func (fs *proofFileSystem) SetFS(newFS FS) { + fs.proofMutex.Lock() + defer fs.proofMutex.Unlock() + fs.fs = newFS +} + +func (fs *proofFileSystem) Open(name string) (http.File, error) { + if name == "/" || name == "" { + return &proofDir{proofFileSystem: fs}, nil + } + + // Clean the path and remove leading slash + name = strings.TrimPrefix(filepath.Clean(name), "/") + + fs.proofMutex.RLock() + defer fs.proofMutex.RUnlock() + + var targetFile string + if strings.HasSuffix(name, ".json") { + hash := strings.TrimSuffix(name, ".json") + if variablePart, ok := fs.proofFiles[hash]; ok { + targetFile = fmt.Sprintf("prestate-proof%s.json", variablePart) + } + } else if strings.HasSuffix(name, ".bin.gz") { + hash := strings.TrimSuffix(name, ".bin.gz") + if variablePart, ok := fs.proofFiles[hash]; ok { + targetFile = fmt.Sprintf("prestate%s.bin.gz", variablePart) + } + } + + if targetFile == "" { + return nil, fs.Error("file not found") + } + + file, err := fs.fs.Open(fs.fs.Join(fs.root, targetFile)) + if err != nil { + return nil, err + } + + return &proofFile{file: file}, nil +} + +func (fs *proofFileSystem) scanProofFiles() error { + fs.proofMutex.Lock() + defer fs.proofMutex.Unlock() + + // Clear existing mappings + fs.proofFiles = make(map[string]string) + + // Read directory entries + entries, err := fs.fs.ReadDir(fs.root) + if err != nil { + return fmt.Errorf("failed to read proofs directory: %w", err) + } + + // Regexp for matching prestate-proof files and extracting the variable part + proofRegexp := regexp.MustCompile(`^prestate-proof(.*)\.json$`) + + for _, entry := range entries { + if entry.IsDir() { + continue + } + + matches := proofRegexp.FindStringSubmatch(entry.Name()) + if matches == nil { + continue + } + + // matches[1] contains the variable part (including the leading hyphen if present) + variablePart := matches[1] + + // Read and parse the JSON file + data, err := fs.fs.ReadFile(fs.fs.Join(fs.root, entry.Name())) + if err != nil { + log.Printf("Warning: failed to read proof file %s: %v", entry.Name(), err) + continue + } + + var proofData struct { + Pre string `json:"pre"` + } + if err := json.Unmarshal(data, &proofData); err != nil { + log.Printf("Warning: failed to parse proof file %s: %v", entry.Name(), err) + continue + } + + // Store the mapping from hash to variable part of filename + fs.proofFiles[proofData.Pre] = variablePart + log.Printf("Mapped hash %s to proof file pattern%s", proofData.Pre, variablePart) + } + + return nil +} + +func (fs *proofFileSystem) Error(msg string) error { + return &os.PathError{Op: "open", Path: "virtual path", Err: errors.New(msg)} +} diff --git a/kurtosis-devnet/op-program-svc/fs_test.go b/kurtosis-devnet/op-program-svc/fs_test.go new file mode 100644 index 00000000000..3f61b1a3dfd --- /dev/null +++ b/kurtosis-devnet/op-program-svc/fs_test.go @@ -0,0 +1,104 @@ +package main + +import ( + "bytes" + "encoding/json" + "io" + "testing" +) + +func TestProofFileSystem(t *testing.T) { + // Create mock filesystem + mockfs := NewMockFS() + + // Add test files + proofData := map[string]interface{}{ + "pre": "hash123", + } + proofJSON, _ := json.Marshal(proofData) + + mockfs.Files["/proofs/prestate-proof-test.json"] = NewMockFile( + "prestate-proof-test.json", + proofJSON, + ) + mockfs.Files["/proofs/prestate-test.bin.gz"] = NewMockFile( + "prestate-test.bin.gz", + []byte("mock binary data"), + ) + + // Create proof filesystem and set mock fs + pfs := newProofFileSystem("/proofs") + pfs.SetFS(mockfs) + + // Test scanning proof files + t.Run("ScanProofFiles", func(t *testing.T) { + err := pfs.scanProofFiles() + if err != nil { + t.Errorf("scanProofFiles failed: %v", err) + } + + // Verify mapping was created + if mapping, ok := pfs.proofFiles["hash123"]; !ok || mapping != "-test" { + t.Errorf("Expected mapping for hash123 to be -test, got %v", mapping) + } + }) + + t.Run("OpenJSONFile", func(t *testing.T) { + file, err := pfs.Open("/hash123.json") + if err != nil { + t.Errorf("Failed to open JSON file: %v", err) + } + defer file.Close() + + // Read contents + contents, err := io.ReadAll(file) + if err != nil { + t.Errorf("Failed to read file contents: %v", err) + } + + if !bytes.Equal(contents, proofJSON) { + t.Errorf("File contents don't match expected") + } + }) + + t.Run("OpenBinaryFile", func(t *testing.T) { + file, err := pfs.Open("/hash123.bin.gz") + if err != nil { + t.Errorf("Failed to open binary file: %v", err) + } + defer file.Close() + + contents, err := io.ReadAll(file) + if err != nil { + t.Errorf("Failed to read file contents: %v", err) + } + + if !bytes.Equal(contents, []byte("mock binary data")) { + t.Errorf("File contents don't match expected") + } + }) + + t.Run("OpenNonExistentFile", func(t *testing.T) { + _, err := pfs.Open("/nonexistent.json") + if err == nil { + t.Error("Expected error opening non-existent file") + } + }) + + t.Run("ListDirectory", func(t *testing.T) { + dir, err := pfs.Open("/") + if err != nil { + t.Errorf("Failed to open root directory: %v", err) + } + defer dir.Close() + + files, err := dir.Readdir(-1) + if err != nil { + t.Errorf("Failed to read directory: %v", err) + } + + if len(files) != 2 { // We expect both .json and .bin.gz files + t.Errorf("Expected 2 files, got %d", len(files)) + } + }) +} diff --git a/kurtosis-devnet/op-program-svc/interfaces.go b/kurtosis-devnet/op-program-svc/interfaces.go new file mode 100644 index 00000000000..701890459c8 --- /dev/null +++ b/kurtosis-devnet/op-program-svc/interfaces.go @@ -0,0 +1,47 @@ +package main + +import ( + "io" + "io/fs" + "os" +) + +// File interface abstracts file operations +type File interface { + fs.File + io.Seeker + Readdir(count int) ([]fs.FileInfo, error) +} + +// FS defines the interface for all filesystem operations +type FS interface { + // Core FS operations + Open(name string) (File, error) + ReadDir(name string) ([]fs.DirEntry, error) + ReadFile(name string) ([]byte, error) + Join(elem ...string) string + + // Additional FileSystem operations + MkdirAll(path string, perm os.FileMode) error + Create(name string) (io.WriteCloser, error) +} + +// UploadedFile represents a file that has been uploaded +type UploadedFile interface { + Open() (io.ReadCloser, error) + GetFilename() string +} + +// CommandRunner abstracts command execution for testing +type CommandRunner interface { + Start() error + Wait() error + StdoutPipe() (io.ReadCloser, error) + StderrPipe() (io.ReadCloser, error) + SetDir(dir string) +} + +// CommandFactory creates commands +type CommandFactory interface { + CreateCommand(name string, args ...string) CommandRunner +} diff --git a/kurtosis-devnet/op-program-svc/justfile b/kurtosis-devnet/op-program-svc/justfile new file mode 100644 index 00000000000..79b88eb825a --- /dev/null +++ b/kurtosis-devnet/op-program-svc/justfile @@ -0,0 +1,5 @@ +op-program-base: + docker buildx build -f ../../op-program/Dockerfile.repro --target=src -t op-program-base:latest ../.. + +op-program-svc: op-program-base + docker buildx build -f Dockerfile -t op-program-svc:latest . diff --git a/kurtosis-devnet/op-program-svc/main.go b/kurtosis-devnet/op-program-svc/main.go new file mode 100644 index 00000000000..53375f594c1 --- /dev/null +++ b/kurtosis-devnet/op-program-svc/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + "path/filepath" +) + +var ( + flagAppRoot = flag.String("app-root", "/app", "Root directory for the application") + flagConfigsDir = flag.String("configs-dir", "chainconfig/configs", "Directory for config files (relative to build-dir)") + flagBuildDir = flag.String("build-dir", "op-program", "Directory where the build command will be executed (relative to app-root)") + flagBuildCmd = flag.String("build-cmd", "just -f repro.justfile build-all", "Build command to execute") + flagPort = flag.Int("port", 8080, "Port to listen on") +) + +func main() { + flag.Parse() + + srv := createServer() + + // Set up routes + http.HandleFunc("/", srv.handleUpload) + http.Handle("/proofs/", http.StripPrefix("/proofs/", http.FileServer(srv.proofFS))) + + log.Printf("Starting server on :%d with:", srv.port) + log.Printf(" app-root: %s", srv.appRoot) + log.Printf(" configs-dir: %s", filepath.Join(srv.appRoot, srv.buildDir, srv.configsDir)) + log.Printf(" build-dir: %s", filepath.Join(srv.appRoot, srv.buildDir)) + log.Printf(" build-cmd: %s", srv.buildCmd) + log.Printf(" proofs-dir: %s", filepath.Join(srv.appRoot, srv.buildDir, "bin")) + + if err := http.ListenAndServe(fmt.Sprintf(":%d", srv.port), nil); err != nil { + log.Fatalf("Failed to start server: %v", err) + } +} diff --git a/kurtosis-devnet/op-program-svc/mocks.go b/kurtosis-devnet/op-program-svc/mocks.go new file mode 100644 index 00000000000..6d52f31009a --- /dev/null +++ b/kurtosis-devnet/op-program-svc/mocks.go @@ -0,0 +1,273 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "io/fs" + "net/http" + "os" + "path/filepath" + "strings" + "time" +) + +// MockFile implements both File and fs.FileInfo interfaces for testing +type MockFile struct { + name string + contents []byte + pos int64 + isDir bool +} + +func NewMockFile(name string, contents []byte) *MockFile { + return &MockFile{ + name: name, + contents: contents, + } +} + +func (m *MockFile) Close() error { return nil } +func (m *MockFile) Read(p []byte) (n int, err error) { + if m.pos >= int64(len(m.contents)) { + return 0, io.EOF + } + n = copy(p, m.contents[m.pos:]) + m.pos += int64(n) + return n, nil +} + +func (m *MockFile) Seek(offset int64, whence int) (int64, error) { + var abs int64 + switch whence { + case io.SeekStart: + abs = offset + case io.SeekCurrent: + abs = m.pos + offset + case io.SeekEnd: + abs = int64(len(m.contents)) + offset + default: + return 0, fmt.Errorf("invalid whence") + } + if abs < 0 { + return 0, fmt.Errorf("negative position") + } + m.pos = abs + return abs, nil +} + +func (m *MockFile) Stat() (fs.FileInfo, error) { return m, nil } +func (m *MockFile) Name() string { return m.name } +func (m *MockFile) Size() int64 { return int64(len(m.contents)) } +func (m *MockFile) Mode() fs.FileMode { return 0644 } +func (m *MockFile) ModTime() time.Time { return time.Now() } +func (m *MockFile) IsDir() bool { return m.isDir } +func (m *MockFile) Sys() interface{} { return nil } +func (m *MockFile) Readdir(count int) ([]fs.FileInfo, error) { + return nil, fmt.Errorf("not implemented") +} + +// MockFS implements both FS and FileSystem interfaces for testing +type MockFS struct { + Files map[string]*MockFile + MkdirCalls []string + CreateCalls []string + JoinCalls [][]string + ShouldFail bool +} + +func NewMockFS() *MockFS { + return &MockFS{ + Files: make(map[string]*MockFile), + MkdirCalls: make([]string, 0), + CreateCalls: make([]string, 0), + JoinCalls: make([][]string, 0), + } +} + +// FS interface methods +func (m *MockFS) Open(name string) (File, error) { + if m.ShouldFail { + return nil, fmt.Errorf("mock open error") + } + if file, ok := m.Files[name]; ok { + file.pos = 0 // Reset position for new reads + return file, nil + } + return nil, fs.ErrNotExist +} + +func (m *MockFS) ReadDir(name string) ([]fs.DirEntry, error) { + if m.ShouldFail { + return nil, fmt.Errorf("mock readdir error") + } + var entries []fs.DirEntry + for path, file := range m.Files { + if filepath.Dir(path) == name { + entries = append(entries, fs.FileInfoToDirEntry(file)) + } + } + return entries, nil +} + +func (m *MockFS) ReadFile(name string) ([]byte, error) { + if m.ShouldFail { + return nil, fmt.Errorf("mock readfile error") + } + if file, ok := m.Files[name]; ok { + return file.contents, nil + } + return nil, fs.ErrNotExist +} + +// FileSystem interface methods +func (m *MockFS) MkdirAll(path string, perm os.FileMode) error { + if m.ShouldFail { + return fmt.Errorf("mock mkdir error") + } + m.MkdirCalls = append(m.MkdirCalls, path) + return nil +} + +func (m *MockFS) Create(name string) (io.WriteCloser, error) { + if m.ShouldFail { + return nil, fmt.Errorf("mock create error") + } + m.CreateCalls = append(m.CreateCalls, name) + return &MockWriteCloser{}, nil +} + +func (m *MockFS) Join(elem ...string) string { + m.JoinCalls = append(m.JoinCalls, elem) + return filepath.Join(elem...) +} + +// MockWriteCloser implements io.WriteCloser for testing +type MockWriteCloser struct { + bytes.Buffer +} + +func (m *MockWriteCloser) Close() error { + return nil +} + +// MockUploadedFile implements UploadedFile for testing +type MockUploadedFile struct { + filename string + content []byte +} + +func NewMockUploadedFile(filename string, content []byte) *MockUploadedFile { + return &MockUploadedFile{ + filename: filename, + content: content, + } +} + +func (m *MockUploadedFile) Open() (io.ReadCloser, error) { + return io.NopCloser(bytes.NewReader(m.content)), nil +} + +func (m *MockUploadedFile) GetFilename() string { + return m.filename +} + +// MockCommandRunner implements CommandRunner for testing +type MockCommandRunner struct { + StartCalled bool + WaitCalled bool + ShouldFail bool + Stdout string + Stderr string + Dir string +} + +func (m *MockCommandRunner) Start() error { + if m.ShouldFail { + return fmt.Errorf("mock start error") + } + m.StartCalled = true + return nil +} + +func (m *MockCommandRunner) Wait() error { + if m.ShouldFail { + return fmt.Errorf("mock wait error") + } + m.WaitCalled = true + return nil +} + +func (m *MockCommandRunner) StdoutPipe() (io.ReadCloser, error) { + return io.NopCloser(strings.NewReader(m.Stdout)), nil +} + +func (m *MockCommandRunner) StderrPipe() (io.ReadCloser, error) { + return io.NopCloser(strings.NewReader(m.Stderr)), nil +} + +func (m *MockCommandRunner) SetDir(dir string) { + m.Dir = dir +} + +// MockCommandFactory implements CommandFactory for testing +type MockCommandFactory struct { + Runner *MockCommandRunner +} + +func (f *MockCommandFactory) CreateCommand(name string, args ...string) CommandRunner { + return f.Runner +} + +// MockProofFS is a mock implementation of ProofFS +type MockProofFS struct { + scanProofFilesFn func() error + fs *MockFS +} + +func NewMockProofFS() *MockProofFS { + return &MockProofFS{ + fs: NewMockFS(), + } +} + +func (m *MockProofFS) scanProofFiles() error { + if m.scanProofFilesFn != nil { + return m.scanProofFilesFn() + } + return nil +} + +func (m *MockProofFS) Open(name string) (http.File, error) { + file, err := m.fs.Open(name) + if err != nil { + return nil, err + } + // MockFile implements http.File (including Seek) + return file.(http.File), nil +} + +// AddFile adds a file to the mock filesystem +func (m *MockProofFS) AddFile(name string, contents []byte) { + m.fs.Files[name] = NewMockFile(name, contents) +} + +// MockBuilder is a mock implementation of BuildSystem +type MockBuilder struct { + saveUploadedFilesFn func(files []UploadedFile) error + executeBuildFn func() ([]byte, error) +} + +func (m *MockBuilder) SaveUploadedFiles(files []UploadedFile) error { + if m.saveUploadedFilesFn != nil { + return m.saveUploadedFilesFn(files) + } + return nil +} + +func (m *MockBuilder) ExecuteBuild() ([]byte, error) { + if m.executeBuildFn != nil { + return m.executeBuildFn() + } + return []byte("mock build output"), nil +} diff --git a/kurtosis-devnet/op-program-svc/server.go b/kurtosis-devnet/op-program-svc/server.go new file mode 100644 index 00000000000..401e92ff9e4 --- /dev/null +++ b/kurtosis-devnet/op-program-svc/server.go @@ -0,0 +1,158 @@ +package main + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "log" + "mime/multipart" + "net/http" + "path/filepath" + "sync" +) + +// ProofFS represents the interface for the proof filesystem +type ProofFS interface { + http.FileSystem + scanProofFiles() error +} + +// BuildSystem represents the interface for the build system +type BuildSystem interface { + SaveUploadedFiles(files []UploadedFile) error + ExecuteBuild() ([]byte, error) +} + +type server struct { + appRoot string + configsDir string + buildDir string + buildCmd string + port int + lastBuildHash string + buildMutex sync.Mutex + proofFS ProofFS + builder BuildSystem +} + +func createServer() *server { + srv := &server{ + appRoot: *flagAppRoot, + configsDir: *flagConfigsDir, + buildDir: *flagBuildDir, + buildCmd: *flagBuildCmd, + port: *flagPort, + } + + // Initialize the proof filesystem + proofsDir := filepath.Join(srv.appRoot, srv.buildDir, "bin") + proofFS := newProofFileSystem(proofsDir) + srv.proofFS = proofFS + + // Initialize the builder + builder := NewBuilder(srv.appRoot, srv.configsDir, srv.buildDir, srv.buildCmd) + srv.builder = builder + + return srv +} + +func (s *server) handleUpload(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + log.Printf("Received upload request from %s", r.RemoteAddr) + + multipartFiles, currentHash, err := s.processMultipartForm(r) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + s.buildMutex.Lock() + defer s.buildMutex.Unlock() + + // Check if we need to rebuild + if currentHash == s.lastBuildHash { + log.Printf("Hash matches last build, skipping") + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "Files unchanged, skipping build") + return + } + + log.Printf("Hash differs from last build (%s), proceeding with build", s.lastBuildHash) + + // Convert multipart files to UploadedFile interface + files := make([]UploadedFile, len(multipartFiles)) + for i, f := range multipartFiles { + files[i] = NewMultipartUploadedFile(f) + } + + // Save the files using the builder + if err := s.builder.SaveUploadedFiles(files); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Execute the build + out, err := s.builder.ExecuteBuild() + if err != nil { + http.Error(w, fmt.Sprintf("%v\nOutput: %s", err, out), http.StatusInternalServerError) + return + } + + // After successful build, scan for new proof files + if err := s.proofFS.scanProofFiles(); err != nil { + log.Printf("Warning: failed to scan proof files: %v", err) + } + + // Update the last successful build hash + s.lastBuildHash = currentHash + + // Redirect to the proofs endpoint + http.Redirect(w, r, "/proofs", http.StatusSeeOther) +} + +func (s *server) processMultipartForm(r *http.Request) ([]*multipart.FileHeader, string, error) { + // Parse the multipart form + if err := r.ParseMultipartForm(1 << 30); err != nil { // 1GB max memory + return nil, "", fmt.Errorf("failed to parse form: %w", err) + } + + // Get uploaded files + files := r.MultipartForm.File["files[]"] + if len(files) == 0 { + return nil, "", fmt.Errorf("no files uploaded") + } + + log.Printf("Processing %d files:", len(files)) + for _, fileHeader := range files { + log.Printf(" - %s (size: %d bytes)", fileHeader.Filename, fileHeader.Size) + } + + // Calculate hash of all files + hash, err := s.calculateFilesHash(files) + if err != nil { + return nil, "", fmt.Errorf("failed to calculate files hash: %w", err) + } + + return files, hash, nil +} + +func (s *server) calculateFilesHash(files []*multipart.FileHeader) (string, error) { + hasher := sha256.New() + for _, fileHeader := range files { + file, err := fileHeader.Open() + if err != nil { + return "", fmt.Errorf("failed to open file: %w", err) + } + if _, err := io.Copy(hasher, file); err != nil { + file.Close() + return "", fmt.Errorf("failed to hash file: %w", err) + } + file.Close() + } + return hex.EncodeToString(hasher.Sum(nil)), nil +} diff --git a/kurtosis-devnet/op-program-svc/server_test.go b/kurtosis-devnet/op-program-svc/server_test.go new file mode 100644 index 00000000000..f66cea90bd0 --- /dev/null +++ b/kurtosis-devnet/op-program-svc/server_test.go @@ -0,0 +1,252 @@ +package main + +import ( + "bytes" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func createTestServer(t *testing.T) (*server, *MockProofFS, *MockBuilder) { + t.Helper() + mockProofFS := NewMockProofFS() + mockBuilder := &MockBuilder{} + + srv := &server{ + appRoot: "test-root", + configsDir: "test-configs", + buildDir: "test-build", + buildCmd: "test-cmd", + port: 8080, + proofFS: mockProofFS, + builder: mockBuilder, + } + + return srv, mockProofFS, mockBuilder +} + +func createMultipartRequest(t *testing.T, files map[string][]byte) (*http.Request, error) { + t.Helper() + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + + for filename, content := range files { + part, err := writer.CreateFormFile("files[]", filename) + if err != nil { + return nil, err + } + if _, err := io.Copy(part, bytes.NewReader(content)); err != nil { + return nil, err + } + } + + if err := writer.Close(); err != nil { + return nil, err + } + + req := httptest.NewRequest("POST", "/upload", body) + req.Header.Set("Content-Type", writer.FormDataContentType()) + return req, nil +} + +func TestHandleUpload_MethodNotAllowed(t *testing.T) { + srv, _, _ := createTestServer(t) + + req := httptest.NewRequest("GET", "/upload", nil) + w := httptest.NewRecorder() + + srv.handleUpload(w, req) + + if w.Code != http.StatusMethodNotAllowed { + t.Errorf("Expected status code %d, got %d", http.StatusMethodNotAllowed, w.Code) + } +} + +func TestHandleUpload_NoFiles(t *testing.T) { + srv, _, _ := createTestServer(t) + + req := httptest.NewRequest("POST", "/upload", nil) + req.Header.Set("Content-Type", "multipart/form-data; boundary=xxx") + w := httptest.NewRecorder() + + srv.handleUpload(w, req) + + if w.Code != http.StatusBadRequest { + t.Errorf("Expected status code %d, got %d", http.StatusBadRequest, w.Code) + } +} + +func TestHandleUpload_Success(t *testing.T) { + srv, mockProofFS, mockBuilder := createTestServer(t) + + // Setup test data + files := map[string][]byte{ + "test.txt": []byte("test content"), + } + + // Setup mocks + mockBuilder.saveUploadedFilesFn = func(files []UploadedFile) error { + return nil + } + mockBuilder.executeBuildFn = func() ([]byte, error) { + return []byte("build successful"), nil + } + mockProofFS.scanProofFilesFn = func() error { + return nil + } + + // Create request + req, err := createMultipartRequest(t, files) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + w := httptest.NewRecorder() + srv.handleUpload(w, req) + + if w.Code != http.StatusSeeOther { + t.Errorf("Expected status code %d, got %d", http.StatusSeeOther, w.Code) + } + + if location := w.Header().Get("Location"); location != "/proofs" { + t.Errorf("Expected redirect to /proofs, got %s", location) + } +} + +func TestHandleUpload_SaveError(t *testing.T) { + srv, _, mockBuilder := createTestServer(t) + + // Setup test data + files := map[string][]byte{ + "test.txt": []byte("test content"), + } + + // Setup mock to return error + mockBuilder.saveUploadedFilesFn = func(files []UploadedFile) error { + return fmt.Errorf("failed to save files") + } + + // Create request + req, err := createMultipartRequest(t, files) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + w := httptest.NewRecorder() + srv.handleUpload(w, req) + + if w.Code != http.StatusInternalServerError { + t.Errorf("Expected status code %d, got %d", http.StatusInternalServerError, w.Code) + } +} + +func TestHandleUpload_BuildError(t *testing.T) { + srv, _, mockBuilder := createTestServer(t) + + // Setup test data + files := map[string][]byte{ + "test.txt": []byte("test content"), + } + + // Setup mocks + mockBuilder.saveUploadedFilesFn = func(files []UploadedFile) error { + return nil + } + mockBuilder.executeBuildFn = func() ([]byte, error) { + return []byte("build failed"), fmt.Errorf("build error") + } + + // Create request + req, err := createMultipartRequest(t, files) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + w := httptest.NewRecorder() + srv.handleUpload(w, req) + + if w.Code != http.StatusInternalServerError { + t.Errorf("Expected status code %d, got %d", http.StatusInternalServerError, w.Code) + } + + if !strings.Contains(w.Body.String(), "build error") { + t.Errorf("Expected error message to contain 'build error', got %s", w.Body.String()) + } +} + +func TestHandleUpload_ScanError(t *testing.T) { + srv, mockProofFS, mockBuilder := createTestServer(t) + + // Setup test data + files := map[string][]byte{ + "test.txt": []byte("test content"), + } + + // Setup mocks + mockBuilder.saveUploadedFilesFn = func(files []UploadedFile) error { + return nil + } + mockBuilder.executeBuildFn = func() ([]byte, error) { + return []byte("build successful"), nil + } + mockProofFS.scanProofFilesFn = func() error { + return fmt.Errorf("scan error") + } + + // Create request + req, err := createMultipartRequest(t, files) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + w := httptest.NewRecorder() + srv.handleUpload(w, req) + + // Even with scan error, we should still redirect + if w.Code != http.StatusSeeOther { + t.Errorf("Expected status code %d, got %d", http.StatusSeeOther, w.Code) + } +} + +func TestHandleUpload_UnchangedFiles(t *testing.T) { + srv, _, _ := createTestServer(t) + + // Setup test data + files := map[string][]byte{ + "test.txt": []byte("test content"), + } + + // First request + req1, err := createMultipartRequest(t, files) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + w1 := httptest.NewRecorder() + srv.handleUpload(w1, req1) + + if w1.Code != http.StatusSeeOther { + t.Errorf("Expected status code %d, got %d", http.StatusSeeOther, w1.Code) + } + + // Second request with same files + req2, err := createMultipartRequest(t, files) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + w2 := httptest.NewRecorder() + srv.handleUpload(w2, req2) + + if w2.Code != http.StatusOK { + t.Errorf("Expected status code %d, got %d", http.StatusOK, w2.Code) + } + + if !strings.Contains(w2.Body.String(), "Files unchanged") { + t.Errorf("Expected response to contain 'Files unchanged', got %s", w2.Body.String()) + } +} diff --git a/op-program/Dockerfile.repro b/op-program/Dockerfile.repro index 0bb95ed1202..cda354c3e3e 100644 --- a/op-program/Dockerfile.repro +++ b/op-program/Dockerfile.repro @@ -1,6 +1,6 @@ -FROM golang:1.22.7-alpine3.20 AS builder +FROM golang:1.22.7-alpine3.20 AS src -RUN apk add --no-cache make gcc musl-dev linux-headers git jq bash +RUN apk add --no-cache make gcc musl-dev linux-headers git jq bash just COPY ./go.mod /app/go.mod COPY ./go.sum /app/go.sum @@ -14,6 +14,8 @@ RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache COPY . /app +# we need a separate stage for src so we can build a service provides prestates for unnanounced chains +FROM src AS builder # We avoid copying the full .git dir into the build for just some metadata. # Instead, specify: # --build-arg GIT_COMMIT=$(git rev-parse HEAD) @@ -26,35 +28,21 @@ ARG OP_PROGRAM_VERSION=v0.0.0 ARG TARGETOS TARGETARCH -# Build the cannon and op-program-client.elf binaries. -RUN --mount=type=cache,target=/root/.cache/go-build cd cannon && make cannon \ - GOOS=$TARGETOS GOARCH=$TARGETARCH GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$CANNON_VERSION" -RUN --mount=type=cache,target=/root/.cache/go-build cd op-program && make op-program-client-mips \ - GOOS=linux GOARCH=mips GOMIPS=softfloat GITCOMMIT=$GIT_COMMIT GITDATE=$GIT_DATE VERSION="$OP_PROGRAM_VERSION" - -# Run the op-program-client.elf binary directly through cannon's load-elf subcommand. -RUN /app/cannon/bin/cannon load-elf --type singlethreaded-2 --path /app/op-program/bin/op-program-client.elf --out /app/op-program/bin/prestate.bin.gz --meta "/app/op-program/bin/meta.json" -RUN /app/cannon/bin/cannon load-elf --type multithreaded64-3 --path /app/op-program/bin/op-program-client64.elf --out /app/op-program/bin/prestate-mt64.bin.gz --meta "/app/op-program/bin/meta-mt64.json" -RUN /app/cannon/bin/cannon load-elf --type multithreaded64-3 --path /app/op-program/bin/op-program-client-interop.elf --out /app/op-program/bin/prestate-interop.bin.gz --meta "/app/op-program/bin/meta-interop.json" - -# Generate the prestate proof containing the absolute pre-state hash. -RUN /app/cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input /app/op-program/bin/prestate.bin.gz --meta "" --proof-fmt '/app/op-program/bin/%d.json' --output "" -RUN mv /app/op-program/bin/0.json /app/op-program/bin/prestate-proof.json - -RUN /app/cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input /app/op-program/bin/prestate-mt64.bin.gz --meta "" --proof-fmt '/app/op-program/bin/%d-mt64.json' --output "" -RUN mv /app/op-program/bin/0-mt64.json /app/op-program/bin/prestate-proof-mt64.json - -RUN /app/cannon/bin/cannon run --proof-at '=0' --stop-at '=1' --input /app/op-program/bin/prestate-interop.bin.gz --meta "" --proof-fmt '/app/op-program/bin/%d-interop.json' --output "" -RUN mv /app/op-program/bin/0-interop.json /app/op-program/bin/prestate-proof-interop.json +WORKDIR /app +RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build just \ + -d /app/op-program \ + -f /app/op-program/repro.justfile \ + GOOS="$TARGETOS" \ + GOARCH="$TARGETARCH" \ + GIT_COMMIT="$GIT_COMMIT" \ + GIT_DATE="$GIT_DATE" \ + CANNON_VERSION="$CANNON_VERSION" \ + OP_PROGRAM_VERSION="$OP_PROGRAM_VERSION" \ + build-all # Exports files to the specified output location. # Writing files to host requires buildkit to be enabled. # e.g. `BUILDKIT=1 docker build ...` -FROM scratch AS export-stage-proofs -COPY --from=builder /app/op-program/bin/prestate-proof.json . -COPY --from=builder /app/op-program/bin/prestate-proof-mt64.json . -COPY --from=builder /app/op-program/bin/prestate-proof-interop.json . - FROM scratch AS export-stage COPY --from=builder /app/op-program/bin/op-program-client.elf . COPY --from=builder /app/op-program/bin/op-program-client64.elf . diff --git a/op-program/repro.justfile b/op-program/repro.justfile new file mode 100644 index 00000000000..ced510206ad --- /dev/null +++ b/op-program/repro.justfile @@ -0,0 +1,60 @@ +GIT_COMMIT := "" +GIT_DATE := "" + +CANNON_VERSION := "v0.0.0" +OP_PROGRAM_VERSION := "v0.0.0" + +GOOS := "" +GOARCH := "" + +# Build the cannon binary +cannon: + #!/bin/bash + # in devnet scenario, the cannon binary is already built. + [ -x /app/cannon/bin/cannon ] && exit 0 + cd ../cannon + make cannon \ + GOOS={{GOOS}} \ + GOARCH={{GOARCH}} \ + GITCOMMIT={{GIT_COMMIT}} \ + GITDATE={{GIT_DATE}} \ + VERSION={{CANNON_VERSION}} + +# Build the op-program-client elf binaries +op-program-client-mips: + #!/bin/bash + cd ../op-program + make op-program-client-mips \ + GOOS=linux \ + GOARCH=mips \ + GOMIPS=softfloat \ + GITCOMMIT={{GIT_COMMIT}} \ + GITDATE={{GIT_DATE}} \ + VERSION={{OP_PROGRAM_VERSION}} + +# Run the op-program-client elf binary directly through cannon's load-elf subcommand. +client TYPE CLIENT_SUFFIX PRESTATE_SUFFIX: cannon op-program-client-mips + #!/bin/bash + /app/cannon/bin/cannon load-elf \ + --type {{TYPE}} \ + --path /app/op-program/bin/op-program-client{{CLIENT_SUFFIX}}.elf \ + --out /app/op-program/bin/prestate{{PRESTATE_SUFFIX}}.bin.gz \ + --meta "/app/op-program/bin/meta{{PRESTATE_SUFFIX}}.json" + +# Generate the prestate proof containing the absolute pre-state hash. +prestate TYPE CLIENT_SUFFIX PRESTATE_SUFFIX: (client TYPE CLIENT_SUFFIX PRESTATE_SUFFIX) + #!/bin/bash + /app/cannon/bin/cannon run \ + --proof-at '=0' \ + --stop-at '=1' \ + --input /app/op-program/bin/prestate{{PRESTATE_SUFFIX}}.bin.gz \ + --meta "" \ + --proof-fmt '/app/op-program/bin/%d{{PRESTATE_SUFFIX}}.json' \ + --output "" + mv /app/op-program/bin/0{{PRESTATE_SUFFIX}}.json /app/op-program/bin/prestate-proof{{PRESTATE_SUFFIX}}.json + +build-default: (prestate "singlethreaded-2" "" "") +build-mt64: (prestate "multithreaded64-3" "64" "-mt64") +build-interop: (prestate "multithreaded64-3" "-interop" "-interop") + +build-all: build-default build-mt64 build-interop From 5f227cce1d2afc3a7f83a518e25815a3754a9c94 Mon Sep 17 00:00:00 2001 From: Sam Stokes <35908605+bitwiseguy@users.noreply.github.com> Date: Tue, 25 Feb 2025 11:48:33 -0500 Subject: [PATCH 004/130] op-service: make signer client compatible with SetCodeTx type (#14489) * op-service: make signer client compatible with SetCodeTx type * use hexutil.U256 instead of hexutil.Big * add explanatory comments * Fix authorizationList json tag Co-authored-by: protolambda * use helper fcn with panic for big.Int to uint256.Int * use uint256.MustFromBig instead of custom helper --------- Co-authored-by: protolambda --- op-service/signer/transaction_args.go | 93 ++++++++++++++------------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/op-service/signer/transaction_args.go b/op-service/signer/transaction_args.go index bd7fec62adf..2132a07f3b3 100644 --- a/op-service/signer/transaction_args.go +++ b/op-service/signer/transaction_args.go @@ -21,10 +21,10 @@ type TransactionArgs struct { From *common.Address `json:"from"` To *common.Address `json:"to"` Gas *hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice"` - MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"` - MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"` - Value *hexutil.Big `json:"value"` + GasPrice *hexutil.U256 `json:"gasPrice"` + MaxFeePerGas *hexutil.U256 `json:"maxFeePerGas"` + MaxPriorityFeePerGas *hexutil.U256 `json:"maxPriorityFeePerGas"` + Value *hexutil.U256 `json:"value"` Nonce *hexutil.Uint64 `json:"nonce"` // We accept "data" and "input" for backwards-compatibility reasons. @@ -34,34 +34,38 @@ type TransactionArgs struct { Input *hexutil.Bytes `json:"input"` AccessList *types.AccessList `json:"accessList,omitempty"` - ChainID *hexutil.Big `json:"chainId,omitempty"` + ChainID *hexutil.U256 `json:"chainId,omitempty"` // Custom extension for EIP-4844 support BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` - BlobFeeCap *hexutil.Big `json:"maxFeePerBlobGas,omitempty"` + BlobFeeCap *hexutil.U256 `json:"maxFeePerBlobGas,omitempty"` + + // Custom extension for EIP-7702 support + AuthList []types.SetCodeAuthorization `json:"authorizationList,omitempty"` } -// NewTransactionArgsFromTransaction creates a TransactionArgs struct from an EIP-1559 or EIP-4844 transaction +// NewTransactionArgsFromTransaction creates a TransactionArgs struct from an EIP-1559, EIP-4844, or EIP-7702 transaction func NewTransactionArgsFromTransaction(chainId *big.Int, from *common.Address, tx *types.Transaction) *TransactionArgs { data := hexutil.Bytes(tx.Data()) nonce := hexutil.Uint64(tx.Nonce()) gas := hexutil.Uint64(tx.Gas()) accesses := tx.AccessList() - args := &TransactionArgs{ + + return &TransactionArgs{ From: from, Input: &data, Nonce: &nonce, - Value: (*hexutil.Big)(tx.Value()), + Value: (*hexutil.U256)(uint256.MustFromBig(tx.Value())), Gas: &gas, To: tx.To(), - ChainID: (*hexutil.Big)(chainId), - MaxFeePerGas: (*hexutil.Big)(tx.GasFeeCap()), - MaxPriorityFeePerGas: (*hexutil.Big)(tx.GasTipCap()), + ChainID: (*hexutil.U256)(uint256.MustFromBig(chainId)), + MaxFeePerGas: (*hexutil.U256)(uint256.MustFromBig(tx.GasFeeCap())), + MaxPriorityFeePerGas: (*hexutil.U256)(uint256.MustFromBig(tx.GasTipCap())), AccessList: &accesses, BlobVersionedHashes: tx.BlobHashes(), - BlobFeeCap: (*hexutil.Big)(tx.BlobGasFeeCap()), + BlobFeeCap: (*hexutil.U256)(uint256.MustFromBig(tx.BlobGasFeeCap())), + AuthList: tx.SetCodeAuthorizations(), } - return args } // data retrieves the transaction calldata. Input field is preferred. @@ -86,7 +90,7 @@ func (args *TransactionArgs) Check() error { return errors.New("missing maxFeePerGas or maxPriorityFeePerGas") } // Both EIP-1559 fee parameters are now set; sanity check them. - if args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 { + if (*uint256.Int)(args.MaxFeePerGas).Cmp((*uint256.Int)(args.MaxPriorityFeePerGas)) < 0 { return fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas) } if args.Nonce == nil { @@ -102,7 +106,7 @@ func (args *TransactionArgs) Check() error { return errors.New("chain id not specified") } if args.Value == nil { - args.Value = new(hexutil.Big) + args.Value = new(hexutil.U256) } if args.AccessList == nil { args.AccessList = &types.AccessList{} @@ -119,6 +123,11 @@ func (args *TransactionArgs) Check() error { return errors.New("unexpected blob-fee-cap, transaction does not include blobs") } } + if args.AuthList != nil { + if len(args.AuthList) == 0 { + return errors.New("non-null auth list should not be empty") + } + } return nil } @@ -129,49 +138,43 @@ func (args *TransactionArgs) ToTransactionData() (types.TxData, error) { if args.AccessList != nil { al = *args.AccessList } - if len(args.BlobVersionedHashes) > 0 { - chainID, overflow := uint256.FromBig((*big.Int)(args.ChainID)) - if overflow { - return nil, fmt.Errorf("chainID %s too large for blob tx", args.ChainID) - } - maxFeePerGas, overflow := uint256.FromBig((*big.Int)(args.MaxFeePerGas)) - if overflow { - return nil, fmt.Errorf("maxFeePerGas %s too large for blob tx", args.MaxFeePerGas) - } - maxPriorityFeePerGas, overflow := uint256.FromBig((*big.Int)(args.MaxPriorityFeePerGas)) - if overflow { - return nil, fmt.Errorf("maxPriorityFeePerGas %s too large for blob tx", args.MaxPriorityFeePerGas) - } - value, overflow := uint256.FromBig((*big.Int)(args.Value)) - if overflow { - return nil, fmt.Errorf("value %s too large for blob tx", args.Value) - } - blobFeeCap, overflow := uint256.FromBig((*big.Int)(args.BlobFeeCap)) - if overflow { - return nil, fmt.Errorf("blobFeeCap %s too large for blob tx", args.BlobFeeCap) + + if args.AuthList != nil { + data = &types.SetCodeTx{ + ChainID: (*uint256.Int)(args.ChainID), + Nonce: uint64(*args.Nonce), + GasTipCap: (*uint256.Int)(args.MaxPriorityFeePerGas), + GasFeeCap: (*uint256.Int)(args.MaxFeePerGas), + Gas: uint64(*args.Gas), + To: *args.To, + Value: (*uint256.Int)(args.Value), + Data: args.data(), + AccessList: al, + AuthList: args.AuthList, } + } else if len(args.BlobVersionedHashes) > 0 { data = &types.BlobTx{ - ChainID: chainID, + ChainID: (*uint256.Int)(args.ChainID), Nonce: uint64(*args.Nonce), - GasTipCap: maxPriorityFeePerGas, - GasFeeCap: maxFeePerGas, + GasTipCap: (*uint256.Int)(args.MaxPriorityFeePerGas), + GasFeeCap: (*uint256.Int)(args.MaxFeePerGas), Gas: uint64(*args.Gas), To: *args.To, - Value: value, + Value: (*uint256.Int)(args.Value), Data: args.data(), AccessList: al, - BlobFeeCap: blobFeeCap, + BlobFeeCap: (*uint256.Int)(args.BlobFeeCap), BlobHashes: args.BlobVersionedHashes, } } else { data = &types.DynamicFeeTx{ - ChainID: (*big.Int)(args.ChainID), + ChainID: (*uint256.Int)(args.ChainID).ToBig(), Nonce: uint64(*args.Nonce), - GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas), - GasFeeCap: (*big.Int)(args.MaxFeePerGas), + GasTipCap: (*uint256.Int)(args.MaxPriorityFeePerGas).ToBig(), + GasFeeCap: (*uint256.Int)(args.MaxFeePerGas).ToBig(), Gas: uint64(*args.Gas), To: args.To, - Value: (*big.Int)(args.Value), + Value: (*uint256.Int)(args.Value).ToBig(), Data: args.data(), AccessList: al, } From d5286fa1648356ad37667d8c41cac82bd1318c33 Mon Sep 17 00:00:00 2001 From: smartcontracts Date: Tue, 25 Feb 2025 11:06:44 -0600 Subject: [PATCH 005/130] maint: clean up security reviews table, update codeowners (#14523) Cleans up the security reviews table because it had some errors and a few missing entries. Updates codeowners so that the security reviewers team can merge into the docs folder. --- .github/CODEOWNERS | 3 + ...M-EVom.pdf => 2024_12-DPM-RadiantLabs.pdf} | Bin docs/security-reviews/README.md | 53 +++++++++--------- 3 files changed, 31 insertions(+), 25 deletions(-) rename docs/security-reviews/{2024_12-DPM-EVom.pdf => 2024_12-DPM-RadiantLabs.pdf} (100%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 251586de866..337be514509 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -34,3 +34,6 @@ # Contracts /packages/contracts-bedrock @ethereum-optimism/contract-reviewers + +# Security docs +/docs @ethereum-optimism/evm-safety diff --git a/docs/security-reviews/2024_12-DPM-EVom.pdf b/docs/security-reviews/2024_12-DPM-RadiantLabs.pdf similarity index 100% rename from docs/security-reviews/2024_12-DPM-EVom.pdf rename to docs/security-reviews/2024_12-DPM-RadiantLabs.pdf diff --git a/docs/security-reviews/README.md b/docs/security-reviews/README.md index beba6420b15..6e5383b0429 100644 --- a/docs/security-reviews/README.md +++ b/docs/security-reviews/README.md @@ -5,30 +5,33 @@ The following is a list of past security reviews. Each review is focused on a different part of the codebase, and at a different point in time. Please see the report for the specific details. -| Date | Reviewer | Focus and Scope | Report Link | Commit | Subsequent Release | -|---------|-----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------| -------------------------------------------- |---------------------| -| 2020-10 | Trail of Bits | Rollup | [2020_10-TrailOfBits.pdf](./2020_10-Rollup-TrailOfBits.pdf) | | | -| 2020-11 | Dapphub | ECDSA Wallet | [2020_11-Dapphub-ECDSA_Wallet.pdf](./2020_11-Dapphub-ECDSA_Wallet.pdf) | | | -| 2021-03 | OpenZeppelin | OVM and Rollup | [2021_03-OVM_and_Rollup-OpenZeppelin.pdf](./2021_03-OVM_and_Rollup-OpenZeppelin.pdf) | | | -| 2021-03 | ConsenSys Diligence | Safety Checker | [2021_03-SafetyChecker-ConsenSysDiligence.pdf](./2021_03-SafetyChecker-ConsenSysDiligence.pdf) | | | -| 2022-05 | Zeppelin | Bedrock Contracts | [2022_05-Bedrock_Contracts-Zeppelin.pdf](./2022_05-Bedrock_Contracts-Zeppelin.pdf) | | | -| 2022-05 | Trail of Bits | OpNode | [2022_05-OpNode-TrailOfBits.pdf](./2022_05-OpNode-TrailOfBits.pdf) | | | -| 2022-08 | Sigma Prime | Bedrock GoLang | [2022_08-Bedrock_GoLang-SigmaPrime.pdf](./2022_08-Bedrock_GoLang-SigmaPrime.pdf) | | | -| 2022-09 | Zeppelin | Bedrock and Periphery: All contracts in `packages/contracts-bedrock/contracts` | [2022_09-Bedrock_and_Periphery-Zeppelin.pdf](./2022_09-Bedrock_and_Periphery-Zeppelin.pdf) | 93d3bd411a8ae75702539ac9c5fe00bad21d4104 | op-contracts/v1.0.0 | -| 2022-10 | Spearbit | Drippie: `Drippie.sol` | [2022_10-Drippie-Spearbit.pdf](./2022_10-Drippie-Spearbit.pdf) | 2a7be367634f147736f960eb2f38a77291cdfcad | op-contracts/v1.0.0 | -| 2022-11 | Trail of Bits | Invariant Testing: `OptimismPortal.sol` | [2022_11-Invariant_Testing-TrailOfBits.pdf](./2022_11-Invariant_Testing-TrailOfBits.pdf) | b31d35b67755479645dd150e7cc8c6710f0b4a56 | op-contracts/v1.0.0 | -| 2022-12 | Runtime Verification | Deposit Transaction: `OptimismPortal.sol` | [2022_12-DepositTransaction-RuntimeVerification.pdf](./2022_12-DepositTransaction-RuntimeVerification.pdf) | | op-contracts/v1.0.0 | -| 2023-01 | Trail of Bits | Bedrock Updates: `SystemConfig.sol` | [2023_01-Bedrock_Updates-TrailOfBits.pdf](./2023_01-Bedrock_Updates-TrailOfBits.pdf) | ee96ff8585699b054c95c6ff4a2411ee9fedcc87 | op-contracts/v1.0.0 | -| 2023-01 | Sherlock | Bedrock: All contracts in `packages/contracts-bedrock/src` | Sherlock Bedrock Contest ([site](https://audits.sherlock.xyz/contests/38), [repo](https://github.com/sherlock-audit/2023-01-optimism)) | 3f4b3c328153a8aa03611158b6984d624b17c1d9 | op-contracts/v1.0.0 | -| 2023-03 | Sherlock | Bedrock Fixes: All contracts in `packages/contracts-bedrock/src` | Sherlock Bedrock Contest: Fix Review ([site](https://audits.sherlock.xyz/contests/63), [repo](https://github.com/sherlock-audit/2023-03-optimism)) | 20229b9f78c6613c6ee53b93ca43c71bb74479f4b975 | op-contracts/v1.0.0 | -| 2023-12 | Trust | Superchain Config Upgrade: `SuperchainConfig.sol`, `L1CrossDomainMessenger.sol`, `L1ERC721Bridge.sol`, `L1StandardBridge.sol`, `OptimismPortal.sol`, `CrossDomainMessenger.sol`, `ERC721Bridge.sol`, `StandardBridge.sol` | [2023_12_SuperchainConfigUpgrade_Trust.pdf](./2023_12_SuperchainConfigUpgrade_Trust.pdf) | d1651bb22645ebd41ac4bb2ab4786f9a56fc1003 | op-contracts/v1.2.0 | -| 2024-02 | Runtime Verification | Pausability | [Kontrol Verification][kontrol] | | | -| 2024-02 | Cantina | MCP L1: `OptimismPortal.sol`, `L1CrossDomainMessenger.sol`, `L1StandardBridge.sol`, `L1ERC721Bridge.sol`, `OptimismMintableERC20Factory.sol`, `L2OutputOracle.sol`, `SystemConfig.sol` | [2024_02-MCP_L1-Cantina.pdf](./2024_02-MCP_L1-Cantina.pdf) | e6ef3a900c42c8722e72c2e2314027f85d12ced5 | op-contracts/v1.3.0 | -| 2024-03 | Sherlock | Fault Proofs | Sherlock Optimism Fault Proofs Contest ([site](https://audits.sherlock.xyz/contests/205), [repo](https://github.com/sherlock-audit/2024-02-optimism-2024)) | | | -| 2024-08 | Cantina | Fault proof MIPS: `MIPS.sol` | [./2024_08_Fault-Proofs-MIPS_Cantina.pdf](./2024_08_Fault-Proofs-MIPS_Cantina.pdf) | 71b93116738ee98c9f8713b1a5dfe626ce06c1b2 | op-contracts/v1.4.0 | -| 2024-08 | Spearbit | Fault proof no-MIPS: All contracts in the `packages/contracts-bedrock/src/dispute` directory | [./2024_08_Fault-Proofs-No-MIPS_Spearbit.pdf](./2024_08_Fault-Proofs-No-MIPS_Spearbit.pdf) | 1f7081798ce2d49b8643514663d10681cb853a3d | op-contracts/v1.6.0 | -| 2024-10 | 3Doc Security | Fault proof MIPS: `MIPS.sol` | [./2024_10-Cannon-FGETFD-3DocSecurity.md](./2024_10-Cannon-FGETFD-3DocSecurity.md) | 52d0e60c16498ad4efec8798e3fc1b36b13f46a2 | op-contracts/v1.8.0 | -| 2025-01 | Spearbit | 64-bit Multithreaded Cannon: `MIPS64.sol` | [2025_01-MT-Cannon-Spearbit.pdf](./2025_01-MT-Cannon-Spearbit.pdf) | cc2715c3d6ebef374451b598f48980ad817e0a0e | | -| 2025-01 | Coinbase Protocol Security | Multi-thread & 64-bit Cannon | [2025_01-MT-Cannon-Base.pdf](./2025_01-MT-Cannon-Base.pdf) | b8c011f18c79d735e01168345fc1c6f02fac584f | | +| Date | Reviewer | Focus and Scope | Report Link | Commit | Subsequent Release | +|---------|-----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------| ---------------------------------------- |---------------------| +| 2020-10 | Trail of Bits | Rollup | [2020_10-TrailOfBits.pdf](./2020_10-Rollup-TrailOfBits.pdf) | | | +| 2020-11 | Dapphub | ECDSA Wallet | [2020_11-Dapphub-ECDSA_Wallet.pdf](./2020_11-Dapphub-ECDSA_Wallet.pdf) | | | +| 2021-03 | OpenZeppelin | OVM and Rollup | [2021_03-OVM_and_Rollup-OpenZeppelin.pdf](./2021_03-OVM_and_Rollup-OpenZeppelin.pdf) | | | +| 2021-03 | ConsenSys Diligence | Safety Checker | [2021_03-SafetyChecker-ConsenSysDiligence.pdf](./2021_03-SafetyChecker-ConsenSysDiligence.pdf) | | | +| 2022-05 | Zeppelin | Bedrock Contracts | [2022_05-Bedrock_Contracts-Zeppelin.pdf](./2022_05-Bedrock_Contracts-Zeppelin.pdf) | | | +| 2022-05 | Trail of Bits | OpNode | [2022_05-OpNode-TrailOfBits.pdf](./2022_05-OpNode-TrailOfBits.pdf) | | | +| 2022-08 | Sigma Prime | Bedrock GoLang | [2022_08-Bedrock_GoLang-SigmaPrime.pdf](./2022_08-Bedrock_GoLang-SigmaPrime.pdf) | | | +| 2022-09 | Zeppelin | Bedrock and Periphery: All contracts in `packages/contracts-bedrock/contracts` | [2022_09-Bedrock_and_Periphery-Zeppelin.pdf](./2022_09-Bedrock_and_Periphery-Zeppelin.pdf) | 93d3bd411a8ae75702539ac9c5fe00bad21d4104 | op-contracts/v1.0.0 | +| 2022-10 | Spearbit | Drippie: `Drippie.sol` | [2022_10-Drippie-Spearbit.pdf](./2022_10-Drippie-Spearbit.pdf) | 2a7be367634f147736f960eb2f38a77291cdfcad | op-contracts/v1.0.0 | +| 2022-11 | Trail of Bits | Invariant Testing: `OptimismPortal.sol` | [2022_11-Invariant_Testing-TrailOfBits.pdf](./2022_11-Invariant_Testing-TrailOfBits.pdf) | b31d35b67755479645dd150e7cc8c6710f0b4a56 | op-contracts/v1.0.0 | +| 2022-12 | Runtime Verification | Deposit Transaction: `OptimismPortal.sol` | [2022_12-DepositTransaction-RuntimeVerification.pdf](./2022_12-DepositTransaction-RuntimeVerification.pdf) | | op-contracts/v1.0.0 | +| 2023-01 | Trail of Bits | Bedrock Updates: `SystemConfig.sol` | [2023_01-Bedrock_Updates-TrailOfBits.pdf](./2023_01-Bedrock_Updates-TrailOfBits.pdf) | ee96ff8585699b054c95c6ff4a2411ee9fedcc87 | op-contracts/v1.0.0 | +| 2023-01 | Sherlock | Bedrock: All contracts in `packages/contracts-bedrock/src` | Sherlock Bedrock Contest ([site](https://audits.sherlock.xyz/contests/38), [repo](https://github.com/sherlock-audit/2023-01-optimism)) | 3f4b3c328153a8aa03611158b6984d624b17c1d9 | op-contracts/v1.0.0 | +| 2023-03 | Sherlock | Bedrock Fixes: All contracts in `packages/contracts-bedrock/src` | Sherlock Bedrock Contest: Fix Review ([site](https://audits.sherlock.xyz/contests/63), [repo](https://github.com/sherlock-audit/2023-03-optimism)) | 9b9f78c6613c6ee53b93ca43c71bb74479f4b975 | op-contracts/v1.0.0 | +| 2023-12 | Trust | Superchain Config Upgrade: `SuperchainConfig.sol`, `L1CrossDomainMessenger.sol`, `L1ERC721Bridge.sol`, `L1StandardBridge.sol`, `OptimismPortal.sol`, `CrossDomainMessenger.sol`, `ERC721Bridge.sol`, `StandardBridge.sol` | [2023_12_SuperchainConfigUpgrade_Trust.pdf](./2023_12_SuperchainConfigUpgrade_Trust.pdf) | d1651bb22645ebd41ac4bb2ab4786f9a56fc1003 | op-contracts/v1.2.0 | +| 2024-02 | Runtime Verification | Pausability | [Kontrol Verification][kontrol] | | | +| 2024-02 | Cantina | MCP L1: `OptimismPortal.sol`, `L1CrossDomainMessenger.sol`, `L1StandardBridge.sol`, `L1ERC721Bridge.sol`, `OptimismMintableERC20Factory.sol`, `L2OutputOracle.sol`, `SystemConfig.sol` | [2024_02-MCP_L1-Cantina.pdf](./2024_02-MCP_L1-Cantina.pdf) | e6ef3a900c42c8722e72c2e2314027f85d12ced5 | op-contracts/v1.3.0 | +| 2024-03 | Sherlock | Fault Proofs | Sherlock Optimism Fault Proofs Contest ([site](https://audits.sherlock.xyz/contests/205), [repo](https://github.com/sherlock-audit/2024-02-optimism-2024)) | | | +| 2024-08 | Cantina | Fault proof MIPS: `MIPS.sol` | [2024_08_Fault-Proofs-MIPS_Cantina.pdf](./2024_08_Fault-Proofs-MIPS_Cantina.pdf) | 71b93116738ee98c9f8713b1a5dfe626ce06c1b2 | op-contracts/v1.4.0 | +| 2024-08 | Spearbit | Fault proof no-MIPS: All contracts in the `packages/contracts-bedrock/src/dispute` directory | [2024_08_Fault-Proofs-No-MIPS_Spearbit.pdf](./2024_08_Fault-Proofs-No-MIPS_Spearbit.pdf) | 1f7081798ce2d49b8643514663d10681cb853a3d | op-contracts/v1.6.0 | +| 2024-10 | 3Doc Security | Fault proof MIPS: `MIPS.sol` | [2024_10-Cannon-FGETFD-3DocSecurity.md](./2024_10-Cannon-FGETFD-3DocSecurity.md) | 52d0e60c16498ad4efec8798e3fc1b36b13f46a2 | op-contracts/v1.8.0 | +| 2024-12 | MiloTruck (independent) | DeputyPauseModule | [2024_12-DPM-MiloTruck.pdf](./2024_12-DPM-MiloTruck.pdf) | 2f17e6b67c61de5d8073d556272796d201bc740b | | +| 2024-12 | Radiant Labs | DeputyPauseModule | [2024_12-DPM-RadiantLabs.pdf](./2024_12-DPM-RadiantLabs.pdf) | 2f17e6b67c61de5d8073d556272796d201bc740b | | +| 2025-01 | Offbeat Labs | Incident Response Improvements | [2025_01-IRI-OffbeatLabs.pdf](./2025_01-IRI-OffbeatLabs.pdf) | 984bae9146398a2997ec13757bfe2438ca8f92eb | | +| 2025-01 | Spearbit | 64-bit Multithreaded Cannon: `MIPS64.sol` | [2025_01-MT-Cannon-Spearbit.pdf](./2025_01-MT-Cannon-Spearbit.pdf) | cc2715c3d6ebef374451b598f48980ad817e0a0e | | +| 2025-01 | Coinbase Protocol Security | Multi-thread & 64-bit Cannon | [2025_01-MT-Cannon-Base.pdf](./2025_01-MT-Cannon-Base.pdf) | b8c011f18c79d735e01168345fc1c6f02fac584f | | [kontrol]: https://github.com/ethereum-optimism/optimism/blob/876e16ad04968f0bb641eb76f98eb77e7e1a3e16/packages/contracts-bedrock/test/kontrol/README.md From ee82433581144122009478bb207aebfc4dc536a7 Mon Sep 17 00:00:00 2001 From: Paul Dowman Date: Tue, 25 Feb 2025 11:43:08 -0700 Subject: [PATCH 006/130] chore: Update drippie config files with actual config from runbook (#14451) --- ...olia-ops.json => sepolia-maintenance.json} | 44 ++++++++-------- .../drippie/sepolia-test.json | 50 ------------------- 2 files changed, 22 insertions(+), 72 deletions(-) rename packages/contracts-bedrock/deploy-config-periphery/drippie/{sepolia-ops.json => sepolia-maintenance.json} (67%) delete mode 100644 packages/contracts-bedrock/deploy-config-periphery/drippie/sepolia-test.json diff --git a/packages/contracts-bedrock/deploy-config-periphery/drippie/sepolia-ops.json b/packages/contracts-bedrock/deploy-config-periphery/drippie/sepolia-maintenance.json similarity index 67% rename from packages/contracts-bedrock/deploy-config-periphery/drippie/sepolia-ops.json rename to packages/contracts-bedrock/deploy-config-periphery/drippie/sepolia-maintenance.json index 2a4bce08d26..009a2c4a238 100644 --- a/packages/contracts-bedrock/deploy-config-periphery/drippie/sepolia-ops.json +++ b/packages/contracts-bedrock/deploy-config-periphery/drippie/sepolia-maintenance.json @@ -1,5 +1,6 @@ { - "drippie": "0xa0fF2a54AdC3fB33c44a141E67d194CF249258cb", + "__comment": "Main contract addresses", + "drippie": "0x41d918d94fDF4A354AB515ea646ce73b36A17c7b", "__comment": "Addresses of dripcheck contracts to be used in drips", "dripchecks": [ @@ -16,47 +17,46 @@ "01__address": "0xcBCb3896Ddec35d91901768733C5d3738e10509F" } ], - "__comment": "Prefix is used to namespace drips so that drip management can be handled modularly", - "prefix": "operations", + "prefix": "maintenance", "__comment": "Object attributes below are prefixed with numbers because of how foundry parses JSON into structs in alphabetical order", "drips": [ { - "00__name": "sequencer_v4", + "00__name": "proposer_v1", "01__dripcheck": "CheckBalanceLow", "02__checkparams": { - "01__target": "0x8F23BB38F531600e5d8FDDaAEC41F13FaB46E98c", - "02__threshold": 1000000000000000000000 + "01__target": "0x49277EE36A024120Ee218127354c4a3591dc90A9", + "02__threshold": 20000000000000000000 }, - "03__recipient": "0x8F23BB38F531600e5d8FDDaAEC41F13FaB46E98c", - "04__value": 100000000000000000000, - "05__interval": 3600, + "03__recipient": "0x49277EE36A024120Ee218127354c4a3591dc90A9", + "04__value": 10000000000000000000, + "05__interval": 86400, "06__data": "" }, { - "00__name": "proposer_v3", + "00__name": "challenger_v1", "01__dripcheck": "CheckBalanceLow", "02__checkparams": { - "01__target": "0x49277EE36A024120Ee218127354c4a3591dc90A9", - "02__threshold": 100000000000000000000 + "01__target": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301", + "02__threshold": 50000000000000000000 }, - "03__recipient": "0x49277EE36A024120Ee218127354c4a3591dc90A9", - "04__value": 20000000000000000000, + "03__recipient": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301", + "04__value": 10000000000000000000, "05__interval": 86400, "06__data": "" }, { - "00__name": "challenger_v2", + "00__name": "batcher_v1", "01__dripcheck": "CheckBalanceLow", "02__checkparams": { - "01__target": "0xffb026F67DA0869EB3ABB090cB7F015CE0925CdF", - "02__threshold": 1000000000000000000000 + "01__target": "0x8F23BB38F531600e5d8FDDaAEC41F13FaB46E98c", + "02__threshold": 100000000000000000000 }, - "03__recipient": "0x49277EE36A024120Ee218127354c4a3591dc90A9", - "04__value": 100000000000000000000, - "05__interval": 3600, + "03__recipient": "0x8F23BB38F531600e5d8FDDaAEC41F13FaB46E98c", + "04__value": 10000000000000000000, + "05__interval": 86400, "06__data": "" - } - ] + }, + ], } diff --git a/packages/contracts-bedrock/deploy-config-periphery/drippie/sepolia-test.json b/packages/contracts-bedrock/deploy-config-periphery/drippie/sepolia-test.json deleted file mode 100644 index 7cdb874ef6d..00000000000 --- a/packages/contracts-bedrock/deploy-config-periphery/drippie/sepolia-test.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "drippie": "0x7ECe0FdeA734B9E77dDd04362c312562924857F6", - - "__comment": "Addresses of dripcheck contracts to be used in drips", - "dripchecks": [ - { - "00__name": "CheckBalanceLow", - "01__address": "0xaF8C77CfeB57620c4D9dCC81df75a1F0Da7064Af" - }, - { - "00__name": "CheckSecrets", - "01__address": "0x32c1e36E733913388076D7c3055300072814bF2A" - }, - { - "00__name": "CheckTrue", - "01__address": "0xcBCb3896Ddec35d91901768733C5d3738e10509F" - } - ], - - "__comment": "Prefix is used to namespace drips so that drip management can be handled modularly", - "prefix": "operations", - - "__comment": "Object attributes below are prefixed with numbers because of how foundry parses JSON into structs in alphabetical order", - "drips": [ - { - "00__name": "test_v2", - "01__dripcheck": "CheckBalanceLow", - "02__checkparams": { - "01__target": "0x68108902De3A5031197a6eB3b74b3b033e8E8e4d", - "02__threshold": 10000000000000000000 - }, - "03__recipient": "0x68108902De3A5031197a6eB3b74b3b033e8E8e4d", - "04__value": 10000000000000000, - "05__interval": 1, - "06__data": "" - }, - { - "00__name": "test_2_v1", - "01__dripcheck": "CheckBalanceLow", - "02__checkparams": { - "01__target": "0x68108902De3A5031197a6eB3b74b3b033e8E8e4d", - "02__threshold": 10000000000000000000 - }, - "03__recipient": "0x68108902De3A5031197a6eB3b74b3b033e8E8e4d", - "04__value": 1000000000000000, - "05__interval": 20, - "06__data": "" - } - ] -} From 18d5011154eda6f1865bef3744c8a8c186c05ec5 Mon Sep 17 00:00:00 2001 From: Eric Tu <6364934+ec2@users.noreply.github.com> Date: Tue, 25 Feb 2025 14:14:58 -0500 Subject: [PATCH 007/130] Avoid lookup twice (#14354) --- cannon/mipsevm/memory/memory.go | 42 +++++++++++---------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/cannon/mipsevm/memory/memory.go b/cannon/mipsevm/memory/memory.go index 1234af4dc4b..375aac49920 100644 --- a/cannon/mipsevm/memory/memory.go +++ b/cannon/mipsevm/memory/memory.go @@ -81,33 +81,6 @@ func (m *Memory) ForEachPage(fn func(pageIndex Word, page *Page) error) error { return nil } -func (m *Memory) invalidate(addr Word) { - // addr must be aligned - if addr&arch.ExtMask != 0 { - panic(fmt.Errorf("unaligned memory access: %x", addr)) - } - - // find page, and invalidate addr within it - if p, ok := m.pageLookup(addr >> PageAddrSize); ok { - prevValid := p.Ok[1] - p.invalidate(addr & PageAddrMask) - if !prevValid { // if the page was already invalid before, then nodes to mem-root will also still be. - return - } - } else { // no page? nothing to invalidate - return - } - - // find the gindex of the first page covering the address: i.e. ((1 << WordSize) | addr) >> PageAddrSize - // Avoid 64-bit overflow by distributing the right shift across the OR. - gindex := (uint64(1) << (WordSize - PageAddrSize)) | uint64(addr>>PageAddrSize) - - for gindex > 0 { - m.nodes[gindex] = nil - gindex >>= 1 - } -} - func (m *Memory) MerkleizeSubtree(gindex uint64) [32]byte { l := uint64(bits.Len64(gindex)) if l > MemProofLeafCount { @@ -207,7 +180,20 @@ func (m *Memory) SetWord(addr Word, v Word) { // Go may mmap relatively large ranges, but we only allocate the pages just in time. p = m.AllocPage(pageIndex) } else { - m.invalidate(addr) // invalidate this branch of memory, now that the value changed + prevValid := p.Ok[1] + p.invalidate(pageAddr) + if prevValid { // if the page was already invalid before, then nodes to mem-root will also still be. + + // find the gindex of the first page covering the address: i.e. ((1 << WordSize) | addr) >> PageAddrSize + // Avoid 64-bit overflow by distributing the right shift across the OR. + gindex := (uint64(1) << (WordSize - PageAddrSize)) | uint64(addr>>PageAddrSize) + + for gindex > 0 { + m.nodes[gindex] = nil + gindex >>= 1 + } + + } } arch.ByteOrderWord.PutWord(p.Data[pageAddr:pageAddr+arch.WordSizeBytes], v) } From c267dd519ca45a6f9c07bb5ea1805b83eea49f98 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Tue, 25 Feb 2025 14:48:31 -0500 Subject: [PATCH 008/130] vm-runner: Add metrics to track vm panics (#14504) * Treat non-zero vm exits as a special case * Only handle panics as a special case * Add more tests * Record panic metrics * Tweak error message --- op-challenger/game/fault/trace/vm/executor.go | 13 +- .../game/fault/trace/vm/executor_test.go | 173 +++++++++++++----- op-challenger/runner/metrics.go | 11 ++ op-challenger/runner/runner.go | 5 + 4 files changed, 156 insertions(+), 46 deletions(-) diff --git a/op-challenger/game/fault/trace/vm/executor.go b/op-challenger/game/fault/trace/vm/executor.go index 1e8c9682105..cef8bbc429c 100644 --- a/op-challenger/game/fault/trace/vm/executor.go +++ b/op-challenger/game/fault/trace/vm/executor.go @@ -6,18 +6,19 @@ import ( "fmt" "math" "os" + "os/exec" "path/filepath" "strconv" "strings" "time" - "github.com/ethereum-optimism/optimism/op-node/chaincfg" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/metrics" + "github.com/ethereum-optimism/optimism/op-node/chaincfg" "github.com/ethereum-optimism/optimism/op-service/jsonutil" ) @@ -32,6 +33,8 @@ var ( ErrMissingRollupConfig = errors.New("missing network or rollup config path") ErrMissingL2Genesis = errors.New("missing network or l2 genesis path") ErrNetworkUnknown = errors.New("unknown network") + + ErrVMPanic = errors.New("vm exited with exit code 2 (panic)") ) type Metricer = metrics.TypedVmMetricer @@ -178,6 +181,14 @@ func (e *Executor) DoGenerateProof(ctx context.Context, dir string, begin uint64 e.logger.Info("Generating trace", "proof", end, "cmd", e.cfg.VmBin, "args", strings.Join(args, ", ")) execStart := time.Now() err = e.cmdExecutor(ctx, e.logger.New("proof", end), e.cfg.VmBin, args...) + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + e.logger.Error("VM command exited with non-zero exit code", "exit_code", exitErr.ExitCode()) + if exitErr.ExitCode() == 2 { + // Handle panics specially + err = ErrVMPanic + } + } execTime := time.Since(execStart) memoryUsed := "unknown" e.metrics.RecordExecutionTime(execTime) diff --git a/op-challenger/game/fault/trace/vm/executor_test.go b/op-challenger/game/fault/trace/vm/executor_test.go index 83137ace303..6deccea9813 100644 --- a/op-challenger/game/fault/trace/vm/executor_test.go +++ b/op-challenger/game/fault/trace/vm/executor_test.go @@ -2,9 +2,12 @@ package vm import ( "context" + "fmt" "math" "math/big" + "os/exec" "path/filepath" + "strings" "testing" "time" @@ -21,7 +24,6 @@ import ( ) func TestGenerateProof(t *testing.T) { - input := "starting.json" tempDir := t.TempDir() dir := filepath.Join(tempDir, "gameDir") cfg := Config{ @@ -35,7 +37,6 @@ func TestGenerateProof(t *testing.T) { SnapshotFreq: 500, InfoFreq: 900, } - prestate := "pre.json" inputs := utils.LocalGameInputs{ L1Head: common.Hash{0x11}, @@ -56,42 +57,10 @@ func TestGenerateProof(t *testing.T) { IdleStepCountThread0: 1314, } - captureExec := func(t *testing.T, cfg Config, proofAt uint64, m Metricer) (string, string, map[string]string) { - executor := NewExecutor(testlog.Logger(t, log.LevelInfo), m, cfg, &noArgServerExecutor{}, prestate, inputs) - executor.selectSnapshot = func(logger log.Logger, dir string, absolutePreState string, i uint64, binary bool) (string, error) { - return input, nil - } - var binary string - var subcommand string - args := make(map[string]string) - executor.cmdExecutor = func(ctx context.Context, l log.Logger, b string, a ...string) error { - binary = b - subcommand = a[0] - for i := 1; i < len(a); { - if a[i] == "--" { - // Skip over the divider between vm and server program - i += 1 - continue - } - args[a[i]] = a[i+1] - i += 2 - } - - // Write debuginfo file - debugPath := args["--debug-info"] - err := jsonutil.WriteJSON(info, ioutil.ToStdOutOrFileOrNoop(debugPath, 0o755)) - require.NoError(t, err) - return nil - } - err := executor.GenerateProof(context.Background(), dir, proofAt) - require.NoError(t, err) - return binary, subcommand, args - } - t.Run("NoStopAtWhenProofIsMaxUInt", func(t *testing.T) { m := newMetrics() cfg.DebugInfo = true - _, _, args := captureExec(t, cfg, math.MaxUint64, m) + _, _, args := captureExec(t, dir, cfg, inputs, info, math.MaxUint64, m) // stop-at would need to be one more than the proof step which would overflow back to 0 // so expect that it will be omitted. We'll ultimately want asterisc to execute until the program exits. require.NotContains(t, args, "--stop-at") @@ -101,7 +70,7 @@ func TestGenerateProof(t *testing.T) { t.Run("BinarySnapshots", func(t *testing.T) { m := newMetrics() cfg.BinarySnapshots = true - _, _, args := captureExec(t, cfg, 100, m) + _, _, args := captureExec(t, dir, cfg, inputs, info, 100, m) require.Equal(t, filepath.Join(dir, SnapsDir, "%d.bin.gz"), args["--snapshot-fmt"]) validateMetrics(t, m, info, cfg) }) @@ -109,10 +78,120 @@ func TestGenerateProof(t *testing.T) { t.Run("JsonSnapshots", func(t *testing.T) { m := newMetrics() cfg.BinarySnapshots = false - _, _, args := captureExec(t, cfg, 100, m) + _, _, args := captureExec(t, dir, cfg, inputs, info, 100, m) require.Equal(t, filepath.Join(dir, SnapsDir, "%d.json.gz"), args["--snapshot-fmt"]) validateMetrics(t, m, info, cfg) }) + + t.Run("ExecPanics", func(t *testing.T) { + m := newMetrics() + cfg.DebugInfo = true + cfg.BinarySnapshots = false + err, _ := customExec(t, panickingExecCmd(), dir, cfg, inputs, info, 100, m) + require.Equal(t, ErrVMPanic, err) + requireEmptyMetrics(t, m) + }) + + t.Run("ExecExitCode1", func(t *testing.T) { + m := newMetrics() + cfg.DebugInfo = true + cfg.BinarySnapshots = false + err, _ := customExec(t, failingExecCmd(1), dir, cfg, inputs, info, 100, m) + require.NotNil(t, err) + require.NotEqual(t, ErrVMPanic, err) + requireEmptyMetrics(t, m) + }) + + t.Run("ExecExitCode2", func(t *testing.T) { + m := newMetrics() + cfg.DebugInfo = true + cfg.BinarySnapshots = false + err, _ := customExec(t, failingExecCmd(2), dir, cfg, inputs, info, 100, m) + require.Equal(t, ErrVMPanic, err) + requireEmptyMetrics(t, m) + }) +} + +func captureExec(t *testing.T, dir string, cfg Config, inputs utils.LocalGameInputs, info *mipsevm.DebugInfo, proofAt uint64, m Metricer) (string, string, map[string]string) { + input := "starting.json" + prestate := "pre.json" + executor := NewExecutor(testlog.Logger(t, log.LevelInfo), m, cfg, &noArgServerExecutor{}, prestate, inputs) + executor.selectSnapshot = func(logger log.Logger, dir string, absolutePreState string, i uint64, binary bool) (string, error) { + return input, nil + } + var binary string + var subcommand string + var args map[string]string + executor.cmdExecutor = func(ctx context.Context, l log.Logger, b string, a ...string) error { + binary = b + subcommand = a[0] + args = copyArgs(a) + + // Write debuginfo file + debugPath := args["--debug-info"] + err := jsonutil.WriteJSON(info, ioutil.ToStdOutOrFileOrNoop(debugPath, 0o755)) + require.NoError(t, err) + return nil + } + err := executor.GenerateProof(context.Background(), dir, proofAt) + require.NoError(t, err) + return binary, subcommand, args +} + +func failingExecCmd(exitCode int) *exec.Cmd { + exitCmd := fmt.Sprintf("exit %d", exitCode) + return exec.Command("sh", "-c", exitCmd) +} + +func panickingExecCmd() *exec.Cmd { + cmd := exec.Command("go", "run", "-e", "-") + cmd.Stdin = strings.NewReader(` + package main + func main() { + panic("simulated panic") + } + `) + return cmd +} + +func customExec(t *testing.T, cmd *exec.Cmd, dir string, cfg Config, inputs utils.LocalGameInputs, info *mipsevm.DebugInfo, proofAt uint64, m Metricer) (error, map[string]string) { + input := "starting.json" + prestate := "pre.json" + executor := NewExecutor(testlog.Logger(t, log.LevelInfo), m, cfg, &noArgServerExecutor{}, prestate, inputs) + executor.selectSnapshot = func(logger log.Logger, dir string, absolutePreState string, i uint64, binary bool) (string, error) { + return input, nil + } + + var args map[string]string + executor.cmdExecutor = func(ctx context.Context, l log.Logger, b string, a ...string) error { + args = copyArgs(a) + + // Write debuginfo file + debugPath := args["--debug-info"] + err := jsonutil.WriteJSON(info, ioutil.ToStdOutOrFileOrNoop(debugPath, 0o755)) + require.NoError(t, err) + + cmdError := cmd.Run() + + return cmdError + } + err := executor.GenerateProof(context.Background(), dir, proofAt) + + return err, args +} + +func copyArgs(a []string) map[string]string { + args := make(map[string]string) + for i := 1; i < len(a); { + if a[i] == "--" { + // Skip over the divider between vm and server program + i += 1 + continue + } + args[a[i]] = a[i+1] + i += 2 + } + return args } func validateMetrics(t require.TestingT, m *capturingVmMetrics, expected *mipsevm.DebugInfo, cfg Config) { @@ -130,17 +209,21 @@ func validateMetrics(t require.TestingT, m *capturingVmMetrics, expected *mipsev require.Equal(t, expected.IdleStepCountThread0, m.idleStepsThread0) } else { // If debugInfo is disabled, json file should not be written and metrics should be zeroed out - require.Equal(t, hexutil.Uint64(0), m.memoryUsed) - require.Equal(t, uint64(0), m.steps) - require.Equal(t, uint64(0), m.rmwSuccessCount) - require.Equal(t, uint64(0), m.rmwFailCount) - require.Equal(t, uint64(0), m.maxStepsBetweenLLAndSC) - require.Equal(t, uint64(0), m.reservationInvalidations) - require.Equal(t, uint64(0), m.forcedPreemptions) - require.Equal(t, uint64(0), m.idleStepsThread0) + requireEmptyMetrics(t, m) } } +func requireEmptyMetrics(t require.TestingT, m *capturingVmMetrics) { + require.Equal(t, hexutil.Uint64(0), m.memoryUsed) + require.Equal(t, uint64(0), m.steps) + require.Equal(t, uint64(0), m.rmwSuccessCount) + require.Equal(t, uint64(0), m.rmwFailCount) + require.Equal(t, uint64(0), m.maxStepsBetweenLLAndSC) + require.Equal(t, uint64(0), m.reservationInvalidations) + require.Equal(t, uint64(0), m.forcedPreemptions) + require.Equal(t, uint64(0), m.idleStepsThread0) +} + func newMetrics() *capturingVmMetrics { return &capturingVmMetrics{} } diff --git a/op-challenger/runner/metrics.go b/op-challenger/runner/metrics.go index 7bf3319fe6a..5977727caec 100644 --- a/op-challenger/runner/metrics.go +++ b/op-challenger/runner/metrics.go @@ -24,6 +24,7 @@ type Metrics struct { vmLastMemoryUsed *prometheus.GaugeVec successTotal *prometheus.CounterVec failuresTotal *prometheus.CounterVec + panicsTotal *prometheus.CounterVec invalidTotal *prometheus.CounterVec } @@ -69,6 +70,11 @@ func NewMetrics(runConfigs []RunConfig) *Metrics { Name: "failures_total", Help: "Number of failures to execute a VM", }, []string{"type"}), + panicsTotal: factory.NewCounterVec(prometheus.CounterOpts{ + Namespace: Namespace, + Name: "panics_total", + Help: "Number of times the VM panicked", + }, []string{"type"}), invalidTotal: factory.NewCounterVec(prometheus.CounterOpts{ Namespace: Namespace, Name: "invalid_total", @@ -79,6 +85,7 @@ func NewMetrics(runConfigs []RunConfig) *Metrics { for _, runConfig := range runConfigs { metrics.successTotal.WithLabelValues(runConfig.Name).Add(0) metrics.failuresTotal.WithLabelValues(runConfig.Name).Add(0) + metrics.panicsTotal.WithLabelValues(runConfig.Name).Add(0) metrics.invalidTotal.WithLabelValues(runConfig.Name).Add(0) metrics.RecordUp() } @@ -113,6 +120,10 @@ func (m *Metrics) RecordFailure(vmType string) { m.failuresTotal.WithLabelValues(vmType).Inc() } +func (m *Metrics) RecordPanic(vmType string) { + m.panicsTotal.WithLabelValues(vmType).Inc() +} + func (m *Metrics) RecordInvalid(vmType string) { m.invalidTotal.WithLabelValues(vmType).Inc() } diff --git a/op-challenger/runner/runner.go b/op-challenger/runner/runner.go index b830c046e12..8ec7e95fb34 100644 --- a/op-challenger/runner/runner.go +++ b/op-challenger/runner/runner.go @@ -20,6 +20,7 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger/config" contractMetrics "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" + trace "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/metrics" "github.com/ethereum-optimism/optimism/op-service/cliapp" @@ -39,6 +40,7 @@ type Metricer interface { metrics.VmMetricer RecordFailure(vmType string) + RecordPanic(vmType string) RecordInvalid(vmType string) RecordSuccess(vmType string) } @@ -122,6 +124,9 @@ func (r *Runner) runAndRecordOnce(ctx context.Context, runConfig RunConfig, clie if errors.Is(err, ErrUnexpectedStatusCode) { log.Error("Incorrect status code", "type", runConfig.Name, "err", err) m.RecordInvalid(traceType) + } else if errors.Is(err, trace.ErrVMPanic) { + log.Error("VM panicked", "type", runConfig.Name) + m.RecordPanic(traceType) } else if err != nil { log.Error("Failed to run", "type", runConfig.Name, "err", err) m.RecordFailure(traceType) From 2e7568802dfc8c0f5bbab10fda874d8fe88fe92b Mon Sep 17 00:00:00 2001 From: NaijaCoderGirl <150683513+NaijaCoderGirl@users.noreply.github.com> Date: Tue, 25 Feb 2025 20:09:00 +0000 Subject: [PATCH 009/130] update links in directory structure after PRs #12968 & #12828 (#14525) --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 184b4ae984c..e89b1078562 100644 --- a/README.md +++ b/README.md @@ -74,12 +74,11 @@ The Optimism Immunefi program offers up to $2,000,042 for in-scope critical vuln ├── op-program: Fault proof program ├── op-proposer: L2-Output Submitter, submits proposals to L1 ├── op-service: Common codebase utilities -├── op-ufm: Simulations for monitoring end-to-end transaction latency ├── op-wheel: Database utilities ├── ops: Various operational packages ├── packages │ ├── contracts-bedrock: OP Stack smart contracts -├── semgrep: Semgrep rules and tests +├── semgrep: Semgrep rules and tests ## Development and Release Process From 8d0dd96e494b2ba154587877351e87788336a4ec Mon Sep 17 00:00:00 2001 From: smartcontracts Date: Tue, 25 Feb 2025 14:13:01 -0600 Subject: [PATCH 010/130] feat: do not alias 7702 addresses (#14506) Updates the OptimismPortal to not alias 7702 addresses. Updates several other contracts to use a standard library for checking if the msg sender is an EOA. --- .../scripts/checks/check-frozen-files.sh | 8 +- .../snapshots/semver-lock.json | 32 +++---- .../src/L1/L1ERC721Bridge.sol | 4 +- .../src/L1/L1StandardBridge.sol | 4 +- .../src/L1/OptimismPortal2.sol | 7 +- .../src/L1/OptimismPortalInterop.sol | 4 +- .../src/L2/L2ERC721Bridge.sol | 4 +- .../src/L2/L2StandardBridge.sol | 4 +- .../src/L2/L2StandardBridgeInterop.sol | 4 +- .../src/cannon/PreimageOracle.sol | 14 ++-- .../contracts-bedrock/src/libraries/EOA.sol | 22 +++++ .../src/universal/ERC721Bridge.sol | 4 +- .../src/universal/StandardBridge.sol | 4 +- .../test/L1/L1ERC721Bridge.t.sol | 44 ++++++++-- .../test/L1/L1StandardBridge.t.sol | 17 +++- .../test/L1/OptimismPortal2.t.sol | 51 +++++++++++ .../test/L2/L2ERC721Bridge.t.sol | 12 +-- .../test/libraries/EOA.t.sol | 84 +++++++++++++++++++ 18 files changed, 264 insertions(+), 59 deletions(-) create mode 100644 packages/contracts-bedrock/src/libraries/EOA.sol create mode 100644 packages/contracts-bedrock/test/libraries/EOA.t.sol diff --git a/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh b/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh index c67fafaa1f4..b38ba40165d 100755 --- a/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh +++ b/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh @@ -50,12 +50,12 @@ changed_contracts=$(jq -r ' ALLOWED_FILES=( "src/L1/DataAvailabilityChallenge.sol" # "src/L1/L1CrossDomainMessenger.sol" - # "src/L1/L1ERC721Bridge.sol" - # "src/L1/L1StandardBridge.sol" + "src/L1/L1ERC721Bridge.sol" + "src/L1/L1StandardBridge.sol" # "src/L1/OPContractsManager.sol" # "src/L1/OPContractsManagerInterop.sol" - # "src/L1/OptimismPortal2.sol" - # "src/L1/OptimismPortalInterop.sol" + "src/L1/OptimismPortal2.sol" + "src/L1/OptimismPortalInterop.sol" # "src/L1/ProtocolVersions.sol" # "src/L1/SuperchainConfig.sol" # "src/L1/SystemConfig.sol" diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 1fbdb21081f..937ad5c0926 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -8,12 +8,12 @@ "sourceCodeHash": "0x5907cdb82ec5f6bef2184558a511d049ab3ee65388cf44d0c20d0f234ef8ca44" }, "src/L1/L1ERC721Bridge.sol": { - "initCodeHash": "0x86ff2f104ae7aa24e34abac9b5467b4c803183d507ba8dae6e156ae2d7c4aca7", - "sourceCodeHash": "0x79730cdb479b034943319c4c7a465befb0c93d5d286d2e2f716d12dfd75e7d79" + "initCodeHash": "0xa54dbc482f557966a636158bc3a566c351cb04ee8b1cb13aa2de61b9a0ddb064", + "sourceCodeHash": "0xe9c85de57005fe964acb9c62f5009aa5a4ba0eacc24bec67a0e4a043d2db5757" }, "src/L1/L1StandardBridge.sol": { - "initCodeHash": "0x6aca5250f7c6248e455ccc7a689a818815610449a5fb8762bb1902646694f6ac", - "sourceCodeHash": "0xf9ba98657dc235355146e381b654fe3ed766feb7cd87636ec0c9d4c6dd3e1973" + "initCodeHash": "0xe95886a6408f77fb433687049d85dd3c2200dfacc2dd6c5fd7f9554f6fc5b11b", + "sourceCodeHash": "0x7f614d0c5251365580c02dd898900a583ece2aa28579adea74a3808b575949b4" }, "src/L1/OPContractsManager.sol": { "initCodeHash": "0xfc2b4c9a4b589d3bd5267a9335b0471d2adb13af3f9ae4c01a74fe7289dc5e48", @@ -28,12 +28,12 @@ "sourceCodeHash": "0x7d2ec5f151a244e83f483a9a99598bee4915d0624a7af6c07cc82d82bf0dbb93" }, "src/L1/OptimismPortal2.sol": { - "initCodeHash": "0x332494d4362ac28b0b99182ad4077400aa2f49c72caa16ed370e62890e52a2f3", - "sourceCodeHash": "0xfc55cdde215c0086482e377c0cebccacfc36ac312794cf806dba6a73472ff94a" + "initCodeHash": "0x93c5e8ff52ff8b1cedd985b4a8890c12b56d5959832405c7622615c3541908f5", + "sourceCodeHash": "0x753465f9317d89f8ea64bcd3fc3fb1164d040b9be2d5ba3d401d951f2ceff023" }, "src/L1/OptimismPortalInterop.sol": { - "initCodeHash": "0x1f2468a793a3dabc1b2c130d9d588bbbcabe8754ba7ee69476dda673134d3125", - "sourceCodeHash": "0x278e28fdb6b4cea0ae892cd8afd9532456b1de7397969b0c73307f48188caada" + "initCodeHash": "0x12ecac882d3a45901993f93661a5e8ada1fb964d25e7843cfb9f56b2a30aa7b4", + "sourceCodeHash": "0xa031a40aa6f722466a76a36871989942f34351a327227a62266bb9a26c439fe2" }, "src/L1/ProtocolVersions.sol": { "initCodeHash": "0x5a76c8530cb24cf23d3baacc6eefaac226382af13f1e2a35535d2ec2b0573b29", @@ -84,16 +84,16 @@ "sourceCodeHash": "0x48001529220d274c5cd2e84787239b6d2244780d23894e0a8e96550a40be18fe" }, "src/L2/L2ERC721Bridge.sol": { - "initCodeHash": "0xea899e672803634b98d619174bf85dc8b3f7e6407bb7306eb72ed4c8eefce0c0", - "sourceCodeHash": "0xea896e18eceb9ba6e8125e9f3371549787e082db4b26d642b279b5697651d473" + "initCodeHash": "0xe0c4646e3372d53c294028ecbeff97b1ecb14cf7e44361b112c62b0c0d956ea2", + "sourceCodeHash": "0xc3c7fe397f91baa0b89567b8ecb2c0aff522000fd4889346e1dfec2a281486fe" }, "src/L2/L2StandardBridge.sol": { - "initCodeHash": "0xbc702854c3b6da0e5c476fabc29b1a2464bee3a1ebda7175232a5c41d7ace5e6", - "sourceCodeHash": "0x0a2ea11f3114fd1c62fae90feec5032930afc898ac75af52a67a5266eeeedf9b" + "initCodeHash": "0x0b5ec1511a7059f8eed938e68a2258485a3e24ac9ebef1f386e8dd6725b1a7c4", + "sourceCodeHash": "0xf59e693939236d00dbc5e21a5ba3e94eb442967e6a4533973d805c391e554465" }, "src/L2/L2StandardBridgeInterop.sol": { - "initCodeHash": "0x38977293cf77c047b1c3f0df5848ee9d52f742ddc7916b67c4f714c103a5a2a5", - "sourceCodeHash": "0xd0f5eaf7c8f1d14f2f997f634e279d0c4fd6843cb20b0e7eb9b9c757da591d38" + "initCodeHash": "0xa34db426a412a36c06b7a172a107af4b075d1df6997e9bb5619e14f8e52de49c", + "sourceCodeHash": "0xc2c5c34bfe7bf2833c2b4dec73f403b4c3a57d26ef0b19a9b6e4958dc0908090" }, "src/L2/L2ToL1MessagePasser.sol": { "initCodeHash": "0xf9d82084dcef31a3737a76d8ee4e5842ea190d0f77ed4678adb3bbb95217050f", @@ -156,8 +156,8 @@ "sourceCodeHash": "0xb710bd6d4844f9ee45f301bb815786619b5e2d6b2f85ae17f39bee4f414f1957" }, "src/cannon/PreimageOracle.sol": { - "initCodeHash": "0x17d3b3df1aaaf7a705b8d48de8a05e6511b910fdafdbe5eb7f7f95ec944fba9a", - "sourceCodeHash": "0xb7b0a06cd971c4647247dc19ce997d0c64a73e87c81d30731da9cf9efa1b952a" + "initCodeHash": "0x6af5b0e83b455aab8d0946c160a4dc049a4e03be69f8a2a9e87b574f27b25a66", + "sourceCodeHash": "0x03c160168986ffc8d26a90c37366e7ad6da03f49d83449e1f8b3de0f4b590f6f" }, "src/dispute/AnchorStateRegistry.sol": { "initCodeHash": "0x08cc5a5e41eadb6c411fa6387ddc0cf12be360855599dd622cce84c0ba081e77", diff --git a/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol b/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol index 89b93012717..78afeef759c 100644 --- a/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol +++ b/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol @@ -28,8 +28,8 @@ contract L1ERC721Bridge is ERC721Bridge, ISemver { ISuperchainConfig public superchainConfig; /// @notice Semantic version. - /// @custom:semver 2.3.0 - string public constant version = "2.3.0"; + /// @custom:semver 2.3.1 + string public constant version = "2.3.1"; /// @notice Constructs the L1ERC721Bridge contract. constructor() ERC721Bridge() { diff --git a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol index bb5737716a2..3a22ef24adc 100644 --- a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol +++ b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol @@ -74,8 +74,8 @@ contract L1StandardBridge is StandardBridge, ISemver { ); /// @notice Semantic version. - /// @custom:semver 2.2.1 - string public constant version = "2.2.1"; + /// @custom:semver 2.2.2 + string public constant version = "2.2.2"; /// @notice Address of the SuperchainConfig contract. ISuperchainConfig public superchainConfig; diff --git a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol index a9914b2a3df..0573afdf46f 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol @@ -7,6 +7,7 @@ import { ResourceMetering } from "src/L1/ResourceMetering.sol"; // Libraries import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { EOA } from "src/libraries/EOA.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; import { Constants } from "src/libraries/Constants.sol"; import { Types } from "src/libraries/Types.sol"; @@ -177,9 +178,9 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { } /// @notice Semantic version. - /// @custom:semver 3.12.1 + /// @custom:semver 3.13.0 function version() public pure virtual returns (string memory) { - return "3.12.1"; + return "3.13.0"; } /// @notice Constructs the OptimismPortal contract. @@ -469,7 +470,7 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { // Transform the from-address to its alias if the caller is a contract. address from = msg.sender; - if (msg.sender != tx.origin) { + if (!EOA.isSenderEOA()) { from = AddressAliasHelper.applyL1ToL2Alias(msg.sender); } diff --git a/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol b/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol index 63bab524428..5993b7f27dc 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol @@ -25,9 +25,9 @@ contract OptimismPortalInterop is OptimismPortal2 { OptimismPortal2(_proofMaturityDelaySeconds, _disputeGameFinalityDelaySeconds) { } - /// @custom:semver +interop-beta.1 + /// @custom:semver +interop.2 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop-beta.1"); + return string.concat(super.version(), "+interop.2"); } /// @notice Sets static configuration options for the L2 system. diff --git a/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol b/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol index 92256edd026..5f56ca1bf1f 100644 --- a/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol +++ b/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol @@ -24,8 +24,8 @@ import { ISemver } from "interfaces/universal/ISemver.sol"; /// **WARNING**: Do not bridge an ERC721 that was originally deployed on Optimism. This /// bridge ONLY supports ERC721s originally deployed on Ethereum. contract L2ERC721Bridge is ERC721Bridge, ISemver { - /// @custom:semver 1.8.0-beta.5 - string public constant version = "1.8.0-beta.5"; + /// @custom:semver 1.9.0 + string public constant version = "1.9.0"; /// @notice Constructs the L2ERC721Bridge contract. constructor() ERC721Bridge() { diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridge.sol b/packages/contracts-bedrock/src/L2/L2StandardBridge.sol index 7fe46d67b7f..53418eec973 100644 --- a/packages/contracts-bedrock/src/L2/L2StandardBridge.sol +++ b/packages/contracts-bedrock/src/L2/L2StandardBridge.sol @@ -57,9 +57,9 @@ contract L2StandardBridge is StandardBridge, ISemver { ); /// @notice Semantic version. - /// @custom:semver 1.11.1-beta.8 + /// @custom:semver 1.12.0 function version() public pure virtual returns (string memory) { - return "1.11.1-beta.8"; + return "1.12.0"; } /// @notice Constructs the L2StandardBridge contract. diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol index 0ac13b27f8b..485d862a293 100644 --- a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol +++ b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol @@ -39,9 +39,9 @@ contract L2StandardBridgeInterop is L2StandardBridge { event Converted(address indexed from, address indexed to, address indexed caller, uint256 amount); /// @notice Semantic version. - /// @custom:semver +interop-beta.8 + /// @custom:semver +interop.9 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop-beta.8"); + return string.concat(super.version(), "+interop.9"); } /// @notice Converts `amount` of `from` token to `to` token. diff --git a/packages/contracts-bedrock/src/cannon/PreimageOracle.sol b/packages/contracts-bedrock/src/cannon/PreimageOracle.sol index e04b9290932..ed3120092b6 100644 --- a/packages/contracts-bedrock/src/cannon/PreimageOracle.sol +++ b/packages/contracts-bedrock/src/cannon/PreimageOracle.sol @@ -50,8 +50,8 @@ contract PreimageOracle is ISemver { uint256 public constant PRECOMPILE_CALL_RESERVED_GAS = 100_000; /// @notice The semantic version of the Preimage Oracle contract. - /// @custom:semver 1.1.3-beta.9 - string public constant version = "1.1.3-beta.9"; + /// @custom:semver 1.1.4 + string public constant version = "1.1.4"; //////////////////////////////////////////////////////////////// // Authorized Preimage Parts // @@ -504,7 +504,9 @@ contract PreimageOracle is ISemver { // The bond provided must be at least `MIN_BOND_SIZE`. if (msg.value < MIN_BOND_SIZE) revert InsufficientBond(); - // The caller of `addLeavesLPP` must be an EOA, so that the call inputs are always available in block bodies. + // Legacy check, no longer technically required but keeping for now. Can be bypassed using + // EIP-7702. Challenger loads this information directly from the proposals array instead of + // looking at transaction calldata. if (msg.sender != tx.origin) revert NotEOA(); // The part offset must be within the bounds of the claimed size + 8. @@ -549,9 +551,9 @@ contract PreimageOracle is ISemver { LPPMetaData metaData = proposalMetadata[msg.sender][_uuid]; uint256 blocksProcessed = metaData.blocksProcessed(); - // The caller of `addLeavesLPP` must be an EOA. - // Note: This check may break if EIPs like EIP-3074 are introduced. We may query the data in the logs if this - // is the case. + // Legacy check, no longer technically required but keeping for now. Can be bypassed using + // EIP-7702. Challenger loads this information from the log at the end of this function + // instead of looking at transaction calldata. if (msg.sender != tx.origin) revert NotEOA(); // Revert if the proposal has not been initialized. 0-size preimages are *not* allowed. diff --git a/packages/contracts-bedrock/src/libraries/EOA.sol b/packages/contracts-bedrock/src/libraries/EOA.sol new file mode 100644 index 00000000000..7d9d5cb7197 --- /dev/null +++ b/packages/contracts-bedrock/src/libraries/EOA.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title EOA +/// @notice A library for detecting if an address is an EOA. +library EOA { + /// @notice Returns true if sender address is an EOA. + /// @return isEOA_ True if the sender address is an EOA. + function isSenderEOA() internal view returns (bool isEOA_) { + if (msg.sender == tx.origin) { + isEOA_ = true; + } else { + // If the sender is not the origin, check for 7702 delegated EOAs. + assembly { + let ptr := mload(0x40) + mstore(0x40, add(ptr, 0x20)) + extcodecopy(caller(), ptr, 0, 0x20) + isEOA_ := eq(shr(232, mload(ptr)), 0xEF0100) + } + } + } +} diff --git a/packages/contracts-bedrock/src/universal/ERC721Bridge.sol b/packages/contracts-bedrock/src/universal/ERC721Bridge.sol index c989da56c7b..abb9ab1551e 100644 --- a/packages/contracts-bedrock/src/universal/ERC721Bridge.sol +++ b/packages/contracts-bedrock/src/universal/ERC721Bridge.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.15; import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; // Libraries -import { Address } from "@openzeppelin/contracts/utils/Address.sol"; +import { EOA } from "src/libraries/EOA.sol"; // Interfaces import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; @@ -138,7 +138,7 @@ abstract contract ERC721Bridge is Initializable { // the NFT if they use this function because it sends the NFT to the same address as the // caller. This check could be bypassed by a malicious contract via initcode, but it takes // care of the user error we want to avoid. - require(!Address.isContract(msg.sender), "ERC721Bridge: account is not externally owned"); + require(EOA.isSenderEOA(), "ERC721Bridge: account is not externally owned"); _initiateBridgeERC721(_localToken, _remoteToken, msg.sender, msg.sender, _tokenId, _minGasLimit, _extraData); } diff --git a/packages/contracts-bedrock/src/universal/StandardBridge.sol b/packages/contracts-bedrock/src/universal/StandardBridge.sol index 0bfa5698ce8..9473b5f3210 100644 --- a/packages/contracts-bedrock/src/universal/StandardBridge.sol +++ b/packages/contracts-bedrock/src/universal/StandardBridge.sol @@ -5,10 +5,10 @@ pragma solidity 0.8.15; import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; // Libraries -import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; +import { EOA } from "src/libraries/EOA.sol"; // Interfaces import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -103,7 +103,7 @@ abstract contract StandardBridge is Initializable { /// calling code within their constructors, but also doesn't really matter since we're /// just trying to prevent users accidentally depositing with smart contract wallets. modifier onlyEOA() { - require(!Address.isContract(msg.sender), "StandardBridge: function can only be called from an EOA"); + require(EOA.isSenderEOA(), "StandardBridge: function can only be called from an EOA"); _; } diff --git a/packages/contracts-bedrock/test/L1/L1ERC721Bridge.t.sol b/packages/contracts-bedrock/test/L1/L1ERC721Bridge.t.sol index 41063dfbb25..96dc11634a4 100644 --- a/packages/contracts-bedrock/test/L1/L1ERC721Bridge.t.sol +++ b/packages/contracts-bedrock/test/L1/L1ERC721Bridge.t.sol @@ -91,7 +91,7 @@ contract L1ERC721Bridge_Test is CommonTest { } /// @dev Tests that the ERC721 can be bridged successfully. - function test_bridgeERC721_succeeds() public { + function test_bridgeERC721_fromEOA_succeeds() public { // Expect a call to the messenger. vm.expectCall( address(l1CrossDomainMessenger), @@ -112,6 +112,40 @@ contract L1ERC721Bridge_Test is CommonTest { vm.expectEmit(true, true, true, true); emit ERC721BridgeInitiated(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); + // Bridge the token. + vm.prank(alice, alice); + l1ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); + + // Token is locked in the bridge. + assertEq(l1ERC721Bridge.deposits(address(localToken), address(remoteToken), tokenId), true); + assertEq(localToken.ownerOf(tokenId), address(l1ERC721Bridge)); + } + + /// @dev Tests that the ERC721 can be bridged successfully. + function test_bridgeERC721_fromEOA7702_succeeds() public { + // Expect a call to the messenger. + vm.expectCall( + address(l1CrossDomainMessenger), + abi.encodeCall( + l1CrossDomainMessenger.sendMessage, + ( + address(l2ERC721Bridge), + abi.encodeCall( + IL2ERC721Bridge.finalizeBridgeERC721, + (address(remoteToken), address(localToken), alice, alice, tokenId, hex"5678") + ), + 1234 + ) + ) + ); + + // Expect an event to be emitted. + vm.expectEmit(true, true, true, true); + emit ERC721BridgeInitiated(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); + + // Set alice to have 7702 code. + vm.etch(alice, abi.encodePacked(hex"EF0100", address(0))); + // Bridge the token. vm.prank(alice); l1ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); @@ -137,7 +171,7 @@ contract L1ERC721Bridge_Test is CommonTest { /// @dev Tests that the ERC721 bridge reverts for a zero address local token. function test_bridgeERC721_localTokenZeroAddress_reverts() external { // Bridge the token. - vm.prank(alice); + vm.prank(alice, alice); vm.expectRevert(bytes("")); l1ERC721Bridge.bridgeERC721(address(0), address(remoteToken), tokenId, 1234, hex"5678"); @@ -149,7 +183,7 @@ contract L1ERC721Bridge_Test is CommonTest { /// @dev Tests that the ERC721 bridge reverts for a zero address remote token. function test_bridgeERC721_remoteTokenZeroAddress_reverts() external { // Bridge the token. - vm.prank(alice); + vm.prank(alice, alice); vm.expectRevert("L1ERC721Bridge: remote token cannot be address(0)"); l1ERC721Bridge.bridgeERC721(address(localToken), address(0), tokenId, 1234, hex"5678"); @@ -161,7 +195,7 @@ contract L1ERC721Bridge_Test is CommonTest { /// @dev Tests that the ERC721 bridge reverts for an incorrect owner. function test_bridgeERC721_wrongOwner_reverts() external { // Bridge the token. - vm.prank(bob); + vm.prank(bob, bob); vm.expectRevert("ERC721: transfer from incorrect owner"); l1ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); @@ -252,7 +286,7 @@ contract L1ERC721Bridge_Test is CommonTest { /// @dev Tests that the ERC721 bridge successfully finalizes a withdrawal. function test_finalizeBridgeERC721_succeeds() external { // Bridge the token. - vm.prank(alice); + vm.prank(alice, alice); l1ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); // Expect an event to be emitted. diff --git a/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol b/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol index 1eb9e3f952e..093c87c5e19 100644 --- a/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol +++ b/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol @@ -264,7 +264,18 @@ contract L1StandardBridge_DepositETH_Test is PreBridgeETH { /// Calls depositTransaction on the OptimismPortal. /// Only EOA can call depositETH. /// ETH ends up in the optimismPortal. - function test_depositETH_succeeds() external { + function test_depositETH_fromEOA_succeeds() external { + _preBridgeETH({ isLegacy: true, value: 500 }); + uint256 balanceBefore = address(optimismPortal2).balance; + l1StandardBridge.depositETH{ value: 500 }(50000, hex"dead"); + assertEq(address(optimismPortal2).balance, balanceBefore + 500); + } + + /// @dev Tests that depositing ETH succeeds for an EOA using 7702 delegation. + function test_depositETH_fromEOA7702_succeeds() external { + // Set alice to have 7702 code. + vm.etch(alice, abi.encodePacked(hex"EF0100", address(0))); + _preBridgeETH({ isLegacy: true, value: 500 }); uint256 balanceBefore = address(optimismPortal2).balance; l1StandardBridge.depositETH{ value: 500 }(50000, hex"dead"); @@ -461,7 +472,7 @@ contract L1StandardBridge_DepositERC20_Test is CommonTest { vm.expectEmit(address(l1CrossDomainMessenger)); emit SentMessageExtension1(address(l1StandardBridge), 0); - vm.prank(alice); + vm.prank(alice, alice); l1StandardBridge.depositERC20(address(L1Token), address(L2Token), 100, 10000, hex""); assertEq(l1StandardBridge.deposits(address(L1Token), address(L2Token)), 100); } @@ -475,7 +486,7 @@ contract L1StandardBridge_DepositERC20_TestFail is CommonTest { vm.etch(alice, hex"ffff"); vm.expectRevert("StandardBridge: function can only be called from an EOA"); - vm.prank(alice, alice); + vm.prank(alice); l1StandardBridge.depositERC20(address(0), address(0), 100, 100, hex""); } } diff --git a/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol index 4a1ab358c1f..337adcdc8eb 100644 --- a/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol +++ b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol @@ -265,6 +265,57 @@ contract OptimismPortal2_Test is CommonTest { assertEq(address(optimismPortal2).balance, balanceBefore + _mint); } + /// @dev Tests that `depositTransaction` succeeds for an EOA using 7702 delegation. + function testFuzz_depositTransaction_eoa7702_succeeds( + address _to, + uint64 _gasLimit, + uint256 _value, + uint256 _mint, + bool _isCreation, + bytes memory _data, + address _7702Target + ) + external + { + _gasLimit = uint64( + bound( + _gasLimit, + optimismPortal2.minimumGasLimit(uint64(_data.length)), + systemConfig.resourceConfig().maxResourceLimit + ) + ); + if (_isCreation) _to = address(0); + + uint256 balanceBefore = address(optimismPortal2).balance; + _mint = bound(_mint, 0, type(uint256).max - balanceBefore); + + // EOA emulation + vm.expectEmit(address(optimismPortal2)); + emitTransactionDeposited({ + _from: depositor, + _to: _to, + _value: _value, + _mint: _mint, + _gasLimit: _gasLimit, + _isCreation: _isCreation, + _data: _data + }); + + // 7702 delegation using the 7702 prefix + vm.etch(depositor, abi.encodePacked(hex"EF0100", _7702Target)); + + vm.deal(depositor, _mint); + vm.prank(depositor, address(0x0420)); + optimismPortal2.depositTransaction{ value: _mint }({ + _to: _to, + _value: _value, + _gasLimit: _gasLimit, + _isCreation: _isCreation, + _data: _data + }); + assertEq(address(optimismPortal2).balance, balanceBefore + _mint); + } + /// @dev Tests that `depositTransaction` succeeds for a contract. function testFuzz_depositTransaction_contract_succeeds( address _to, diff --git a/packages/contracts-bedrock/test/L2/L2ERC721Bridge.t.sol b/packages/contracts-bedrock/test/L2/L2ERC721Bridge.t.sol index 92171c0fdad..afc45826e96 100644 --- a/packages/contracts-bedrock/test/L2/L2ERC721Bridge.t.sol +++ b/packages/contracts-bedrock/test/L2/L2ERC721Bridge.t.sol @@ -109,7 +109,7 @@ contract L2ERC721Bridge_Test is CommonTest { emit ERC721BridgeInitiated(address(localToken), address(remoteToken), alice, alice, tokenId, hex"5678"); // Bridge the token. - vm.prank(alice); + vm.prank(alice, alice); l2ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); // Token is burned. @@ -132,7 +132,7 @@ contract L2ERC721Bridge_Test is CommonTest { /// @dev Tests that `bridgeERC721` reverts if the local token is the zero address. function test_bridgeERC721_localTokenZeroAddress_reverts() external { // Bridge the token. - vm.prank(alice); + vm.prank(alice, alice); vm.expectRevert(bytes("")); l2ERC721Bridge.bridgeERC721(address(0), address(remoteToken), tokenId, 1234, hex"5678"); @@ -143,7 +143,7 @@ contract L2ERC721Bridge_Test is CommonTest { /// @dev Tests that `bridgeERC721` reverts if the remote token is the zero address. function test_bridgeERC721_remoteTokenZeroAddress_reverts() external { // Bridge the token. - vm.prank(alice); + vm.prank(alice, alice); vm.expectRevert("L2ERC721Bridge: remote token cannot be address(0)"); l2ERC721Bridge.bridgeERC721(address(localToken), address(0), tokenId, 1234, hex"5678"); @@ -154,7 +154,7 @@ contract L2ERC721Bridge_Test is CommonTest { /// @dev Tests that `bridgeERC721` reverts if the caller is not the token owner. function test_bridgeERC721_wrongOwner_reverts() external { // Bridge the token. - vm.prank(bob); + vm.prank(bob, bob); vm.expectRevert("L2ERC721Bridge: Withdrawal is not being initiated by NFT owner"); l2ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); @@ -238,7 +238,7 @@ contract L2ERC721Bridge_Test is CommonTest { /// @dev Tests that `finalizeBridgeERC721` correctly finalizes a bridged token. function test_finalizeBridgeERC721_succeeds() external { // Bridge the token. - vm.prank(alice); + vm.prank(alice, alice); l2ERC721Bridge.bridgeERC721(address(localToken), address(remoteToken), tokenId, 1234, hex"5678"); // Expect an event to be emitted. @@ -265,7 +265,7 @@ contract L2ERC721Bridge_Test is CommonTest { NonCompliantERC721 nonCompliantToken = new NonCompliantERC721(alice); // Bridge the non-compliant token. - vm.prank(alice); + vm.prank(alice, alice); l2ERC721Bridge.bridgeERC721(address(nonCompliantToken), address(0x01), tokenId, 1234, hex"5678"); // Attempt to finalize the withdrawal. Should revert because the token does not claim diff --git a/packages/contracts-bedrock/test/libraries/EOA.t.sol b/packages/contracts-bedrock/test/libraries/EOA.t.sol new file mode 100644 index 00000000000..ddead83ffe3 --- /dev/null +++ b/packages/contracts-bedrock/test/libraries/EOA.t.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Forge +import { Test } from "forge-std/Test.sol"; + +// Libraries +import { EOA } from "src/libraries/EOA.sol"; + +/// @title EOA_Harness +/// @notice A helper contract to test the EOA library. +contract EOA_Harness { + /// @notice Returns true if the sender is an EOA. + /// @return isEOA_ True if the sender is an EOA. + function isSenderEOA() external view returns (bool isEOA_) { + return EOA.isSenderEOA(); + } +} + +/// @title EOA_isEOA_Test +contract EOA_isEOA_Test is Test { + EOA_Harness harness; + + /// @notice Sets up the test. + function setUp() public { + harness = new EOA_Harness(); + } + + /// @notice Tests that a standard EOA is detected as an EOA. + /// @param _privateKey The private key of the sender. + function testFuzz_isEOA_isStandardEOA_succeeds(uint256 _privateKey) external { + // Make sure that the private key is in the range of a valid secp256k1 private key. + _privateKey = boundPrivateKey(_privateKey); + + // Make sure that the sender is a standard EOA with no code. + address sender = vm.addr(_privateKey); + vm.assume(sender.code.length == 0); + + // Should be considered an EOA + vm.prank(sender, sender); + assertEq(harness.isSenderEOA(), true); + } + + /// @notice Tests that a 7702 EOA is detected as an EOA. + /// @param _privateKey The private key of the sender. + /// @param _7702Target The target of the 7702 EOA. + function testFuzz_isEOA_is7702EOA_succeeds(uint256 _privateKey, address _7702Target) external { + // Make sure that the private key is in the range of a valid secp256k1 private key. + _privateKey = boundPrivateKey(_privateKey); + + // Make sure that the sender is a 7702 EOA. + address sender = vm.addr(_privateKey); + vm.etch(sender, abi.encodePacked(hex"EF0100", _7702Target)); + + // Should be considered a 7702 EOA. + vm.prank(sender, sender); + assertEq(harness.isSenderEOA(), true); + + // Should still be considered an EOA even if origin is different. + vm.prank(sender, address(0x0420)); + assertEq(harness.isSenderEOA(), true); + } + + /// @notice Tests that a contract is not detected as an EOA. + /// @param _privateKey The private key of the sender. + /// @param _code The code of the sender. + function testFuzz_isEOA_isContract_succeeds(uint256 _privateKey, bytes memory _code) external { + // Make sure that the private key is in the range of a valid secp256k1 private key. + _privateKey = boundPrivateKey(_privateKey); + + // If code is empty or starts with EF, change it. + if (_code.length == 0 || _code[0] == 0xEF) { + _code = bytes.concat(hex"FFFFFF", _code); + } + + // Make sure that the sender is a contract. + address sender = vm.addr(_privateKey); + vm.etch(sender, _code); + + // Should not be considered an EOA. + vm.prank(sender); + assertEq(harness.isSenderEOA(), false); + } +} From 1c94e624533e5f18d62e4d8fb71acf06ccc4a1c0 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Tue, 25 Feb 2025 13:57:48 -0700 Subject: [PATCH 011/130] Revert "Added footer to op-deployer (#14374)" (#14528) This reverts commit 9d94936939484b7898d29db581de5703f7685367. --- op-deployer/book/book.toml | 3 +- op-deployer/book/theme/css/Footer.css | 71 --------------------------- op-deployer/book/theme/js/Footer.js | 41 ---------------- 3 files changed, 1 insertion(+), 114 deletions(-) delete mode 100644 op-deployer/book/theme/css/Footer.css delete mode 100644 op-deployer/book/theme/js/Footer.js diff --git a/op-deployer/book/book.toml b/op-deployer/book/book.toml index 7a3e685f2fb..acb79766830 100644 --- a/op-deployer/book/book.toml +++ b/op-deployer/book/book.toml @@ -9,5 +9,4 @@ title = "OP Deployer Book" site-url = "/op-deployer/" git-repository-url = "https://github.com/ethereum-optimism/optimism/tree/develop/op-deployer/book" edit-url-template = "https://github.com/ethereum-optimism/optimism/tree/develop/op-deployer/book/{path}" -additional-css = ["custom.css", "theme/css/footer.css"] -additional-js = ["theme/js/footer.js"] +additional-css = ["custom.css"] diff --git a/op-deployer/book/theme/css/Footer.css b/op-deployer/book/theme/css/Footer.css deleted file mode 100644 index cb7be80ab21..00000000000 --- a/op-deployer/book/theme/css/Footer.css +++ /dev/null @@ -1,71 +0,0 @@ -.mdbook-footer { - width: 100%; - padding: 4rem 2.5rem; /* Increased padding */ - background-color: var(--bg); - border-top: 1px solid var(--sidebar-bg); - margin-top: 5rem; /* Increased margin */ -} - -.mdbook-footer .footer-container { - max-width: 1200px; - margin: 0 auto; - display: flex; - flex-direction: column; - gap: 2.5rem; /* Increased gap */ - align-items: center; -} - -.mdbook-footer .policy-links { - display: flex; - gap: 4rem; /* Increased gap between links */ - flex-wrap: wrap; - justify-content: center; -} - -.mdbook-footer .policy-links a { - color: var(--fg); - text-decoration: none; - transition: opacity 0.2s; - font-size: 1.35rem; /* Increased font size */ - opacity: 0.85; - font-weight: 400; - line-height: 1.6; /* Increased line height */ -} - -.mdbook-footer .policy-links a:hover { - opacity: 1; - text-decoration: underline; -} - -.mdbook-footer .copyright { - color: var(--fg); - font-size: 1.35rem; /* Increased font size */ - opacity: 0.85; - text-align: center; - font-weight: 400; - line-height: 1.6; /* Increased line height */ -} - -.mdbook-footer .copyright a { - color: var(--fg); - text-decoration: none; -} - -.mdbook-footer .copyright a:hover { - text-decoration: underline; -} - -@media (max-width: 640px) { - .mdbook-footer .policy-links { - gap: 2.5rem; /* Increased gap for mobile */ - } - - .mdbook-footer { - padding: 3rem 2rem; /* Increased padding for mobile */ - } - - .mdbook-footer .policy-links a, - .mdbook-footer .copyright { - font-size: 1.25rem; /* Increased font size for mobile */ - } -} \ No newline at end of file diff --git a/op-deployer/book/theme/js/Footer.js b/op-deployer/book/theme/js/Footer.js deleted file mode 100644 index 014f44f2d6c..00000000000 --- a/op-deployer/book/theme/js/Footer.js +++ /dev/null @@ -1,41 +0,0 @@ -// Create footer element -function createFooter() { - const footer = document.createElement('footer'); - footer.className = 'mdbook-footer'; - - const container = document.createElement('div'); - container.className = 'footer-container'; - - // Add legal links - const policyLinks = document.createElement('div'); - policyLinks.className = 'policy-links'; - - const links = [ - { href: 'https://optimism.io/community-agreement', text: 'Community Agreement' }, - { href: 'https://optimism.io/terms', text: 'Terms of Service' }, - { href: 'https://optimism.io/data-privacy-policy', text: 'Privacy Policy' } - ]; - - links.forEach(link => { - const a = document.createElement('a'); - a.href = link.href; - a.textContent = link.text; - policyLinks.appendChild(a); - }); - - // Add copyright notice - const copyright = document.createElement('div'); - copyright.className = 'copyright'; - copyright.innerHTML = `© ${new Date().getFullYear()} Optimism Foundation. All rights reserved.`; - - // Assemble footer - container.appendChild(policyLinks); - container.appendChild(copyright); - footer.appendChild(container); - - // Add footer to page - document.body.appendChild(footer); -} - -// Run after DOM is loaded -document.addEventListener('DOMContentLoaded', createFooter); \ No newline at end of file From cae6fd1d5ae6e66d56dca21afbf47367c1ff46a3 Mon Sep 17 00:00:00 2001 From: George Knee Date: Tue, 25 Feb 2025 21:43:40 +0000 Subject: [PATCH 012/130] op-batcher: always `updateCursorAndMetrics` when returning from `processBlocks()` (#14520) * op-batcher: always updateCursorAndMetrics when returning from processBlocks() * Update op-batcher/batcher/channel_manager.go Co-authored-by: Sebastian Stammler --------- Co-authored-by: Sebastian Stammler --- op-batcher/batcher/channel_manager.go | 32 ++++++++++++++------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/op-batcher/batcher/channel_manager.go b/op-batcher/batcher/channel_manager.go index 1ea412c4b43..3c6d7beee05 100644 --- a/op-batcher/batcher/channel_manager.go +++ b/op-batcher/batcher/channel_manager.go @@ -346,6 +346,23 @@ func (s *channelManager) processBlocks() error { latestL2ref eth.L2BlockRef ) + defer func() { + s.blockCursor += blocksAdded + + s.metr.RecordL2BlocksAdded(latestL2ref, + blocksAdded, + s.pendingBlocks(), + s.currentChannel.InputBytes(), + s.currentChannel.ReadyBytes()) + s.log.Debug("Added blocks to channel", + "blocks_added", blocksAdded, + "blocks_pending", s.pendingBlocks(), + "channel_full", s.currentChannel.IsFull(), + "input_bytes", s.currentChannel.InputBytes(), + "ready_bytes", s.currentChannel.ReadyBytes(), + ) + }() + for i := s.blockCursor; ; i++ { block, ok := s.blocks.PeekN(i) if !ok { @@ -369,21 +386,6 @@ func (s *channelManager) processBlocks() error { break } } - - s.blockCursor += blocksAdded - - s.metr.RecordL2BlocksAdded(latestL2ref, - blocksAdded, - s.pendingBlocks(), - s.currentChannel.InputBytes(), - s.currentChannel.ReadyBytes()) - s.log.Debug("Added blocks to channel", - "blocks_added", blocksAdded, - "blocks_pending", s.pendingBlocks(), - "channel_full", s.currentChannel.IsFull(), - "input_bytes", s.currentChannel.InputBytes(), - "ready_bytes", s.currentChannel.ReadyBytes(), - ) return nil } From df65a15a2beecc905a26549f1e264688906b5a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1n=20Jakub=20Nani=C5=A1ta?= Date: Tue, 25 Feb 2025 13:45:10 -0800 Subject: [PATCH 013/130] fix(devnet-sdk): Unique sentinels for devnet-sdk (#14514) * fix: Unique markers for devnet-sdk * chore: Add a test --- .../testing/testlib/validators/lowlevel.go | 2 +- .../testlib/validators/validators_test.go | 162 ++++++++++++++++++ .../testing/testlib/validators/wallet.go | 2 +- 3 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 devnet-sdk/testing/testlib/validators/validators_test.go diff --git a/devnet-sdk/testing/testlib/validators/lowlevel.go b/devnet-sdk/testing/testlib/validators/lowlevel.go index 4a1456e4974..29e99f0d127 100644 --- a/devnet-sdk/testing/testlib/validators/lowlevel.go +++ b/devnet-sdk/testing/testlib/validators/lowlevel.go @@ -54,7 +54,7 @@ func lowLevelSystemValidator(sysMarker interface{}) systest.PreconditionValidato } func AcquireLowLevelSystem() (LowLevelSystemGetter, systest.PreconditionValidator) { - sysMarker := &struct{}{} + sysMarker := new(byte) validator := lowLevelSystemValidator(sysMarker) return func(ctx context.Context) system.LowLevelSystem { return ctx.Value(sysMarker).(system.LowLevelSystem) diff --git a/devnet-sdk/testing/testlib/validators/validators_test.go b/devnet-sdk/testing/testlib/validators/validators_test.go new file mode 100644 index 00000000000..4d11d52ba90 --- /dev/null +++ b/devnet-sdk/testing/testlib/validators/validators_test.go @@ -0,0 +1,162 @@ +package validators + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" + "github.com/ethereum-optimism/optimism/devnet-sdk/system" + "github.com/ethereum-optimism/optimism/devnet-sdk/testing/systest" + "github.com/ethereum-optimism/optimism/devnet-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/require" +) + +// TestSystemTestHelper tests the basic implementation of systemTestHelper +func TestValidators(t *testing.T) { + t.Run("multiple validators", func(t *testing.T) { + walletGetter1, validator1 := AcquireL2WalletWithFunds(0, types.NewBalance(big.NewInt(1))) + walletGetter2, validator2 := AcquireL2WalletWithFunds(0, types.NewBalance(big.NewInt(10))) + lowLevelSystemGetter, validator3 := AcquireLowLevelSystem() + + // We create a system that has a low-level L1 chain and at least one wallet + systestSystem := &mockSystem{ + l1: &mockChain{}, + l2s: []system.Chain{ + &mockChain{ + wallets: []system.Wallet{ + &mockWallet{ + balance: types.NewBalance(big.NewInt(2)), + }, + &mockWallet{ + balance: types.NewBalance(big.NewInt(11)), + }, + }, + }, + }, + } + + // Now we apply all validators, accumulating contexts + + systestT := systest.NewT(t) + + ctx1, err := validator1(systestT, systestSystem) + systestT = systestT.WithContext(ctx1) + require.NoError(t, err) + + ctx2, err := validator2(systestT, systestSystem) + systestT = systestT.WithContext(ctx2) + require.NoError(t, err) + + ctx3, err := validator3(systestT, systestSystem) + systestT = systestT.WithContext(ctx3) + require.NoError(t, err) + + ctx := systestT.Context() + + // Now we call all the getters to make sure they work + wallet1 := walletGetter1(ctx) + wallet2 := walletGetter2(ctx) + lowLevelSystem := lowLevelSystemGetter(ctx) + + // And we ensure that the values are not mismatched + require.NotEqual(t, wallet1, wallet2) + require.NotEqual(t, wallet1, lowLevelSystem) + require.NotEqual(t, wallet2, lowLevelSystem) + + // And that we got a lowlevelSystem + require.NotNil(t, lowLevelSystem) + }) +} + +type mockSystem struct { + l1 system.Chain + l2s []system.Chain +} + +func (sys *mockSystem) Identifier() string { + return "mock" +} + +func (sys *mockSystem) L1() system.Chain { + return sys.l1 +} + +func (sys *mockSystem) L2s() []system.Chain { + return sys.l2s +} + +type mockChain struct { + wallets []system.Wallet +} + +func (m *mockChain) RPCURL() string { return "http://localhost:8545" } +func (m *mockChain) Client() (*ethclient.Client, error) { return ethclient.Dial(m.RPCURL()) } +func (m *mockChain) ID() types.ChainID { return types.ChainID(big.NewInt(1)) } +func (m *mockChain) ContractsRegistry() interfaces.ContractsRegistry { return nil } +func (m *mockChain) Wallets(ctx context.Context) ([]system.Wallet, error) { + return m.wallets, nil +} +func (m *mockChain) GasPrice(ctx context.Context) (*big.Int, error) { + return big.NewInt(1), nil +} +func (m *mockChain) GasLimit(ctx context.Context, tx system.TransactionData) (uint64, error) { + return 1000000, nil +} +func (m *mockChain) PendingNonceAt(ctx context.Context, address common.Address) (uint64, error) { + return 0, nil +} +func (m *mockChain) SupportsEIP(ctx context.Context, eip uint64) bool { + return true +} + +type mockWallet struct { + balance types.Balance + address types.Address +} + +func (m mockWallet) Balance() types.Balance { + return m.balance +} + +func (m mockWallet) Address() types.Address { + return m.address +} + +func (m mockWallet) PrivateKey() types.Key { + key, _ := crypto.HexToECDSA("123") + return types.Key(key) +} + +func (m mockWallet) SendETH(to types.Address, amount types.Balance) types.WriteInvocation[any] { + panic("not implemented") +} + +func (m mockWallet) Nonce() uint64 { + return 0 +} + +func (m mockWallet) Sign(tx system.Transaction) (system.Transaction, error) { + return tx, nil +} + +func (m mockWallet) Send(ctx context.Context, tx system.Transaction) error { + return nil +} + +func (m mockWallet) Transactor() *bind.TransactOpts { + return nil +} + +var ( + _ system.Chain = (*mockChain)(nil) + _ system.LowLevelChain = (*mockChain)(nil) + + _ system.System = (*mockSystem)(nil) + + _ system.Wallet = (*mockWallet)(nil) +) diff --git a/devnet-sdk/testing/testlib/validators/wallet.go b/devnet-sdk/testing/testlib/validators/wallet.go index 65c7621b427..4757ec03046 100644 --- a/devnet-sdk/testing/testlib/validators/wallet.go +++ b/devnet-sdk/testing/testlib/validators/wallet.go @@ -33,7 +33,7 @@ func walletFundsValidator(chainIdx uint64, minFunds types.Balance, userMarker in } func AcquireL2WalletWithFunds(chainIdx uint64, minFunds types.Balance) (WalletGetter, systest.PreconditionValidator) { - userMarker := &struct{}{} + userMarker := new(byte) validator := walletFundsValidator(chainIdx, minFunds, userMarker) return func(ctx context.Context) system.Wallet { return ctx.Value(userMarker).(system.Wallet) From 0718d51b2ce92345b5a71e2dfe2348659c4e9db2 Mon Sep 17 00:00:00 2001 From: Maurelian Date: Tue, 25 Feb 2025 18:13:30 -0500 Subject: [PATCH 014/130] feat: remove freeze from L1 and dispute contracts (#14438) --- .../scripts/checks/check-frozen-files.sh | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh b/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh index b38ba40165d..f400ab5cab5 100755 --- a/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh +++ b/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh @@ -49,17 +49,17 @@ changed_contracts=$(jq -r ' # All files in semver-lock.json should be in this list. ALLOWED_FILES=( "src/L1/DataAvailabilityChallenge.sol" - # "src/L1/L1CrossDomainMessenger.sol" + "src/L1/L1CrossDomainMessenger.sol" "src/L1/L1ERC721Bridge.sol" "src/L1/L1StandardBridge.sol" - # "src/L1/OPContractsManager.sol" - # "src/L1/OPContractsManagerInterop.sol" + "src/L1/OPContractsManager.sol" + "src/L1/OPContractsManagerInterop.sol" "src/L1/OptimismPortal2.sol" "src/L1/OptimismPortalInterop.sol" - # "src/L1/ProtocolVersions.sol" - # "src/L1/SuperchainConfig.sol" - # "src/L1/SystemConfig.sol" - # "src/L1/SystemConfigInterop.sol" + "src/L1/ProtocolVersions.sol" + "src/L1/SuperchainConfig.sol" + "src/L1/SystemConfig.sol" + "src/L1/SystemConfigInterop.sol" "src/L2/BaseFeeVault.sol" "src/L2/CrossL2Inbox.sol" "src/L2/ETHLiquidity.sol" @@ -87,11 +87,11 @@ ALLOWED_FILES=( # "src/cannon/MIPS2.sol" # "src/cannon/MIPS64.sol" "src/cannon/PreimageOracle.sol" - # "src/dispute/AnchorStateRegistry.sol" - # "src/dispute/DelayedWETH.sol" - # "src/dispute/DisputeGameFactory.sol" - # "src/dispute/FaultDisputeGame.sol" - # "src/dispute/PermissionedDisputeGame.sol" + "src/dispute/AnchorStateRegistry.sol" + "src/dispute/DelayedWETH.sol" + "src/dispute/DisputeGameFactory.sol" + "src/dispute/FaultDisputeGame.sol" + "src/dispute/PermissionedDisputeGame.sol" "src/dispute/SuperFaultDisputeGame.sol" "src/dispute/SuperPermissionedDisputeGame.sol" "src/legacy/DeployerWhitelist.sol" From f45e785bf9bf6424cf0efa981a0cc75739467321 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Wed, 26 Feb 2025 09:54:57 +1000 Subject: [PATCH 015/130] op-challenger: Avoid loading duplicate leaves in same block (#14516) * op-challenger: Avoid loading duplicate leaves. * op-challenger: Sanity check length of fetched input data. Provides a clearer error message if the fetch is incorrect rather than just InvalidProof() revert reason. * op-challenger: Deduplicate blocks in contract bindings. * Deduplicate block numbers in test setup now that the contract bindings handle it. * Fix more tests. --- op-challenger/game/fault/contracts/oracle.go | 9 +- .../game/fault/contracts/oracle_test.go | 8 +- op-challenger/game/keccak/verifier.go | 5 ++ op-challenger/game/keccak/verifier_test.go | 21 ++++- .../disputegame/preimage/preimage_helper.go | 9 +- op-e2e/e2eutils/transactions/send.go | 85 +++++++++++++++---- op-e2e/faultproofs/challenge_preimage_test.go | 8 +- 7 files changed, 116 insertions(+), 29 deletions(-) diff --git a/op-challenger/game/fault/contracts/oracle.go b/op-challenger/game/fault/contracts/oracle.go index fdb28b003e4..3541a13cbae 100644 --- a/op-challenger/game/fault/contracts/oracle.go +++ b/op-challenger/game/fault/contracts/oracle.go @@ -292,7 +292,14 @@ func (c *PreimageOracleContractLatest) GetInputDataBlocks(ctx context.Context, b } blockNums := make([]uint64, 0, len(results)) for _, result := range results { - blockNums = append(blockNums, result.GetUint64(0)) + num := result.GetUint64(0) + if len(blockNums) > 0 && blockNums[len(blockNums)-1] == num { + // Deduplicate block numbers. The contract guarantees they are in order so we just need to check if the + // previous block number is the same as this one. + // Duplicate entries happen when there are two addLeavesLPP calls in the same block. + continue + } + blockNums = append(blockNums, num) } return blockNums, nil } diff --git a/op-challenger/game/fault/contracts/oracle_test.go b/op-challenger/game/fault/contracts/oracle_test.go index 9989d7beb48..16890dc2b98 100644 --- a/op-challenger/game/fault/contracts/oracle_test.go +++ b/op-challenger/game/fault/contracts/oracle_test.go @@ -559,9 +559,11 @@ func TestGetInputDataBlocks(t *testing.T) { methodProposalBlocksLen, block, []interface{}{preimage.Claimant, preimage.UUID}, - []interface{}{big.NewInt(3)}) + []interface{}{big.NewInt(6)}) - blockNums := []uint64{10, 35, 67} + blockNums := []uint64{10, 35, 35, 35, 67, 67} + // Returned block numbers should be deduplicated. + expectedBlockNums := []uint64{10, 35, 67} for i, blockNum := range blockNums { stubRpc.SetResponse( @@ -575,7 +577,7 @@ func TestGetInputDataBlocks(t *testing.T) { actual, err := oracle.GetInputDataBlocks(context.Background(), block, preimage) require.NoError(t, err) require.Len(t, actual, 3) - require.Equal(t, blockNums, actual) + require.Equal(t, expectedBlockNums, actual) }) } } diff --git a/op-challenger/game/keccak/verifier.go b/op-challenger/game/keccak/verifier.go index 026462b127e..79857e2f9e3 100644 --- a/op-challenger/game/keccak/verifier.go +++ b/op-challenger/game/keccak/verifier.go @@ -66,9 +66,14 @@ func (v *PreimageVerifier) CreateChallenge(ctx context.Context, blockHash common } readers := make([]io.Reader, 0, len(inputs)) var commitments []common.Hash + dataLen := 0 for _, input := range inputs { readers = append(readers, bytes.NewReader(input.Input)) commitments = append(commitments, input.Commitments...) + dataLen += len(input.Input) + } + if dataLen != int(preimage.BytesProcessed) { + return keccakTypes.Challenge{}, fmt.Errorf("retrieved incorrect input length, expected %v but got %v", preimage.BytesProcessed, dataLen) } challenge, err := matrix.Challenge(io.MultiReader(readers...), commitments) if errors.Is(err, matrix.ErrValid) { diff --git a/op-challenger/game/keccak/verifier_test.go b/op-challenger/game/keccak/verifier_test.go index fdf7bf062df..d25d6e0f113 100644 --- a/op-challenger/game/keccak/verifier_test.go +++ b/op-challenger/game/keccak/verifier_test.go @@ -69,11 +69,15 @@ func TestVerify(t *testing.T) { for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { + inputs := test.inputs() fetcher := &stubFetcher{ - inputs: test.inputs(), + inputs: inputs, } + verifier := NewPreimageVerifier(logger, fetcher) - preimage := keccakTypes.LargePreimageMetaData{} + preimage := keccakTypes.LargePreimageMetaData{ + BytesProcessed: inputLength(inputs), + } oracle := &stubOracle{ treeRoots: map[keccakTypes.LargePreimageIdent]common.Hash{ preimage.LargePreimageIdent: {0xde}, @@ -94,8 +98,9 @@ func TestVerify(t *testing.T) { func TestCacheValidRoots(t *testing.T) { logger := testlog.Logger(t, log.LvlInfo) + inputs := validInputs(t, 1) fetcher := &stubFetcher{ - inputs: validInputs(t, 1), + inputs: inputs, } verifier := NewPreimageVerifier(logger, fetcher) preimage1 := keccakTypes.LargePreimageMetaData{ @@ -103,12 +108,14 @@ func TestCacheValidRoots(t *testing.T) { Claimant: common.Address{0x12}, UUID: big.NewInt(1), }, + BytesProcessed: inputLength(inputs), } preimage2 := keccakTypes.LargePreimageMetaData{ LargePreimageIdent: keccakTypes.LargePreimageIdent{ Claimant: common.Address{0x23}, UUID: big.NewInt(2), }, + BytesProcessed: inputLength(inputs), } oracle := &stubOracle{ treeRoots: map[keccakTypes.LargePreimageIdent]common.Hash{ @@ -162,3 +169,11 @@ func (s *stubFetcher) FetchInputs(_ context.Context, _ common.Hash, _ fetcher.Or s.fetchCount.Add(1) return s.inputs, nil } + +func inputLength(inputs []keccakTypes.InputData) uint32 { + inputLen := uint32(0) + for _, input := range inputs { + inputLen += uint32(len(input.Input)) + } + return inputLen +} diff --git a/op-e2e/e2eutils/disputegame/preimage/preimage_helper.go b/op-e2e/e2eutils/disputegame/preimage/preimage_helper.go index 62287c8ff13..aa36869e15f 100644 --- a/op-e2e/e2eutils/disputegame/preimage/preimage_helper.go +++ b/op-e2e/e2eutils/disputegame/preimage/preimage_helper.go @@ -20,6 +20,7 @@ import ( "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum-optimism/optimism/op-service/testutils" + "github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" @@ -82,6 +83,7 @@ func (h *Helper) UploadLargePreimage(ctx context.Context, dataSize int, modifier startBlock := big.NewInt(0) totalBlocks := len(data) / types.BlockSize in := bytes.NewReader(data) + var txs []txmgr.TxCandidate for { inputData, err := s.AbsorbUpTo(in, preimages.MaxChunkSize) if !errors.Is(err, io.EOF) { @@ -93,12 +95,17 @@ func (h *Helper) UploadLargePreimage(ctx context.Context, dataSize int, modifier h.t.Logf("Uploading %v parts of preimage %v starting at block %v of about %v Finalize: %v", len(inputData.Commitments), uuid.Uint64(), startBlock.Uint64(), totalBlocks, inputData.Finalize) tx, err := h.oracle.AddLeaves(uuid, startBlock, inputData.Input, inputData.Commitments, inputData.Finalize) h.require.NoError(err) - transactions.RequireSendTx(h.t, ctx, h.client, tx, h.privKey) + // Can't use EstimateGas because all the transactions will be sent in a batch, and the contract checks the + // start block, which won't match subsequent transactions until the first one is processed. So set the gas limit + // to what EstimateGas was returning when this was written plus a bunch of headroom. + tx.GasLimit = 2000000 // 826536 + txs = append(txs, tx) startBlock = new(big.Int).Add(startBlock, big.NewInt(int64(len(inputData.Commitments)))) if inputData.Finalize { break } } + transactions.RequireSendTxs(h.t, ctx, h.client, txs, h.privKey) return types.LargePreimageIdent{ Claimant: crypto.PubkeyToAddress(h.privKey.PublicKey), diff --git a/op-e2e/e2eutils/transactions/send.go b/op-e2e/e2eutils/transactions/send.go index a5f3f83ba1b..bf5f29faa1f 100644 --- a/op-e2e/e2eutils/transactions/send.go +++ b/op-e2e/e2eutils/transactions/send.go @@ -53,21 +53,75 @@ func RequireSendTx(t *testing.T, ctx context.Context, client *ethclient.Client, return tx, rcpt } +// RequireSendTxs submits multiple transactions attempting to batch them together in blocks as much as possible. +// There is however no guarantee that all transactions will be included in the same block or that any transactions +// will share a block. +// Note that if the transactions depend on one another, the gas limit may need to be manually set as estimateGas will +// be executed before the earlier transactions have been processed. +func RequireSendTxs(t *testing.T, ctx context.Context, client *ethclient.Client, candidates []txmgr.TxCandidate, privKey *ecdsa.PrivateKey, opts ...SendTxOpt) ([]*types.Transaction, []*types.Receipt) { + cfg := makeSendTxCfg(opts...) + // First convert all the candidates to signed transactions so they are most likely to be included in the same block + // This still isn't guaranteed but minimises the delay between each transaction submission. + nonce, err := client.PendingNonceAt(ctx, crypto.PubkeyToAddress(privKey.PublicKey)) + require.NoError(t, err, "Failed to get pending nonce") + txs := make([]*types.Transaction, len(candidates)) + for i, candidate := range candidates { + tx, err := createTx(ctx, client, candidate, privKey, nonce) + require.NoErrorf(t, err, "Failed to create transaction %v", i) + txs[i] = tx + nonce++ + } + + // Then send all transactions (some may be included in the same block) + for i, tx := range txs { + err := client.SendTransaction(ctx, tx) + require.NoErrorf(t, err, "Failed to send transaction %v", i) + } + + // Then wait for all receipts + receipts := make([]*types.Receipt, len(txs)) + for i, tx := range txs { + receipt, err := wait.ForReceiptMaybe(ctx, client, tx.Hash(), cfg.receiptStatus, cfg.ignoreReceiptStatus) + if err != nil { + fmt.Printf("Failed to get receipt for %v: %v", i, err) + } + require.NoErrorf(t, err, "Failed to find receipt for tx %v (%s)", i, tx.Hash()) + receipts[i] = receipt + } + return txs, receipts +} + func SendTx(ctx context.Context, client *ethclient.Client, candidate txmgr.TxCandidate, privKey *ecdsa.PrivateKey, opts ...SendTxOpt) (*types.Transaction, *types.Receipt, error) { cfg := makeSendTxCfg(opts...) - from := crypto.PubkeyToAddress(privKey.PublicKey) - chainID, err := client.ChainID(ctx) + nonce, err := client.PendingNonceAt(ctx, crypto.PubkeyToAddress(privKey.PublicKey)) if err != nil { - return nil, nil, fmt.Errorf("failed to get chain ID: %w", err) + return nil, nil, fmt.Errorf("failed to get next nonce: %w", err) } - nonce, err := client.PendingNonceAt(ctx, from) + tx, err := createTx(ctx, client, candidate, privKey, nonce) if err != nil { - return nil, nil, fmt.Errorf("failed to get next nonce: %w", err) + return nil, nil, err + } + err = client.SendTransaction(ctx, tx) + if err != nil { + return nil, nil, fmt.Errorf("failed to send transaction (tx: %s): %w", tx.Hash(), errutil.TryAddRevertReason(err)) + } + receipt, err := wait.ForReceiptMaybe(ctx, client, tx.Hash(), cfg.receiptStatus, cfg.ignoreReceiptStatus) + if err != nil { + return tx, receipt, fmt.Errorf("failed to find OK receipt (tx: %s): %w", tx.Hash(), err) + } + return tx, receipt, nil +} + +func createTx(ctx context.Context, client *ethclient.Client, candidate txmgr.TxCandidate, privKey *ecdsa.PrivateKey, nonce uint64) (*types.Transaction, error) { + from := crypto.PubkeyToAddress(privKey.PublicKey) + chainID, err := client.ChainID(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get chain ID: %w", err) } latestBlock, err := client.HeaderByNumber(ctx, nil) if err != nil { - return nil, nil, fmt.Errorf("failed to get latest block: %w", err) + return nil, fmt.Errorf("failed to get latest block: %w", err) } gasFeeCap := new(big.Int).Mul(latestBlock.BaseFee, big.NewInt(3)) gasTipCap := big.NewInt(1 * params.GWei) @@ -84,9 +138,12 @@ func SendTx(ctx context.Context, client *ethclient.Client, candidate txmgr.TxCan GasFeeCap: gasFeeCap, Data: candidate.TxData, } - gas, err := client.EstimateGas(ctx, msg) - if err != nil { - return nil, nil, fmt.Errorf("failed to estimate gas: %w", errutil.TryAddRevertReason(err)) + gas := candidate.GasLimit + if gas == 0 { + gas, err = client.EstimateGas(ctx, msg) + if err != nil { + return nil, fmt.Errorf("failed to estimate gas: %w", errutil.TryAddRevertReason(err)) + } } tx := types.MustSignNewTx(privKey, types.LatestSignerForChainID(chainID), &types.DynamicFeeTx{ @@ -99,13 +156,5 @@ func SendTx(ctx context.Context, client *ethclient.Client, candidate txmgr.TxCan Data: candidate.TxData, Gas: gas, }) - err = client.SendTransaction(ctx, tx) - if err != nil { - return nil, nil, fmt.Errorf("failed to send transaction (tx: %s): %w", tx.Hash(), errutil.TryAddRevertReason(err)) - } - receipt, err := wait.ForReceiptMaybe(ctx, client, tx.Hash(), cfg.receiptStatus, cfg.ignoreReceiptStatus) - if err != nil { - return tx, receipt, fmt.Errorf("failed to find OK receipt (tx: %s): %w", tx.Hash(), err) - } - return tx, receipt, nil + return tx, nil } diff --git a/op-e2e/faultproofs/challenge_preimage_test.go b/op-e2e/faultproofs/challenge_preimage_test.go index 3bed79ad068..12d2396e5c2 100644 --- a/op-e2e/faultproofs/challenge_preimage_test.go +++ b/op-e2e/faultproofs/challenge_preimage_test.go @@ -13,6 +13,8 @@ import ( "github.com/stretchr/testify/require" ) +const testPreimageSize = preimage.MinPreimageSize * 20 + func TestChallengeLargePreimages_ChallengeFirst(t *testing.T) { op_e2e.InitParallel(t) ctx := context.Background() @@ -24,7 +26,7 @@ func TestChallengeLargePreimages_ChallengeFirst(t *testing.T) { challenger.WithAlphabet(), challenger.WithPrivKey(sys.Cfg.Secrets.Alice)) preimageHelper := disputeGameFactory.PreimageHelper(ctx) - ident := preimageHelper.UploadLargePreimage(ctx, preimage.MinPreimageSize, + ident := preimageHelper.UploadLargePreimage(ctx, testPreimageSize, preimage.WithReplacedCommitment(0, common.Hash{0xaa})) require.NotEqual(t, ident.Claimant, common.Address{}) @@ -42,7 +44,7 @@ func TestChallengeLargePreimages_ChallengeMiddle(t *testing.T) { challenger.WithAlphabet(), challenger.WithPrivKey(sys.Cfg.Secrets.Mallory)) preimageHelper := disputeGameFactory.PreimageHelper(ctx) - ident := preimageHelper.UploadLargePreimage(ctx, preimage.MinPreimageSize, + ident := preimageHelper.UploadLargePreimage(ctx, testPreimageSize, preimage.WithReplacedCommitment(10, common.Hash{0xaa})) require.NotEqual(t, ident.Claimant, common.Address{}) @@ -60,7 +62,7 @@ func TestChallengeLargePreimages_ChallengeLast(t *testing.T) { challenger.WithAlphabet(), challenger.WithPrivKey(sys.Cfg.Secrets.Mallory)) preimageHelper := disputeGameFactory.PreimageHelper(ctx) - ident := preimageHelper.UploadLargePreimage(ctx, preimage.MinPreimageSize, + ident := preimageHelper.UploadLargePreimage(ctx, testPreimageSize, preimage.WithLastCommitment(common.Hash{0xaa})) require.NotEqual(t, ident.Claimant, common.Address{}) From 4ef678aa0869f1956e0117ab8a48955ff9cf3081 Mon Sep 17 00:00:00 2001 From: smartcontracts Date: Wed, 26 Feb 2025 09:08:31 -0600 Subject: [PATCH 016/130] feat: semgrep rules and check for reinitializer modifiers (#14534) Adds semgrep rules and a new golang contracts check for reinitializer modifiers. Important safety checks now that we are using upgrade functions. --- .circleci/config.yml | 2 + .semgrep/rules/sol-rules.yaml | 31 +- ...-rules.sol-safety-proper-initializer.t.sol | 21 +- ...s.sol-safety-proper-upgrade-function.t.sol | 64 +++ op-chain-ops/solc/types.go | 13 +- .../upgrade/v2_0_0/testdata/config.json | 2 +- packages/contracts-bedrock/justfile | 8 + .../scripts/checks/reinitializer/main.go | 119 +++++ .../scripts/checks/reinitializer/main_test.go | 497 ++++++++++++++++++ 9 files changed, 746 insertions(+), 11 deletions(-) create mode 100644 .semgrep/tests/sol-rules.sol-safety-proper-upgrade-function.t.sol create mode 100644 packages/contracts-bedrock/scripts/checks/reinitializer/main.go create mode 100644 packages/contracts-bedrock/scripts/checks/reinitializer/main_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 56bc77d444f..ac858b59b79 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -900,6 +900,8 @@ jobs: command: snapshots-check-no-build - run-contracts-check: command: interfaces-check-no-build + - run-contracts-check: + command: reinitializer-check-no-build - run-contracts-check: command: size-check - run-contracts-check: diff --git a/.semgrep/rules/sol-rules.yaml b/.semgrep/rules/sol-rules.yaml index 32fe684ccf3..2e97715f699 100644 --- a/.semgrep/rules/sol-rules.yaml +++ b/.semgrep/rules/sol-rules.yaml @@ -249,7 +249,7 @@ rules: - id: sol-safety-proper-initializer languages: [solidity] severity: ERROR - message: Proxied contracts must have an initialize function with the initializer modifier and external visibility + message: Proxied contracts must have an initialize function with the initializer or reinitializer modifier and external or public visibility patterns: - pattern-regex: "///\\s*@custom:proxied\\s+true(?P[\\s\\S]*)" - focus-metavariable: $CONTRACT @@ -261,7 +261,34 @@ rules: function initialize(...) external initializer { ... } + - pattern-not: | + function initialize(...) public initializer { + ... + } + - pattern-not: | + function initialize(...) external reinitializer(...) { + ... + } + - pattern-not: | + function initialize(...) public reinitializer(...) { + ... + } paths: exclude: - - packages/contracts-bedrock/src/L1/SystemConfig.sol - packages/contracts-bedrock/src/L1/SystemConfigInterop.sol + + - id: sol-safety-proper-upgrade-function + languages: [solidity] + severity: ERROR + message: Upgrade functions must be external and have the reinitializer modifier + patterns: + - pattern-regex: "///\\s*@custom:proxied\\s+true(?P[\\s\\S]*)" + - focus-metavariable: $CONTRACT + - pattern: | + function upgrade(...) { + ... + } + - pattern-not: | + function upgrade(...) external reinitializer(...) { + ... + } diff --git a/.semgrep/tests/sol-rules.sol-safety-proper-initializer.t.sol b/.semgrep/tests/sol-rules.sol-safety-proper-initializer.t.sol index a45a7d069d3..23ad245b1e9 100644 --- a/.semgrep/tests/sol-rules.sol-safety-proper-initializer.t.sol +++ b/.semgrep/tests/sol-rules.sol-safety-proper-initializer.t.sol @@ -32,13 +32,28 @@ contract SemgrepTest__sol_safety_proper_initializer { // ... } - // ruleid: sol-safety-proper-initializer - function initialize() external { + // ok: sol-safety-proper-initializer + function initialize() public initializer { + // ... + } + + // ok: sol-safety-proper-initializer + function initialize() external reinitializer(1) { + // ... + } + + // ok: sol-safety-proper-initializer + function initialize() external reinitializer(1) { + // ... + } + + // ok: sol-safety-proper-initializer + function initialize() public reinitializer(2) { // ... } // ruleid: sol-safety-proper-initializer - function initialize() public initializer { + function initialize() internal { // ... } diff --git a/.semgrep/tests/sol-rules.sol-safety-proper-upgrade-function.t.sol b/.semgrep/tests/sol-rules.sol-safety-proper-upgrade-function.t.sol new file mode 100644 index 00000000000..e8567b09939 --- /dev/null +++ b/.semgrep/tests/sol-rules.sol-safety-proper-upgrade-function.t.sol @@ -0,0 +1,64 @@ +// Semgrep tests for Solidity rules are defined in this file. +// Semgrep tests do not need to be valid Solidity code but should be syntactically correct so that +// Semgrep can parse them. You don't need to be able to *run* the code here but it should look like +// the code that you expect to catch with the rule. +// +// Semgrep testing 101 +// Use comments like "ruleid: " to assert that the rule catches the code. +// Use comments like "ok: " to assert that the rule does not catch the code. + +/// NOTE: Semgrep limitations mean that the rule for this check is defined as a relatively loose regex that searches the +/// remainder of the file after the `@custom:proxied` natspec tag is detected. This means that we must test the case +/// without this natspec tag BEFORE the case with the tag or the rule will apply to the remainder of the file. + +// If no proxied natspec, upgrade functions can have no upgrade modifier and be public or external +contract SemgrepTest__sol_safety_proper_upgrade_function_1 { + // ok: sol-safety-proper-upgrade-function + function upgrade() external { + // ... + } + + // ok: sol-safety-proper-upgrade-function + function upgrade() public { + // ... + } +} + +/// NOTE: the proxied natspec below is valid for all contracts after this one +/// @custom:proxied true +contract SemgrepTest__sol_safety_proper_upgrade_function_2 { + // ok: sol-safety-proper-upgrade-function + function upgrade() external reinitializer(1) { + // ... + } + + // ok: sol-safety-proper-upgrade-function + function upgrade() external reinitializer(1) { + // ... + } + + // ruleid: sol-safety-proper-upgrade-function + function upgrade() public reinitializer(2) { + // ... + } + + // ruleid: sol-safety-proper-upgrade-function + function upgrade() external initializer { + // ... + } + + // ruleid: sol-safety-proper-upgrade-function + function upgrade() public initializer { + // ... + } + + // ruleid: sol-safety-proper-upgrade-function + function upgrade() external { + // ... + } + + // ruleid: sol-safety-proper-upgrade-function + function upgrade() public { + // ... + } +} diff --git a/op-chain-ops/solc/types.go b/op-chain-ops/solc/types.go index f69416aadd4..d109231efff 100644 --- a/op-chain-ops/solc/types.go +++ b/op-chain-ops/solc/types.go @@ -201,11 +201,13 @@ type AstNode struct { Value interface{} `json:"value,omitempty"` // Other fields - Arguments []Expression `json:"arguments,omitempty"` - Condition *Expression `json:"condition,omitempty"` - TrueBody *AstBlock `json:"trueBody,omitempty"` - FalseBody *AstBlock `json:"falseBody,omitempty"` - Operator string `json:"operator,omitempty"` + ModifierName *Expression `json:"modifierName,omitempty"` + Modifiers []AstNode `json:"modifiers,omitempty"` + Arguments []Expression `json:"arguments,omitempty"` + Condition *Expression `json:"condition,omitempty"` + TrueBody *AstBlock `json:"trueBody,omitempty"` + FalseBody *AstBlock `json:"falseBody,omitempty"` + Operator string `json:"operator,omitempty"` } type AstBaseContract struct { @@ -259,6 +261,7 @@ type Expression struct { OverloadedDeclarations []int `json:"overloadedDeclarations,omitempty"` ReferencedDeclaration int `json:"referencedDeclaration,omitempty"` ArgumentTypes []AstTypeDescriptions `json:"argumentTypes,omitempty"` + Value interface{} `json:"value,omitempty"` } type ForgeArtifact struct { diff --git a/op-deployer/pkg/deployer/upgrade/v2_0_0/testdata/config.json b/op-deployer/pkg/deployer/upgrade/v2_0_0/testdata/config.json index 5f66fec5635..7ecb24c9bab 100644 --- a/op-deployer/pkg/deployer/upgrade/v2_0_0/testdata/config.json +++ b/op-deployer/pkg/deployer/upgrade/v2_0_0/testdata/config.json @@ -8,4 +8,4 @@ "absolutePrestate": "0x0000000000000000000000000000000000000000000000000000000000000abc" } ] -} \ No newline at end of file +} diff --git a/packages/contracts-bedrock/justfile b/packages/contracts-bedrock/justfile index 4e457a9b866..9069958da79 100644 --- a/packages/contracts-bedrock/justfile +++ b/packages/contracts-bedrock/justfile @@ -212,6 +212,14 @@ interfaces-check-no-build: # artifacts can cause the script to detect issues incorrectly. interfaces-check: clean build interfaces-check-no-build +# Checks that all upgrade/initialize funcitons have proper reinitializer modifiers. +reinitializer-check: build-source reinitializer-check-no-build + +# Checks that all upgrade/initialize funcitons have proper reinitializer modifiers. +# Does not build contracts. +reinitializer-check-no-build: + go run ./scripts/checks/reinitializer + # Checks that the size of the contracts is within the limit. size-check: forge build --sizes --skip "/**/test/**" --skip "/**/scripts/**" diff --git a/packages/contracts-bedrock/scripts/checks/reinitializer/main.go b/packages/contracts-bedrock/scripts/checks/reinitializer/main.go new file mode 100644 index 00000000000..c41e403b985 --- /dev/null +++ b/packages/contracts-bedrock/scripts/checks/reinitializer/main.go @@ -0,0 +1,119 @@ +package main + +import ( + "fmt" + "os" + "strconv" + "strings" + + "github.com/ethereum-optimism/optimism/op-chain-ops/solc" + "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/scripts/checks/common" +) + +func main() { + if _, err := common.ProcessFilesGlob( + []string{"forge-artifacts/**/*.json"}, + []string{}, + processFile, + ); err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } +} + +func processFile(path string) (*common.Void, []error) { + artifact, err := common.ReadForgeArtifact(path) + if err != nil { + return nil, []error{err} + } + + if err := checkArtifact(artifact); err != nil { + return nil, []error{err} + } + + return nil, nil +} + +func checkArtifact(artifact *solc.ForgeArtifact) error { + // Skip interfaces. + if strings.HasPrefix(artifact.Ast.AbsolutePath, "interfaces/") { + return nil + } + + // Skip if we have no upgrade function. + upgradeFn := getFunctionByName(artifact, "upgrade") + if upgradeFn == nil { + return nil + } + + // We can have an upgrade function without an initialize function. + initializeFn := getFunctionByName(artifact, "initialize") + if initializeFn == nil { + return nil + } + + // Grab the reinitializer value from the upgrade function. + upgradeFnReinitializerValue, err := getReinitializerValue(upgradeFn) + if err != nil { + return fmt.Errorf("error getting reinitializer value from upgrade function: %w", err) + } + + // Grab the reinitializer value from the initialize function. + initializeFnReinitializerValue, err := getReinitializerValue(initializeFn) + if err != nil { + return fmt.Errorf("error getting reinitializer value from initialize function: %w", err) + } + + // If the reinitializer values are different, return an error. + if upgradeFnReinitializerValue != initializeFnReinitializerValue { + return fmt.Errorf("upgrade function and initialize function have different reinitializer values") + } + + return nil +} + +func getContractDefinition(artifact *solc.ForgeArtifact) *solc.AstNode { + for _, node := range artifact.Ast.Nodes { + if node.NodeType == "ContractDefinition" { + return &node + } + } + return nil +} + +func getFunctionByName(artifact *solc.ForgeArtifact, name string) *solc.AstNode { + contract := getContractDefinition(artifact) + if contract == nil { + return nil + } + for _, node := range contract.Nodes { + if node.NodeType == "FunctionDefinition" { + if node.Name == name { + return &node + } + } + } + return nil +} + +func getReinitializerValue(node *solc.AstNode) (uint64, error) { + if node.Modifiers == nil { + return 0, fmt.Errorf("no modifiers found") + } + + for _, modifier := range node.Modifiers { + if modifier.ModifierName.Name == "reinitializer" { + valStr, ok := modifier.Arguments[0].Value.(string) + if !ok { + return 0, fmt.Errorf("reinitializer value is not a string") + } + val, err := strconv.Atoi(valStr) + if err != nil { + return 0, fmt.Errorf("reinitializer value is not an integer") + } + return uint64(val), nil + } + } + + return 0, fmt.Errorf("reinitializer modifier not found") +} diff --git a/packages/contracts-bedrock/scripts/checks/reinitializer/main_test.go b/packages/contracts-bedrock/scripts/checks/reinitializer/main_test.go new file mode 100644 index 00000000000..93ca35173b6 --- /dev/null +++ b/packages/contracts-bedrock/scripts/checks/reinitializer/main_test.go @@ -0,0 +1,497 @@ +package main + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-chain-ops/solc" +) + +func TestGetContractDefinition(t *testing.T) { + tests := []struct { + name string + artifact *solc.ForgeArtifact + want *solc.AstNode + }{ + { + name: "Find contract", + artifact: &solc.ForgeArtifact{ + Ast: solc.Ast{ + Nodes: []solc.AstNode{ + {NodeType: "ContractDefinition", Name: "Test"}, + }, + }, + }, + want: &solc.AstNode{NodeType: "ContractDefinition", Name: "Test"}, + }, + { + name: "No contract", + artifact: &solc.ForgeArtifact{ + Ast: solc.Ast{ + Nodes: []solc.AstNode{ + {NodeType: "PragmaDirective"}, + }, + }, + }, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getContractDefinition(tt.artifact) + if (got == nil) != (tt.want == nil) { + t.Errorf("getContractDefinition() = %v, want %v", got, tt.want) + } + if got != nil && got.NodeType != tt.want.NodeType { + t.Errorf("getContractDefinition() NodeType = %v, want %v", got.NodeType, tt.want.NodeType) + } + }) + } +} + +func TestGetFunctionByName(t *testing.T) { + tests := []struct { + name string + artifact *solc.ForgeArtifact + functionName string + want *solc.AstNode + }{ + { + name: "Find function", + artifact: &solc.ForgeArtifact{ + Ast: solc.Ast{ + Nodes: []solc.AstNode{ + { + NodeType: "ContractDefinition", + Nodes: []solc.AstNode{ + {NodeType: "FunctionDefinition", Name: "initialize"}, + {NodeType: "FunctionDefinition", Name: "upgrade"}, + }, + }, + }, + }, + }, + functionName: "initialize", + want: &solc.AstNode{NodeType: "FunctionDefinition", Name: "initialize"}, + }, + { + name: "Function not found", + artifact: &solc.ForgeArtifact{ + Ast: solc.Ast{ + Nodes: []solc.AstNode{ + { + NodeType: "ContractDefinition", + Nodes: []solc.AstNode{ + {NodeType: "FunctionDefinition", Name: "otherFunction"}, + }, + }, + }, + }, + }, + functionName: "initialize", + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getFunctionByName(tt.artifact, tt.functionName) + if (got == nil) != (tt.want == nil) { + t.Errorf("getFunctionByName() = %v, want %v", got, tt.want) + } + if got != nil && got.Name != tt.want.Name { + t.Errorf("getFunctionByName() Name = %v, want %v", got.Name, tt.want.Name) + } + }) + } +} + +func TestGetReinitializerValue(t *testing.T) { + tests := []struct { + name string + node *solc.AstNode + want uint64 + wantErr bool + }{ + { + name: "Valid reinitializer", + node: &solc.AstNode{ + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{Value: "2"}}, + }, + }, + }, + want: 2, + wantErr: false, + }, + { + name: "No modifiers", + node: &solc.AstNode{}, + want: 0, + wantErr: true, + }, + { + name: "No reinitializer modifier", + node: &solc.AstNode{ + Modifiers: []solc.AstNode{ + {ModifierName: &solc.Expression{Name: "onlyOwner"}}, + }, + }, + want: 0, + wantErr: true, + }, + { + name: "Invalid reinitializer value", + node: &solc.AstNode{ + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{Value: "invalid"}}, + }, + }, + }, + want: 0, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getReinitializerValue(tt.node) + if (err != nil) != tt.wantErr { + t.Errorf("getReinitializerValue() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("getReinitializerValue() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestCheckArtifact(t *testing.T) { + tests := []struct { + name string + artifact *solc.ForgeArtifact + wantErr bool + }{ + { + name: "Matching reinitializer values", + artifact: &solc.ForgeArtifact{ + Ast: solc.Ast{ + Nodes: []solc.AstNode{ + { + NodeType: "ContractDefinition", + Nodes: []solc.AstNode{ + { + NodeType: "FunctionDefinition", + Name: "initialize", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{Value: "2"}}, + }, + }, + }, + { + NodeType: "FunctionDefinition", + Name: "upgrade", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{Value: "2"}}, + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "Mismatched reinitializer values", + artifact: &solc.ForgeArtifact{ + Ast: solc.Ast{ + Nodes: []solc.AstNode{ + { + NodeType: "ContractDefinition", + Nodes: []solc.AstNode{ + { + NodeType: "FunctionDefinition", + Name: "initialize", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{Value: "1"}}, + }, + }, + }, + { + NodeType: "FunctionDefinition", + Name: "upgrade", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{Value: "2"}}, + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "No upgrade function", + artifact: &solc.ForgeArtifact{ + Ast: solc.Ast{ + Nodes: []solc.AstNode{ + { + NodeType: "ContractDefinition", + Nodes: []solc.AstNode{ + { + NodeType: "FunctionDefinition", + Name: "initialize", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{Value: "1"}}, + }, + }, + }, + { + NodeType: "FunctionDefinition", + Name: "someOtherFunction", + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "No initialize function", + artifact: &solc.ForgeArtifact{ + Ast: solc.Ast{ + Nodes: []solc.AstNode{ + { + NodeType: "ContractDefinition", + Nodes: []solc.AstNode{ + { + NodeType: "FunctionDefinition", + Name: "someOtherFunction", + }, + { + NodeType: "FunctionDefinition", + Name: "upgrade", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{Value: "2"}}, + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "Error getting reinitializer value from upgrade function - no modifiers", + artifact: &solc.ForgeArtifact{ + Ast: solc.Ast{ + Nodes: []solc.AstNode{ + { + NodeType: "ContractDefinition", + Nodes: []solc.AstNode{ + { + NodeType: "FunctionDefinition", + Name: "initialize", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{Value: "1"}}, + }, + }, + }, + { + NodeType: "FunctionDefinition", + Name: "upgrade", + // No modifiers + }, + }, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "Error getting reinitializer value from initialize function - no modifiers", + artifact: &solc.ForgeArtifact{ + Ast: solc.Ast{ + Nodes: []solc.AstNode{ + { + NodeType: "ContractDefinition", + Nodes: []solc.AstNode{ + { + NodeType: "FunctionDefinition", + Name: "initialize", + // No modifiers + }, + { + NodeType: "FunctionDefinition", + Name: "upgrade", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{Value: "2"}}, + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "Error getting reinitializer value - no reinitializer modifier", + artifact: &solc.ForgeArtifact{ + Ast: solc.Ast{ + Nodes: []solc.AstNode{ + { + NodeType: "ContractDefinition", + Nodes: []solc.AstNode{ + { + NodeType: "FunctionDefinition", + Name: "initialize", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "someOtherModifier"}, + Arguments: []solc.Expression{{Value: "1"}}, + }, + }, + }, + { + NodeType: "FunctionDefinition", + Name: "upgrade", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{Value: "2"}}, + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "Error getting reinitializer value - non-integer value", + artifact: &solc.ForgeArtifact{ + Ast: solc.Ast{ + Nodes: []solc.AstNode{ + { + NodeType: "ContractDefinition", + Nodes: []solc.AstNode{ + { + NodeType: "FunctionDefinition", + Name: "initialize", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{Value: "not-an-integer"}}, + }, + }, + }, + { + NodeType: "FunctionDefinition", + Name: "upgrade", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{Value: "2"}}, + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "Error getting reinitializer value - non-string value", + artifact: &solc.ForgeArtifact{ + Ast: solc.Ast{ + Nodes: []solc.AstNode{ + { + NodeType: "ContractDefinition", + Nodes: []solc.AstNode{ + { + NodeType: "FunctionDefinition", + Name: "initialize", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{Value: 2}}, // Integer instead of string + }, + }, + }, + { + NodeType: "FunctionDefinition", + Name: "upgrade", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{Value: "2"}}, + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: true, + }, + { + name: "No contract definition", + artifact: &solc.ForgeArtifact{ + Ast: solc.Ast{ + Nodes: []solc.AstNode{ + { + NodeType: "SomeOtherNodeType", // Not a ContractDefinition + Nodes: []solc.AstNode{}, + }, + }, + }, + }, + wantErr: false, // Should return nil without error + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := checkArtifact(tt.artifact) + if (err != nil) != tt.wantErr { + t.Errorf("checkArtifact() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} From aa09c2271bc4e61d0ce4e21a8947e4070129090a Mon Sep 17 00:00:00 2001 From: Rebustron Date: Wed, 26 Feb 2025 18:25:13 +0300 Subject: [PATCH 017/130] Update 404 link VERSIONING.md (#14519) --- packages/contracts-bedrock/meta/VERSIONING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/meta/VERSIONING.md b/packages/contracts-bedrock/meta/VERSIONING.md index 204215660ec..c1acacd9158 100644 --- a/packages/contracts-bedrock/meta/VERSIONING.md +++ b/packages/contracts-bedrock/meta/VERSIONING.md @@ -67,7 +67,7 @@ Versioning for monorepo releases works as follows: ## Optimism Contracts Manager (OPCM) Versioning -The [OPCM](https://github.com/ethereum-optimism/optimism/blob/main/packages/contracts-bedrock/src/L1/OPContractsManager.sol) is the contract that manages the deployment of all contracts on L1. Its version is the same as the [associated monorepo contracts release](./VERSIONING.md#monorepo-contracts-release-versioning). +The [OPCM](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L1/OPContractsManager.sol) is the contract that manages the deployment of all contracts on L1. Its version is the same as the [associated monorepo contracts release](./VERSIONING.md#monorepo-contracts-release-versioning). The `OPCM` is the source of truth for the contracts that belong in a release, available as on-chain addresses by querying [the `getImplementations` function](https://github.com/ethereum-optimism/optimism/blob/4c8764f0453e141555846d8c9dd2af9edbc1d014/packages/contracts-bedrock/src/L1/OPContractsManager.sol#L1061). From aabf3fe054c5979d6a0008f26fe1a73fdf3aad9f Mon Sep 17 00:00:00 2001 From: Sebastian Stammler Date: Wed, 26 Feb 2025 19:45:48 +0100 Subject: [PATCH 018/130] all: update op-geth to v1.101500.1 (#14500) * all: update op-geth to v1.101500.1 * op-batcher,op-e2e/system: Take floor data gas into account, fix AutoDA test Fixes #14513 * Fix EIP-2935 implementation and tests * op-program/client/l2: Fix chainContext.Config() to return actual cfg * op-program/client/l2/test: Nilify chain config blob schedule * update op-geth v1.101500.2-synctest.4 * Replace IntrinsicGas with FloorDataGas for calldata estimations * Remove static MaxBlobsPerBlobTx constant * update op-geth/v1.101500.2-rc.1 * revert usage of params.HistoryServeWindow --- cannon/mipsevm/testutil/evm.go | 10 +++- go.mod | 2 +- go.sum | 4 +- op-batcher/batcher/channel_test.go | 2 +- op-batcher/batcher/config.go | 14 ++++-- op-batcher/batcher/config_test.go | 11 +++-- op-batcher/batcher/driver.go | 36 ++------------ op-batcher/batcher/test_batch_submitter.go | 2 +- op-chain-ops/cmd/check-derivation/main.go | 6 +-- op-chain-ops/cmd/check-ecotone/main.go | 4 +- op-chain-ops/cmd/op-simulate/main.go | 12 +++-- op-chain-ops/foundry/allocs_test.go | 6 +-- op-chain-ops/genesis/genesis.go | 2 + op-chain-ops/script/cheatcodes_environment.go | 6 +-- op-chain-ops/script/forking/forking_test.go | 14 +++--- op-chain-ops/script/forking/state.go | 8 +++- op-chain-ops/script/script.go | 12 +++-- op-deployer/pkg/deployer/broadcaster/keyed.go | 14 +++++- op-e2e/actions/batcher/eip4844_test.go | 11 +++-- op-e2e/actions/derivation/reorg_test.go | 6 +-- op-e2e/actions/helpers/l1_miner.go | 10 +--- op-e2e/actions/helpers/l2_batcher.go | 15 +++--- op-e2e/actions/safedb/safedb_test.go | 2 +- op-e2e/actions/sync/sync_test.go | 2 +- op-e2e/actions/upgrades/dencun_fork_test.go | 2 +- op-e2e/actions/upgrades/isthmus_fork_test.go | 7 ++- op-e2e/system/da/eip4844_test.go | 28 ++++++----- op-e2e/system/fees/l1info_test.go | 3 +- .../derive/isthmus_upgrade_transactions.go | 10 ++-- .../isthmus_upgrade_transactions_test.go | 3 +- op-program/client/l2/db_test.go | 2 +- op-program/client/l2/fast_canon.go | 7 ++- op-program/client/l2/test/miner.go | 2 + op-service/eth/blob.go | 48 ++++++++++++++++--- op-service/eth/blob_test.go | 20 ++++++++ op-service/eth/block_info.go | 5 +- op-service/predeploys/eip2935.go | 11 +++-- op-service/txmgr/estimator.go | 4 +- op-service/txmgr/txmgr.go | 3 +- op-service/txmgr/txmgr_test.go | 7 +-- op-wheel/cheat/cheat.go | 7 +-- 41 files changed, 224 insertions(+), 156 deletions(-) diff --git a/cannon/mipsevm/testutil/evm.go b/cannon/mipsevm/testutil/evm.go index 745f7c662b2..b986711c2fc 100644 --- a/cannon/mipsevm/testutil/evm.go +++ b/cannon/mipsevm/testutil/evm.go @@ -98,7 +98,10 @@ func NewEVMEnv(contracts *ContractMetadata) (*vm.EVM, *state.StateDB) { cancunActivation := *chainCfg.ShanghaiTime + 10 chainCfg.CancunTime = &cancunActivation offsetBlocks := uint64(1000) // blocks after cancun fork - bc := &testChain{startTime: *chainCfg.CancunTime + offsetBlocks*12} + bc := &testChain{ + config: chainCfg, + startTime: *chainCfg.CancunTime + offsetBlocks*12, + } header := bc.GetHeader(common.Hash{}, 17034870+offsetBlocks) db := rawdb.NewMemoryDatabase() statedb := state.NewDatabase(triedb.NewDatabase(db, nil), nil) @@ -129,6 +132,7 @@ func NewEVMEnv(contracts *ContractMetadata) (*vm.EVM, *state.StateDB) { } type testChain struct { + config *params.ChainConfig startTime uint64 } @@ -136,6 +140,10 @@ func (d *testChain) Engine() consensus.Engine { return ethash.NewFullFaker() } +func (d *testChain) Config() *params.ChainConfig { + return d.config +} + func (d *testChain) GetHeader(h common.Hash, n uint64) *types.Header { parentHash := common.Hash{0: 0xff} binary.BigEndian.PutUint64(parentHash[1:], n-1) diff --git a/go.mod b/go.mod index dd274fdf4e1..4d839b84604 100644 --- a/go.mod +++ b/go.mod @@ -256,7 +256,7 @@ require ( rsc.io/tmplfunc v0.0.3 // indirect ) -replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101500.1-rc.1 +replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101500.2-rc.1 //replace github.com/ethereum/go-ethereum => ../op-geth diff --git a/go.sum b/go.sum index c9d862d3309..a6dd712478e 100644 --- a/go.sum +++ b/go.sum @@ -192,8 +192,8 @@ github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/u github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs= -github.com/ethereum-optimism/op-geth v1.101500.1-rc.1 h1:nvfeMbZdXgrNI5EZtIUVZqql9Jms3F2Q5CWHz3Catr4= -github.com/ethereum-optimism/op-geth v1.101500.1-rc.1/go.mod h1:OMpyVMMy5zpAAHlR5s/aGbXRk+7cIKczUEIJj54APbY= +github.com/ethereum-optimism/op-geth v1.101500.2-rc.1 h1:CCQtyKKobjMbK9jYd676YbH6Xcvntdn6jIOxFTCb5B8= +github.com/ethereum-optimism/op-geth v1.101500.2-rc.1/go.mod h1:OMpyVMMy5zpAAHlR5s/aGbXRk+7cIKczUEIJj54APbY= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250205201532-8ff62ada16e1 h1:OqRYDcjiOx5QCLn5krpd3BK1CW+VfSZx7YIa6zY9ePE= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250205201532-8ff62ada16e1/go.mod h1:NZ816PzLU1TLv1RdAvYAb6KWOj4Zm5aInT0YpDVml2Y= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= diff --git a/op-batcher/batcher/channel_test.go b/op-batcher/batcher/channel_test.go index b36ce9311bc..0e1365eceec 100644 --- a/op-batcher/batcher/channel_test.go +++ b/op-batcher/batcher/channel_test.go @@ -169,7 +169,7 @@ func TestChannel_NextTxData_singleFrameTx(t *testing.T) { func TestChannel_NextTxData_multiFrameTx(t *testing.T) { require := require.New(t) - const n = eth.MaxBlobsPerBlobTx + const n = 6 lgr := testlog.Logger(t, log.LevelWarn) ch, err := newChannelWithChannelOut(lgr, metrics.NoopMetrics, ChannelConfig{ UseBlobs: true, diff --git a/op-batcher/batcher/config.go b/op-batcher/batcher/config.go index 048e879eb0c..c712b74bc8e 100644 --- a/op-batcher/batcher/config.go +++ b/op-batcher/batcher/config.go @@ -6,13 +6,13 @@ import ( "strings" "time" + "github.com/ethereum/go-ethereum/params" "github.com/urfave/cli/v2" altda "github.com/ethereum-optimism/optimism/op-alt-da" "github.com/ethereum-optimism/optimism/op-batcher/compressor" "github.com/ethereum-optimism/optimism/op-batcher/flags" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" - "github.com/ethereum-optimism/optimism/op-service/eth" oplog "github.com/ethereum-optimism/optimism/op-service/log" opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" "github.com/ethereum-optimism/optimism/op-service/oppprof" @@ -20,6 +20,10 @@ import ( "github.com/ethereum-optimism/optimism/op-service/txmgr" ) +// Current max blobs const, irrespective of active fork, is that of the Prague +// blob config. +var maxBlobsPerBlock = params.DefaultPragueBlobConfig.Max + type CLIConfig struct { // L1EthRpc is the HTTP provider URL for L1. L1EthRpc string @@ -155,9 +159,11 @@ func (c *CLIConfig) Check() error { if !flags.ValidDataAvailabilityType(c.DataAvailabilityType) { return fmt.Errorf("unknown data availability type: %q", c.DataAvailabilityType) } - // we want to enforce it for both blobs and auto - if c.DataAvailabilityType != flags.CalldataType && c.TargetNumFrames > eth.MaxBlobsPerBlobTx { - return fmt.Errorf("too many frames for blob transactions, max %d", eth.MaxBlobsPerBlobTx) + // Most chains' L1s still have only Cancun active, but we don't want to + // overcomplicate this check with a dynamic L1 query, so we just use maxBlobsPerBlock. + // We want to check for both, blobs and auto da-type. + if c.DataAvailabilityType != flags.CalldataType && c.TargetNumFrames > maxBlobsPerBlock { + return fmt.Errorf("too many frames for blob transactions, max %d", maxBlobsPerBlock) } if err := c.MetricsConfig.Check(); err != nil { return err diff --git a/op-batcher/batcher/config_test.go b/op-batcher/batcher/config_test.go index 19d4a5c9ec0..96f5fdaf419 100644 --- a/op-batcher/batcher/config_test.go +++ b/op-batcher/batcher/config_test.go @@ -9,12 +9,12 @@ import ( "github.com/ethereum-optimism/optimism/op-batcher/compressor" "github.com/ethereum-optimism/optimism/op-batcher/flags" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" - "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/log" "github.com/ethereum-optimism/optimism/op-service/metrics" "github.com/ethereum-optimism/optimism/op-service/oppprof" "github.com/ethereum-optimism/optimism/op-service/rpc" "github.com/ethereum-optimism/optimism/op-service/txmgr" + "github.com/ethereum/go-ethereum/params" "github.com/stretchr/testify/require" ) @@ -50,6 +50,9 @@ func TestValidBatcherConfig(t *testing.T) { require.NoError(t, cfg.Check(), "valid config should pass the check function") } +// Set current +var maxBlobsPerBlock = params.DefaultPragueBlobConfig.Max + func TestBatcherConfig(t *testing.T) { tests := []struct { name string @@ -102,12 +105,12 @@ func TestBatcherConfig(t *testing.T) { errString: "TargetNumFrames must be at least 1", }, { - name: fmt.Sprintf("larger %d TargetNumFrames for blobs", eth.MaxBlobsPerBlobTx), + name: fmt.Sprintf("larger %d TargetNumFrames for blobs", maxBlobsPerBlock), override: func(c *batcher.CLIConfig) { - c.TargetNumFrames = eth.MaxBlobsPerBlobTx + 1 + c.TargetNumFrames = maxBlobsPerBlock + 1 c.DataAvailabilityType = flags.BlobsType }, - errString: fmt.Sprintf("too many frames for blob transactions, max %d", eth.MaxBlobsPerBlobTx), + errString: fmt.Sprintf("too many frames for blob transactions, max %d", maxBlobsPerBlock), }, { name: "invalid compr ratio for ratio compressor", diff --git a/op-batcher/batcher/driver.go b/op-batcher/batcher/driver.go index de935cce74c..3bbeb5f1706 100644 --- a/op-batcher/batcher/driver.go +++ b/op-batcher/batcher/driver.go @@ -1,12 +1,10 @@ package batcher import ( - "bytes" "context" "errors" "fmt" "io" - "math" "math/big" _ "net/http/pprof" "sync" @@ -892,45 +890,17 @@ type TxSender[T any] interface { // sendTx uses the txmgr queue to send the given transaction candidate after setting its // gaslimit. It will block if the txmgr queue has reached its MaxPendingTransactions limit. func (l *BatchSubmitter) sendTx(txdata txData, isCancel bool, candidate *txmgr.TxCandidate, queue TxSender[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) { - intrinsicGas, err := core.IntrinsicGas(candidate.TxData, nil, nil, false, true, true, false) + floorDataGas, err := core.FloorDataGas(candidate.TxData) if err != nil { - // we log instead of return an error here because txmgr can do its own gas estimation - l.Log.Error("Failed to calculate intrinsic gas", "err", err) + // We log instead of return an error here because the txmgr will do its own gas estimation. + l.Log.Warn("Failed to calculate floor data gas", "err", err) } else { - candidate.GasLimit = intrinsicGas - } - - floorDataGas, err := floorDataGas(candidate.TxData) - if err != nil { - l.Log.Warn("Failed to compute FloorDataGas: %v, continuing with intrinsic gas.") - } else if floorDataGas > candidate.GasLimit { - l.Log.Debug("Bumping gas limit to floor data gas", "intrinsic_gas", intrinsicGas, "floor_data_gas", floorDataGas) candidate.GasLimit = floorDataGas } queue.Send(txRef{id: txdata.ID(), isCancel: isCancel, isBlob: txdata.asBlob}, *candidate, receiptsCh) } -// Copypaste from upstream geth -// TODO remove in #14500 -func floorDataGas(data []byte) (uint64, error) { - TxTokenPerNonZeroByte := uint64(4) - TxGas := uint64(21000) - TxCostFloorPerToken := uint64(10) - - var ( - z = uint64(bytes.Count(data, []byte{0})) - nz = uint64(len(data)) - z - tokens = nz*TxTokenPerNonZeroByte + z - ) - // Check for overflow - if (math.MaxUint64-TxGas)/TxCostFloorPerToken < tokens { - return 0, core.ErrGasUintOverflow - } - // Minimum gas required for a transaction based on its data tokens (EIP-7623). - return TxGas + tokens*TxCostFloorPerToken, nil -} - func (l *BatchSubmitter) blobTxCandidate(data txData) (*txmgr.TxCandidate, error) { blobs, err := data.Blobs() if err != nil { diff --git a/op-batcher/batcher/test_batch_submitter.go b/op-batcher/batcher/test_batch_submitter.go index 93083aa0dc6..e3e17f1542e 100644 --- a/op-batcher/batcher/test_batch_submitter.go +++ b/op-batcher/batcher/test_batch_submitter.go @@ -33,7 +33,7 @@ func (l *TestBatchSubmitter) JamTxPool(ctx context.Context) error { } else if candidate, err = l.blobTxCandidate(emptyTxData); err != nil { return err } - if candidate.GasLimit, err = core.IntrinsicGas(candidate.TxData, nil, nil, false, true, true, false); err != nil { + if candidate.GasLimit, err = core.FloorDataGas(candidate.TxData); err != nil { return err } diff --git a/op-chain-ops/cmd/check-derivation/main.go b/op-chain-ops/cmd/check-derivation/main.go index 08d52cca6a2..71c0fec1b9a 100644 --- a/op-chain-ops/cmd/check-derivation/main.go +++ b/op-chain-ops/cmd/check-derivation/main.go @@ -225,7 +225,7 @@ func getRandomSignedTransaction(ctx context.Context, ethClient *ethclient.Client var txData types.TxData switch txType { case types.LegacyTxType: - gasLimit, err := core.IntrinsicGas(data, nil, nil, false, true, true, false) + gasLimit, err := core.FloorDataGas(data) if err != nil { return nil, fmt.Errorf("failed to get intrinsicGas: %w", err) } @@ -242,7 +242,7 @@ func getRandomSignedTransaction(ctx context.Context, ethClient *ethclient.Client Address: randomAddress, StorageKeys: []common.Hash{common.HexToHash("0x1234")}, }} - gasLimit, err := core.IntrinsicGas(data, accessList, nil, false, true, true, false) + gasLimit, err := core.FloorDataGas(data) if err != nil { return nil, fmt.Errorf("failed to get intrinsicGas: %w", err) } @@ -257,7 +257,7 @@ func getRandomSignedTransaction(ctx context.Context, ethClient *ethclient.Client Data: data, } case types.DynamicFeeTxType: - gasLimit, err := core.IntrinsicGas(data, nil, nil, false, true, true, false) + gasLimit, err := core.FloorDataGas(data) if err != nil { return nil, fmt.Errorf("failed to get intrinsicGas: %w", err) } diff --git a/op-chain-ops/cmd/check-ecotone/main.go b/op-chain-ops/cmd/check-ecotone/main.go index 58a11e9ce1a..b49bb0de331 100644 --- a/op-chain-ops/cmd/check-ecotone/main.go +++ b/op-chain-ops/cmd/check-ecotone/main.go @@ -17,7 +17,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" @@ -512,7 +511,8 @@ func checkBlobTxDenial(ctx context.Context, env *actionEnv) error { if latestHeader.ExcessBlobGas == nil { return fmt.Errorf("the L1 block %s (time %d) is not ecotone yet", latestHeader.Hash(), latestHeader.Time) } - blobBaseFee := eip4844.CalcBlobFee(*latestHeader.ExcessBlobGas) + + blobBaseFee := eth.CalcBlobFeeDefault(latestHeader) blobFeeCap := new(uint256.Int).Mul(uint256.NewInt(2), uint256.MustFromBig(blobBaseFee)) if blobFeeCap.Lt(uint256.NewInt(params.GWei)) { // ensure we meet 1 gwei geth tx-pool minimum blobFeeCap = uint256.NewInt(params.GWei) diff --git a/op-chain-ops/cmd/op-simulate/main.go b/op-chain-ops/cmd/op-simulate/main.go index 9c67c9529f1..5c7d95feb2d 100644 --- a/op-chain-ops/cmd/op-simulate/main.go +++ b/op-chain-ops/cmd/op-simulate/main.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/beacon" + "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" gstate "github.com/ethereum/go-ethereum/core/state" @@ -190,7 +191,7 @@ func fetchChainConfig(ctx context.Context, cl *rpc.Client) (*params.ChainConfig, } // if not already recognized, then fetch the chain config manually var config params.ChainConfig - if err := cl.CallContext(ctx, &config, "eth_chainConfig"); err != nil { + if err := cl.CallContext(ctx, &config, "debug_chainConfig"); err != nil { return nil, fmt.Errorf("failed to retrieve chain config: %w", err) } return &config, nil @@ -234,6 +235,7 @@ func readDump(prestatePath string) (map[common.Address]DumpAccount, error) { type simChainContext struct { eng consensus.Engine head *types.Header + cfg *params.ChainConfig } func (d *simChainContext) Engine() consensus.Engine { @@ -247,6 +249,10 @@ func (d *simChainContext) GetHeader(h common.Hash, n uint64) *types.Header { panic(fmt.Errorf("header retrieval not supported, cannot fetch %s %d", h, n)) } +func (d *simChainContext) Config() *params.ChainConfig { + return d.cfg +} + func simulate(ctx context.Context, logger log.Logger, conf *params.ChainConfig, prestatePath string, tx *types.Transaction, header *types.Header, doProfile bool) error { memDB := rawdb.NewMemoryDatabase() @@ -262,7 +268,7 @@ func simulate(ctx context.Context, logger log.Logger, conf *params.ChainConfig, for addr, acc := range dump { state.CreateAccount(addr) state.SetBalance(addr, uint256.MustFromBig((*big.Int)(&acc.Balance)), tracing.BalanceChangeUnspecified) - state.SetNonce(addr, acc.Nonce) + state.SetNonce(addr, acc.Nonce, tracing.NonceChangeUnspecified) state.SetCode(addr, acc.Code) state.SetStorage(addr, acc.Storage) } @@ -284,7 +290,7 @@ func simulate(ctx context.Context, logger log.Logger, conf *params.ChainConfig, state.Prepare(rules, sender, header.Coinbase, tx.To(), precompiles, tx.AccessList()) state.SetTxContext(tx.Hash(), 0) - cCtx := &simChainContext{eng: beacon.NewFaker(), head: header} + cCtx := &simChainContext{eng: beacon.New(ethash.NewFaker()), head: header, cfg: conf} gp := core.GasPool(tx.Gas()) usedGas := uint64(0) vmConfig := vm.Config{} diff --git a/op-chain-ops/foundry/allocs_test.go b/op-chain-ops/foundry/allocs_test.go index 4bb9ed4d4b5..8fdeee6da0d 100644 --- a/op-chain-ops/foundry/allocs_test.go +++ b/op-chain-ops/foundry/allocs_test.go @@ -40,19 +40,19 @@ func TestForgeAllocs_FromState(t *testing.T) { alice := common.HexToAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266") st.CreateAccount(alice) st.SetBalance(alice, uint256.NewInt(123), tracing.BalanceChangeUnspecified) - st.SetNonce(alice, 42) + st.SetNonce(alice, 42, tracing.NonceChangeUnspecified) bob := common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8") st.CreateAccount(bob) st.CreateContract(bob) st.SetBalance(bob, uint256.NewInt(100), tracing.BalanceChangeUnspecified) - st.SetNonce(bob, 1) + st.SetNonce(bob, 1, tracing.NonceChangeUnspecified) st.SetState(bob, common.Hash{0: 0x42}, common.Hash{0: 7}) contract := common.HexToAddress("0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC") st.CreateAccount(contract) st.CreateContract(contract) - st.SetNonce(contract, 30) + st.SetNonce(contract, 30, tracing.NonceChangeUnspecified) st.SetBalance(contract, uint256.NewInt(0), tracing.BalanceChangeUnspecified) st.SetCode(contract, []byte{10, 11, 12, 13, 14}) diff --git a/op-chain-ops/genesis/genesis.go b/op-chain-ops/genesis/genesis.go index 5607dd182ab..091adf15c58 100644 --- a/op-chain-ops/genesis/genesis.go +++ b/op-chain-ops/genesis/genesis.go @@ -149,6 +149,8 @@ func NewL1Genesis(config *DeployConfig) (*core.Genesis, error) { // To enable post-Merge consensus at genesis MergeNetsplitBlock: big.NewInt(0), TerminalTotalDifficulty: big.NewInt(0), + // use default Ethereum prod blob schedules + BlobScheduleConfig: params.DefaultBlobSchedule, } gasLimit := config.L1GenesisBlockGasLimit diff --git a/op-chain-ops/script/cheatcodes_environment.go b/op-chain-ops/script/cheatcodes_environment.go index 55584c835db..9d48e4fb5a0 100644 --- a/op-chain-ops/script/cheatcodes_environment.go +++ b/op-chain-ops/script/cheatcodes_environment.go @@ -136,7 +136,7 @@ type Log struct { // SetNonce implements https://book.getfoundry.sh/cheatcodes/set-nonce func (c *CheatCodesPrecompile) SetNonce(account common.Address, nonce uint64) { - c.h.state.SetNonce(account, nonce) + c.h.state.SetNonce(account, nonce, tracing.NonceChangeUnspecified) } // GetNonce implements https://book.getfoundry.sh/cheatcodes/get-nonce @@ -149,9 +149,9 @@ func (c *CheatCodesPrecompile) ResetNonce(addr common.Address) { // Resets nonce to 0 if EOA, or 1 if contract. // In scripts often set code to empty first when using it, it then becomes 0. if c.h.state.GetCodeHash(addr) == types.EmptyCodeHash { - c.h.state.SetNonce(addr, 0) + c.h.state.SetNonce(addr, 0, tracing.NonceChangeUnspecified) } else { - c.h.state.SetNonce(addr, 1) + c.h.state.SetNonce(addr, 1, tracing.NonceChangeUnspecified) } } diff --git a/op-chain-ops/script/forking/forking_test.go b/op-chain-ops/script/forking/forking_test.go index e6e62c19fc1..dd5f1f8518e 100644 --- a/op-chain-ops/script/forking/forking_test.go +++ b/op-chain-ops/script/forking/forking_test.go @@ -90,7 +90,7 @@ func TestForking(t *testing.T) { bob := common.Address(bytes.Repeat([]byte{0xbb}, 20)) forkState.CreateAccount(alice) - forkState.SetNonce(alice, 3) + forkState.SetNonce(alice, 3, tracing.NonceChangeUnspecified) forkState.AddBalance(alice, uint256.NewInt(123), tracing.BalanceChangeUnspecified) // Check if writes worked require.Equal(t, uint64(123), forkState.GetBalance(alice).Uint64()) @@ -126,7 +126,7 @@ func TestForking(t *testing.T) { require.Equal(t, uint64(1000), forkState.GetNonce(bob)) // Apply a diff change on top of the fork - forkState.SetNonce(bob, 99999) + forkState.SetNonce(bob, 99999, tracing.NonceChangeUnspecified) // Now unselect the fork, going back to the default again. require.NoError(t, forkState.SelectFork(ForkID{})) @@ -140,7 +140,7 @@ func TestForking(t *testing.T) { require.Equal(t, uint64(0), forkState.GetNonce(bob)) // Make a change to the base-state, to see if it survives going back to the fork. - forkState.SetNonce(bob, 5) + forkState.SetNonce(bob, 5, tracing.NonceChangeUnspecified) // Re-select the fork, see if the changes come back, including the diff we made require.NoError(t, forkState.SelectFork(forkA)) @@ -149,7 +149,7 @@ func TestForking(t *testing.T) { // This change will continue to be visible across forks, // alice is going to be persistent. - forkState.SetNonce(alice, 777) + forkState.SetNonce(alice, 777, tracing.NonceChangeUnspecified) // Now make Alice persistent, see if we can get the original value forkState.MakePersistent(alice) @@ -173,8 +173,8 @@ func TestForking(t *testing.T) { require.Equal(t, uint64(222), forkState.GetNonce(bob), "bob is forked") // Mutate both, and undo the fork, to test if the persistent change is still there in non-fork mode - forkState.SetNonce(alice, 1001) // this mutates forkA, because alice was made persistent there - forkState.SetNonce(bob, 1002) + forkState.SetNonce(alice, 1001, tracing.NonceChangeUnspecified) // this mutates forkA, because alice was made persistent there + forkState.SetNonce(bob, 1002, tracing.NonceChangeUnspecified) require.NoError(t, forkState.SelectFork(ForkID{})) require.Equal(t, uint64(1001), forkState.GetNonce(alice), "alice is persistent") require.Equal(t, uint64(5), forkState.GetNonce(bob), "bob is not persistent") @@ -255,7 +255,7 @@ func TestForking(t *testing.T) { require.Equal(t, uint64(9000), forkState.GetNonce(bob)) // Put in some mutations, for the fork-diff testing - forkState.SetNonce(alice, 1234) + forkState.SetNonce(alice, 1234, tracing.NonceChangeUnspecified) forkState.SetBalance(alice, uint256.NewInt(100_000), tracing.BalanceChangeUnspecified) forkState.SetState(alice, common.Hash{4}, common.Hash{42}) forkState.SetState(alice, common.Hash{5}, common.Hash{100}) diff --git a/op-chain-ops/script/forking/state.go b/op-chain-ops/script/forking/state.go index de628712f5c..e200e8903ef 100644 --- a/op-chain-ops/script/forking/state.go +++ b/op-chain-ops/script/forking/state.go @@ -266,8 +266,8 @@ func (fst *ForkableState) GetNonce(address common.Address) uint64 { return fst.stateFor(address).GetNonce(address) } -func (fst *ForkableState) SetNonce(address common.Address, u uint64) { - fst.stateFor(address).SetNonce(address, u) +func (fst *ForkableState) SetNonce(address common.Address, u uint64, reason tracing.NonceChangeReason) { + fst.stateFor(address).SetNonce(address, u, reason) } func (fst *ForkableState) GetCodeHash(address common.Address) common.Hash { @@ -386,6 +386,10 @@ func (fst *ForkableState) Witness() *stateless.Witness { return fst.selected.Witness() } +func (fst *ForkableState) AccessEvents() *state.AccessEvents { + return fst.selected.AccessEvents() +} + func (fst *ForkableState) SetBalance(addr common.Address, amount *uint256.Int, reason tracing.BalanceChangeReason) { fst.stateFor(addr).SetBalance(addr, amount, reason) } diff --git a/op-chain-ops/script/script.go b/op-chain-ops/script/script.go index d95ab96bc63..65523ccbd27 100644 --- a/op-chain-ops/script/script.go +++ b/op-chain-ops/script/script.go @@ -214,6 +214,8 @@ func NewHost( CancunTime: new(uint64), PragueTime: nil, VerkleTime: nil, + // Select default Ethereum prod blob schedules + BlobScheduleConfig: params.DefaultBlobSchedule, // OP-Stack forks are disabled, since we use this for L1. BedrockBlock: nil, RegolithTime: nil, @@ -402,13 +404,13 @@ func (h *Host) Wipe(addr common.Address) { if h.state.GetCodeSize(addr) > 0 { h.state.SetCode(addr, nil) } - h.state.SetNonce(addr, 0) + h.state.SetNonce(addr, 0, tracing.NonceChangeUnspecified) h.state.SetBalance(addr, uint256.NewInt(0), tracing.BalanceChangeUnspecified) } // SetNonce sets an account's nonce in state. func (h *Host) SetNonce(addr common.Address, nonce uint64) { - h.state.SetNonce(addr, nonce) + h.state.SetNonce(addr, nonce, tracing.NonceChangeUnspecified) } // GetNonce returs an account's nonce from state. @@ -433,7 +435,7 @@ func (h *Host) ImportAccount(addr common.Address, account types.Account) { balance = uint256.MustFromBig(account.Balance) } h.state.SetBalance(addr, balance, tracing.BalanceChangeUnspecified) - h.state.SetNonce(addr, account.Nonce) + h.state.SetNonce(addr, account.Nonce, tracing.NonceChangeUnspecified) h.state.SetCode(addr, account.Code) for key, value := range account.Storage { h.state.SetState(addr, key, value) @@ -505,7 +507,7 @@ func (h *Host) onEnter(depth int, typ byte, from common.Address, to common.Addre if parentCallFrame.Prank.Sender != nil { sender = *parentCallFrame.Prank.Sender } - h.state.SetNonce(sender, h.state.GetNonce(sender)+1) + h.state.SetNonce(sender, h.state.GetNonce(sender)+1, tracing.NonceChangeUnspecified) } if h.isolateBroadcasts { @@ -829,7 +831,7 @@ func (h *Host) NewScriptAddress() common.Address { deployNonce := h.state.GetNonce(deployer) // compute address of script contract to be deployed addr := crypto.CreateAddress(deployer, deployNonce) - h.state.SetNonce(deployer, deployNonce+1) + h.state.SetNonce(deployer, deployNonce+1, tracing.NonceChangeUnspecified) return addr } diff --git a/op-deployer/pkg/deployer/broadcaster/keyed.go b/op-deployer/pkg/deployer/broadcaster/keyed.go index 2031ebe296e..fe3a4b18d7f 100644 --- a/op-deployer/pkg/deployer/broadcaster/keyed.go +++ b/op-deployer/pkg/deployer/broadcaster/keyed.go @@ -78,7 +78,6 @@ func NewKeyedBroadcaster(cfg KeyedBroadcasterOpts) (*KeyedBroadcaster, error) { &metrics.NoopTxMetrics{}, mgrCfg, ) - if err != nil { return nil, fmt.Errorf("failed to create tx manager: %w", err) } @@ -236,7 +235,18 @@ func padGasLimit(data []byte, gasUsed uint64, creation bool, blockGasLimit uint6 panic(err) } - limit := uint64(float64(intrinsicGas+gasUsed) * GasPadFactor) + floorDataGas, err := core.FloorDataGas(data) + // We should never cause an overflow here. + if err != nil { + panic(err) + } + + gas := intrinsicGas + gasUsed + if floorDataGas > gas { + gas = floorDataGas + } + + limit := uint64(float64(gas) * GasPadFactor) if limit > blockGasLimit { return blockGasLimit } diff --git a/op-e2e/actions/batcher/eip4844_test.go b/op-e2e/actions/batcher/eip4844_test.go index 06f2a86f60c..04f52eeff9c 100644 --- a/op-e2e/actions/batcher/eip4844_test.go +++ b/op-e2e/actions/batcher/eip4844_test.go @@ -10,11 +10,11 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" batcherFlags "github.com/ethereum-optimism/optimism/op-batcher/flags" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-node/rollup/sync" - "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/testlog" ) @@ -85,9 +85,14 @@ func TestEIP4844DataAvailability(gt *testing.T) { func TestEIP4844MultiBlobs(gt *testing.T) { t := helpers.NewDefaultTesting(gt) + // Feel free to bump to Prague when updating this test's L1 config to activate Prague log := testlog.Logger(t, log.LevelDebug) sd, dp, miner, sequencer, seqEngine, verifier, _ := setupEIP4844Test(t, log) + // We could use eip4844.MaxBlobsPerBlock(sd.L1Cfg.Config, sd.L1Cfg.Timestamp) here, but + // we don't have the L1 chain config available in the action test batcher. So we just + // stick to Cancun max blobs for now, which is sufficient for this test. + maxBlobsPerBlock := params.DefaultCancunBlobConfig.Max batcher := setupBatcher(t, log, sd, dp, miner, sequencer, seqEngine, batcherFlags.BlobsType) @@ -105,10 +110,10 @@ func TestEIP4844MultiBlobs(gt *testing.T) { sequencer.ActBuildToL1Head(t) // submit all new L2 blocks - batcher.ActSubmitAllMultiBlobs(t, eth.MaxBlobsPerBlobTx) + batcher.ActSubmitAllMultiBlobs(t, maxBlobsPerBlock) batchTx := batcher.LastSubmitted require.Equal(t, uint8(types.BlobTxType), batchTx.Type(), "batch tx must be blob-tx") - require.Len(t, batchTx.BlobTxSidecar().Blobs, eth.MaxBlobsPerBlobTx) + require.Len(t, batchTx.BlobTxSidecar().Blobs, maxBlobsPerBlock) // new L1 block with L2 batch miner.ActL1StartBlock(12)(t) diff --git a/op-e2e/actions/derivation/reorg_test.go b/op-e2e/actions/derivation/reorg_test.go index 4ccbec6839a..2f26a886240 100644 --- a/op-e2e/actions/derivation/reorg_test.go +++ b/op-e2e/actions/derivation/reorg_test.go @@ -105,7 +105,7 @@ func ReorgOrphanBlock(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { miner.ActL1SetFeeRecipient(common.Address{'C'}) // note: the geth tx pool reorgLoop is too slow (responds to chain head events, but async), // and there's no way to manually trigger runReorg, so we re-insert it ourselves. - require.NoError(t, miner.Eth.TxPool().Add([]*types.Transaction{batchTx}, true, true)[0]) + require.NoError(t, miner.Eth.TxPool().Add([]*types.Transaction{batchTx}, true)[0]) // need to re-insert previously included tx into the block miner.ActL1IncludeTx(sd.RollupCfg.Genesis.SystemConfig.BatcherAddr)(t) miner.ActL1EndBlock(t) @@ -177,7 +177,7 @@ func ReorgFlipFlop(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { // re-include the batch tx that submitted L2 chain data that pointed to A0, in the new block B1 miner.ActL1SetFeeRecipient(common.Address{'B', 1}) miner.ActL1StartBlock(12)(t) - require.NoError(t, miner.Eth.TxPool().Add([]*types.Transaction{batchTxA}, true, true)[0]) + require.NoError(t, miner.Eth.TxPool().Add([]*types.Transaction{batchTxA}, true)[0]) miner.ActL1IncludeTx(sd.RollupCfg.Genesis.SystemConfig.BatcherAddr)(t) miner.ActL1EndBlock(t) @@ -242,7 +242,7 @@ func ReorgFlipFlop(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { miner.ActL1SetFeeRecipient(common.Address{'A', 2}) miner.ActL1StartBlock(12)(t) - require.NoError(t, miner.Eth.TxPool().Add([]*types.Transaction{batchTxA}, true, true)[0]) // replay chain A batches, but now in A2 instead of A1 + require.NoError(t, miner.Eth.TxPool().Add([]*types.Transaction{batchTxA}, true)[0]) // replay chain A batches, but now in A2 instead of A1 miner.ActL1IncludeTx(sd.RollupCfg.Genesis.SystemConfig.BatcherAddr)(t) miner.ActL1EndBlock(t) diff --git a/op-e2e/actions/helpers/l1_miner.go b/op-e2e/actions/helpers/l1_miner.go index 316ffc6e57d..b33a68c12c4 100644 --- a/op-e2e/actions/helpers/l1_miner.go +++ b/op-e2e/actions/helpers/l1_miner.go @@ -224,15 +224,7 @@ func (s *L1Miner) ActL1EndBlock(t Testing) *types.Block { isCancun := s.l1Cfg.Config.IsCancun(s.l1BuildingHeader.Number, s.l1BuildingHeader.Time) if isCancun { parent := s.l1Chain.GetHeaderByHash(s.l1BuildingHeader.ParentHash) - var ( - parentExcessBlobGas uint64 - parentBlobGasUsed uint64 - ) - if parent.ExcessBlobGas != nil { - parentExcessBlobGas = *parent.ExcessBlobGas - parentBlobGasUsed = *parent.BlobGasUsed - } - excessBlobGas := eip4844.CalcExcessBlobGas(parentExcessBlobGas, parentBlobGasUsed) + excessBlobGas := eip4844.CalcExcessBlobGas(s.l1Cfg.Config, parent, s.l1BuildingHeader.Time) s.l1BuildingHeader.ExcessBlobGas = &excessBlobGas } diff --git a/op-e2e/actions/helpers/l2_batcher.go b/op-e2e/actions/helpers/l2_batcher.go index f270bddb717..3e67166d92f 100644 --- a/op-e2e/actions/helpers/l2_batcher.go +++ b/op-e2e/actions/helpers/l2_batcher.go @@ -12,7 +12,6 @@ import ( "github.com/stretchr/testify/require" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -343,8 +342,8 @@ func (s *L2Batcher) ActL2BatchSubmitRaw(t Testing, payload []byte, txOpts ...fun opt(rawTx) } - gas, err := core.IntrinsicGas(rawTx.Data, nil, nil, false, true, true, false) - require.NoError(t, err, "need to compute intrinsic gas") + gas, err := core.FloorDataGas(rawTx.Data) + require.NoError(t, err, "need to compute floor data gas") rawTx.Gas = gas txData = rawTx } else if s.l2BatcherCfg.DataAvailabilityType == batcherFlags.BlobsType { @@ -353,7 +352,7 @@ func (s *L2Batcher) ActL2BatchSubmitRaw(t Testing, payload []byte, txOpts ...fun sidecar, blobHashes, err := txmgr.MakeSidecar([]*eth.Blob{&b}) require.NoError(t, err) require.NotNil(t, pendingHeader.ExcessBlobGas, "need L1 header with 4844 properties") - blobBaseFee := eip4844.CalcBlobFee(*pendingHeader.ExcessBlobGas) + blobBaseFee := eth.CalcBlobFeeDefault(pendingHeader) blobFeeCap := new(uint256.Int).Mul(uint256.NewInt(2), uint256.MustFromBig(blobBaseFee)) if blobFeeCap.Lt(uint256.NewInt(params.GWei)) { // ensure we meet 1 gwei geth tx-pool minimum blobFeeCap = uint256.NewInt(params.GWei) @@ -384,11 +383,13 @@ func (s *L2Batcher) ActL2BatchSubmitRaw(t Testing, payload []byte, txOpts ...fun } func (s *L2Batcher) ActL2BatchSubmitMultiBlob(t Testing, numBlobs int) { + // Update to Prague if L1 changes to Prague and we need more blobs in multi-blob tests. + maxBlobsPerBlock := params.DefaultCancunBlobConfig.Max if s.l2BatcherCfg.DataAvailabilityType != batcherFlags.BlobsType { t.InvalidAction("ActL2BatchSubmitMultiBlob only available for Blobs DA type") return - } else if numBlobs > eth.MaxBlobsPerBlobTx || numBlobs < 1 { - t.InvalidAction("invalid number of blobs %d, must be within [1,%d]", numBlobs, eth.MaxBlobsPerBlobTx) + } else if numBlobs > maxBlobsPerBlock || numBlobs < 1 { + t.InvalidAction("invalid number of blobs %d, must be within [1,%d]", numBlobs, maxBlobsPerBlock) } // Don't run this action if there's no data to submit @@ -435,7 +436,7 @@ func (s *L2Batcher) ActL2BatchSubmitMultiBlob(t Testing, numBlobs int) { sidecar, blobHashes, err := txmgr.MakeSidecar(blobs) require.NoError(t, err) require.NotNil(t, pendingHeader.ExcessBlobGas, "need L1 header with 4844 properties") - blobBaseFee := eip4844.CalcBlobFee(*pendingHeader.ExcessBlobGas) + blobBaseFee := eth.CalcBlobFeeDefault(pendingHeader) blobFeeCap := new(uint256.Int).Mul(uint256.NewInt(2), uint256.MustFromBig(blobBaseFee)) if blobFeeCap.Lt(uint256.NewInt(params.GWei)) { // ensure we meet 1 gwei geth tx-pool minimum blobFeeCap = uint256.NewInt(params.GWei) diff --git a/op-e2e/actions/safedb/safedb_test.go b/op-e2e/actions/safedb/safedb_test.go index f4a2a1767a1..8e2e7b78385 100644 --- a/op-e2e/actions/safedb/safedb_test.go +++ b/op-e2e/actions/safedb/safedb_test.go @@ -88,7 +88,7 @@ func TestRecordSafeHeadUpdates(gt *testing.T) { miner.ActL1SetFeeRecipient(common.Address{'C'}) // note: the geth tx pool reorgLoop is too slow (responds to chain head events, but async), // and there's no way to manually trigger runReorg, so we re-insert it ourselves. - require.NoError(t, miner.Eth.TxPool().Add([]*types.Transaction{batchTx}, true, true)[0]) + require.NoError(t, miner.Eth.TxPool().Add([]*types.Transaction{batchTx}, true)[0]) // need to re-insert previously included tx into the block miner.ActL1IncludeTx(sd.RollupCfg.Genesis.SystemConfig.BatcherAddr)(t) miner.ActL1EndBlock(t) diff --git a/op-e2e/actions/sync/sync_test.go b/op-e2e/actions/sync/sync_test.go index 54cb72a0114..8531e2ef527 100644 --- a/op-e2e/actions/sync/sync_test.go +++ b/op-e2e/actions/sync/sync_test.go @@ -956,7 +956,7 @@ func TestInvalidPayloadInSpanBatch(gt *testing.T) { aliceNonce, err := seqEng.EthClient().PendingNonceAt(t.Ctx(), dp.Addresses.Alice) require.NoError(t, err) data := make([]byte, rand.Intn(100)) - gas, err := core.IntrinsicGas(data, nil, nil, false, true, true, false) + gas, err := core.FloorDataGas(data) require.NoError(t, err) baseFee := seqEng.L2Chain().CurrentBlock().BaseFee tx := types.MustSignNewTx(dp.Secrets.Alice, signer, &types.DynamicFeeTx{ diff --git a/op-e2e/actions/upgrades/dencun_fork_test.go b/op-e2e/actions/upgrades/dencun_fork_test.go index d634a836be0..0541a11904d 100644 --- a/op-e2e/actions/upgrades/dencun_fork_test.go +++ b/op-e2e/actions/upgrades/dencun_fork_test.go @@ -216,7 +216,7 @@ func TestDencunBlobTxInTxPool(gt *testing.T) { log := testlog.Logger(t, log.LevelDebug) engine := newEngine(t, sd, log) tx := aliceSimpleBlobTx(t, dp) - errs := engine.Eth.TxPool().Add([]*types.Transaction{tx}, true, true) + errs := engine.Eth.TxPool().Add([]*types.Transaction{tx}, true) require.ErrorContains(t, errs[0], "transaction type not supported") } diff --git a/op-e2e/actions/upgrades/isthmus_fork_test.go b/op-e2e/actions/upgrades/isthmus_fork_test.go index cd7de34f9bf..ce774be1a1b 100644 --- a/op-e2e/actions/upgrades/isthmus_fork_test.go +++ b/op-e2e/actions/upgrades/isthmus_fork_test.go @@ -11,7 +11,6 @@ import ( "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" "github.com/ethereum-optimism/optimism/op-node/rollup" - "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/predeploys" @@ -323,10 +322,10 @@ func TestIsthmusNetworkUpgradeTransactions(gt *testing.T) { zero := hexutil.Uint64(0) - // Activate all forks at genesis, and schedule Ecotone the block after + // Activate all forks at genesis, and schedule Isthmus the block after dp.DeployConfig.L2GenesisHoloceneTimeOffset = &zero dp.DeployConfig.L2GenesisIsthmusTimeOffset = &isthmusOffset - dp.DeployConfig.L1PragueTimeOffset = nil + dp.DeployConfig.L1PragueTimeOffset = &zero // New forks have to be added here... require.NoError(t, dp.DeployConfig.Check(log), "must have valid config") @@ -364,7 +363,7 @@ func TestIsthmusNetworkUpgradeTransactions(gt *testing.T) { require.NotEmpty(t, txn.Data(), "upgrade tx must provide input data") // EIP-2935 contract is deployed - expectedBlockHashAddress := crypto.CreateAddress(derive.BlockHashDeployerAddress, 0) + expectedBlockHashAddress := crypto.CreateAddress(predeploys.EIP2935ContractDeployer, 0) require.Equal(t, predeploys.EIP2935ContractAddr, expectedBlockHashAddress) code := verifyCodeHashMatches(t, ethCl, predeploys.EIP2935ContractAddr, predeploys.EIP2935ContractCodeHash) require.Equal(t, predeploys.EIP2935ContractCode, code) diff --git a/op-e2e/system/da/eip4844_test.go b/op-e2e/system/da/eip4844_test.go index 7c5b0c89d06..c90097b7f9c 100644 --- a/op-e2e/system/da/eip4844_test.go +++ b/op-e2e/system/da/eip4844_test.go @@ -35,6 +35,9 @@ import ( "github.com/ethereum/go-ethereum/params" ) +// Update to Prague if L1 changes to Prague and we need more blobs in multi-blob tests. +var maxBlobsPerBlock = params.DefaultCancunBlobConfig.Max + // TestSystem4844E2E* run the SystemE2E test with 4844 enabled on L1, and active on the rollup in // the op-batcher and verifier. It submits a txpool-blocking transaction before running // each test to ensure the batcher is able to clear it. @@ -60,9 +63,9 @@ func testSystem4844E2E(t *testing.T, multiBlob bool, daType batcherFlags.DataAva var maxL1TxSize int if multiBlob { - cfg.BatcherTargetNumFrames = eth.MaxBlobsPerBlobTx + cfg.BatcherTargetNumFrames = maxBlobsPerBlock cfg.BatcherUseMaxTxSizeForBlobs = true - // leads to eth.MaxBlobsPerBlobTx blobs for an L2 block with a user tx with 400 random bytes + // leads to maxBlobsPerBlock blobs for an L2 block with a user tx with 400 random bytes // while all other L2 blocks take 1 blob (deposit tx) maxL1TxSize = derive.FrameV0OverHeadSize + 100 cfg.BatcherMaxL1TxSizeBytes = uint64(maxL1TxSize) @@ -137,7 +140,7 @@ func testSystem4844E2E(t *testing.T, multiBlob bool, daType batcherFlags.DataAva opts.Value = big.NewInt(1_000_000_000) opts.Nonce = 1 // Already have deposit opts.ToAddr = &common.Address{0xff, 0xff} - // put some random data in the tx to make it fill up eth.MaxBlobsPerBlobTx blobs (multi-blob case) + // put some random data in the tx to make it fill up maxBlobsPerBlock blobs (multi-blob case) opts.Data = testutils.RandomData(rand.New(rand.NewSource(420)), 400) opts.Gas, err = core.IntrinsicGas(opts.Data, nil, nil, false, true, true, false) require.NoError(t, err) @@ -215,21 +218,20 @@ func testSystem4844E2E(t *testing.T, multiBlob bool, daType batcherFlags.DataAva if !multiBlob { require.NotZero(t, numBlobs, "single-blob: expected to find L1 blob tx") } else { - const maxBlobs = eth.MaxBlobsPerBlobTx - require.Equal(t, maxBlobs, numBlobs, fmt.Sprintf("multi-blob: expected to find L1 blob tx with %d blobs", maxBlobs)) + require.Equal(t, maxBlobsPerBlock, numBlobs, fmt.Sprintf("multi-blob: expected to find L1 blob tx with %d blobs", maxBlobsPerBlock)) // blob tx should have filled up all but last blob bcl := sys.L1BeaconHTTPClient() hashes := toIndexedBlobHashes(blobTx.BlobHashes()...) sidecars, err := bcl.BeaconBlobSideCars(context.Background(), false, sys.L1Slot(blobBlock.Time()), hashes) require.NoError(t, err) - require.Len(t, sidecars.Data, maxBlobs) - for i := 0; i < maxBlobs-1; i++ { + require.Len(t, sidecars.Data, maxBlobsPerBlock) + for i := 0; i < maxBlobsPerBlock-1; i++ { data, err := sidecars.Data[i].Blob.ToData() require.NoError(t, err) require.Len(t, data, maxL1TxSize) } // last blob should only be partially filled - data, err := sidecars.Data[maxBlobs-1].Blob.ToData() + data, err := sidecars.Data[maxBlobsPerBlock-1].Blob.ToData() require.NoError(t, err) require.Less(t, len(data), maxL1TxSize) } @@ -261,12 +263,14 @@ func TestBatcherAutoDA(t *testing.T) { cfg.DataAvailabilityType = batcherFlags.AutoType // We set the genesis fee values and block gas limit such that calldata txs are initially cheaper, // but then manipulate the fee markets over the coming L1 blocks such that blobs become cheaper again. - cfg.DeployConfig.L1GenesisBlockBaseFeePerGas = (*hexutil.Big)(big.NewInt(3100)) - // 100 blob targets leads to 130_393 starting blob base fee, which is ~ 42 * 2_000 (equilibrium is ~16x or ~40x under Pectra) - cfg.DeployConfig.L1GenesisBlockExcessBlobGas = (*hexutil.Uint64)(u64Ptr(100 * params.BlobTxTargetBlobGasPerBlock)) + cfg.DeployConfig.L1GenesisBlockBaseFeePerGas = (*hexutil.Big)(big.NewInt(3000)) + // The following excess blob gas leads to a blob base fee ~41 times higher than the base fee at genesis, + // so the batcher starts with calldata (equilibrium is ~16x or ~40x under Pectra). + cfg.DeployConfig.L1GenesisBlockExcessBlobGas = (*hexutil.Uint64)(u64Ptr( + 450 * params.BlobTxBlobGasPerBlob)) cfg.DeployConfig.L1GenesisBlockBlobGasUsed = (*hexutil.Uint64)(u64Ptr(0)) cfg.DeployConfig.L1GenesisBlockGasLimit = 2_500_000 - cfg.BatcherTargetNumFrames = eth.MaxBlobsPerBlobTx + cfg.BatcherTargetNumFrames = maxBlobsPerBlock sys, err := cfg.Start(t) require.NoError(t, err, "Error starting up system") log := testlog.Logger(t, log.LevelInfo) diff --git a/op-e2e/system/fees/l1info_test.go b/op-e2e/system/fees/l1info_test.go index a4fc16d94a5..7ae066d993d 100644 --- a/op-e2e/system/fees/l1info_test.go +++ b/op-e2e/system/fees/l1info_test.go @@ -16,7 +16,6 @@ import ( "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/stretchr/testify/require" @@ -172,7 +171,7 @@ func TestL1InfoContract(t *testing.T) { l1blocks[h].BlobBaseFeeScalar = scalars.BlobBaseFeeScalar l1blocks[h].BaseFeeScalar = scalars.BaseFeeScalar if excess := b.ExcessBlobGas(); excess != nil { - l1blocks[h].BlobBaseFee = eip4844.CalcBlobFee(*excess) + l1blocks[h].BlobBaseFee = eth.CalcBlobFeeDefault(b.Header()) } else { l1blocks[h].BlobBaseFee = big.NewInt(1) } diff --git a/op-node/rollup/derive/isthmus_upgrade_transactions.go b/op-node/rollup/derive/isthmus_upgrade_transactions.go index fd8663e46f3..595bf50fd94 100644 --- a/op-node/rollup/derive/isthmus_upgrade_transactions.go +++ b/op-node/rollup/derive/isthmus_upgrade_transactions.go @@ -3,23 +3,21 @@ package derive import ( "math/big" + "github.com/ethereum-optimism/optimism/op-service/predeploys" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" ) var ( - BlockHashDeployerAddress = common.HexToAddress("0xE9f0662359Bb2c8111840eFFD73B9AFA77CbDE10") blockHashDeployerSource = UpgradeDepositSource{Intent: "Isthmus: EIP-2935 Contract Deployment"} blockHashDeploymentBytecode = common.FromHex("0x60538060095f395ff33373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500") ) func IsthmusNetworkUpgradeTransactions() ([]hexutil.Bytes, error) { - upgradeTxns := make([]hexutil.Bytes, 0, 1) - deployHistoricalBlockHashesContract, err := types.NewTx(&types.DepositTx{ SourceHash: blockHashDeployerSource.SourceHash(), - From: BlockHashDeployerAddress, + From: predeploys.EIP2935ContractDeployer, To: nil, Mint: big.NewInt(0), Value: big.NewInt(0), @@ -32,7 +30,5 @@ func IsthmusNetworkUpgradeTransactions() ([]hexutil.Bytes, error) { return nil, err } - upgradeTxns = append(upgradeTxns, deployHistoricalBlockHashesContract) - - return upgradeTxns, nil + return []hexutil.Bytes{deployHistoricalBlockHashesContract}, nil } diff --git a/op-node/rollup/derive/isthmus_upgrade_transactions_test.go b/op-node/rollup/derive/isthmus_upgrade_transactions_test.go index 8a2181702f8..5527e76e20e 100644 --- a/op-node/rollup/derive/isthmus_upgrade_transactions_test.go +++ b/op-node/rollup/derive/isthmus_upgrade_transactions_test.go @@ -3,6 +3,7 @@ package derive import ( "testing" + "github.com/ethereum-optimism/optimism/op-service/predeploys" "github.com/stretchr/testify/require" "github.com/ethereum/go-ethereum/common" @@ -28,7 +29,7 @@ func TestIsthmusNetworkTransactions(t *testing.T) { require.Len(t, upgradeTxns, 1) deployBlockHashesSender, deployBlockHashesContract := toDepositTxn(t, upgradeTxns[0]) - require.Equal(t, deployBlockHashesSender, common.HexToAddress("0xE9f0662359Bb2c8111840eFFD73B9AFA77CbDE10")) + require.Equal(t, deployBlockHashesSender, predeploys.EIP2935ContractDeployer) require.Equal(t, blockHashDeployerSource.SourceHash(), deployBlockHashesContract.SourceHash()) require.Nil(t, deployBlockHashesContract.To()) require.Equal(t, uint64(250_000), deployBlockHashesContract.Gas()) diff --git a/op-program/client/l2/db_test.go b/op-program/client/l2/db_test.go index 5b92a516504..6395fd525cf 100644 --- a/op-program/client/l2/db_test.go +++ b/op-program/client/l2/db_test.go @@ -135,7 +135,7 @@ func TestUpdateState(t *testing.T) { statedb.MakeSinglethreaded() statedb.SetBalance(userAccount, uint256.NewInt(50), tracing.BalanceChangeUnspecified) require.Equal(t, uint256.NewInt(50), statedb.GetBalance(userAccount)) - statedb.SetNonce(userAccount, uint64(5)) + statedb.SetNonce(userAccount, uint64(5), tracing.NonceChangeUnspecified) require.Equal(t, uint64(5), statedb.GetNonce(userAccount)) statedb.SetBalance(unknownAccount, uint256.NewInt(60), tracing.BalanceChangeUnspecified) diff --git a/op-program/client/l2/fast_canon.go b/op-program/client/l2/fast_canon.go index e450b16e994..4b40786dd77 100644 --- a/op-program/client/l2/fast_canon.go +++ b/op-program/client/l2/fast_canon.go @@ -44,7 +44,7 @@ func NewFastCanonicalBlockHeaderOracle( fallback *CanonicalBlockHeaderOracle, ) *FastCanonicalBlockHeaderOracle { chainID := eth.ChainIDFromBig(chainCfg.ChainID) - ctx := &chainContext{engine: beacon.New(nil)} + ctx := &chainContext{engine: beacon.New(nil), config: chainCfg} db := NewOracleBackedDB(kvdb, stateOracle, chainID) cache, _ := simplelru.NewLRU[uint64, *types.Header](historicalCacheSize, nil) return &FastCanonicalBlockHeaderOracle{ @@ -150,12 +150,17 @@ func (o *FastCanonicalBlockHeaderOracle) SetCanonical(head *types.Header) common type chainContext struct { engine consensus.Engine + config *params.ChainConfig } func (c *chainContext) Engine() consensus.Engine { return c.engine } +func (c *chainContext) Config() *params.ChainConfig { + return c.config +} + func (c *chainContext) GetHeader(hash common.Hash, number uint64) *types.Header { // The EVM should never call this method during eip-2935 historical block retrieval panic("unexpected call to GetHeader") diff --git a/op-program/client/l2/test/miner.go b/op-program/client/l2/test/miner.go index e1fd48d9010..94635e58a6a 100644 --- a/op-program/client/l2/test/miner.go +++ b/op-program/client/l2/test/miner.go @@ -44,6 +44,8 @@ func NewMiner(t *testing.T, logger log.Logger, isthmusTime uint64) (*Miner, *cor EIP1559Elasticity: 10, EIP1559DenominatorCanyon: &denomCanyon, } + // OP-Stack chain configs must have nil blob schedule + config.BlobScheduleConfig = nil genesis := &core.Genesis{ Config: &config, Difficulty: common.Big0, diff --git a/op-service/eth/blob.go b/op-service/eth/blob.go index b7cf4524a48..142feba0896 100644 --- a/op-service/eth/blob.go +++ b/op-service/eth/blob.go @@ -4,21 +4,23 @@ import ( "crypto/sha256" "errors" "fmt" + "math/big" "reflect" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/params" ) const ( - BlobSize = 4096 * 32 - MaxBlobDataSize = (4*31+3)*1024 - 4 - EncodingVersion = 0 - VersionOffset = 1 // offset of the version byte in the blob encoding - Rounds = 1024 // number of encode/decode rounds - MaxBlobsPerBlobTx = params.MaxBlobGasPerBlock / params.BlobTxBlobGasPerBlob + BlobSize = 4096 * 32 + MaxBlobDataSize = (4*31+3)*1024 - 4 + EncodingVersion = 0 + VersionOffset = 1 // offset of the version byte in the blob encoding + Rounds = 1024 // number of encode/decode rounds ) var ( @@ -280,3 +282,37 @@ func (b *Blob) Clear() { b[i] = 0 } } + +// CalcBlobFeeDefault calculates the blob fee for the given header using eip4844.CalcBlobFee, +// using the requests hash field of the header as a best-effort heuristic whether +// Prague is active, and the default Ethereum blob schedule. +// +// This is to deal in a best-effort way with situations where the chain config is not +// available, but it can be assumed that per the definition of the Prague fork that +// Prague is active iff the requests hash field is present. +func CalcBlobFeeDefault(header *types.Header) *big.Int { + // We make the assumption that eip4844.CalcBlobFee only needs + // - London and Cancun to be active + // - the Prague time to be set relative to the header time + // and that the caller assumes the default prod Ethereum Blob schedule config. + dummyChainCfg := ¶ms.ChainConfig{ + LondonBlock: common.Big0, + CancunTime: ptr(uint64(0)), + BlobScheduleConfig: params.DefaultBlobSchedule, + } + // We assume that the requests hash is set iff Prague is active. + if header.RequestsHash != nil { + dummyChainCfg.PragueTime = ptr(uint64(0)) + } + return eip4844.CalcBlobFee(dummyChainCfg, header) +} + +func CalcBlobFeeCancun(excessBlobGas uint64) *big.Int { + // Dummy Cancun header for calculation. + cancunHeader := &types.Header{ + ExcessBlobGas: &excessBlobGas, + } + return CalcBlobFeeDefault(cancunHeader) +} + +func ptr[T any](t T) *T { return &t } diff --git a/op-service/eth/blob_test.go b/op-service/eth/blob_test.go index 83e21ed88b4..5b77e9bd29a 100644 --- a/op-service/eth/blob_test.go +++ b/op-service/eth/blob_test.go @@ -1,9 +1,13 @@ package eth import ( + "math/big" "math/rand" "testing" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" "github.com/stretchr/testify/require" ) @@ -291,3 +295,19 @@ func TestExtraneousData(t *testing.T) { require.ErrorIs(t, err, ErrBlobExtraneousData, len(decoded)) } } + +// TestCalcBlobFeeDefault ensures that the best-effort implementation of CalcBlobFeeDefault +// works as expected. In particular, this test will quickly fail and help detect any changes +// made to the internals of the upstream eip4844.CalcBlobFee function, on which +// CalcBlobFeeDefault relies on with certain assumptions. +func TestCalcBlobFeeDefault(t *testing.T) { + header := &types.Header{ + ExcessBlobGas: ptr(uint64(20 * params.DefaultCancunBlobConfig.UpdateFraction)), + } + cancunBlobFee := CalcBlobFeeDefault(header) + require.Equal(t, big.NewInt(485165195), cancunBlobFee) + + header.RequestsHash = &(common.Hash{}) + pragueBlobFee := CalcBlobFeeDefault(header) + require.Equal(t, big.NewInt(617436), pragueBlobFee) +} diff --git a/op-service/eth/block_info.go b/op-service/eth/block_info.go index f0e203233fe..9530c17ca66 100644 --- a/op-service/eth/block_info.go +++ b/op-service/eth/block_info.go @@ -4,7 +4,6 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" ) @@ -62,7 +61,7 @@ func (b blockInfo) BlobBaseFee() *big.Int { if ebg == nil { return nil } - return eip4844.CalcBlobFee(*ebg) + return CalcBlobFeeDefault(b.Header()) } func (b blockInfo) HeaderRLP() ([]byte, error) { @@ -128,7 +127,7 @@ func (h *headerBlockInfo) BlobBaseFee() *big.Int { if h.header.ExcessBlobGas == nil { return nil } - return eip4844.CalcBlobFee(*h.header.ExcessBlobGas) + return CalcBlobFeeDefault(h.header) } func (h *headerBlockInfo) ReceiptHash() common.Hash { diff --git a/op-service/predeploys/eip2935.go b/op-service/predeploys/eip2935.go index 52cb5bea2cf..7566accf1f0 100644 --- a/op-service/predeploys/eip2935.go +++ b/op-service/predeploys/eip2935.go @@ -1,12 +1,15 @@ package predeploys -import "github.com/ethereum/go-ethereum/common" +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/params" +) // EIP-2935 defines a deterministic deployment transaction that deploys the recent block hashes contract. // See https://eips.ethereum.org/EIPS/eip-2935 var ( - EIP2935ContractAddr = common.HexToAddress("0x0F792be4B0c0cb4DAE440Ef133E90C0eCD48CCCC") - EIP2935ContractCode = common.FromHex("0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500") + EIP2935ContractAddr = params.HistoryStorageAddress + EIP2935ContractCode = params.HistoryStorageCode EIP2935ContractCodeHash = common.HexToHash("0x6e49e66782037c0555897870e29fa5e552daf4719552131a0abce779daec0a5d") - EIP2935ContractDeployer = common.HexToAddress("0xE9f0662359Bb2c8111840eFFD73B9AFA77CbDE10") + EIP2935ContractDeployer = common.HexToAddress("0x3462413Af4609098e1E27A490f554f260213D685") ) diff --git a/op-service/txmgr/estimator.go b/op-service/txmgr/estimator.go index c9968a1018a..627e6991044 100644 --- a/op-service/txmgr/estimator.go +++ b/op-service/txmgr/estimator.go @@ -5,7 +5,7 @@ import ( "errors" "math/big" - "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum-optimism/optimism/op-service/eth" ) type GasPriceEstimatorFn func(ctx context.Context, backend ETHBackend) (*big.Int, *big.Int, *big.Int, error) @@ -26,7 +26,7 @@ func DefaultGasPriceEstimatorFn(ctx context.Context, backend ETHBackend) (*big.I var blobFee *big.Int if head.ExcessBlobGas != nil { - blobFee = eip4844.CalcBlobFee(*head.ExcessBlobGas) + blobFee = eth.CalcBlobFeeDefault(head) } return tip, head.BaseFee, blobFee, nil diff --git a/op-service/txmgr/txmgr.go b/op-service/txmgr/txmgr.go index 97fd45954b3..f439d086f07 100644 --- a/op-service/txmgr/txmgr.go +++ b/op-service/txmgr/txmgr.go @@ -13,7 +13,6 @@ import ( "github.com/ethereum-optimism/optimism/op-service/errutil" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" @@ -760,7 +759,7 @@ func (m *SimpleTxManager) queryReceipt(ctx context.Context, txHash common.Hash, m.metr.RecordBaseFee(tip.BaseFee) if tip.ExcessBlobGas != nil { - blobFee := eip4844.CalcBlobFee(*tip.ExcessBlobGas) + blobFee := eth.CalcBlobFeeDefault(tip) m.metr.RecordBlobBaseFee(blobFee) } diff --git a/op-service/txmgr/txmgr_test.go b/op-service/txmgr/txmgr_test.go index 9b96338e3fa..b79319689e3 100644 --- a/op-service/txmgr/txmgr_test.go +++ b/op-service/txmgr/txmgr_test.go @@ -15,7 +15,6 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" @@ -152,7 +151,8 @@ func (g *gasPricer) expGasFeeCap() *big.Int { func (g *gasPricer) expBlobFeeCap() *big.Int { _, _, excessBlobGas := g.feesForEpoch(g.mineAtEpoch) - return eip4844.CalcBlobFee(excessBlobGas) + // Needs to be adjusted when Prague gas pricing is needed. + return eth.CalcBlobFeeCancun(excessBlobGas) } func (g *gasPricer) shouldMine(gasFeeCap *big.Int) bool { @@ -504,7 +504,8 @@ func TestTxMgrConfirmsBlobTxAtHigherGasPrice(t *testing.T) { h := newTestHarness(t) gasTipCap, gasFeeCap, excessBlobGas := h.gasPricer.sample() - blobFeeCap := eip4844.CalcBlobFee(excessBlobGas) + // Needs to be adjusted when testing with Prague activated on L1. + blobFeeCap := eth.CalcBlobFeeCancun(excessBlobGas) t.Log("Blob fee cap:", blobFeeCap, "gasFeeCap:", gasFeeCap) tx := types.NewTx(&types.BlobTx{ diff --git a/op-wheel/cheat/cheat.go b/op-wheel/cheat/cheat.go index ae43bd56243..0f1ce16c88e 100644 --- a/op-wheel/cheat/cheat.go +++ b/op-wheel/cheat/cheat.go @@ -134,11 +134,6 @@ func (ch *Cheater) RunAndClose(fn HeadFn) error { // rawdb.WriteTxLookupEntriesByBlock(batch, block) rawdb.WriteHeadBlockHash(batch, blockHash) - // Geth stores the TD for each block separately from the block itself. We must update this - // manually, otherwise Geth thinks we haven't reached TTD yet and tries to build a block - // using pre-merge consensus, which causes a panic. - rawdb.WriteTd(batch, blockHash, preID.Number, ch.Blockchain.GetTd(preID.Hash, preID.Number)) - // Need to copy over receipts since they are keyed by block hash. receipts := rawdb.ReadReceipts(ch.DB, preID.Hash, preID.Number, preHeader.Time, ch.Blockchain.Config()) rawdb.WriteReceipts(batch, blockHash, preID.Number, receipts) @@ -351,7 +346,7 @@ func SetCode(addr common.Address, code hexutil.Bytes) HeadFn { func SetNonce(addr common.Address, nonce uint64) HeadFn { return func(_ *types.Header, headState *state.StateDB) error { - headState.SetNonce(addr, nonce) + headState.SetNonce(addr, nonce, tracing.NonceChangeEoACall) return nil } } From 6b03d9897ad550132771f4eb290b2ebb610bd7a7 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Wed, 26 Feb 2025 14:03:37 -0500 Subject: [PATCH 019/130] Retry chain-id queries (#14539) --- op-program/host/common/l2_sources.go | 16 ++++++++++------ op-program/host/prefetcher/l2_sources.go | 16 ++++++++++------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/op-program/host/common/l2_sources.go b/op-program/host/common/l2_sources.go index 369b9121495..8a9a7a3e23a 100644 --- a/op-program/host/common/l2_sources.go +++ b/op-program/host/common/l2_sources.go @@ -9,6 +9,8 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/client" + "github.com/ethereum-optimism/optimism/op-service/retry" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" ) @@ -103,10 +105,12 @@ func NewL2Sources(ctx context.Context, logger log.Logger, configs []*rollup.Conf } func loadChainID(ctx context.Context, rpc client.RPC) (uint64, error) { - var id hexutil.Big - err := rpc.CallContext(ctx, &id, "eth_chainId") - if err != nil { - return 0, err - } - return (*big.Int)(&id).Uint64(), nil + return retry.Do(ctx, 3, retry.Exponential(), func() (uint64, error) { + var id hexutil.Big + err := rpc.CallContext(ctx, &id, "eth_chainId") + if err != nil { + return 0, err + } + return (*big.Int)(&id).Uint64(), nil + }) } diff --git a/op-program/host/prefetcher/l2_sources.go b/op-program/host/prefetcher/l2_sources.go index b6187e1b768..30dda2931e0 100644 --- a/op-program/host/prefetcher/l2_sources.go +++ b/op-program/host/prefetcher/l2_sources.go @@ -12,6 +12,8 @@ import ( "github.com/ethereum-optimism/optimism/op-program/host/types" "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/retry" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" ) @@ -130,10 +132,12 @@ func (s *RetryingL2Sources) ForChainIDWithoutRetries(chainID eth.ChainID) (types } func loadChainID(ctx context.Context, rpc client.RPC) (eth.ChainID, error) { - var id hexutil.Big - err := rpc.CallContext(ctx, &id, "eth_chainId") - if err != nil { - return eth.ChainID{}, err - } - return eth.ChainIDFromBig((*big.Int)(&id)), nil + return retry.Do(ctx, 3, retry.Exponential(), func() (eth.ChainID, error) { + var id hexutil.Big + err := rpc.CallContext(ctx, &id, "eth_chainId") + if err != nil { + return eth.ChainID{}, err + } + return eth.ChainIDFromBig((*big.Int)(&id)), nil + }) } From 5adfa0ed6c5f2ed638c935ef292a980dab3e3a71 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Wed, 26 Feb 2025 12:41:05 -0700 Subject: [PATCH 020/130] devnet-sdk: Fix casing in book theme (#14545) --- devnet-sdk/book/theme/css/{Footer.css => footer.css} | 0 devnet-sdk/book/theme/js/{Footer.js => footer.js} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename devnet-sdk/book/theme/css/{Footer.css => footer.css} (100%) rename devnet-sdk/book/theme/js/{Footer.js => footer.js} (100%) diff --git a/devnet-sdk/book/theme/css/Footer.css b/devnet-sdk/book/theme/css/footer.css similarity index 100% rename from devnet-sdk/book/theme/css/Footer.css rename to devnet-sdk/book/theme/css/footer.css diff --git a/devnet-sdk/book/theme/js/Footer.js b/devnet-sdk/book/theme/js/footer.js similarity index 100% rename from devnet-sdk/book/theme/js/Footer.js rename to devnet-sdk/book/theme/js/footer.js From c016168ea46694c43e1785491b6d7a60e4a37362 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Wed, 26 Feb 2025 12:57:57 -0700 Subject: [PATCH 021/130] chore: Fix another footer (#14548) --- kurtosis-devnet/book/theme/css/{Footer.css => footer.css} | 0 kurtosis-devnet/book/theme/js/{Footer.js => footer.js} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename kurtosis-devnet/book/theme/css/{Footer.css => footer.css} (100%) rename kurtosis-devnet/book/theme/js/{Footer.js => footer.js} (100%) diff --git a/kurtosis-devnet/book/theme/css/Footer.css b/kurtosis-devnet/book/theme/css/footer.css similarity index 100% rename from kurtosis-devnet/book/theme/css/Footer.css rename to kurtosis-devnet/book/theme/css/footer.css diff --git a/kurtosis-devnet/book/theme/js/Footer.js b/kurtosis-devnet/book/theme/js/footer.js similarity index 100% rename from kurtosis-devnet/book/theme/js/Footer.js rename to kurtosis-devnet/book/theme/js/footer.js From 1d05a2a8d91484ecfad85ae562e4bea7b580762c Mon Sep 17 00:00:00 2001 From: Inphi Date: Wed, 26 Feb 2025 16:11:04 -0500 Subject: [PATCH 022/130] op-supervisor: Check message expiry (#14463) * op-supervisor: Check message expiry * add safe_update tests * fix override expiry window description * log warn when overriding expiry window * lint * fix tests * Fix supervisor_checkMessages; use ErrConflict * tweak supervisor_checkMessages API * add supervisor_checkMessagesV2 * update op-geth dependency * move override to depset config; fix checkMessagev1 * go.mod: drop diff again, op-geth was updated in develop branch --------- Co-authored-by: protolambda --- op-e2e/actions/interop/dsl/dsl.go | 5 ++ op-e2e/actions/interop/dsl/interop.go | 2 +- op-e2e/actions/interop/proofs_test.go | 29 ++------- op-e2e/interop/interop_test.go | 6 +- op-program/client/interop/consolidate.go | 21 +------ op-service/sources/supervisor_client.go | 8 ++- op-supervisor/supervisor/backend/backend.go | 42 ++++++++++++- .../backend/cross/safe_frontier_test.go | 14 ++++- .../supervisor/backend/cross/safe_start.go | 4 ++ .../backend/cross/safe_start_test.go | 31 ++++++++++ .../backend/cross/safe_update_test.go | 60 +++++++++++++++++++ .../supervisor/backend/cross/unsafe_start.go | 4 ++ .../backend/cross/unsafe_start_test.go | 34 +++++++++++ .../backend/cross/unsafe_update_test.go | 7 ++- .../supervisor/backend/depset/depset.go | 3 + .../supervisor/backend/depset/depset_test.go | 38 ++++++++++++ .../supervisor/backend/depset/static.go | 27 ++++++++- op-supervisor/supervisor/backend/mock.go | 6 +- .../backend/rewinder/rewinder_test.go | 7 +-- op-supervisor/supervisor/frontend/frontend.go | 17 +++++- op-supervisor/supervisor/service_test.go | 2 +- op-supervisor/supervisor/types/types.go | 24 ++++++++ 22 files changed, 322 insertions(+), 69 deletions(-) diff --git a/op-e2e/actions/interop/dsl/dsl.go b/op-e2e/actions/interop/dsl/dsl.go index e2457c2b9ca..de9e367ef89 100644 --- a/op-e2e/actions/interop/dsl/dsl.go +++ b/op-e2e/actions/interop/dsl/dsl.go @@ -5,6 +5,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/event" "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" "github.com/stretchr/testify/require" ) @@ -97,6 +98,10 @@ func NewInteropDSL(t helpers.Testing) *InteropDSL { } } +func (d *InteropDSL) DepSet() *depset.StaticConfigDependencySet { + return d.setup.DepSet +} + func (d *InteropDSL) defaultChainOpts() ChainOpts { return ChainOpts{ // Defensive copy to make sure the original slice isn't modified diff --git a/op-e2e/actions/interop/dsl/interop.go b/op-e2e/actions/interop/dsl/interop.go index 9bcf2e18381..d4498a60d1a 100644 --- a/op-e2e/actions/interop/dsl/interop.go +++ b/op-e2e/actions/interop/dsl/interop.go @@ -165,7 +165,7 @@ func worldToDepSet(t helpers.Testing, worldOutput *interopgen.WorldOutput) *deps HistoryMinTime: 0, } } - depSet, err := depset.NewStaticConfigDependencySet(depSetCfg) + depSet, err := depset.NewStaticConfigDependencySetWithMessageExpiryOverride(depSetCfg, messageExpiryTime) require.NoError(t, err) return depSet } diff --git a/op-e2e/actions/interop/proofs_test.go b/op-e2e/actions/interop/proofs_test.go index 34de3b5c16b..a65184cff0f 100644 --- a/op-e2e/actions/interop/proofs_test.go +++ b/op-e2e/actions/interop/proofs_test.go @@ -17,7 +17,6 @@ import ( "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" - supervisortypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" @@ -533,9 +532,6 @@ func TestInteropFaultProofs_CascadeInvalidBlock(gt *testing.T) { func TestInteropFaultProofs_MessageExpiry(gt *testing.T) { t := helpers.NewDefaultTesting(gt) - // TODO(#14234): Check message expiry in op-supervisor - t.Skip("Message expiry not yet implemented") - system := dsl.NewInteropDSL(t) actors := system.Actors @@ -772,7 +768,7 @@ func runFppAndChallengerTests(gt *testing.T, system *dsl.InteropDSL, tests []*tr for _, test := range tests { test := test gt.Run(fmt.Sprintf("%s-fpp", test.name), func(gt *testing.T) { - runFppTest(gt, test, system.Actors) + runFppTest(gt, test, system.Actors, system.DepSet()) }) gt.Run(fmt.Sprintf("%s-challenger", test.name), func(gt *testing.T) { @@ -781,7 +777,7 @@ func runFppAndChallengerTests(gt *testing.T, system *dsl.InteropDSL, tests []*tr } } -func runFppTest(gt *testing.T, test *transitionTest, actors *dsl.InteropActors) { +func runFppTest(gt *testing.T, test *transitionTest, actors *dsl.InteropActors, depSet *depset.StaticConfigDependencySet) { t := helpers.NewDefaultTesting(gt) if test.skipProgram { t.Skip("Not yet implemented") @@ -805,7 +801,7 @@ func runFppTest(gt *testing.T, test *transitionTest, actors *dsl.InteropActors) logger, actors.L1Miner, checkResult, - WithInteropEnabled(t, actors, test.agreedClaim, crypto.Keccak256Hash(test.disputedClaim), proposalTimestamp), + WithInteropEnabled(t, actors, depSet, test.agreedClaim, crypto.Keccak256Hash(test.disputedClaim), proposalTimestamp), fpHelpers.WithL1Head(l1Head), ) } @@ -853,29 +849,14 @@ func runChallengerTest(gt *testing.T, test *transitionTest, actors *dsl.InteropA } } -func WithInteropEnabled(t helpers.StatefulTesting, actors *dsl.InteropActors, agreedPrestate []byte, disputedClaim common.Hash, claimTimestamp uint64) fpHelpers.FixtureInputParam { +func WithInteropEnabled(t helpers.StatefulTesting, actors *dsl.InteropActors, depSet *depset.StaticConfigDependencySet, agreedPrestate []byte, disputedClaim common.Hash, claimTimestamp uint64) fpHelpers.FixtureInputParam { return func(f *fpHelpers.FixtureInputs) { f.InteropEnabled = true f.AgreedPrestate = agreedPrestate f.L2OutputRoot = crypto.Keccak256Hash(agreedPrestate) f.L2Claim = disputedClaim f.L2BlockNumber = claimTimestamp - - deps := map[eth.ChainID]*depset.StaticConfigDependency{ - actors.ChainA.ChainID: { - ChainIndex: supervisortypes.ChainIndex(0), - ActivationTime: 0, - HistoryMinTime: 0, - }, - actors.ChainB.ChainID: { - ChainIndex: supervisortypes.ChainIndex(1), - ActivationTime: 0, - HistoryMinTime: 0, - }, - } - var err error - f.DependencySet, err = depset.NewStaticConfigDependencySet(deps) - require.NoError(t, err) + f.DependencySet = depSet for _, chain := range []*dsl.Chain{actors.ChainA, actors.ChainB} { f.L2Sources = append(f.L2Sources, &fpHelpers.FaultProofProgramL2Source{ diff --git a/op-e2e/interop/interop_test.go b/op-e2e/interop/interop_test.go index 732c9350c77..03168d05cc1 100644 --- a/op-e2e/interop/interop_test.go +++ b/op-e2e/interop/interop_test.go @@ -215,7 +215,7 @@ func TestInterop_EmitLogs(t *testing.T) { // all logs should be cross-safe for _, log := range logsA { identifier, expectedHash := logToIdentifier(chainA, log) - safety, err := supervisor.CheckMessage(context.Background(), identifier, expectedHash) + safety, err := supervisor.CheckMessage(context.Background(), identifier, expectedHash, types.ExecutingDescriptor{Timestamp: identifier.Timestamp}) require.NoError(t, err) // the supervisor could progress the safety level more quickly than we expect, // which is why we check for a minimum safety level @@ -223,7 +223,7 @@ func TestInterop_EmitLogs(t *testing.T) { } for _, log := range logsB { identifier, expectedHash := logToIdentifier(chainB, log) - safety, err := supervisor.CheckMessage(context.Background(), identifier, expectedHash) + safety, err := supervisor.CheckMessage(context.Background(), identifier, expectedHash, types.ExecutingDescriptor{Timestamp: identifier.Timestamp}) require.NoError(t, err) // the supervisor could progress the safety level more quickly than we expect, // which is why we check for a minimum safety level @@ -234,7 +234,7 @@ func TestInterop_EmitLogs(t *testing.T) { identifier, expectedHash := logToIdentifier(chainA, logsA[0]) // make the timestamp incorrect identifier.Timestamp = 333 - safety, err := supervisor.CheckMessage(context.Background(), identifier, expectedHash) + safety, err := supervisor.CheckMessage(context.Background(), identifier, expectedHash, types.ExecutingDescriptor{Timestamp: 333}) require.NoError(t, err) require.Equal(t, types.Invalid, safety) diff --git a/op-program/client/interop/consolidate.go b/op-program/client/interop/consolidate.go index edbd8e217ea..09eb78357aa 100644 --- a/op-program/client/interop/consolidate.go +++ b/op-program/client/interop/consolidate.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" - "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-program/client/boot" "github.com/ethereum-optimism/optimism/op-program/client/interop/types" "github.com/ethereum-optimism/optimism/op-program/client/l1" @@ -102,11 +101,7 @@ func RunConsolidation( Number: optimisticBlock.NumberU64(), Timestamp: optimisticBlock.Time(), } - rollupCfg, err := bootInfo.Configs.RollupConfig(chain.ChainID) - if err != nil { - return eth.Bytes32{}, fmt.Errorf("no rollup config available for chain ID %v: %w", chain.ChainID, err) - } - if err := checkHazards(rollupCfg, deps, candidate, chain.ChainID, execMsgs); err != nil { + if err := checkHazards(deps, candidate, chain.ChainID, execMsgs); err != nil { if !isInvalidMessageError(err) { return eth.Bytes32{}, err } @@ -159,27 +154,15 @@ func isInvalidMessageError(err error) bool { type ConsolidateCheckDeps interface { cross.UnsafeFrontierCheckDeps cross.CycleCheckDeps - Contains(chain eth.ChainID, query supervisortypes.ContainsQuery) (includedIn supervisortypes.BlockSeal, err error) + cross.UnsafeStartDeps } func checkHazards( - rollupCfg *rollup.Config, deps ConsolidateCheckDeps, candidate supervisortypes.BlockSeal, chainID eth.ChainID, execMsgs []*supervisortypes.ExecutingMessage, ) error { - // TODO(#14234): remove this check once the supervisor is updated handle msg expiry - messageExpiryTimeSeconds := rollupCfg.GetMessageExpiryTimeInterop() - for _, msg := range execMsgs { - if msg.Timestamp+messageExpiryTimeSeconds < candidate.Timestamp { - return fmt.Errorf( - "message timestamp is too old: %d < %d: %w", - msg.Timestamp+messageExpiryTimeSeconds, candidate.Timestamp, supervisortypes.ErrConflict, - ) - } - } - hazards, err := cross.CrossUnsafeHazards(deps, chainID, candidate, execMsgs) if err != nil { return err diff --git a/op-service/sources/supervisor_client.go b/op-service/sources/supervisor_client.go index 50bcea1fe64..a656d9fe85a 100644 --- a/op-service/sources/supervisor_client.go +++ b/op-service/sources/supervisor_client.go @@ -60,20 +60,22 @@ func (cl *SupervisorClient) AddL2RPC(ctx context.Context, rpc string, auth eth.B return result } -func (cl *SupervisorClient) CheckMessage(ctx context.Context, identifier types.Identifier, logHash common.Hash) (types.SafetyLevel, error) { +func (cl *SupervisorClient) CheckMessage(ctx context.Context, identifier types.Identifier, logHash common.Hash, executingDescriptor types.ExecutingDescriptor) (types.SafetyLevel, error) { var result types.SafetyLevel err := cl.client.CallContext( ctx, &result, "supervisor_checkMessage", identifier, - logHash) + logHash, + executingDescriptor) if err != nil { - return types.Invalid, fmt.Errorf("failed to check message (chain %s), (block %v), (index %v), (logHash %s): %w", + return types.Invalid, fmt.Errorf("failed to check message (chain %s), (block %v), (index %v), (logHash %s), (executingTimestamp %v): %w", identifier.ChainID, identifier.BlockNumber, identifier.LogIndex, logHash, + executingDescriptor.Timestamp, err) } return result, nil diff --git a/op-supervisor/supervisor/backend/backend.go b/op-supervisor/supervisor/backend/backend.go index 6b5b80d5462..d06e266fbc2 100644 --- a/op-supervisor/supervisor/backend/backend.go +++ b/op-supervisor/supervisor/backend/backend.go @@ -423,7 +423,7 @@ func (su *SupervisorBackend) DependencySet() depset.DependencySet { // Query methods // ---------------------------- -func (su *SupervisorBackend) CheckMessage(identifier types.Identifier, payloadHash common.Hash) (types.SafetyLevel, error) { +func (su *SupervisorBackend) CheckMessage(identifier types.Identifier, payloadHash common.Hash, executingDescriptor types.ExecutingDescriptor) (types.SafetyLevel, error) { logHash := types.PayloadHashToLogHash(payloadHash, identifier.Origin) chainID := identifier.ChainID blockNum := identifier.BlockNumber @@ -446,9 +446,45 @@ func (su *SupervisorBackend) CheckMessage(identifier types.Identifier, payloadHa if err != nil { return types.Invalid, fmt.Errorf("failed to check log: %w", err) } + if identifier.Timestamp+su.depSet.MessageExpiryWindow() < executingDescriptor.Timestamp { + su.logger.Debug("Message expired", "identifier", identifier, "payloadHash", payloadHash, "executingTimestamp", executingDescriptor.Timestamp) + return types.Invalid, nil + } + if identifier.Timestamp > executingDescriptor.Timestamp { + su.logger.Debug("Message timestamp is in the future", "identifier", identifier, "payloadHash", payloadHash, "executingTimestamp", executingDescriptor.Timestamp) + return types.Invalid, nil + } return su.chainDBs.Safest(chainID, blockNum, logIdx) } +func (su *SupervisorBackend) CheckMessagesV2( + messages []types.Message, + minSafety types.SafetyLevel, + executingDescriptor types.ExecutingDescriptor) error { + su.logger.Debug("Checking messages", "count", len(messages), "minSafety", minSafety, "executingTimestamp", executingDescriptor.Timestamp) + + for _, msg := range messages { + su.logger.Debug("Checking message", + "identifier", msg.Identifier, "payloadHash", msg.PayloadHash.String(), "executingTimestamp", executingDescriptor.Timestamp) + safety, err := su.CheckMessage(msg.Identifier, msg.PayloadHash, executingDescriptor) + if err != nil { + su.logger.Error("Check message failed", "err", err, + "identifier", msg.Identifier, "payloadHash", msg.PayloadHash.String(), "executingTimestamp", executingDescriptor.Timestamp) + return fmt.Errorf("failed to check message: %w", err) + } + if !safety.AtLeastAsSafe(minSafety) { + su.logger.Error("Message is not sufficiently safe", + "safety", safety, "minSafety", minSafety, + "identifier", msg.Identifier, "payloadHash", msg.PayloadHash.String(), "executingTimestamp", executingDescriptor.Timestamp) + return fmt.Errorf("message %v (safety level: %v) does not meet the minimum safety %v", + msg.Identifier, + safety, + minSafety) + } + } + return nil +} + func (su *SupervisorBackend) CheckMessages( messages []types.Message, minSafety types.SafetyLevel) error { @@ -457,7 +493,9 @@ func (su *SupervisorBackend) CheckMessages( for _, msg := range messages { su.logger.Debug("Checking message", "identifier", msg.Identifier, "payloadHash", msg.PayloadHash.String()) - safety, err := su.CheckMessage(msg.Identifier, msg.PayloadHash) + // Guarantee message expiry checks do not fail by setting the executing timestamp to the message timestamp + // This is intentionally done to avoid breaking checkMessagesV1 which doesn't handle message expiry checks + safety, err := su.CheckMessage(msg.Identifier, msg.PayloadHash, types.ExecutingDescriptor{Timestamp: msg.Identifier.Timestamp}) if err != nil { su.logger.Error("Check message failed", "err", err, "identifier", msg.Identifier, "payloadHash", msg.PayloadHash.String()) diff --git a/op-supervisor/supervisor/backend/cross/safe_frontier_test.go b/op-supervisor/supervisor/backend/cross/safe_frontier_test.go index 71c45db208b..157b7ed8602 100644 --- a/op-supervisor/supervisor/backend/cross/safe_frontier_test.go +++ b/op-supervisor/supervisor/backend/cross/safe_frontier_test.go @@ -166,9 +166,10 @@ func (m *mockSafeFrontierCheckDeps) DependencySet() depset.DependencySet { } type mockDependencySet struct { - chainIDFromIndexfn func() (eth.ChainID, error) - canExecuteAtfn func() (bool, error) - canInitiateAtfn func() (bool, error) + chainIDFromIndexfn func() (eth.ChainID, error) + canExecuteAtfn func() (bool, error) + canInitiateAtfn func() (bool, error) + messageExpiryWindow uint64 } func (m mockDependencySet) CanExecuteAt(chain eth.ChainID, timestamp uint64) (bool, error) { @@ -209,3 +210,10 @@ func (m mockDependencySet) Chains() []eth.ChainID { func (m mockDependencySet) HasChain(chain eth.ChainID) bool { return true } + +func (m mockDependencySet) MessageExpiryWindow() uint64 { + if m.messageExpiryWindow == 0 { + return 100 + } + return m.messageExpiryWindow +} diff --git a/op-supervisor/supervisor/backend/cross/safe_start.go b/op-supervisor/supervisor/backend/cross/safe_start.go index 497065578b0..f4b5061d007 100644 --- a/op-supervisor/supervisor/backend/cross/safe_start.go +++ b/op-supervisor/supervisor/backend/cross/safe_start.go @@ -77,6 +77,10 @@ func CrossSafeHazards(d SafeStartDeps, chainID eth.ChainID, inL1Source eth.Block return nil, fmt.Errorf("msg %s was included in block %s derived from %s which is not in cross-safe scope %s: %w", msg, includedIn, initSource, inL1Source, types.ErrOutOfScope) } + // Run expiry window invariant check *after* verifying that the message is non-conflicting. + if msg.Timestamp+depSet.MessageExpiryWindow() < candidate.Timestamp { + return nil, fmt.Errorf("timestamp of message %s (chain %s) has expired: %d < %d: %w", msg, chainID, msg.Timestamp+depSet.MessageExpiryWindow(), candidate.Timestamp, types.ErrConflict) + } } else if msg.Timestamp == candidate.Timestamp { // If timestamp is equal: we have to inspect ordering of individual // log events to ensure non-cyclic cross-chain message ordering. diff --git a/op-supervisor/supervisor/backend/cross/safe_start_test.go b/op-supervisor/supervisor/backend/cross/safe_start_test.go index c3a0bdd9e1e..fb6ae7dd812 100644 --- a/op-supervisor/supervisor/backend/cross/safe_start_test.go +++ b/op-supervisor/supervisor/backend/cross/safe_start_test.go @@ -314,6 +314,37 @@ func TestCrossSafeHazards(t *testing.T) { require.NoError(t, err) require.Empty(t, hazards) }) + t.Run("message expiry", func(t *testing.T) { + ssd := &mockSafeStartDeps{} + ssd.deps.messageExpiryWindow = 10 + chainID := eth.ChainIDFromUInt64(0) + inL1Source := eth.BlockID{Number: 1} + candidate := types.BlockSeal{Timestamp: 12} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} + execMsgs := []*types.ExecutingMessage{em1} + // when there is one execMsg, and the timestamp is less than the candidate, + // and DerivedToSource returns a BlockSeal with a equal to the Number of inL1Source, + // no error is returned + hazards, err := CrossSafeHazards(ssd, chainID, inL1Source, candidate, execMsgs) + require.ErrorIs(t, err, types.ErrConflict) + require.ErrorContains(t, err, "has expired") + require.Empty(t, hazards) + }) + t.Run("message close to expiry", func(t *testing.T) { + ssd := &mockSafeStartDeps{} + ssd.deps.messageExpiryWindow = 10 + chainID := eth.ChainIDFromUInt64(0) + inL1Source := eth.BlockID{Number: 1} + candidate := types.BlockSeal{Timestamp: 11} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} + execMsgs := []*types.ExecutingMessage{em1} + // when there is one execMsg, and the timestamp is less than the candidate, + // and DerivedToSource returns a BlockSeal with a equal to the Number of inL1Source, + // no error is returned + hazards, err := CrossSafeHazards(ssd, chainID, inL1Source, candidate, execMsgs) + require.NoError(t, err) + require.Empty(t, hazards) + }) } type mockSafeStartDeps struct { diff --git a/op-supervisor/supervisor/backend/cross/safe_update_test.go b/op-supervisor/supervisor/backend/cross/safe_update_test.go index d319a01d2db..4b9f2f03e42 100644 --- a/op-supervisor/supervisor/backend/cross/safe_update_test.go +++ b/op-supervisor/supervisor/backend/cross/safe_update_test.go @@ -109,6 +109,42 @@ func TestCrossSafeUpdate(t *testing.T) { require.NoError(t, err) require.True(t, invalidated) }) + t.Run("scopedCrossSafeUpdate returns ErrExpired and triggers invalidate-local-safe", func(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + chainID := eth.ChainIDFromUInt64(0) + csd := &mockCrossSafeDeps{} + candidate := eth.BlockRef{Number: 1, Time: 11} + candidateScope := eth.BlockRef{Number: 2} + csd.candidateCrossSafeFn = func() (pair types.DerivedBlockRefPair, err error) { + return types.DerivedBlockRefPair{ + Source: candidateScope, + Derived: candidate, + }, nil + } + opened := eth.BlockRef{Number: 1, Time: 11} + execs := map[uint32]*types.ExecutingMessage{1: {}} + csd.openBlockFn = func(chainID eth.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + return opened, 10, execs, nil + } + csd.checkFn = func(chainID eth.ChainID, blockNum uint64, logIdx uint32, logHash common.Hash) (types.BlockSeal, error) { + return types.BlockSeal{Number: 1, Timestamp: 1}, nil + } + invalidated := false + csd.invalidateLocalSafeFn = func(id eth.ChainID, p types.DerivedBlockRefPair) error { + require.Equal(t, chainID, id) + require.Equal(t, candidate, p.Derived) + require.Equal(t, candidateScope, p.Source) + invalidated = true + return nil + } + csd.deps = mockDependencySet{} + csd.deps.messageExpiryWindow = 10 + // when scopedCrossSafeUpdate returns no error, + // no error is returned + err := CrossSafeUpdate(logger, chainID, csd) + require.NoError(t, err) + require.True(t, invalidated) + }) t.Run("scopedCrossSafeUpdate returns ErrOutOfScope", func(t *testing.T) { logger := testlog.Logger(t, log.LevelDebug) chainID := eth.ChainIDFromUInt64(0) @@ -407,6 +443,30 @@ func TestScopedCrossSafeUpdate(t *testing.T) { require.ErrorContains(t, err, "failed to update") require.Equal(t, eth.BlockRef{Number: 2}, pair.Source) }) + t.Run("UpdateCrossSafe returns ErrExpired", func(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + chainID := eth.ChainIDFromUInt64(0) + csd := &mockCrossSafeDeps{} + csd.deps.messageExpiryWindow = 10 + candidate := eth.BlockRef{Number: 1, Time: 11} + csd.candidateCrossSafeFn = func() (types.DerivedBlockRefPair, error) { + return types.DerivedBlockRefPair{ + Source: eth.BlockRef{}, + Derived: candidate, + }, nil + } + opened := eth.BlockRef{Number: 1, Time: 11} + execs := map[uint32]*types.ExecutingMessage{1: {}} + csd.openBlockFn = func(chainID eth.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + return opened, 0, execs, nil + } + // when OpenBlock and CandidateCrossSafe return different blocks, + // an ErrConflict is returned + pair, err := scopedCrossSafeUpdate(logger, chainID, csd) + require.ErrorIs(t, err, types.ErrConflict) + require.ErrorContains(t, err, "has expired") + require.Equal(t, eth.BlockRef{}, pair.Source) + }) t.Run("successful update", func(t *testing.T) { logger := testlog.Logger(t, log.LevelDebug) chainID := eth.ChainIDFromUInt64(0) diff --git a/op-supervisor/supervisor/backend/cross/unsafe_start.go b/op-supervisor/supervisor/backend/cross/unsafe_start.go index 8a57d9cd197..d90e78f3862 100644 --- a/op-supervisor/supervisor/backend/cross/unsafe_start.go +++ b/op-supervisor/supervisor/backend/cross/unsafe_start.go @@ -75,6 +75,10 @@ func CrossUnsafeHazards(d UnsafeStartDeps, chainID eth.ChainID, if includedIn.Timestamp != msg.Timestamp { return nil, fmt.Errorf("executing msg %s exists, but has different timestamp than block %s: %w", msg, includedIn, types.ErrConflict) } + // Run expiry window invariant check *after* verifying that the message is non-conflicting. + if msg.Timestamp+depSet.MessageExpiryWindow() < candidate.Timestamp { + return nil, fmt.Errorf("timestamp of message %s (chain %s) has expired: %d < %d: %w", msg, chainID, msg.Timestamp+depSet.MessageExpiryWindow(), candidate.Timestamp, types.ErrConflict) + } } else if msg.Timestamp == candidate.Timestamp { // If timestamp is equal: we have to inspect ordering of individual // log events to ensure non-cyclic cross-chain message ordering. diff --git a/op-supervisor/supervisor/backend/cross/unsafe_start_test.go b/op-supervisor/supervisor/backend/cross/unsafe_start_test.go index e12d7c14fa4..962c9fba9e7 100644 --- a/op-supervisor/supervisor/backend/cross/unsafe_start_test.go +++ b/op-supervisor/supervisor/backend/cross/unsafe_start_test.go @@ -253,6 +253,40 @@ func TestCrossUnsafeHazards(t *testing.T) { require.NoError(t, err) require.Empty(t, hazards) }) + t.Run("message expiry", func(t *testing.T) { + usd := &mockUnsafeStartDeps{} + usd.deps.messageExpiryWindow = 10 + sampleBlockSeal := types.BlockSeal{Timestamp: 1} + usd.checkFn = func() (includedIn types.BlockSeal, err error) { + return sampleBlockSeal, nil + } + chainID := eth.ChainIDFromUInt64(0) + candidate := types.BlockSeal{Timestamp: 12} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} + execMsgs := []*types.ExecutingMessage{em1} + // when there is one execMsg that has just expired, + // ErrExpired is returned + hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + require.ErrorIs(t, err, types.ErrConflict) + require.ErrorContains(t, err, "has expired") + require.Empty(t, hazards) + }) + t.Run("message near expiry", func(t *testing.T) { + usd := &mockUnsafeStartDeps{} + usd.deps.messageExpiryWindow = 10 + sampleBlockSeal := types.BlockSeal{Timestamp: 1} + usd.checkFn = func() (includedIn types.BlockSeal, err error) { + return sampleBlockSeal, nil + } + chainID := eth.ChainIDFromUInt64(0) + candidate := types.BlockSeal{Timestamp: 11} + em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} + execMsgs := []*types.ExecutingMessage{em1} + // when there is one execMsg that is near expiry, then no error is returned + hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + require.NoError(t, err) + require.Empty(t, hazards) + }) } type mockUnsafeStartDeps struct { diff --git a/op-supervisor/supervisor/backend/cross/unsafe_update_test.go b/op-supervisor/supervisor/backend/cross/unsafe_update_test.go index f030b9712cd..6371fc598e8 100644 --- a/op-supervisor/supervisor/backend/cross/unsafe_update_test.go +++ b/op-supervisor/supervisor/backend/cross/unsafe_update_test.go @@ -71,7 +71,7 @@ func TestCrossUnsafeUpdate(t *testing.T) { err := CrossUnsafeUpdate(logger, chainID, usd) require.ErrorIs(t, err, types.ErrConflict) }) - t.Run("CrossSafeHazards returns error", func(t *testing.T) { + t.Run("CrossUnsafeHazards returns error", func(t *testing.T) { logger := testlog.Logger(t, log.LevelDebug) chainID := eth.ChainIDFromUInt64(0) usd := &mockCrossUnsafeDeps{} @@ -181,6 +181,7 @@ func TestCrossUnsafeUpdate(t *testing.T) { type mockCrossUnsafeDeps struct { deps mockDependencySet + messageExpiryWindow uint64 crossUnsafeFn func(chainID eth.ChainID) (types.BlockSeal, error) openBlockFn func(chainID eth.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) updateCrossUnsafeFn func(chain eth.ChainID, crossUnsafe types.BlockSeal) error @@ -198,6 +199,10 @@ func (m *mockCrossUnsafeDeps) DependencySet() depset.DependencySet { return m.deps } +func (m *mockCrossUnsafeDeps) MessageExpiryWindow() uint64 { + return m.messageExpiryWindow +} + func (m *mockCrossUnsafeDeps) Contains(chainID eth.ChainID, q types.ContainsQuery) (types.BlockSeal, error) { if m.checkFn != nil { return m.checkFn(chainID, q.BlockNum, q.Timestamp, q.LogIdx, q.LogHash) diff --git a/op-supervisor/supervisor/backend/depset/depset.go b/op-supervisor/supervisor/backend/depset/depset.go index 415a849699f..4d03e60405d 100644 --- a/op-supervisor/supervisor/backend/depset/depset.go +++ b/op-supervisor/supervisor/backend/depset/depset.go @@ -36,6 +36,9 @@ type DependencySet interface { ChainIndexFromID(id eth.ChainID) (types.ChainIndex, error) + // MessageExpiryWindow returns the message expiry window to use for this dependency set. + MessageExpiryWindow() uint64 + ChainIndexFromID ChainIDFromIndex } diff --git a/op-supervisor/supervisor/backend/depset/depset_test.go b/op-supervisor/supervisor/backend/depset/depset_test.go index 0522b26eca5..31b86cbd0b0 100644 --- a/op-supervisor/supervisor/backend/depset/depset_test.go +++ b/op-supervisor/supervisor/backend/depset/depset_test.go @@ -7,6 +7,7 @@ import ( "path" "testing" + "github.com/ethereum-optimism/optimism/op-node/params" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/stretchr/testify/require" ) @@ -75,4 +76,41 @@ func TestDependencySet(t *testing.T) { v, err = result.CanInitiateAt(eth.ChainIDFromUInt64(902), 100000) require.NoError(t, err) require.False(t, v, "902 not a dependency") + + require.Equal(t, uint64(params.MessageExpiryTimeSecondsInterop), result.MessageExpiryWindow()) +} + +func TestDependencySetWithMessageExpiryOverride(t *testing.T) { + d := path.Join(t.TempDir(), "tmp_dep_set.json") + + depSet, err := NewStaticConfigDependencySet( + map[eth.ChainID]*StaticConfigDependency{ + eth.ChainIDFromUInt64(900): { + ChainIndex: 900, + ActivationTime: 42, + HistoryMinTime: 100, + }, + eth.ChainIDFromUInt64(901): { + ChainIndex: 901, + ActivationTime: 30, + HistoryMinTime: 20, + }, + }) + require.NoError(t, err) + depSet.overrideMessageExpiryWindow = 10 + data, err := json.Marshal(depSet) + require.NoError(t, err) + + require.NoError(t, os.WriteFile(d, data, 0644)) + + loader := &JsonDependencySetLoader{Path: d} + result, err := loader.LoadDependencySet(context.Background()) + require.NoError(t, err) + + chainIDs := result.Chains() + require.Equal(t, []eth.ChainID{ + eth.ChainIDFromUInt64(900), + eth.ChainIDFromUInt64(901), + }, chainIDs) + require.Equal(t, uint64(10), result.MessageExpiryWindow()) } diff --git a/op-supervisor/supervisor/backend/depset/static.go b/op-supervisor/supervisor/backend/depset/static.go index a1dbeff809a..8dad6d25086 100644 --- a/op-supervisor/supervisor/backend/depset/static.go +++ b/op-supervisor/supervisor/backend/depset/static.go @@ -7,6 +7,7 @@ import ( "slices" "sort" + "github.com/ethereum-optimism/optimism/op-node/params" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" ) @@ -34,6 +35,8 @@ type StaticConfigDependencySet struct { indexToID map[types.ChainIndex]eth.ChainID // cached list of chain IDs, sorted by ID value chainIDs []eth.ChainID + // overrideMessageExpiryWindow is the message expiry window to use for this dependency set + overrideMessageExpiryWindow uint64 } func NewStaticConfigDependencySet(dependencies map[eth.ChainID]*StaticConfigDependency) (*StaticConfigDependencySet, error) { @@ -44,16 +47,28 @@ func NewStaticConfigDependencySet(dependencies map[eth.ChainID]*StaticConfigDepe return out, nil } +// NewStaticConfigDependencySetWithMessageExpiryOverride creates a new StaticConfigDependencySet with a message expiry window override. +// To be used only for testing. +func NewStaticConfigDependencySetWithMessageExpiryOverride(dependencies map[eth.ChainID]*StaticConfigDependency, overrideMessageExpiryWindow uint64) (*StaticConfigDependencySet, error) { + out := &StaticConfigDependencySet{dependencies: dependencies, overrideMessageExpiryWindow: overrideMessageExpiryWindow} + if err := out.hydrate(); err != nil { + return nil, err + } + return out, nil +} + // jsonStaticConfigDependencySet is a util for JSON encoding/decoding, // to encode/decode just the attributes that matter, // while wrapping the decoding functionality with additional hydration step. type jsonStaticConfigDependencySet struct { - Dependencies map[eth.ChainID]*StaticConfigDependency `json:"dependencies"` + Dependencies map[eth.ChainID]*StaticConfigDependency `json:"dependencies"` + OverrideMessageExpiryWindow uint64 `json:"overrideMessageExpiryWindow,omitempty"` } func (ds *StaticConfigDependencySet) MarshalJSON() ([]byte, error) { out := &jsonStaticConfigDependencySet{ - Dependencies: ds.dependencies, + Dependencies: ds.dependencies, + OverrideMessageExpiryWindow: ds.overrideMessageExpiryWindow, } return json.Marshal(out) } @@ -64,6 +79,7 @@ func (ds *StaticConfigDependencySet) UnmarshalJSON(data []byte) error { return err } ds.dependencies = v.Dependencies + ds.overrideMessageExpiryWindow = v.OverrideMessageExpiryWindow return ds.hydrate() } @@ -132,3 +148,10 @@ func (ds *StaticConfigDependencySet) ChainIDFromIndex(index types.ChainIndex) (e } return id, nil } + +func (ds *StaticConfigDependencySet) MessageExpiryWindow() uint64 { + if ds.overrideMessageExpiryWindow == 0 { + return params.MessageExpiryTimeSecondsInterop + } + return ds.overrideMessageExpiryWindow +} diff --git a/op-supervisor/supervisor/backend/mock.go b/op-supervisor/supervisor/backend/mock.go index 9022f6bf7fa..c6de2b91df4 100644 --- a/op-supervisor/supervisor/backend/mock.go +++ b/op-supervisor/supervisor/backend/mock.go @@ -47,7 +47,7 @@ func (m *MockBackend) AddL2RPC(ctx context.Context, rpc string, jwtSecret eth.By return nil } -func (m *MockBackend) CheckMessage(identifier types.Identifier, payloadHash common.Hash) (types.SafetyLevel, error) { +func (m *MockBackend) CheckMessage(identifier types.Identifier, payloadHash common.Hash, executingDescriptor types.ExecutingDescriptor) (types.SafetyLevel, error) { return types.CrossUnsafe, nil } @@ -55,6 +55,10 @@ func (m *MockBackend) CheckMessages(messages []types.Message, minSafety types.Sa return nil } +func (m *MockBackend) CheckMessagesV2(messages []types.Message, minSafety types.SafetyLevel, executingDescriptor types.ExecutingDescriptor) error { + return nil +} + func (m *MockBackend) LocalUnsafe(ctx context.Context, chainID eth.ChainID) (eth.BlockID, error) { return eth.BlockID{}, nil } diff --git a/op-supervisor/supervisor/backend/rewinder/rewinder_test.go b/op-supervisor/supervisor/backend/rewinder/rewinder_test.go index b01a8d613ca..692a3354687 100644 --- a/op-supervisor/supervisor/backend/rewinder/rewinder_test.go +++ b/op-supervisor/supervisor/backend/rewinder/rewinder_test.go @@ -1016,10 +1016,5 @@ func (m *mockL1Node) L1BlockRefByNumber(ctx context.Context, number uint64) (eth if !ok { return eth.L1BlockRef{}, fmt.Errorf("block not found: %d", number) } - return eth.L1BlockRef{ - Hash: block.Hash, - Number: block.Number, - Time: block.Time, - ParentHash: block.ParentHash, - }, nil + return eth.L1BlockRef(block), nil } diff --git a/op-supervisor/supervisor/frontend/frontend.go b/op-supervisor/supervisor/frontend/frontend.go index 1901b8f5a4b..91dfe3fe29e 100644 --- a/op-supervisor/supervisor/frontend/frontend.go +++ b/op-supervisor/supervisor/frontend/frontend.go @@ -16,8 +16,9 @@ type AdminBackend interface { } type QueryBackend interface { - CheckMessage(identifier types.Identifier, payloadHash common.Hash) (types.SafetyLevel, error) + CheckMessage(identifier types.Identifier, payloadHash common.Hash, executingDescriptor types.ExecutingDescriptor) (types.SafetyLevel, error) CheckMessages(messages []types.Message, minSafety types.SafetyLevel) error + CheckMessagesV2(messages []types.Message, minSafety types.SafetyLevel, executingDescriptor types.ExecutingDescriptor) error CrossDerivedToSource(ctx context.Context, chainID eth.ChainID, derived eth.BlockID) (derivedFrom eth.BlockRef, err error) LocalUnsafe(ctx context.Context, chainID eth.ChainID) (eth.BlockID, error) CrossSafe(ctx context.Context, chainID eth.ChainID) (types.DerivedIDPair, error) @@ -41,12 +42,22 @@ var _ QueryBackend = (*QueryFrontend)(nil) // CheckMessage checks the safety-level of an individual message. // The payloadHash references the hash of the message-payload of the message. -func (q *QueryFrontend) CheckMessage(identifier types.Identifier, payloadHash common.Hash) (types.SafetyLevel, error) { - return q.Supervisor.CheckMessage(identifier, payloadHash) +func (q *QueryFrontend) CheckMessage(identifier types.Identifier, payloadHash common.Hash, executingDescriptor types.ExecutingDescriptor) (types.SafetyLevel, error) { + return q.Supervisor.CheckMessage(identifier, payloadHash, executingDescriptor) +} + +// CheckMessagesV2 checks the safety-level of a collection of messages, +// and returns if the minimum safety-level is met for all messages. +func (q *QueryFrontend) CheckMessagesV2( + messages []types.Message, + minSafety types.SafetyLevel, + executingDescriptor types.ExecutingDescriptor) error { + return q.Supervisor.CheckMessagesV2(messages, minSafety, executingDescriptor) } // CheckMessages checks the safety-level of a collection of messages, // and returns if the minimum safety-level is met for all messages. +// Deprecated: This method does not check for message expiry. func (q *QueryFrontend) CheckMessages( messages []types.Message, minSafety types.SafetyLevel) error { diff --git a/op-supervisor/supervisor/service_test.go b/op-supervisor/supervisor/service_test.go index 4b17f8269be..01003e17f6e 100644 --- a/op-supervisor/supervisor/service_test.go +++ b/op-supervisor/supervisor/service_test.go @@ -73,7 +73,7 @@ func TestSupervisorService(t *testing.T) { LogIndex: 42, Timestamp: 1234567, ChainID: eth.ChainID{0xbb}, - }, common.Hash{0xcc}) + }, common.Hash{0xcc}, types.ExecutingDescriptor{Timestamp: 1234568}) cancel() require.NoError(t, err) require.Equal(t, types.CrossUnsafe, dest, "expecting mock to return cross-unsafe") diff --git a/op-supervisor/supervisor/types/types.go b/op-supervisor/supervisor/types/types.go index 6a2a9e1c148..6302a7086bf 100644 --- a/op-supervisor/supervisor/types/types.go +++ b/op-supervisor/supervisor/types/types.go @@ -184,6 +184,30 @@ const ( Invalid SafetyLevel = "invalid" ) +type ExecutingDescriptor struct { + // Timestamp is the timestamp of the executing message + Timestamp uint64 +} + +type executingDescriptorMarshaling struct { + Timestamp hexutil.Uint64 `json:"timestamp"` +} + +func (ed ExecutingDescriptor) MarshalJSON() ([]byte, error) { + var enc executingDescriptorMarshaling + enc.Timestamp = hexutil.Uint64(ed.Timestamp) + return json.Marshal(&enc) +} + +func (ed *ExecutingDescriptor) UnmarshalJSON(input []byte) error { + var dec executingDescriptorMarshaling + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + ed.Timestamp = uint64(dec.Timestamp) + return nil +} + type ReferenceView struct { Local eth.BlockID `json:"local"` Cross eth.BlockID `json:"cross"` From 3085c7390302d0175b0f54b8a40bab5157920a49 Mon Sep 17 00:00:00 2001 From: Inphi Date: Wed, 26 Feb 2025 16:27:51 -0500 Subject: [PATCH 023/130] op-challenger: Add Super DG contract caller (#14551) * op-challenger: Add Super DG contract caller * op-dispute-mon: Support Super FDG game types --- op-challenger/game/fault/contracts/detect.go | 44 +++++++ .../game/fault/contracts/faultdisputegame.go | 15 ++- .../fault/contracts/faultdisputegame_test.go | 121 ++++++++++-------- .../fault/contracts/superfaultdisputegame.go | 71 ++++++++++ .../game/fault/register_task_test.go | 56 ++++---- op-dispute-mon/mon/extract/caller.go | 4 +- op-dispute-mon/mon/extract/caller_test.go | 17 ++- 7 files changed, 247 insertions(+), 81 deletions(-) create mode 100644 op-challenger/game/fault/contracts/detect.go create mode 100644 op-challenger/game/fault/contracts/superfaultdisputegame.go diff --git a/op-challenger/game/fault/contracts/detect.go b/op-challenger/game/fault/contracts/detect.go new file mode 100644 index 00000000000..91abca153f2 --- /dev/null +++ b/op-challenger/game/fault/contracts/detect.go @@ -0,0 +1,44 @@ +package contracts + +import ( + "context" + "fmt" + + faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + "github.com/ethereum-optimism/optimism/op-service/sources/batching" + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + "github.com/ethereum/go-ethereum/common" +) + +var ( + methodGameType = "gameType" + + gameTypeABI = mustParseAbi([]byte(`[{ + "inputs": [], + "name": "gameType", + "outputs": [{"type": "uint32"}], + "stateMutability": "view", + "type": "function" + }]`)) +) + +func DetectGameType(ctx context.Context, addr common.Address, caller *batching.MultiCaller) (faultTypes.GameType, error) { + result, err := caller.SingleCall(ctx, rpcblock.Latest, batching.NewContractCall(gameTypeABI, addr, methodGameType)) + if err != nil { + return faultTypes.UnknownGameType, fmt.Errorf("failed to detect game type: %w", err) + } + gameType := faultTypes.GameType(result.GetUint32(0)) + switch gameType { + case faultTypes.CannonGameType, + faultTypes.PermissionedGameType, + faultTypes.AsteriscGameType, + faultTypes.AlphabetGameType, + faultTypes.FastGameType, + faultTypes.AsteriscKonaGameType, + faultTypes.SuperCannonGameType, + faultTypes.SuperPermissionedGameType: + return gameType, nil + default: + return faultTypes.UnknownGameType, fmt.Errorf("unsupported game type: %d", gameType) + } +} diff --git a/op-challenger/game/fault/contracts/faultdisputegame.go b/op-challenger/game/fault/contracts/faultdisputegame.go index ebb5af9d0a0..f0385cd5013 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame.go +++ b/op-challenger/game/fault/contracts/faultdisputegame.go @@ -76,6 +76,19 @@ type outputRootProof struct { } func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMetricer, addr common.Address, caller *batching.MultiCaller) (FaultDisputeGameContract, error) { + gameType, err := DetectGameType(ctx, addr, caller) + if err != nil { + return nil, fmt.Errorf("failed to detect game type: %w", err) + } + switch gameType { + case types.SuperCannonGameType, types.SuperPermissionedGameType: + return NewSuperFaultDisputeGameContract(ctx, metrics, addr, caller) + default: + return NewPreInteropFaultDisputeGameContract(ctx, metrics, addr, caller) + } +} + +func NewPreInteropFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMetricer, addr common.Address, caller *batching.MultiCaller) (FaultDisputeGameContract, error) { contractAbi := snapshots.LoadFaultDisputeGameABI() var builder VersionedBuilder[FaultDisputeGameContract] @@ -211,7 +224,7 @@ func (f *FaultDisputeGameContractLatest) GetGameMetadata(ctx context.Context, bl return GameMetadata{}, fmt.Errorf("failed to retrieve game metadata: %w", err) } if len(results) != 7 { - return GameMetadata{}, fmt.Errorf("expected 6 results but got %v", len(results)) + return GameMetadata{}, fmt.Errorf("expected 7 results but got %v", len(results)) } l1Head := results[0].GetHash(0) l2BlockNumber := results[1].GetBigInt(0).Uint64() diff --git a/op-challenger/game/fault/contracts/faultdisputegame_test.go b/op-challenger/game/fault/contracts/faultdisputegame_test.go index e3e1901d375..683dd06551a 100644 --- a/op-challenger/game/fault/contracts/faultdisputegame_test.go +++ b/op-challenger/game/fault/contracts/faultdisputegame_test.go @@ -38,57 +38,78 @@ var ( ) type contractVersion struct { - version string - loadAbi func() *abi.ABI + version string + gameType faultTypes.GameType + loadAbi func() *abi.ABI } func (c contractVersion) Is(versions ...string) bool { return slices.Contains(versions, c.version) } +func (c contractVersion) String() string { + return fmt.Sprintf("%s (%s)", c.version, c.gameType) +} + +func (c contractVersion) IsSuperCannon() bool { + return c.gameType == faultTypes.SuperCannonGameType || c.gameType == faultTypes.SuperPermissionedGameType +} + const ( - vers080 = "0.8.0" - vers0180 = "0.18.0" - vers111 = "1.1.1" - vers120 = "1.2.0" - vers131 = "1.3.1" - versLatest = "1.4.0" + vers080 = "0.8.0" + vers0180 = "0.18.0" + vers111 = "1.1.1" + vers120 = "1.2.0" + vers131 = "1.3.1" + versLatest = "1.4.0" + verSuperCannon = "0.1.0" ) var versions = []contractVersion{ { - version: vers080, + version: vers080, + gameType: faultTypes.CannonGameType, loadAbi: func() *abi.ABI { return mustParseAbi(faultDisputeGameAbi020) }, }, { - version: vers0180, + version: vers0180, + gameType: faultTypes.CannonGameType, loadAbi: func() *abi.ABI { return mustParseAbi(faultDisputeGameAbi0180) }, }, { - version: vers111, + version: vers111, + gameType: faultTypes.CannonGameType, loadAbi: func() *abi.ABI { return mustParseAbi(faultDisputeGameAbi111) }, }, { - version: vers120, + version: vers120, + gameType: faultTypes.CannonGameType, loadAbi: func() *abi.ABI { return mustParseAbi(faultDisputeGameAbi120) }, }, { - version: vers131, + version: vers131, + gameType: faultTypes.CannonGameType, loadAbi: func() *abi.ABI { return mustParseAbi(faultDisputeGameAbi131) }, }, { - version: versLatest, - loadAbi: snapshots.LoadFaultDisputeGameABI, + version: versLatest, + gameType: faultTypes.CannonGameType, + loadAbi: snapshots.LoadFaultDisputeGameABI, + }, + { + version: verSuperCannon, + gameType: faultTypes.SuperCannonGameType, + loadAbi: snapshots.LoadFaultDisputeGameABI, }, } @@ -188,7 +209,7 @@ func TestSimpleGetters(t *testing.T) { } for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { for _, test := range tests { test := test t.Run(test.methodAlias, func(t *testing.T) { @@ -214,7 +235,7 @@ func TestBondDistributionMode(t *testing.T) { unsupportedVersions := []string{vers080, vers0180, vers111, vers120, vers131} for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { supported := !slices.Contains(unsupportedVersions, version.version) stubRpc, game := setupFaultDisputeGameTest(t, version) if supported { @@ -272,7 +293,7 @@ func TestClock_EncodingDecoding(t *testing.T) { func TestGetOracleAddr(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) stubRpc.SetResponse(fdgAddr, methodVM, rpcblock.Latest, nil, []interface{}{vmAddr}) stubRpc.SetResponse(vmAddr, methodOracle, rpcblock.Latest, nil, []interface{}{oracleAddr}) @@ -287,7 +308,7 @@ func TestGetOracleAddr(t *testing.T) { func TestGetClaim(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) idx := big.NewInt(2) parentIndex := uint32(1) @@ -319,7 +340,7 @@ func TestGetClaim(t *testing.T) { func TestGetAllClaims(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) claim0 := faultTypes.Claim{ ClaimData: faultTypes.ClaimData{ @@ -372,7 +393,7 @@ func TestGetAllClaims(t *testing.T) { func TestGetBalance(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { wethAddr := common.Address{0x11, 0x55, 0x66} balance := big.NewInt(9995877) delaySeconds := big.NewInt(429829) @@ -396,7 +417,7 @@ func TestGetBalance(t *testing.T) { func TestCallResolveClaim(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) if version.version == vers080 { stubRpc.SetResponse(fdgAddr, methodResolveClaim, rpcblock.Latest, []interface{}{big.NewInt(123)}, nil) @@ -412,7 +433,7 @@ func TestCallResolveClaim(t *testing.T) { func TestResolveClaimTxTest(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) if version.version == vers080 { stubRpc.SetResponse(fdgAddr, methodResolveClaim, rpcblock.Latest, []interface{}{big.NewInt(123)}, nil) @@ -429,7 +450,7 @@ func TestResolveClaimTxTest(t *testing.T) { func TestResolveTx(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) stubRpc.SetResponse(fdgAddr, methodResolve, rpcblock.Latest, nil, nil) tx, err := game.ResolveTx() @@ -442,7 +463,7 @@ func TestResolveTx(t *testing.T) { func TestAttackTx(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) bond := big.NewInt(1044) value := common.Hash{0xaa} @@ -464,7 +485,7 @@ func TestAttackTx(t *testing.T) { func TestDefendTx(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) bond := big.NewInt(1044) value := common.Hash{0xaa} @@ -486,7 +507,7 @@ func TestDefendTx(t *testing.T) { func TestStepTx(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) stateData := []byte{1, 2, 3} proofData := []byte{4, 5, 6, 7, 8, 9} @@ -518,7 +539,7 @@ func expectGetClaim(stubRpc *batchingTest.AbiBasedRpc, block rpcblock.Block, cla func TestGetBlockRange(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, contract := setupFaultDisputeGameTest(t, version) expectedStart := uint64(65) expectedEnd := uint64(102) @@ -535,7 +556,7 @@ func TestGetBlockRange(t *testing.T) { func TestGetSplitDepth(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, contract := setupFaultDisputeGameTest(t, version) expectedSplitDepth := faultTypes.Depth(15) stubRpc.SetResponse(fdgAddr, methodSplitDepth, rpcblock.Latest, nil, []interface{}{new(big.Int).SetUint64(uint64(expectedSplitDepth))}) @@ -549,7 +570,7 @@ func TestGetSplitDepth(t *testing.T) { func TestGetGameMetadata(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, contract := setupFaultDisputeGameTest(t, version) expectedL1Head := common.Hash{0x0a, 0x0b} expectedL2BlockNumber := uint64(123) @@ -563,18 +584,13 @@ func TestGetGameMetadata(t *testing.T) { stubRpc.SetResponse(fdgAddr, methodL2BlockNumber, block, nil, []interface{}{new(big.Int).SetUint64(expectedL2BlockNumber)}) stubRpc.SetResponse(fdgAddr, methodRootClaim, block, nil, []interface{}{expectedRootClaim}) stubRpc.SetResponse(fdgAddr, methodStatus, block, nil, []interface{}{expectedStatus}) - if version.version == vers080 { - expectedL2BlockNumberChallenged = false - expectedL2BlockNumberChallenger = common.Address{} - stubRpc.SetResponse(fdgAddr, methodGameDuration, block, nil, []interface{}{expectedMaxClockDuration * 2}) - } else if version.version == vers0180 { - expectedL2BlockNumberChallenged = false - expectedL2BlockNumberChallenger = common.Address{} - stubRpc.SetResponse(fdgAddr, methodMaxClockDuration, block, nil, []interface{}{expectedMaxClockDuration}) - } else { + supportsL2BlockNumChallenge := (version.version != vers080 && version.version != vers0180) && !version.IsSuperCannon() + if supportsL2BlockNumChallenge { stubRpc.SetResponse(fdgAddr, methodMaxClockDuration, block, nil, []interface{}{expectedMaxClockDuration}) stubRpc.SetResponse(fdgAddr, methodL2BlockNumberChallenged, block, nil, []interface{}{expectedL2BlockNumberChallenged}) stubRpc.SetResponse(fdgAddr, methodL2BlockNumberChallenger, block, nil, []interface{}{expectedL2BlockNumberChallenger}) + } else if expectedL2BlockNumberChallenged { + t.Skip("Can't have challenged L2 block number on this contract version") } actual, err := contract.GetGameMetadata(context.Background(), block) expected := GameMetadata{ @@ -595,7 +611,7 @@ func TestGetGameMetadata(t *testing.T) { func TestGetStartingRootHash(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, contract := setupFaultDisputeGameTest(t, version) expectedOutputRoot := common.HexToHash("0x1234") stubRpc.SetResponse(fdgAddr, methodStartingRootHash, rpcblock.Latest, nil, []interface{}{expectedOutputRoot}) @@ -609,7 +625,7 @@ func TestGetStartingRootHash(t *testing.T) { func TestFaultDisputeGame_UpdateOracleTx(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { t.Run("Local", func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) data := faultTypes.NewPreimageOracleData(common.Hash{0x01, 0xbc}.Bytes(), []byte{1, 2, 3, 4, 5, 6, 7}, 16) @@ -645,7 +661,7 @@ func TestFaultDisputeGame_UpdateOracleTx(t *testing.T) { func TestFaultDisputeGame_GetCredit(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) addr := common.Address{0x01} expectedCredit := big.NewInt(4284) @@ -664,7 +680,7 @@ func TestFaultDisputeGame_GetCredit(t *testing.T) { func TestFaultDisputeGame_GetCredits(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) block := rpcblock.ByNumber(482) @@ -689,7 +705,7 @@ func TestFaultDisputeGame_GetCredits(t *testing.T) { func TestFaultDisputeGame_ClaimCreditTx(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { t.Run("Success", func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) addr := common.Address{0xaa} @@ -716,7 +732,7 @@ func TestFaultDisputeGame_ClaimCreditTx(t *testing.T) { func TestFaultDisputeGame_IsResolved(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { stubRpc, game := setupFaultDisputeGameTest(t, version) block := rpcblock.ByNumber(482) @@ -763,12 +779,16 @@ func TestFaultDisputeGame_IsResolved(t *testing.T) { func TestFaultDisputeGameContractLatest_IsL2BlockNumberChallenged(t *testing.T) { for _, version := range versions { version := version - for _, expected := range []bool{true, false} { + var expectations = []bool{true, false} + if version.IsSuperCannon() { + expectations = []bool{false} + } + for _, expected := range expectations { expected := expected - t.Run(fmt.Sprintf("%v-%v", version.version, expected), func(t *testing.T) { + t.Run(fmt.Sprintf("%v-%v", version.String(), expected), func(t *testing.T) { block := rpcblock.ByHash(common.Hash{0x43}) stubRpc, game := setupFaultDisputeGameTest(t, version) - supportsL2BlockNumChallenge := version.version != vers080 && version.version != vers0180 + supportsL2BlockNumChallenge := (version.version != vers080 && version.version != vers0180) && !version.IsSuperCannon() if supportsL2BlockNumChallenge { stubRpc.SetResponse(fdgAddr, methodL2BlockNumberChallenged, block, nil, []interface{}{expected}) } else if expected { @@ -785,7 +805,7 @@ func TestFaultDisputeGameContractLatest_IsL2BlockNumberChallenged(t *testing.T) func TestFaultDisputeGameContractLatest_ChallengeL2BlockNumberTx(t *testing.T) { for _, version := range versions { version := version - t.Run(version.version, func(t *testing.T) { + t.Run(version.String(), func(t *testing.T) { rng := rand.New(rand.NewSource(0)) stubRpc, game := setupFaultDisputeGameTest(t, version) challenge := &faultTypes.InvalidL2BlockNumberChallenge{ @@ -798,7 +818,7 @@ func TestFaultDisputeGameContractLatest_ChallengeL2BlockNumberTx(t *testing.T) { }, Header: testutils.RandomHeader(rng), } - supportsL2BlockNumChallenge := version.version != vers080 && version.version != vers0180 + supportsL2BlockNumChallenge := (version.version != vers080 && version.version != vers0180) && !version.IsSuperCannon() if supportsL2BlockNumChallenge { headerRlp, err := rlp.EncodeToBytes(challenge.Header) require.NoError(t, err) @@ -835,6 +855,7 @@ func setupFaultDisputeGameTest(t *testing.T, version contractVersion) (*batching stubRpc.AddContract(oracleAddr, oracleAbi) caller := batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize) + stubRpc.SetResponse(fdgAddr, methodGameType, rpcblock.Latest, nil, []interface{}{uint32(version.gameType)}) stubRpc.SetResponse(fdgAddr, methodVersion, rpcblock.Latest, nil, []interface{}{version.version}) stubRpc.SetResponse(oracleAddr, methodVersion, rpcblock.Latest, nil, []interface{}{oracleLatest}) game, err := NewFaultDisputeGameContract(context.Background(), contractMetrics.NoopContractMetrics, fdgAddr, caller) diff --git a/op-challenger/game/fault/contracts/superfaultdisputegame.go b/op-challenger/game/fault/contracts/superfaultdisputegame.go new file mode 100644 index 00000000000..5add719a453 --- /dev/null +++ b/op-challenger/game/fault/contracts/superfaultdisputegame.go @@ -0,0 +1,71 @@ +package contracts + +import ( + "context" + "fmt" + + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" + "github.com/ethereum-optimism/optimism/op-service/sources/batching" + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + "github.com/ethereum-optimism/optimism/op-service/txmgr" + "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots" + "github.com/ethereum/go-ethereum/common" +) + +type SuperFaultDisputeGameContractLatest struct { + FaultDisputeGameContractLatest +} + +func NewSuperFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMetricer, addr common.Address, caller *batching.MultiCaller) (FaultDisputeGameContract, error) { + contractAbi := snapshots.LoadFaultDisputeGameABI() + return &SuperFaultDisputeGameContractLatest{ + FaultDisputeGameContractLatest: FaultDisputeGameContractLatest{ + metrics: metrics, + multiCaller: caller, + contract: batching.NewBoundContract(contractAbi, addr), + }, + }, nil +} + +// GetGameMetadata returns the game's L1 head, L2 block number, root claim, status, max clock duration, and is l2 block number challenged. +func (f *SuperFaultDisputeGameContractLatest) GetGameMetadata(ctx context.Context, block rpcblock.Block) (GameMetadata, error) { + defer f.metrics.StartContractRequest("GetGameMetadata")() + results, err := f.multiCaller.Call(ctx, block, + f.contract.Call(methodL1Head), + f.contract.Call(methodL2BlockNumber), + f.contract.Call(methodRootClaim), + f.contract.Call(methodStatus), + f.contract.Call(methodMaxClockDuration), + ) + if err != nil { + return GameMetadata{}, fmt.Errorf("failed to retrieve game metadata: %w", err) + } + if len(results) != 5 { + return GameMetadata{}, fmt.Errorf("expected 5 results but got %v", len(results)) + } + l1Head := results[0].GetHash(0) + l2BlockNumber := results[1].GetBigInt(0).Uint64() + rootClaim := results[2].GetHash(0) + status, err := gameTypes.GameStatusFromUint8(results[3].GetUint8(0)) + if err != nil { + return GameMetadata{}, fmt.Errorf("failed to convert game status: %w", err) + } + duration := results[4].GetUint64(0) + return GameMetadata{ + L1Head: l1Head, + L2BlockNum: l2BlockNumber, + RootClaim: rootClaim, + Status: status, + MaxClockDuration: duration, + }, nil +} + +func (f *SuperFaultDisputeGameContractLatest) IsL2BlockNumberChallenged(ctx context.Context, block rpcblock.Block) (bool, error) { + return false, nil +} + +func (f *SuperFaultDisputeGameContractLatest) ChallengeL2BlockNumberTx(challenge *types.InvalidL2BlockNumberChallenge) (txmgr.TxCandidate, error) { + return txmgr.TxCandidate{}, ErrChallengeL2BlockNotSupported +} diff --git a/op-challenger/game/fault/register_task_test.go b/op-challenger/game/fault/register_task_test.go index f09c3e79fe3..2e58fadf74a 100644 --- a/op-challenger/game/fault/register_task_test.go +++ b/op-challenger/game/fault/register_task_test.go @@ -2,6 +2,7 @@ package fault import ( "context" + "fmt" "testing" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" @@ -39,33 +40,38 @@ func TestRegisterOracle_MissingGameImpl(t *testing.T) { } func TestRegisterOracle_AddsOracle(t *testing.T) { - gameFactoryAddr := common.Address{0xaa} - gameImplAddr := common.Address{0xbb} - vmAddr := common.Address{0xcc} - oracleAddr := common.Address{0xdd} - rpc := test.NewAbiBasedRpc(t, gameFactoryAddr, snapshots.LoadDisputeGameFactoryABI()) - rpc.AddContract(gameImplAddr, snapshots.LoadFaultDisputeGameABI()) - rpc.AddContract(vmAddr, snapshots.LoadMIPSABI()) - rpc.AddContract(oracleAddr, snapshots.LoadPreimageOracleABI()) - m := metrics.NoopMetrics - caller := batching.NewMultiCaller(rpc, batching.DefaultBatchSize) - gameFactory := contracts.NewDisputeGameFactoryContract(m, gameFactoryAddr, caller) + for _, gameType := range []faultTypes.GameType{faultTypes.CannonGameType, faultTypes.SuperCannonGameType} { + t.Run(fmt.Sprintf("%v", gameType), func(t *testing.T) { + gameFactoryAddr := common.Address{0xaa} + gameImplAddr := common.Address{0xbb} + vmAddr := common.Address{0xcc} + oracleAddr := common.Address{0xdd} + rpc := test.NewAbiBasedRpc(t, gameFactoryAddr, snapshots.LoadDisputeGameFactoryABI()) + rpc.AddContract(gameImplAddr, snapshots.LoadFaultDisputeGameABI()) + rpc.AddContract(vmAddr, snapshots.LoadMIPSABI()) + rpc.AddContract(oracleAddr, snapshots.LoadPreimageOracleABI()) + m := metrics.NoopMetrics + caller := batching.NewMultiCaller(rpc, batching.DefaultBatchSize) + gameFactory := contracts.NewDisputeGameFactoryContract(m, gameFactoryAddr, caller) - logger := testlog.Logger(t, log.LvlInfo) - oracles := registry.NewOracleRegistry() - gameType := faultTypes.CannonGameType + logger := testlog.Logger(t, log.LvlInfo) + oracles := registry.NewOracleRegistry() - // Use the latest v1 of these contracts. Doesn't have to be an exact match for the version. - rpc.SetResponse(gameImplAddr, "version", rpcblock.Latest, []interface{}{}, []interface{}{"1.100.0"}) - rpc.SetResponse(oracleAddr, "version", rpcblock.Latest, []interface{}{}, []interface{}{"1.100.0"}) + // Use the latest v1 of these contracts. Doesn't have to be an exact match for the version. + rpc.SetResponse(gameImplAddr, "version", rpcblock.Latest, []interface{}{}, []interface{}{"1.100.0"}) + rpc.SetResponse(oracleAddr, "version", rpcblock.Latest, []interface{}{}, []interface{}{"1.100.0"}) - rpc.SetResponse(gameFactoryAddr, "gameImpls", rpcblock.Latest, []interface{}{gameType}, []interface{}{gameImplAddr}) - rpc.SetResponse(gameImplAddr, "vm", rpcblock.Latest, []interface{}{}, []interface{}{vmAddr}) - rpc.SetResponse(vmAddr, "oracle", rpcblock.Latest, []interface{}{}, []interface{}{oracleAddr}) + rpc.SetResponse(gameFactoryAddr, "gameImpls", rpcblock.Latest, []interface{}{gameType}, []interface{}{gameImplAddr}) + rpc.SetResponse(gameImplAddr, "vm", rpcblock.Latest, []interface{}{}, []interface{}{vmAddr}) + rpc.SetResponse(vmAddr, "oracle", rpcblock.Latest, []interface{}{}, []interface{}{oracleAddr}) - err := registerOracle(context.Background(), logger, m, oracles, gameFactory, caller, gameType) - require.NoError(t, err) - registered := oracles.Oracles() - require.Len(t, registered, 1) - require.Equal(t, oracleAddr, registered[0].Addr()) + rpc.SetResponse(gameImplAddr, "gameType", rpcblock.Latest, []interface{}{}, []interface{}{uint32(gameType)}) + + err := registerOracle(context.Background(), logger, m, oracles, gameFactory, caller, gameType) + require.NoError(t, err) + registered := oracles.Oracles() + require.Len(t, registered, 1) + require.Equal(t, oracleAddr, registered[0].Addr()) + }) + } } diff --git a/op-dispute-mon/mon/extract/caller.go b/op-dispute-mon/mon/extract/caller.go index 0b88360b69a..cdd2d887d42 100644 --- a/op-dispute-mon/mon/extract/caller.go +++ b/op-dispute-mon/mon/extract/caller.go @@ -56,7 +56,9 @@ func (g *GameCallerCreator) CreateContract(ctx context.Context, game gameTypes.G faultTypes.AsteriscGameType, faultTypes.AlphabetGameType, faultTypes.FastGameType, - faultTypes.AsteriscKonaGameType: + faultTypes.AsteriscKonaGameType, + faultTypes.SuperCannonGameType, + faultTypes.SuperPermissionedGameType: fdg, err := contracts.NewFaultDisputeGameContract(ctx, g.m, game.Proxy, g.caller) if err != nil { return nil, fmt.Errorf("failed to create fault dispute game contract: %w", err) diff --git a/op-dispute-mon/mon/extract/caller_test.go b/op-dispute-mon/mon/extract/caller_test.go index 61c7f349c82..8892c2a794f 100644 --- a/op-dispute-mon/mon/extract/caller_test.go +++ b/op-dispute-mon/mon/extract/caller_test.go @@ -51,17 +51,25 @@ func TestMetadataCreator_CreateContract(t *testing.T) { name: "validAsteriscKonaGameType", game: types.GameMetadata{GameType: uint32(faultTypes.AsteriscKonaGameType), Proxy: fdgAddr}, }, + { + name: "validSuperCannonGameType", + game: types.GameMetadata{GameType: uint32(faultTypes.SuperCannonGameType), Proxy: fdgAddr}, + }, + { + name: "validSuperPermissionedGameType", + game: types.GameMetadata{GameType: uint32(faultTypes.SuperPermissionedGameType), Proxy: fdgAddr}, + }, { name: "InvalidGameType", - game: types.GameMetadata{GameType: 4, Proxy: fdgAddr}, - expectedErr: fmt.Errorf("unsupported game type: 4"), + game: types.GameMetadata{GameType: 6, Proxy: fdgAddr}, + expectedErr: fmt.Errorf("unsupported game type: 6"), }, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { - caller, metrics := setupMetadataLoaderTest(t) + caller, metrics := setupMetadataLoaderTest(t, test.game.GameType) creator := NewGameCallerCreator(metrics, caller) _, err := creator.CreateContract(context.Background(), test.game) require.Equal(t, test.expectedErr, err) @@ -79,11 +87,12 @@ func TestMetadataCreator_CreateContract(t *testing.T) { } } -func setupMetadataLoaderTest(t *testing.T) (*batching.MultiCaller, *mockCacheMetrics) { +func setupMetadataLoaderTest(t *testing.T, gameType uint32) (*batching.MultiCaller, *mockCacheMetrics) { fdgAbi := snapshots.LoadFaultDisputeGameABI() stubRpc := batchingTest.NewAbiBasedRpc(t, fdgAddr, fdgAbi) caller := batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize) stubRpc.SetResponse(fdgAddr, "version", rpcblock.Latest, nil, []interface{}{"0.18.0"}) + stubRpc.SetResponse(fdgAddr, "gameType", rpcblock.Latest, nil, []interface{}{gameType}) return caller, &mockCacheMetrics{} } From 06c1bc071a57b8b992d301be7b6e412904f3f1dd Mon Sep 17 00:00:00 2001 From: Inphi Date: Wed, 26 Feb 2025 18:15:27 -0500 Subject: [PATCH 024/130] op-node: Remove OverrideMessageExpiryTimeInterop from rollup.Config (#14554) * op-node: Remove OverrideMessageExpiryTimeInterop from rollup.Config * fix test fixture --- op-chain-ops/genesis/config.go | 3 --- .../testdata/test-deploy-config-full.json | 1 - op-chain-ops/interopgen/deploy.go | 3 --- op-chain-ops/interopgen/recipe.go | 3 +-- op-e2e/actions/interop/proofs_test.go | 2 +- op-node/rollup/types.go | 16 ---------------- op-node/rollup/types_test.go | 8 -------- 7 files changed, 2 insertions(+), 34 deletions(-) diff --git a/op-chain-ops/genesis/config.go b/op-chain-ops/genesis/config.go index 5a7a01bb2e3..be37cb44030 100644 --- a/op-chain-ops/genesis/config.go +++ b/op-chain-ops/genesis/config.go @@ -83,9 +83,6 @@ type DevDeployConfig struct { // FundDevAccounts configures whether to fund the dev accounts. // This should only be used during devnet deployments. FundDevAccounts bool `json:"fundDevAccounts"` - // OverrideMessageExpiryTime configures the message expiry time of interop messages. - // This should only be used during devnet deployments. - OverrideMessageExpiryTime uint64 `json:"overrideMessageExpiryTime"` } type L2GenesisBlockDeployConfig struct { diff --git a/op-chain-ops/genesis/testdata/test-deploy-config-full.json b/op-chain-ops/genesis/testdata/test-deploy-config-full.json index 67e1605cf92..7fe9a78e715 100644 --- a/op-chain-ops/genesis/testdata/test-deploy-config-full.json +++ b/op-chain-ops/genesis/testdata/test-deploy-config-full.json @@ -74,7 +74,6 @@ "faultGameGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", "faultGameSplitDepth": 0, "faultGameWithdrawalDelay": 604800, - "overrideMessageExpiryTime": 0, "preimageOracleMinProposalSize": 1800000, "preimageOracleChallengePeriod": 86400, "systemConfigStartBlock": 0, diff --git a/op-chain-ops/interopgen/deploy.go b/op-chain-ops/interopgen/deploy.go index d79a402ff92..4ef1d32b658 100644 --- a/op-chain-ops/interopgen/deploy.go +++ b/op-chain-ops/interopgen/deploy.go @@ -329,9 +329,6 @@ func CompleteL2(l2Host *script.Host, cfg *L2Config, l1Block *types.Block, deploy if err != nil { return nil, fmt.Errorf("failed to build L2 rollup config: %w", err) } - if cfg.OverrideMessageExpiryTime != 0 { - rollupCfg.OverrideMessageExpiryTimeInterop = cfg.OverrideMessageExpiryTime - } return &L2Output{ Genesis: l2Genesis, RollupCfg: rollupCfg, diff --git a/op-chain-ops/interopgen/recipe.go b/op-chain-ops/interopgen/recipe.go index c17984044e9..66444982c8c 100644 --- a/op-chain-ops/interopgen/recipe.go +++ b/op-chain-ops/interopgen/recipe.go @@ -187,8 +187,7 @@ func InteropL2DevConfig(l1ChainID, l2ChainID uint64, addrs devkeys.Addresses, me SystemConfigOwner: systemConfigOwner, L2InitializationConfig: genesis.L2InitializationConfig{ DevDeployConfig: genesis.DevDeployConfig{ - FundDevAccounts: true, - OverrideMessageExpiryTime: messageExpiryTime, + FundDevAccounts: true, }, L2GenesisBlockDeployConfig: genesis.L2GenesisBlockDeployConfig{ L2GenesisBlockGasLimit: 60_000_000, diff --git a/op-e2e/actions/interop/proofs_test.go b/op-e2e/actions/interop/proofs_test.go index a65184cff0f..27aaa22c65a 100644 --- a/op-e2e/actions/interop/proofs_test.go +++ b/op-e2e/actions/interop/proofs_test.go @@ -550,7 +550,7 @@ func TestInteropFaultProofs_MessageExpiry(gt *testing.T) { system.SubmitBatchData() // Advance the chain until the init msg expires - msgExpiryTime := actors.ChainA.RollupCfg.GetMessageExpiryTimeInterop() + msgExpiryTime := system.DepSet().MessageExpiryWindow() end := emitTx.Identifier().Timestamp.Uint64() + msgExpiryTime system.AddL2Block(actors.ChainA, dsl.WithL2BlocksUntilTimestamp(end)) system.AddL2Block(actors.ChainB, dsl.WithL2BlocksUntilTimestamp(end)) diff --git a/op-node/rollup/types.go b/op-node/rollup/types.go index ba019dc6319..7b144e09daa 100644 --- a/op-node/rollup/types.go +++ b/op-node/rollup/types.go @@ -15,7 +15,6 @@ import ( "github.com/ethereum/go-ethereum/params" altda "github.com/ethereum-optimism/optimism/op-alt-da" - opparams "github.com/ethereum-optimism/optimism/op-node/params" "github.com/ethereum-optimism/optimism/op-service/eth" ) @@ -150,11 +149,6 @@ type Config struct { // parameters to the protocol values, like the execution layer does. // If missing, it is loaded by the op-node from the embedded superchain config at startup. ChainOpConfig *params.OptimismConfig `json:"chain_op_config,omitempty"` - - // OverrideMessageExpiryTimeInterop is only used for testing purposes. - // It is used to override the protocol-defined interop message time expiry. - // DO NOT this read value directly. Use GetMessageExpiryTimeInterop instead. - OverrideMessageExpiryTimeInterop uint64 `json:"override_message_expiry_time_interop,omitempty"` } // ValidateL1Config checks L1 config variables for errors. @@ -616,16 +610,6 @@ func (c *Config) GetOPAltDAConfig() (altda.Config, error) { }, nil } -// GetMessageExpiryTimeInterop returns the expiry time of interop messages in seconds. -// If a message expiry override is set in the rollup config, it returns the override value. -// Otherwise, it returns the protocol-defined interop message time expiry. -func (c *Config) GetMessageExpiryTimeInterop() uint64 { - if c.OverrideMessageExpiryTimeInterop != 0 { - return c.OverrideMessageExpiryTimeInterop - } - return opparams.MessageExpiryTimeSecondsInterop -} - func (c *Config) AltDAEnabled() bool { return c.AltDAConfig != nil } diff --git a/op-node/rollup/types_test.go b/op-node/rollup/types_test.go index a4dc755f16b..f714bc94723 100644 --- a/op-node/rollup/types_test.go +++ b/op-node/rollup/types_test.go @@ -14,7 +14,6 @@ import ( "github.com/ethereum/go-ethereum/common" - "github.com/ethereum-optimism/optimism/op-node/params" "github.com/ethereum-optimism/optimism/op-service/eth" ) @@ -824,10 +823,3 @@ func TestConfigImplementsBlockType(t *testing.T) { }) } } - -func TestConfig_GetMessageExpiryTimeInterop(t *testing.T) { - config := randConfig() - assert.Equal(t, config.GetMessageExpiryTimeInterop(), uint64(params.MessageExpiryTimeSecondsInterop)) - config.OverrideMessageExpiryTimeInterop = 100 - assert.Equal(t, config.GetMessageExpiryTimeInterop(), uint64(100)) -} From f28cfae0a2d42d543f437090160d567da5dfcd6b Mon Sep 17 00:00:00 2001 From: Maurelian Date: Wed, 26 Feb 2025 22:32:13 -0500 Subject: [PATCH 025/130] feat: remove DeployOPCM (#14556) --- .../scripts/deploy/DeployOPCM.s.sol | 341 ------------------ .../test/opcm/DeployOPCM.t.sol | 288 --------------- 2 files changed, 629 deletions(-) delete mode 100644 packages/contracts-bedrock/scripts/deploy/DeployOPCM.s.sol delete mode 100644 packages/contracts-bedrock/test/opcm/DeployOPCM.t.sol diff --git a/packages/contracts-bedrock/scripts/deploy/DeployOPCM.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployOPCM.s.sol deleted file mode 100644 index c79327b3866..00000000000 --- a/packages/contracts-bedrock/scripts/deploy/DeployOPCM.s.sol +++ /dev/null @@ -1,341 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { Script } from "forge-std/Script.sol"; - -import { LibString } from "@solady/utils/LibString.sol"; - -import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol"; -import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; - -import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; -import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; -import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; - -contract DeployOPCMInput is BaseDeployIO { - ISuperchainConfig internal _superchainConfig; - IProtocolVersions internal _protocolVersions; - IProxyAdmin internal _superchainProxyAdmin; - string internal _l1ContractsRelease; - address internal _upgradeController; - - address internal _addressManagerBlueprint; - address internal _proxyBlueprint; - address internal _proxyAdminBlueprint; - address internal _l1ChugSplashProxyBlueprint; - address internal _resolvedDelegateProxyBlueprint; - address internal _permissionedDisputeGame1Blueprint; - address internal _permissionedDisputeGame2Blueprint; - - address internal _superchainConfigImpl; - address internal _protocolVersionsImpl; - address internal _l1ERC721BridgeImpl; - address internal _optimismPortalImpl; - address internal _systemConfigImpl; - address internal _optimismMintableERC20FactoryImpl; - address internal _l1CrossDomainMessengerImpl; - address internal _l1StandardBridgeImpl; - address internal _disputeGameFactoryImpl; - address internal _anchorStateRegistryImpl; - address internal _delayedWETHImpl; - address internal _mipsImpl; - - // Setter for address type - function set(bytes4 _sel, address _addr) public { - require(_addr != address(0), "DeployOPCMInput: cannot set zero address"); - - // forgefmt: disable-start - if (_sel == this.superchainConfig.selector) _superchainConfig = ISuperchainConfig(_addr); - else if (_sel == this.protocolVersions.selector) _protocolVersions = IProtocolVersions(_addr); - else if (_sel == this.superchainProxyAdmin.selector) _superchainProxyAdmin = IProxyAdmin(_addr); - else if (_sel == this.superchainConfigImpl.selector) _superchainConfigImpl = _addr; - else if (_sel == this.protocolVersionsImpl.selector) _protocolVersionsImpl = _addr; - else if (_sel == this.upgradeController.selector) _upgradeController = _addr; - else if (_sel == this.addressManagerBlueprint.selector) _addressManagerBlueprint = _addr; - else if (_sel == this.proxyBlueprint.selector) _proxyBlueprint = _addr; - else if (_sel == this.proxyAdminBlueprint.selector) _proxyAdminBlueprint = _addr; - else if (_sel == this.l1ChugSplashProxyBlueprint.selector) _l1ChugSplashProxyBlueprint = _addr; - else if (_sel == this.resolvedDelegateProxyBlueprint.selector) _resolvedDelegateProxyBlueprint = _addr; - else if (_sel == this.permissionedDisputeGame1Blueprint.selector) _permissionedDisputeGame1Blueprint = _addr; - else if (_sel == this.permissionedDisputeGame2Blueprint.selector) _permissionedDisputeGame2Blueprint = _addr; - else if (_sel == this.l1ERC721BridgeImpl.selector) _l1ERC721BridgeImpl = _addr; - else if (_sel == this.optimismPortalImpl.selector) _optimismPortalImpl = _addr; - else if (_sel == this.systemConfigImpl.selector) _systemConfigImpl = _addr; - else if (_sel == this.optimismMintableERC20FactoryImpl.selector) _optimismMintableERC20FactoryImpl = _addr; - else if (_sel == this.l1CrossDomainMessengerImpl.selector) _l1CrossDomainMessengerImpl = _addr; - else if (_sel == this.l1StandardBridgeImpl.selector) _l1StandardBridgeImpl = _addr; - else if (_sel == this.disputeGameFactoryImpl.selector) _disputeGameFactoryImpl = _addr; - else if (_sel == this.anchorStateRegistryImpl.selector) _anchorStateRegistryImpl = _addr; - else if (_sel == this.delayedWETHImpl.selector) _delayedWETHImpl = _addr; - else if (_sel == this.mipsImpl.selector) _mipsImpl = _addr; - else revert("DeployOPCMInput: unknown selector"); - // forgefmt: disable-end - } - - // Setter for string type - function set(bytes4 _sel, string memory _value) public { - require(!LibString.eq(_value, ""), "DeployOPCMInput: cannot set empty string"); - if (_sel == this.l1ContractsRelease.selector) _l1ContractsRelease = _value; - else revert("DeployOPCMInput: unknown selector"); - } - - // Getters - function superchainConfig() public view returns (ISuperchainConfig) { - require(address(_superchainConfig) != address(0), "DeployOPCMInput: not set"); - return _superchainConfig; - } - - function protocolVersions() public view returns (IProtocolVersions) { - require(address(_protocolVersions) != address(0), "DeployOPCMInput: not set"); - return _protocolVersions; - } - - function superchainProxyAdmin() public view returns (IProxyAdmin) { - require(address(_superchainProxyAdmin) != address(0), "DeployOPCMInput: not set"); - return _superchainProxyAdmin; - } - - function l1ContractsRelease() public view returns (string memory) { - require(!LibString.eq(_l1ContractsRelease, ""), "DeployOPCMInput: not set"); - return _l1ContractsRelease; - } - - function upgradeController() public view returns (address) { - require(_upgradeController != address(0), "DeployOPCMInput: not set"); - return _upgradeController; - } - - function addressManagerBlueprint() public view returns (address) { - require(_addressManagerBlueprint != address(0), "DeployOPCMInput: not set"); - return _addressManagerBlueprint; - } - - function proxyBlueprint() public view returns (address) { - require(_proxyBlueprint != address(0), "DeployOPCMInput: not set"); - return _proxyBlueprint; - } - - function proxyAdminBlueprint() public view returns (address) { - require(_proxyAdminBlueprint != address(0), "DeployOPCMInput: not set"); - return _proxyAdminBlueprint; - } - - function l1ChugSplashProxyBlueprint() public view returns (address) { - require(_l1ChugSplashProxyBlueprint != address(0), "DeployOPCMInput: not set"); - return _l1ChugSplashProxyBlueprint; - } - - function resolvedDelegateProxyBlueprint() public view returns (address) { - require(_resolvedDelegateProxyBlueprint != address(0), "DeployOPCMInput: not set"); - return _resolvedDelegateProxyBlueprint; - } - - function permissionedDisputeGame1Blueprint() public view returns (address) { - require(_permissionedDisputeGame1Blueprint != address(0), "DeployOPCMInput: not set"); - return _permissionedDisputeGame1Blueprint; - } - - function permissionedDisputeGame2Blueprint() public view returns (address) { - require(_permissionedDisputeGame2Blueprint != address(0), "DeployOPCMInput: not set"); - return _permissionedDisputeGame2Blueprint; - } - - function l1ERC721BridgeImpl() public view returns (address) { - require(_l1ERC721BridgeImpl != address(0), "DeployOPCMInput: not set"); - return _l1ERC721BridgeImpl; - } - - function optimismPortalImpl() public view returns (address) { - require(_optimismPortalImpl != address(0), "DeployOPCMInput: not set"); - return _optimismPortalImpl; - } - - function systemConfigImpl() public view returns (address) { - require(_systemConfigImpl != address(0), "DeployOPCMInput: not set"); - return _systemConfigImpl; - } - - function optimismMintableERC20FactoryImpl() public view returns (address) { - require(_optimismMintableERC20FactoryImpl != address(0), "DeployOPCMInput: not set"); - return _optimismMintableERC20FactoryImpl; - } - - function l1CrossDomainMessengerImpl() public view returns (address) { - require(_l1CrossDomainMessengerImpl != address(0), "DeployOPCMInput: not set"); - return _l1CrossDomainMessengerImpl; - } - - function l1StandardBridgeImpl() public view returns (address) { - require(_l1StandardBridgeImpl != address(0), "DeployOPCMInput: not set"); - return _l1StandardBridgeImpl; - } - - function disputeGameFactoryImpl() public view returns (address) { - require(_disputeGameFactoryImpl != address(0), "DeployOPCMInput: not set"); - return _disputeGameFactoryImpl; - } - - function anchorStateRegistryImpl() public view returns (address) { - require(_anchorStateRegistryImpl != address(0), "DeployOPCMInput: not set"); - return _anchorStateRegistryImpl; - } - - function superchainConfigImpl() public view returns (address) { - require(_superchainConfigImpl != address(0), "DeployOPCMInput: not set"); - return _superchainConfigImpl; - } - - function protocolVersionsImpl() public view returns (address) { - require(_protocolVersionsImpl != address(0), "DeployOPCMInput: not set"); - return _protocolVersionsImpl; - } - - function delayedWETHImpl() public view returns (address) { - require(_delayedWETHImpl != address(0), "DeployOPCMInput: not set"); - return _delayedWETHImpl; - } - - function mipsImpl() public view returns (address) { - require(_mipsImpl != address(0), "DeployOPCMInput: not set"); - return _mipsImpl; - } -} - -contract DeployOPCMOutput is BaseDeployIO { - IOPContractsManager internal _opcm; - - // Setter for address type - function set(bytes4 _sel, address _addr) public { - require(_addr != address(0), "DeployOPCMOutput: cannot set zero address"); - if (_sel == this.opcm.selector) _opcm = IOPContractsManager(_addr); - else revert("DeployOPCMOutput: unknown selector"); - } - - // Getter - function opcm() public view returns (IOPContractsManager) { - require(address(_opcm) != address(0), "DeployOPCMOutput: not set"); - return _opcm; - } -} - -contract DeployOPCM is Script { - function run(DeployOPCMInput _doi, DeployOPCMOutput _doo) public { - IOPContractsManager.Blueprints memory blueprints = IOPContractsManager.Blueprints({ - addressManager: _doi.addressManagerBlueprint(), - proxy: _doi.proxyBlueprint(), - proxyAdmin: _doi.proxyAdminBlueprint(), - l1ChugSplashProxy: _doi.l1ChugSplashProxyBlueprint(), - resolvedDelegateProxy: _doi.resolvedDelegateProxyBlueprint(), - permissionedDisputeGame1: _doi.permissionedDisputeGame1Blueprint(), - permissionedDisputeGame2: _doi.permissionedDisputeGame2Blueprint(), - permissionlessDisputeGame1: address(0), - permissionlessDisputeGame2: address(0) - }); - IOPContractsManager.Implementations memory implementations = IOPContractsManager.Implementations({ - superchainConfigImpl: address(_doi.superchainConfigImpl()), - protocolVersionsImpl: address(_doi.protocolVersionsImpl()), - l1ERC721BridgeImpl: address(_doi.l1ERC721BridgeImpl()), - optimismPortalImpl: address(_doi.optimismPortalImpl()), - systemConfigImpl: address(_doi.systemConfigImpl()), - optimismMintableERC20FactoryImpl: address(_doi.optimismMintableERC20FactoryImpl()), - l1CrossDomainMessengerImpl: address(_doi.l1CrossDomainMessengerImpl()), - l1StandardBridgeImpl: address(_doi.l1StandardBridgeImpl()), - disputeGameFactoryImpl: address(_doi.disputeGameFactoryImpl()), - anchorStateRegistryImpl: address(_doi.anchorStateRegistryImpl()), - delayedWETHImpl: address(_doi.delayedWETHImpl()), - mipsImpl: address(_doi.mipsImpl()) - }); - - IOPContractsManager opcm_ = deployOPCM( - _doi.superchainConfig(), - _doi.protocolVersions(), - _doi.superchainProxyAdmin(), - blueprints, - implementations, - _doi.l1ContractsRelease(), - _doi.upgradeController() - ); - _doo.set(_doo.opcm.selector, address(opcm_)); - - assertValidOpcm(_doi, _doo); - } - - function deployOPCM( - ISuperchainConfig _superchainConfig, - IProtocolVersions _protocolVersions, - IProxyAdmin _superchainProxyAdmin, - IOPContractsManager.Blueprints memory _blueprints, - IOPContractsManager.Implementations memory _implementations, - string memory _l1ContractsRelease, - address _upgradeController - ) - public - returns (IOPContractsManager opcm_) - { - opcm_ = IOPContractsManager( - DeployUtils.createDeterministic({ - _name: "OPContractsManager", - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IOPContractsManager.__constructor__, - ( - _superchainConfig, - _protocolVersions, - _superchainProxyAdmin, - _l1ContractsRelease, - _blueprints, - _implementations, - _upgradeController - ) - ) - ), - _salt: DeployUtils.DEFAULT_SALT - }) - ); - vm.label(address(opcm_), "OPContractsManager"); - } - - function assertValidOpcm(DeployOPCMInput _doi, DeployOPCMOutput _doo) public view { - IOPContractsManager impl = IOPContractsManager(address(_doo.opcm())); - require(address(impl.superchainConfig()) == address(_doi.superchainConfig()), "OPCMI-10"); - require(address(impl.protocolVersions()) == address(_doi.protocolVersions()), "OPCMI-20"); - require(LibString.eq(impl.l1ContractsRelease(), string.concat(_doi.l1ContractsRelease(), "-rc")), "OPCMI-30"); - - require(impl.upgradeController() == _doi.upgradeController(), "OPCMI-40"); - - IOPContractsManager.Blueprints memory blueprints = impl.blueprints(); - require(blueprints.addressManager == _doi.addressManagerBlueprint(), "OPCMI-40"); - require(blueprints.proxy == _doi.proxyBlueprint(), "OPCMI-50"); - require(blueprints.proxyAdmin == _doi.proxyAdminBlueprint(), "OPCMI-60"); - require(blueprints.l1ChugSplashProxy == _doi.l1ChugSplashProxyBlueprint(), "OPCMI-70"); - require(blueprints.resolvedDelegateProxy == _doi.resolvedDelegateProxyBlueprint(), "OPCMI-80"); - require(blueprints.permissionedDisputeGame1 == _doi.permissionedDisputeGame1Blueprint(), "OPCMI-100"); - require(blueprints.permissionedDisputeGame2 == _doi.permissionedDisputeGame2Blueprint(), "OPCMI-110"); - - IOPContractsManager.Implementations memory implementations = impl.implementations(); - require(implementations.l1ERC721BridgeImpl == _doi.l1ERC721BridgeImpl(), "OPCMI-120"); - require(implementations.optimismPortalImpl == _doi.optimismPortalImpl(), "OPCMI-130"); - require(implementations.systemConfigImpl == _doi.systemConfigImpl(), "OPCMI-140"); - require( - implementations.optimismMintableERC20FactoryImpl == _doi.optimismMintableERC20FactoryImpl(), "OPCMI-150" - ); - require(implementations.l1CrossDomainMessengerImpl == _doi.l1CrossDomainMessengerImpl(), "OPCMI-160"); - require(implementations.l1StandardBridgeImpl == _doi.l1StandardBridgeImpl(), "OPCMI-170"); - require(implementations.disputeGameFactoryImpl == _doi.disputeGameFactoryImpl(), "OPCMI-180"); - require(implementations.anchorStateRegistryImpl == _doi.anchorStateRegistryImpl(), "OPCMI-190"); - require(implementations.delayedWETHImpl == _doi.delayedWETHImpl(), "OPCMI-200"); - require(implementations.mipsImpl == _doi.mipsImpl(), "OPCMI-210"); - } - - function etchIOContracts() public returns (DeployOPCMInput doi_, DeployOPCMOutput doo_) { - (doi_, doo_) = getIOContracts(); - vm.etch(address(doi_), type(DeployOPCMInput).runtimeCode); - vm.etch(address(doo_), type(DeployOPCMOutput).runtimeCode); - } - - function getIOContracts() public view returns (DeployOPCMInput doi_, DeployOPCMOutput doo_) { - doi_ = DeployOPCMInput(DeployUtils.toIOAddress(msg.sender, "optimism.DeployOPCMInput")); - doo_ = DeployOPCMOutput(DeployUtils.toIOAddress(msg.sender, "optimism.DeployOPCMOutput")); - } -} diff --git a/packages/contracts-bedrock/test/opcm/DeployOPCM.t.sol b/packages/contracts-bedrock/test/opcm/DeployOPCM.t.sol deleted file mode 100644 index f43f7c1322c..00000000000 --- a/packages/contracts-bedrock/test/opcm/DeployOPCM.t.sol +++ /dev/null @@ -1,288 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { Test } from "forge-std/Test.sol"; -import { DeployOPCM, DeployOPCMInput, DeployOPCMOutput } from "scripts/deploy/DeployOPCM.s.sol"; -import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; -import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; -import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; - -contract DeployOPCMInput_Test is Test { - DeployOPCMInput dii; - string release = "1.0.0"; - - function setUp() public { - dii = new DeployOPCMInput(); - } - - function test_getters_whenNotSet_reverts() public { - vm.expectRevert("DeployOPCMInput: not set"); - dii.superchainConfig(); - - vm.expectRevert("DeployOPCMInput: not set"); - dii.protocolVersions(); - - vm.expectRevert("DeployOPCMInput: not set"); - dii.l1ContractsRelease(); - - vm.expectRevert("DeployOPCMInput: not set"); - dii.addressManagerBlueprint(); - - vm.expectRevert("DeployOPCMInput: not set"); - dii.proxyBlueprint(); - - vm.expectRevert("DeployOPCMInput: not set"); - dii.proxyAdminBlueprint(); - - vm.expectRevert("DeployOPCMInput: not set"); - dii.l1ChugSplashProxyBlueprint(); - - vm.expectRevert("DeployOPCMInput: not set"); - dii.resolvedDelegateProxyBlueprint(); - - vm.expectRevert("DeployOPCMInput: not set"); - dii.permissionedDisputeGame1Blueprint(); - - vm.expectRevert("DeployOPCMInput: not set"); - dii.permissionedDisputeGame2Blueprint(); - - vm.expectRevert("DeployOPCMInput: not set"); - dii.l1ERC721BridgeImpl(); - - vm.expectRevert("DeployOPCMInput: not set"); - dii.optimismPortalImpl(); - - vm.expectRevert("DeployOPCMInput: not set"); - dii.systemConfigImpl(); - - vm.expectRevert("DeployOPCMInput: not set"); - dii.optimismMintableERC20FactoryImpl(); - - vm.expectRevert("DeployOPCMInput: not set"); - dii.l1CrossDomainMessengerImpl(); - - vm.expectRevert("DeployOPCMInput: not set"); - dii.l1StandardBridgeImpl(); - - vm.expectRevert("DeployOPCMInput: not set"); - dii.disputeGameFactoryImpl(); - - vm.expectRevert("DeployOPCMInput: not set"); - dii.anchorStateRegistryImpl(); - - vm.expectRevert("DeployOPCMInput: not set"); - dii.delayedWETHImpl(); - - vm.expectRevert("DeployOPCMInput: not set"); - dii.mipsImpl(); - } - - // Below setter tests are split into two parts to avoid stack too deep errors - - function test_set_part1_succeeds() public { - ISuperchainConfig superchainConfig = ISuperchainConfig(makeAddr("superchainConfig")); - IProtocolVersions protocolVersions = IProtocolVersions(makeAddr("protocolVersions")); - address superchainConfigImpl = makeAddr("superchainConfigImpl"); - address protocolVersionsImpl = makeAddr("protocolVersionsImpl"); - address upgradeController = makeAddr("upgradeController"); - address addressManagerBlueprint = makeAddr("addressManagerBlueprint"); - address proxyBlueprint = makeAddr("proxyBlueprint"); - address proxyAdminBlueprint = makeAddr("proxyAdminBlueprint"); - address l1ChugSplashProxyBlueprint = makeAddr("l1ChugSplashProxyBlueprint"); - address resolvedDelegateProxyBlueprint = makeAddr("resolvedDelegateProxyBlueprint"); - address permissionedDisputeGame1Blueprint = makeAddr("permissionedDisputeGame1Blueprint"); - address permissionedDisputeGame2Blueprint = makeAddr("permissionedDisputeGame2Blueprint"); - - dii.set(dii.superchainConfig.selector, address(superchainConfig)); - dii.set(dii.protocolVersions.selector, address(protocolVersions)); - dii.set(dii.superchainConfigImpl.selector, superchainConfigImpl); - dii.set(dii.protocolVersionsImpl.selector, protocolVersionsImpl); - dii.set(dii.l1ContractsRelease.selector, release); - dii.set(dii.upgradeController.selector, upgradeController); - dii.set(dii.addressManagerBlueprint.selector, addressManagerBlueprint); - dii.set(dii.proxyBlueprint.selector, proxyBlueprint); - dii.set(dii.proxyAdminBlueprint.selector, proxyAdminBlueprint); - dii.set(dii.l1ChugSplashProxyBlueprint.selector, l1ChugSplashProxyBlueprint); - dii.set(dii.resolvedDelegateProxyBlueprint.selector, resolvedDelegateProxyBlueprint); - dii.set(dii.permissionedDisputeGame1Blueprint.selector, permissionedDisputeGame1Blueprint); - dii.set(dii.permissionedDisputeGame2Blueprint.selector, permissionedDisputeGame2Blueprint); - - assertEq(address(dii.superchainConfig()), address(superchainConfig), "50"); - assertEq(address(dii.protocolVersions()), address(protocolVersions), "100"); - assertEq(dii.l1ContractsRelease(), release, "150"); - assertEq(dii.addressManagerBlueprint(), addressManagerBlueprint, "200"); - assertEq(dii.proxyBlueprint(), proxyBlueprint, "250"); - assertEq(dii.proxyAdminBlueprint(), proxyAdminBlueprint, "300"); - assertEq(dii.l1ChugSplashProxyBlueprint(), l1ChugSplashProxyBlueprint, "350"); - assertEq(dii.resolvedDelegateProxyBlueprint(), resolvedDelegateProxyBlueprint, "400"); - assertEq(dii.permissionedDisputeGame1Blueprint(), permissionedDisputeGame1Blueprint, "500"); - assertEq(dii.permissionedDisputeGame2Blueprint(), permissionedDisputeGame2Blueprint, "550"); - assertEq(dii.upgradeController(), upgradeController, "600"); - } - - function test_set_part2_succeeds() public { - address l1ERC721BridgeImpl = makeAddr("l1ERC721BridgeImpl"); - address optimismPortalImpl = makeAddr("optimismPortalImpl"); - address systemConfigImpl = makeAddr("systemConfigImpl"); - address optimismMintableERC20FactoryImpl = makeAddr("optimismMintableERC20FactoryImpl"); - address l1CrossDomainMessengerImpl = makeAddr("l1CrossDomainMessengerImpl"); - address l1StandardBridgeImpl = makeAddr("l1StandardBridgeImpl"); - address disputeGameFactoryImpl = makeAddr("disputeGameFactoryImpl"); - address anchorStateRegistryImpl = makeAddr("anchorStateRegistryImpl"); - address delayedWETHImpl = makeAddr("delayedWETHImpl"); - address mipsImpl = makeAddr("mipsImpl"); - - dii.set(dii.l1ERC721BridgeImpl.selector, l1ERC721BridgeImpl); - dii.set(dii.optimismPortalImpl.selector, optimismPortalImpl); - dii.set(dii.systemConfigImpl.selector, systemConfigImpl); - dii.set(dii.optimismMintableERC20FactoryImpl.selector, optimismMintableERC20FactoryImpl); - dii.set(dii.l1CrossDomainMessengerImpl.selector, l1CrossDomainMessengerImpl); - dii.set(dii.l1StandardBridgeImpl.selector, l1StandardBridgeImpl); - dii.set(dii.disputeGameFactoryImpl.selector, disputeGameFactoryImpl); - dii.set(dii.anchorStateRegistryImpl.selector, anchorStateRegistryImpl); - dii.set(dii.delayedWETHImpl.selector, delayedWETHImpl); - dii.set(dii.mipsImpl.selector, mipsImpl); - - assertEq(dii.l1ERC721BridgeImpl(), l1ERC721BridgeImpl, "600"); - assertEq(dii.optimismPortalImpl(), optimismPortalImpl, "650"); - assertEq(dii.systemConfigImpl(), systemConfigImpl, "700"); - assertEq(dii.optimismMintableERC20FactoryImpl(), optimismMintableERC20FactoryImpl, "750"); - assertEq(dii.l1CrossDomainMessengerImpl(), l1CrossDomainMessengerImpl, "800"); - assertEq(dii.l1StandardBridgeImpl(), l1StandardBridgeImpl, "850"); - assertEq(dii.disputeGameFactoryImpl(), disputeGameFactoryImpl, "900"); - assertEq(dii.delayedWETHImpl(), delayedWETHImpl, "950"); - assertEq(dii.mipsImpl(), mipsImpl, "1000"); - } - - function test_set_withZeroAddress_reverts() public { - vm.expectRevert("DeployOPCMInput: cannot set zero address"); - dii.set(dii.superchainConfig.selector, address(0)); - } - - function test_set_withEmptyString_reverts() public { - vm.expectRevert("DeployOPCMInput: cannot set empty string"); - dii.set(dii.l1ContractsRelease.selector, ""); - } - - function test_set_withInvalidSelector_reverts() public { - vm.expectRevert("DeployOPCMInput: unknown selector"); - dii.set(bytes4(0xdeadbeef), address(1)); - } - - function test_set_withInvalidStringSelector_reverts() public { - vm.expectRevert("DeployOPCMInput: unknown selector"); - dii.set(bytes4(0xdeadbeef), "test"); - } -} - -contract DeployOPCMOutput_Test is Test { - DeployOPCMOutput doo; - - function setUp() public { - doo = new DeployOPCMOutput(); - } - - function test_getters_whenNotSet_reverts() public { - vm.expectRevert("DeployOPCMOutput: not set"); - doo.opcm(); - } - - function test_set_succeeds() public { - IOPContractsManager opcm = IOPContractsManager(makeAddr("opcm")); - vm.etch(address(opcm), hex"01"); - - doo.set(doo.opcm.selector, address(opcm)); - - assertEq(address(doo.opcm()), address(opcm), "50"); - } - - function test_set_withZeroAddress_reverts() public { - vm.expectRevert("DeployOPCMOutput: cannot set zero address"); - doo.set(doo.opcm.selector, address(0)); - } - - function test_set_withInvalidSelector_reverts() public { - vm.expectRevert("DeployOPCMOutput: unknown selector"); - doo.set(bytes4(0xdeadbeef), makeAddr("test")); - } -} - -contract DeployOPCMTest is Test { - DeployOPCM deployOPCM; - DeployOPCMInput doi; - DeployOPCMOutput doo; - - ISuperchainConfig superchainConfigProxy = ISuperchainConfig(makeAddr("superchainConfigProxy")); - IProtocolVersions protocolVersionsProxy = IProtocolVersions(makeAddr("protocolVersionsProxy")); - IProxyAdmin superchainProxyAdmin = IProxyAdmin(makeAddr("superchainProxyAdmin")); - address superchainConfigImpl = makeAddr("superchainConfigImpl"); - address protocolVersionsImpl = makeAddr("protocolVersionsImpl"); - address upgradeController = makeAddr("upgradeController"); - - function setUp() public virtual { - deployOPCM = new DeployOPCM(); - (doi, doo) = deployOPCM.etchIOContracts(); - } - - function test_run_succeeds() public { - doi.set(doi.superchainConfig.selector, address(superchainConfigProxy)); - doi.set(doi.protocolVersions.selector, address(protocolVersionsProxy)); - doi.set(doi.superchainProxyAdmin.selector, address(superchainProxyAdmin)); - doi.set(doi.superchainConfigImpl.selector, address(superchainConfigImpl)); - doi.set(doi.protocolVersionsImpl.selector, address(protocolVersionsImpl)); - doi.set(doi.l1ContractsRelease.selector, "1.0.0"); - doi.set(doi.upgradeController.selector, upgradeController); - - // Set and etch blueprints - doi.set(doi.addressManagerBlueprint.selector, makeAddr("addressManagerBlueprint")); - doi.set(doi.proxyBlueprint.selector, makeAddr("proxyBlueprint")); - doi.set(doi.proxyAdminBlueprint.selector, makeAddr("proxyAdminBlueprint")); - doi.set(doi.l1ChugSplashProxyBlueprint.selector, makeAddr("l1ChugSplashProxyBlueprint")); - doi.set(doi.resolvedDelegateProxyBlueprint.selector, makeAddr("resolvedDelegateProxyBlueprint")); - doi.set(doi.permissionedDisputeGame1Blueprint.selector, makeAddr("permissionedDisputeGame1Blueprint")); - doi.set(doi.permissionedDisputeGame2Blueprint.selector, makeAddr("permissionedDisputeGame2Blueprint")); - - // Set and etch implementations - doi.set(doi.l1ERC721BridgeImpl.selector, makeAddr("l1ERC721BridgeImpl")); - doi.set(doi.optimismPortalImpl.selector, makeAddr("optimismPortalImpl")); - doi.set(doi.systemConfigImpl.selector, makeAddr("systemConfigImpl")); - doi.set(doi.optimismMintableERC20FactoryImpl.selector, makeAddr("optimismMintableERC20FactoryImpl")); - doi.set(doi.l1CrossDomainMessengerImpl.selector, makeAddr("l1CrossDomainMessengerImpl")); - doi.set(doi.l1StandardBridgeImpl.selector, makeAddr("l1StandardBridgeImpl")); - doi.set(doi.disputeGameFactoryImpl.selector, makeAddr("disputeGameFactoryImpl")); - doi.set(doi.anchorStateRegistryImpl.selector, makeAddr("anchorStateRegistryImpl")); - doi.set(doi.delayedWETHImpl.selector, makeAddr("delayedWETHImpl")); - doi.set(doi.mipsImpl.selector, makeAddr("mipsImpl")); - - // Etch all addresses with dummy bytecode - vm.etch(address(doi.superchainConfig()), hex"01"); - vm.etch(address(doi.protocolVersions()), hex"01"); - vm.etch(address(doi.upgradeController()), hex"01"); - - vm.etch(doi.addressManagerBlueprint(), hex"01"); - vm.etch(doi.proxyBlueprint(), hex"01"); - vm.etch(doi.proxyAdminBlueprint(), hex"01"); - vm.etch(doi.l1ChugSplashProxyBlueprint(), hex"01"); - vm.etch(doi.resolvedDelegateProxyBlueprint(), hex"01"); - vm.etch(doi.permissionedDisputeGame1Blueprint(), hex"01"); - vm.etch(doi.permissionedDisputeGame2Blueprint(), hex"01"); - - vm.etch(doi.l1ERC721BridgeImpl(), hex"01"); - vm.etch(doi.optimismPortalImpl(), hex"01"); - vm.etch(doi.systemConfigImpl(), hex"01"); - vm.etch(doi.optimismMintableERC20FactoryImpl(), hex"01"); - vm.etch(doi.l1CrossDomainMessengerImpl(), hex"01"); - vm.etch(doi.l1StandardBridgeImpl(), hex"01"); - vm.etch(doi.disputeGameFactoryImpl(), hex"01"); - vm.etch(doi.delayedWETHImpl(), hex"01"); - vm.etch(doi.mipsImpl(), hex"01"); - - deployOPCM.run(doi, doo); - - assertNotEq(address(doo.opcm()), address(0)); - - // sanity check to ensure that the OPCM is validated - deployOPCM.assertValidOpcm(doi, doo); - } -} From b21a573557f08c879aad172c17afc32f40bb4f03 Mon Sep 17 00:00:00 2001 From: Paul Dowman Date: Thu, 27 Feb 2025 04:48:39 -0700 Subject: [PATCH 026/130] Add updated MT Cannon Spearbit audit (#14497) --- .../2025_01-MT-Cannon-Spearbit.pdf | Bin 176009 -> 189715 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/security-reviews/2025_01-MT-Cannon-Spearbit.pdf b/docs/security-reviews/2025_01-MT-Cannon-Spearbit.pdf index fcadf39a0fec375af1dbe137177a760326d10a4c..5048a3e1f4234a8ad8e30eb2e657c6756644e8ed 100644 GIT binary patch delta 140450 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%0Dk z$p`hC_z4|{#m%2Y11>dTEntBEybxGTDyS(AghO+}vdOg94zqa4?33B}{UTo=!^n;< zw;BA)X@MbQU)ZAxk{xtbMuo*}6vxD9vDfS6Y7njCh}jjTF9JkOclUHRWUy#(3DQ-? zH?2`?l)*_ZrGK+08o~pU5Z4W_JmXvBj5)!_t`yf)t@0MXF(w*jK>(!8_A6nshWs>j zO)ASw9jtxMfH^>Q1VW~(Nh2_8W4f9DzMBx(Ix@T`m-?P+c64ULJ=n(m&V8EfG*q!+ zqyZBlMS(lpPKS5*?wMR%$g99Z9N+)XAPa$ov^;A!h*94|1& z{l@L?0k&<4KC6R+3rP87PVA(UtDP88Wz_>-;>jm?(?#t#W~Hk$H&pF30hD>dQ({D7 zNyJ+U=uWihC<}5KMujCERY%M`TFe+Ca014IUjHCHkZe0mwGNB(go#80ij916eYH_K zM{^BxJgH$%MYG|hr;iYF<%IQzYL%t>LbHe(1xiS!dOU*rfD_H2qV%{sB*=N63N#sk zNc2UKJ^|%z> zF`H?>{QD_by^H9!=;}HKo%Am{8aQK+n&CLvEDBava#LYAp*V}-i}%wDHYRhh+bgWq zD4g#yhwp~7z@&knXw}3w)02^np(@|9Si5(blX(MT9C6bj13xoQ3(~sG1zI`qlorBA z<7+AXzstuKs>F}Y+4x*CPsX%c+Q5ydOzPY?!N1McyVnU`oBlPr;jMXxxKwSm|4%i6?&{`ZZt~yL{~X~t**Mauw;{*@ zHckTsRyAuMb23ggP6SphGCg)OPBQk-q^hH%+h>yP^U$w~z$)ip;rJP`|4$@IrYFcQ zEzQ9%!Nn;lDJ9O%%f`#k#U?Jn#xBjr&Cf2zEha5Y_W$34`uU*$sTFuRxXAveRv=Dp zRnic{8|Z3vT3#HaDX|MGX#r!18+;cv^NoNAq9X%E>ssZddf@#1X$2Akf%4e@j_i~( zao5(@)^a}^Y3ppgWoo^0Yzx=k#sB{q^*v1)+*Mf?3K!%>=teyX~<8G$$_uLJ%ii#cLJ+Kt&F-R zvyvth6Zp0ap*t--{e^y2RA2>97j~aqbkuT6y^OQ|k%@p3Fc)>_-B2#l3O`BdaXOeJU(VA-T3(X}hV17D-jv}FB z$nU98tu$eSfWgy5JPw%P$7H(5ent)=XG5tohWHlbYK)KpJ8KLx7UUfdiMR!!4Tal7 zT?3WcgV2Jc*+b9*G358{95CJY$Bc$9e=WJQudy(uR=~1o4bL{bR%vGAHm2a*^Lbz4LpFyga17E&p0wx zpRFnX%`&{3^K1sQB%9oMAoKZJ0)YU_9z=7&eseqx>5}u-{XurV8A& zV`HR*m)5mHWg=SPdzl77-f4rbIeFV+rKWd8md|zJfdIVW0=3tjP|x`;RCPABSADtv z=eNEM=9&^32@qq0a+~mRy&XE#z=zdg1E-O=y_ev)DW1G1rPxgyNju(yAV%WlX5-*T Lpr)2ml1BJ1Tf{Sc From 0d761a423c7194b633e8a9f7c4b96e1190adee5f Mon Sep 17 00:00:00 2001 From: George Knee Date: Thu, 27 Feb 2025 13:12:55 +0000 Subject: [PATCH 027/130] op-batcher: remove `ChannelManager.CheckExpectedProgress()` and add channel timeout log (#14553) * op-batcher: remove ChannelManager.CheckExpectedProgress * op-batcher: add warning log when a channel times out on chain --- op-batcher/batcher/channel_manager.go | 17 +------ op-batcher/batcher/channel_manager_test.go | 54 ---------------------- 2 files changed, 1 insertion(+), 70 deletions(-) diff --git a/op-batcher/batcher/channel_manager.go b/op-batcher/batcher/channel_manager.go index 3c6d7beee05..9deb301aada 100644 --- a/op-batcher/batcher/channel_manager.go +++ b/op-batcher/batcher/channel_manager.go @@ -111,6 +111,7 @@ func (s *channelManager) TxConfirmed(_id txID, inclusionBlock eth.BlockID) { if channel, ok := s.txChannels[id]; ok { delete(s.txChannels, id) if timedOut := channel.TxConfirmed(id, inclusionBlock); timedOut { + s.log.Warn("channel timed out on chain", "channel_id", channel.ID(), "tx_id", id) s.handleChannelInvalidated(channel) } } else { @@ -498,22 +499,6 @@ func (s *channelManager) PendingDABytes() int64 { return int64(f) } -// CheckExpectedProgress uses the supplied syncStatus to infer -// whether the node providing the status has made the expected -// safe head progress given fully submitted channels held in -// state. -func (m *channelManager) CheckExpectedProgress(syncStatus eth.SyncStatus) error { - for _, ch := range m.channelQueue { - if ch.isFullySubmitted() && // This implies a number of l1 confirmations has passed, depending on how the txmgr was configured - !ch.isTimedOut() && - syncStatus.CurrentL1.Number > ch.maxInclusionBlock && - syncStatus.SafeL2.Number < ch.LatestL2().Number { - return errors.New("safe head did not make expected progress") - } - } - return nil -} - func (m *channelManager) LastStoredBlock() eth.BlockID { if m.blocks.Len() == 0 { return eth.BlockID{} diff --git a/op-batcher/batcher/channel_manager_test.go b/op-batcher/batcher/channel_manager_test.go index d7c8abcd87e..e89663bd652 100644 --- a/op-batcher/batcher/channel_manager_test.go +++ b/op-batcher/batcher/channel_manager_test.go @@ -656,57 +656,3 @@ func TestChannelManager_ChannelOutFactory(t *testing.T) { require.IsType(t, &ChannelOutWrapper{}, m.currentChannel.channelBuilder.co) } - -func TestChannelManager_CheckExpectedProgress(t *testing.T) { - l := testlog.Logger(t, log.LevelCrit) - cfg := channelManagerTestConfig(100, derive.SingularBatchType) - cfg.InitNoneCompressor() - m := NewChannelManager(l, metrics.NoopMetrics, cfg, defaultTestRollupConfig) - - channelMaxInclusionBlockNumber := uint64(3) - channelLatestSafeBlockNumber := uint64(11) - - // Prepare a (dummy) fully submitted channel - // with - // maxInclusionBlock and latest safe block number as above - A, err := newChannelWithChannelOut(l, metrics.NoopMetrics, cfg, m.rollupCfg, 0) - require.NoError(t, err) - rng := rand.New(rand.NewSource(123)) - a0 := derivetest.RandomL2BlockWithChainId(rng, 1, defaultTestRollupConfig.L2ChainID) - a0 = a0.WithSeal(&types.Header{Number: big.NewInt(int64(channelLatestSafeBlockNumber))}) - _, err = A.AddBlock(a0) - require.NoError(t, err) - A.maxInclusionBlock = channelMaxInclusionBlockNumber - A.Close() - A.channelBuilder.frames = nil - A.channelBuilder.frameCursor = 0 - require.True(t, A.isFullySubmitted()) - - m.channelQueue = append(m.channelQueue, A) - - // The current L1 number implies that - // channel A above should have been derived - // from, so we expect safe head to progress to - // the channelLatestSafeBlockNumber. - // Since the safe head moved to 11, there is no error: - ss := eth.SyncStatus{ - CurrentL1: eth.L1BlockRef{Number: channelMaxInclusionBlockNumber + 1}, - SafeL2: eth.L2BlockRef{Number: channelLatestSafeBlockNumber}, - } - err = m.CheckExpectedProgress(ss) - require.NoError(t, err) - - // If the currentL1 is as above but the - // safe head is less than channelLatestSafeBlockNumber, - // the method should return an error: - ss.SafeL2 = eth.L2BlockRef{Number: channelLatestSafeBlockNumber - 1} - err = m.CheckExpectedProgress(ss) - require.Error(t, err) - - // If the safe head is still less than channelLatestSafeBlockNumber - // but the currentL1 is _equal_ to the channelMaxInclusionBlockNumber - // there should be no error as that block is still being derived from: - ss.CurrentL1 = eth.L1BlockRef{Number: channelMaxInclusionBlockNumber} - err = m.CheckExpectedProgress(ss) - require.NoError(t, err) -} From 48b645a69aa40f9b0c6317d0c34f675f8b5d1718 Mon Sep 17 00:00:00 2001 From: zhiqiangxu <652732310@qq.com> Date: Thu, 27 Feb 2025 22:15:22 +0800 Subject: [PATCH 028/130] optimize context usage (#14558) --- kurtosis-devnet/cmd/main.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/kurtosis-devnet/cmd/main.go b/kurtosis-devnet/cmd/main.go index d48ed96bbfd..573743995bf 100644 --- a/kurtosis-devnet/cmd/main.go +++ b/kurtosis-devnet/cmd/main.go @@ -1,7 +1,6 @@ package main import ( - "context" "encoding/json" "fmt" "log" @@ -65,7 +64,6 @@ func writeEnvironment(path string, env *kurtosis.KurtosisEnvironment) error { } func mainAction(c *cli.Context) error { - ctx := context.Background() cfg, err := newConfig(c) if err != nil { @@ -82,7 +80,7 @@ func mainAction(c *cli.Context) error { deploy.WithBaseDir(cfg.baseDir), ) - env, err := deployer.Deploy(ctx, nil) + env, err := deployer.Deploy(c.Context, nil) if err != nil { return fmt.Errorf("error deploying environment: %w", err) } From ade9ad0bb0a9e5115a0e977830746a69c425ccb6 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Thu, 27 Feb 2025 10:56:57 -0500 Subject: [PATCH 029/130] Add 64-bit tests for signExtend (#14543) --- .../mipsevm/exec/mips_instructions64_test.go | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/cannon/mipsevm/exec/mips_instructions64_test.go b/cannon/mipsevm/exec/mips_instructions64_test.go index c293559b671..0f828346d59 100644 --- a/cannon/mipsevm/exec/mips_instructions64_test.go +++ b/cannon/mipsevm/exec/mips_instructions64_test.go @@ -109,3 +109,38 @@ func TestStoreSubWord_64bits(t *testing.T) { }) } } + +func TestSignExtend_64bit(t *testing.T) { + cases := []struct { + name string + data Word + index Word + expected Word + }{ + {name: "idx 32, signed", data: 0x0000_0000_8123_4567, index: 32, expected: 0xFFFF_FFFF_8123_4567}, + {name: "idx 32, unsigned", data: 0x0000_0000_7123_4567, index: 32, expected: 0x0000_0000_7123_4567}, + {name: "idx 32, signed, non-zero upper bits", data: 0x1234_4321_8123_4567, index: 32, expected: 0xFFFF_FFFF_8123_4567}, + {name: "idx 32, unsigned, non-zero upper bits", data: 0xFFFF_FFFF_7123_4567, index: 32, expected: 0x0000_0000_7123_4567}, + {name: "idx 33, signed", data: 0x0000_0001_2345_4321, index: 33, expected: 0xFFFF_FFFF_2345_4321}, + {name: "idx 33, unsigned", data: 0x0000_0000_2345_4321, index: 33, expected: 0x0000_0000_2345_4321}, + {name: "idx 33, signed, non-zero upper bits", data: 0xABCD_4321_2345_4321, index: 33, expected: 0xFFFF_FFFF_2345_4321}, + {name: "idx 33, unsigned, non-zero upper bits", data: 0xFFFF_FFFE_2345_4321, index: 33, expected: 0x0000_0000_2345_4321}, + {name: "idx 48, signed", data: 0x0000_A123_0123_4567, index: 48, expected: 0xFFFF_A123_0123_4567}, + {name: "idx 48, unsigned", data: 0x0000_0123_0123_4567, index: 48, expected: 0x0000_0123_0123_4567}, + {name: "idx 48, signed, non-zero upper bits", data: 0xABCD_A123_0123_4567, index: 48, expected: 0xFFFF_A123_0123_4567}, + {name: "idx 48, unsigned, non-zero upper bits", data: 0xABCD_0123_0123_4567, index: 48, expected: 0x0000_0123_0123_4567}, + {name: "idx 50, signed", data: 0x0002_A123_0123_4567, index: 50, expected: 0xFFFE_A123_0123_4567}, + {name: "idx 50, unsigned", data: 0x0001_0123_0123_4567, index: 50, expected: 0x0001_0123_0123_4567}, + {name: "idx 50, signed, non-zero upper bits", data: 0x1AB2_A123_0123_4567, index: 50, expected: 0xFFFE_A123_0123_4567}, + {name: "idx 50, unsigned, non-zero upper bits", data: 0xFED1_0123_0123_4567, index: 50, expected: 0x0001_0123_0123_4567}, + {name: "idx 64, signed", data: 0xABCD_0101_8123_4567, index: 64, expected: 0xABCD_0101_8123_4567}, + {name: "idx 64, unsigned", data: 0x789A_0101_8123_4567, index: 64, expected: 0x789A_0101_8123_4567}, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + actual := SignExtend(c.data, c.index) + require.Equal(t, c.expected, actual) + }) + } +} From 5523d7a4caa02a23b5d942afc5f3db09413d5df4 Mon Sep 17 00:00:00 2001 From: George Knee Date: Thu, 27 Feb 2025 18:07:02 +0000 Subject: [PATCH 030/130] op-batcher: correctly track block metrics in `handleChannelInvalidated()` (#14561) * op-batcher: correctly track block metrics in handleChannelInvalidated Includes test which fails without the fix. * op-batcher: log out channels which are dropped during handleChannelInvalidated() * change to warn log for dropped channels --- op-batcher/batcher/channel_manager.go | 41 ++++++++++++++++++---- op-batcher/batcher/channel_manager_test.go | 23 ++++++++---- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/op-batcher/batcher/channel_manager.go b/op-batcher/batcher/channel_manager.go index 9deb301aada..8083cc4c82d 100644 --- a/op-batcher/batcher/channel_manager.go +++ b/op-batcher/batcher/channel_manager.go @@ -126,11 +126,21 @@ func (s *channelManager) TxConfirmed(_id txID, inclusionBlock eth.BlockID) { // in the block queue and the blockCursor is ahead of it. // Panics if the block is not in state. func (s *channelManager) rewindToBlock(block eth.BlockID) { + initialCursor := s.blockCursor idx := block.Number - s.blocks[0].Number().Uint64() if s.blocks[idx].Hash() == block.Hash && idx < uint64(s.blockCursor) { s.blockCursor = int(idx) } else { - panic("tried to rewind to nonexistent block") + panic("rewindToBlock: tried to rewind to nonexistent block") + } + + // Ensure metrics stay in sync by re-adding blocks which the cursor rewound over + for i := initialCursor - 1; i >= s.blockCursor; i-- { + block, ok := s.blocks.PeekN(i) + if !ok { + panic("rewindToBlock: block not found at index " + fmt.Sprint(i)) + } + s.metr.RecordL2BlockInPendingQueue(block) } } @@ -145,21 +155,40 @@ func (s *channelManager) handleChannelInvalidated(c *channel) { // In that case we end up with an empty frame (header only), // and there are no blocks to requeue. blockID := eth.ToBlockID(c.channelBuilder.blocks[0]) - for _, block := range c.channelBuilder.blocks { - s.metr.RecordL2BlockInPendingQueue(block) - } s.rewindToBlock(blockID) } else { s.log.Debug("channelManager.handleChannelInvalidated: channel had no blocks") } - // Trim provided channel and any older channels: + // Trim provided channel and any newer channels: + invalidatedChannelIdx := 0 + for i := range s.channelQueue { if s.channelQueue[i] == c { - s.channelQueue = s.channelQueue[:i] + invalidatedChannelIdx = i break } } + + for i := invalidatedChannelIdx; i < len(s.channelQueue); i++ { + s.log.Warn("Dropped channel", + "id", s.channelQueue[i].ID(), + "none_submitted", s.channelQueue[i].NoneSubmitted(), + "fully_submitted", s.channelQueue[i].isFullySubmitted(), + "timed_out", s.channelQueue[i].isTimedOut(), + "full_reason", s.channelQueue[i].FullErr(), + "oldest_l2", s.channelQueue[i].OldestL2(), + "newest_l2", s.channelQueue[i].LatestL2(), + ) + // Remove the channel from the txChannels map + for txID := range s.txChannels { + if s.txChannels[txID] == s.channelQueue[i] { + delete(s.txChannels, txID) + } + } + } + s.channelQueue = s.channelQueue[:invalidatedChannelIdx] + s.metr.RecordChannelQueueLength(len(s.channelQueue)) // We want to start writing to a new channel, so reset currentChannel. diff --git a/op-batcher/batcher/channel_manager_test.go b/op-batcher/batcher/channel_manager_test.go index e89663bd652..8dbab2f9240 100644 --- a/op-batcher/batcher/channel_manager_test.go +++ b/op-batcher/batcher/channel_manager_test.go @@ -397,7 +397,7 @@ func TestChannelManager_TxData(t *testing.T) { // and then calls handleChannelInvalidated. It asserts on the final state of // the channel manager. func TestChannelManager_handleChannelInvalidated(t *testing.T) { - l := testlog.Logger(t, log.LevelCrit) + l := testlog.Logger(t, log.LevelDebug) cfg := channelManagerTestConfig(100, derive.SingularBatchType) metrics := new(metrics.TestMetrics) m := NewChannelManager(l, metrics, cfg, defaultTestRollupConfig) @@ -417,10 +417,9 @@ func TestChannelManager_handleChannelInvalidated(t *testing.T) { // Place an old channel in the queue. // This channel should not be affected by // a requeue or a later channel timing out. - oldChannel := newChannel(l, nil, m.defaultCfg, defaultTestRollupConfig, 0, nil) + require.NoError(t, m.ensureChannelWithSpace(eth.BlockID{})) + oldChannel := m.currentChannel oldChannel.Close() - m.channelQueue = []*channel{oldChannel} - metrics.RecordChannelQueueLength(1) // we need to do this manually b/c we are not using the usual codepath to set state require.Len(t, m.channelQueue, 1) require.Equal(t, metrics.ChannelQueueLength, 1) @@ -433,7 +432,6 @@ func TestChannelManager_handleChannelInvalidated(t *testing.T) { require.NoError(t, m.ensureChannelWithSpace(eth.BlockID{})) require.Len(t, m.channelQueue, 2) require.Equal(t, metrics.ChannelQueueLength, 2) - require.NoError(t, m.processBlocks()) // Assert that at least one block was processed into the channel @@ -445,18 +443,29 @@ func TestChannelManager_handleChannelInvalidated(t *testing.T) { l1OriginBefore := m.l1OriginLastSubmittedChannel - m.handleChannelInvalidated(m.currentChannel) + // Add another newer channel, this will be wiped when we invalidate + channelToInvalidate := m.currentChannel + m.currentChannel.Close() + require.NoError(t, m.ensureChannelWithSpace(eth.BlockID{})) + require.Len(t, m.channelQueue, 3) + require.Equal(t, metrics.ChannelQueueLength, 3) + require.NoError(t, m.processBlocks()) + require.Equal(t, 2, m.blockCursor) + + m.handleChannelInvalidated(channelToInvalidate) // Ensure we got back to the state above require.Equal(t, m.blocks, stateSnapshot) require.Contains(t, m.channelQueue, oldChannel) + require.NotContains(t, m.channelQueue, channelToInvalidate) + require.NotContains(t, m.channelQueue, newChannel) require.Len(t, m.channelQueue, 1) require.Equal(t, metrics.ChannelQueueLength, 1) // Check metric came back up to previous value require.Equal(t, pendingBytesBefore, metrics.PendingBlocksBytesCurrent) - // Ensure the l1OridingLastSubmittedChannel was + // Ensure the l1OriginLastSubmittedChannel was // not changed. This ensures the next channel // has its duration timeout deadline computed // properly. From e4297b42acaed345ce786bccdac0d9212edd1ce6 Mon Sep 17 00:00:00 2001 From: George Knee Date: Thu, 27 Feb 2025 18:31:29 +0000 Subject: [PATCH 031/130] op-batcher: improve `computeSyncActions()` logging (#14563) * improve computeSyncActions logging * fixup test and make sure all cases run (!) * use more friendly format for structured logger --- op-batcher/batcher/sync_actions.go | 54 ++++++++++++++++--------- op-batcher/batcher/sync_actions_test.go | 6 ++- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/op-batcher/batcher/sync_actions.go b/op-batcher/batcher/sync_actions.go index 2ba7138ec2e..232ab5a86ef 100644 --- a/op-batcher/batcher/sync_actions.go +++ b/op-batcher/batcher/sync_actions.go @@ -17,6 +17,11 @@ type channelStatuser interface { } type inclusiveBlockRange struct{ start, end uint64 } + +func (r *inclusiveBlockRange) TerminalString() string { + return fmt.Sprintf("[%d, %d]", r.start, r.end) +} + type syncActions struct { clearState *eth.BlockID blocksToPrune int @@ -25,9 +30,17 @@ type syncActions struct { // NOTE this range is inclusive on both ends, which is a change to previous behaviour. } -func (s syncActions) String() string { +func (s syncActions) TerminalString() string { + cs := "nil" + if s.clearState != nil { + cs = s.clearState.TerminalString() + } + btl := "nil" + if s.blocksToLoad != nil { + btl = s.blocksToLoad.TerminalString() + } return fmt.Sprintf( - "SyncActions{blocksToPrune: %d, channelsToPrune: %d, clearState: %v, blocksToLoad: %v}", s.blocksToPrune, s.channelsToPrune, s.clearState, s.blocksToLoad) + "SyncActions{blocksToPrune: %d, channelsToPrune: %d, clearState: %v, blocksToLoad: %v}", s.blocksToPrune, s.channelsToPrune, cs, btl) } // computeSyncActions determines the actions that should be taken based on the inputs provided. The inputs are the current @@ -36,15 +49,22 @@ func (s syncActions) String() string { // range to load into the local state, and whether to clear the state entirely. Returns an boolean indicating if the sequencer is out of sync. func computeSyncActions[T channelStatuser](newSyncStatus eth.SyncStatus, prevCurrentL1 eth.L1BlockRef, blocks queue.Queue[*types.Block], channels []T, l log.Logger) (syncActions, bool) { + m := l.With( + "syncStatus.headL1", newSyncStatus.HeadL1, + "syncStatus.currentL1", newSyncStatus.CurrentL1, + "syncStatus.localSafeL2", newSyncStatus.LocalSafeL2, + "syncStatus.unsafeL2", newSyncStatus.UnsafeL2, + ) + // PART 1: Initial checks on the sync status if newSyncStatus.HeadL1 == (eth.L1BlockRef{}) { - l.Warn("empty sync status") + m.Warn("empty sync status") return syncActions{}, true } if newSyncStatus.CurrentL1.Number < prevCurrentL1.Number { // This can happen when the sequencer restarts - l.Warn("sequencer currentL1 reversed") + m.Warn("sequencer currentL1 reversed", "prevCurrentL1", prevCurrentL1) return syncActions{}, true } @@ -60,7 +80,7 @@ func computeSyncActions[T channelStatuser](newSyncStatus eth.SyncStatus, prevCur s := syncActions{ blocksToLoad: allUnsafeBlocks, } - l.Info("no blocks in state", "syncActions", s) + m.Info("no blocks in state", "syncActions", s) return s, false } @@ -77,10 +97,9 @@ func computeSyncActions[T channelStatuser](newSyncStatus eth.SyncStatus, prevCur nextSafeBlockNum := newSyncStatus.LocalSafeL2.Number + 1 if nextSafeBlockNum < oldestBlockInStateNum { - l.Warn("next safe block is below oldest block in state", + m.Warn("next safe block is below oldest block in state", "syncActions", startAfresh, - "oldestBlockInState", oldestBlockInState, - "safeL2", newSyncStatus.LocalSafeL2) + "oldestBlockInStateNum", oldestBlockInStateNum) return startAfresh, false } @@ -94,19 +113,17 @@ func computeSyncActions[T channelStatuser](newSyncStatus eth.SyncStatus, prevCur // This could happen if the batcher restarted. // The sequencer may have derived the safe chain // from channels sent by a previous batcher instance. - l.Warn("safe head above newest block in state, clearing channel manager state", + m.Warn("safe head above newest block in state, clearing channel manager state", "syncActions", startAfresh, - "safeL2", newSyncStatus.LocalSafeL2, "newestBlockInState", eth.ToBlockID(newestBlockInState), ) return startAfresh, false } if numBlocksToDequeue > 0 && blocks[numBlocksToDequeue-1].Hash() != newSyncStatus.LocalSafeL2.Hash { - l.Warn("safe chain reorg, clearing channel manager state", + m.Warn("safe chain reorg, clearing channel manager state", "syncActions", startAfresh, - "existingBlock", eth.ToBlockID(blocks[numBlocksToDequeue-1]), - "safeL2", newSyncStatus.LocalSafeL2) + "existingBlock", eth.ToBlockID(blocks[numBlocksToDequeue-1])) return startAfresh, false } @@ -120,10 +137,9 @@ func computeSyncActions[T channelStatuser](newSyncStatus eth.SyncStatus, prevCur // for a fully submitted channel. This indicates // that the derivation pipeline may have stalled // e.g. because of Holocene strict ordering rules. - l.Warn("sequencer did not make expected progress", + m.Warn("sequencer did not make expected progress", "syncActions", startAfresh, - "existingBlock", ch.LatestL2(), - "safeL2", newSyncStatus.LocalSafeL2) + "existingBlock", ch.LatestL2()) return startAfresh, false } } @@ -144,9 +160,11 @@ func computeSyncActions[T channelStatuser](newSyncStatus eth.SyncStatus, prevCur allUnsafeBlocksAboveState = &inclusiveBlockRange{newestBlockInStateNum + 1, newSyncStatus.UnsafeL2.Number} } - return syncActions{ + a := syncActions{ blocksToPrune: int(numBlocksToDequeue), channelsToPrune: numChannelsToPrune, blocksToLoad: allUnsafeBlocksAboveState, - }, false + } + m.Debug("computed sync actions", "syncActions", a) + return a, false } diff --git a/op-batcher/batcher/sync_actions_test.go b/op-batcher/batcher/sync_actions_test.go index 3d04b56a0c1..365b4022a66 100644 --- a/op-batcher/batcher/sync_actions_test.go +++ b/op-batcher/batcher/sync_actions_test.go @@ -55,7 +55,7 @@ func TestBatchSubmitter_computeSyncActions(t *testing.T) { timedOut: false, } - happyCaseLogs := []string{} // in the happy case we expect no logs + happyCaseLogs := []string{"computed sync actions"} type TestCase struct { name string @@ -196,6 +196,7 @@ func TestBatchSubmitter_computeSyncActions(t *testing.T) { expected: syncActions{ blocksToLoad: &inclusiveBlockRange{104, 109}, }, + expectedLogs: happyCaseLogs, }, {name: "no blocks", // This happens when the batcher is starting up for the first time @@ -229,6 +230,7 @@ func TestBatchSubmitter_computeSyncActions(t *testing.T) { channelsToPrune: 1, blocksToLoad: &inclusiveBlockRange{104, 109}, }, + expectedLogs: happyCaseLogs, }, {name: "happy path + multiple channels", newSyncStatus: eth.SyncStatus{ @@ -277,7 +279,7 @@ func TestBatchSubmitter_computeSyncActions(t *testing.T) { }, } - for _, tc := range testCases[len(testCases)-1:] { + for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { l, h := testlog.CaptureLogger(t, log.LevelDebug) From d290a774207bf7ecdfabf1bf03ce30216803934d Mon Sep 17 00:00:00 2001 From: ControlCplusControlV <44706811+ControlCplusControlV@users.noreply.github.com> Date: Thu, 27 Feb 2025 15:03:37 -0500 Subject: [PATCH 032/130] feat: Remove BlockNumber Dispute for SuperFaultDisputeGame + SuperPermissionedGame (#14431) * Initial copy * Make tests work * snapshots * semantic version for pre-release * my size was not size * semver lock fixed oops * added spec tests * fix initialization test * allow modifying super fdg * Update packages/contracts-bedrock/src/dispute/SuperPermissionedDisputeGame.sol Co-authored-by: Inphi * introduced the deploy scripts * added in new tests * Update packages/contracts-bedrock/src/dispute/SuperPermissionedDisputeGame.sol Co-authored-by: Inphi * Update packages/contracts-bedrock/src/dispute/SuperFaultDisputeGame.sol Co-authored-by: Inphi * Patches * fix tests * remove unused errors * new snapshots * semver bump * more snapshots * vm() * fixed spec tests * snapshot + fix tests * fix tests * delete actors tests * unused imports * fix fork tests * fix tests * new test case * use new prestate for deployment * Remove Unused l2ChainId * fix tests * fix tests + snapshot * missed one * correct snapshot --------- Co-authored-by: Inphi Co-authored-by: Michael Amadi --- .../scripts/deploy/Deploy.s.sol | 131 +++++- .../snapshots/abi/SuperFaultDisputeGame.json | 125 +----- .../abi/SuperPermissionedDisputeGame.json | 125 +----- .../snapshots/semver-lock.json | 8 +- .../storageLayout/SuperFaultDisputeGame.json | 36 +- .../SuperPermissionedDisputeGame.json | 36 +- .../src/dispute/SuperFaultDisputeGame.sol | 132 +----- .../dispute/SuperPermissionedDisputeGame.sol | 4 +- .../dispute/PermissionedDisputeGame.t.sol | 2 +- .../test/dispute/SuperFaultDisputeGame.t.sol | 384 ++---------------- .../SuperPermissionedDisputeGame.t.sol | 2 +- .../test/universal/Specs.t.sol | 15 - 12 files changed, 209 insertions(+), 791 deletions(-) diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index 7e6e2253310..ca927105ad9 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -47,6 +47,7 @@ import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; import { IMIPS } from "interfaces/cannon/IMIPS.sol"; +import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; /// @title Deploy @@ -399,6 +400,8 @@ contract Deploy is Deployer { }); setAlphabetFaultGameImplementation(); + setSuperFaultGameImplementation(); + setSuperPermissionedGameImplementation(); setFastFaultGameImplementation(); setCannonFaultGameImplementation(); @@ -670,6 +673,21 @@ contract Deploy is Deployer { } } + function loadInteropDevnetAbsolutePrestate() internal returns (Claim interopDevnetAbsolutePrestate_) { + string memory filePath = string.concat(vm.projectRoot(), "/../../op-program/bin/prestate-proof-interop.json"); + if (bytes(Process.bash(string.concat("[[ -f ", filePath, " ]] && echo \"present\""))).length == 0) { + revert( + "Deploy: cannon prestate dump not found, generate it with `make cannon-prestate` in the monorepo root" + ); + } + interopDevnetAbsolutePrestate_ = + Claim.wrap(abi.decode(bytes(Process.bash(string.concat("cat ", filePath, " | jq -r .pre"))), (bytes32))); + console.log( + "[Cannon Dispute Game] Using devnet Interop Devnet Absolute Prestate: %s", + vm.toString(Claim.unwrap(interopDevnetAbsolutePrestate_)) + ); + } + /// @notice Loads the singlethreaded mips absolute prestate from the prestate-proof for devnets otherwise /// from the config. function _loadDevnetStMipsAbsolutePrestate() internal returns (Claim mipsAbsolutePrestate_) { @@ -747,9 +765,7 @@ contract Deploy is Deployer { splitDepth: cfg.faultGameSplitDepth(), clockExtension: Duration.wrap(uint64(cfg.faultGameClockExtension())), maxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration())), - vm: IBigStepper( - new AlphabetVM(outputAbsolutePrestate, IPreimageOracle(artifacts.mustGetAddress("PreimageOracle"))) - ), + vm: IBigStepper(artifacts.mustGetAddress("MipsSingleton")), weth: weth, anchorStateRegistry: IAnchorStateRegistry(artifacts.mustGetAddress("AnchorStateRegistryProxy")), l2ChainId: cfg.l2ChainID() @@ -757,6 +773,52 @@ contract Deploy is Deployer { }); } + /// @notice Sets the implementation for the `PERMISSIONED_SUPER_CANNON` game type in the `DisputeGameFactory` + function setSuperPermissionedGameImplementation() public onlyDevnet broadcast { + console.log("Setting SuperPermissionedDisputeGame implementation"); + IDisputeGameFactory factory = IDisputeGameFactory(artifacts.mustGetAddress("DisputeGameFactoryProxy")); + IDelayedWETH weth = IDelayedWETH(artifacts.mustGetAddress("DelayedWETHProxy")); + + _setFaultGameImplementation({ + _factory: factory, + _params: IFaultDisputeGame.GameConstructorParams({ + gameType: GameType.wrap(4), + absolutePrestate: loadInteropDevnetAbsolutePrestate(), + maxGameDepth: cfg.faultGameMaxDepth(), + splitDepth: cfg.faultGameSplitDepth(), + clockExtension: Duration.wrap(uint64(cfg.faultGameClockExtension())), + maxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration())), + vm: IBigStepper(artifacts.mustGetAddress("MipsSingleton")), + weth: weth, + anchorStateRegistry: IAnchorStateRegistry(artifacts.mustGetAddress("AnchorStateRegistryProxy")), + l2ChainId: 0 // Unused Param on SuperDisputeGame + }) + }); + } + + /// @notice Sets the implementation for the `SUPER_CANNON` game type in the `DisputeGameFactory` + function setSuperFaultGameImplementation() public onlyDevnet broadcast { + console.log("Setting SuperFaultDisputeGame implementation"); + IDisputeGameFactory factory = IDisputeGameFactory(artifacts.mustGetAddress("DisputeGameFactoryProxy")); + IDelayedWETH weth = IDelayedWETH(artifacts.mustGetAddress("DelayedWETHProxy")); + + _setFaultGameImplementation({ + _factory: factory, + _params: IFaultDisputeGame.GameConstructorParams({ + gameType: GameType.wrap(4), + absolutePrestate: loadInteropDevnetAbsolutePrestate(), + maxGameDepth: cfg.faultGameMaxDepth(), + splitDepth: cfg.faultGameSplitDepth(), + clockExtension: Duration.wrap(uint64(cfg.faultGameClockExtension())), + maxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration())), + vm: IBigStepper(artifacts.mustGetAddress("MipsSingleton")), + weth: weth, + anchorStateRegistry: IAnchorStateRegistry(artifacts.mustGetAddress("AnchorStateRegistryProxy")), + l2ChainId: 0 // Unused Param on SuperDisputeGame + }) + }); + } + /// @notice Sets the implementation for the `ALPHABET` game type in the `DisputeGameFactory` function setFastFaultGameImplementation() public onlyDevnet broadcast { console.log("Setting Fast FaultDisputeGame implementation"); @@ -813,18 +875,51 @@ contract Deploy is Deployer { rawGameType != GameTypes.PERMISSIONED_CANNON.raw(), "Deploy: Permissioned Game should be deployed by OPCM" ); - _factory.setImplementation( - _params.gameType, - IDisputeGame( - DeployUtils.create2AndSave({ - _save: artifacts, - _salt: _implSalt(), - _name: "FaultDisputeGame", - _nick: string.concat("FaultDisputeGame_", vm.toString(rawGameType)), - _args: DeployUtils.encodeConstructor(abi.encodeCall(IFaultDisputeGame.__constructor__, (_params))) - }) - ) - ); + if (rawGameType == 4) { + _factory.setImplementation( + _params.gameType, + IDisputeGame( + DeployUtils.create2AndSave({ + _save: artifacts, + _salt: _implSalt(), + _name: "SuperFaultDisputeGame", + _nick: string.concat("SuperFaultDisputeGame_", vm.toString(rawGameType)), + _args: DeployUtils.encodeConstructor(abi.encodeCall(IFaultDisputeGame.__constructor__, (_params))) + }) + ) + ); + } else if (rawGameType == 5) { + _factory.setImplementation( + _params.gameType, + IDisputeGame( + DeployUtils.create2AndSave({ + _save: artifacts, + _salt: _implSalt(), + _name: "SuperPermissionedDisputeGame", + _nick: string.concat("SuperFaultDisputeGame_", vm.toString(rawGameType)), + _args: DeployUtils.encodeConstructor( + abi.encodeCall( + IPermissionedDisputeGame.__constructor__, + (_params, cfg.l2OutputOracleProposer(), cfg.l2OutputOracleChallenger()) + ) + ) + }) + ) + ); + } else { + _factory.setImplementation( + _params.gameType, + IDisputeGame( + DeployUtils.create2AndSave({ + _save: artifacts, + _salt: _implSalt(), + _name: "FaultDisputeGame", + _nick: string.concat("FaultDisputeGame_", vm.toString(rawGameType)), + _args: DeployUtils.encodeConstructor(abi.encodeCall(IFaultDisputeGame.__constructor__, (_params))) + }) + ) + ); + } string memory gameTypeString; if (rawGameType == GameTypes.CANNON.raw()) { @@ -835,6 +930,10 @@ contract Deploy is Deployer { gameTypeString = "OP Succinct"; } else if (rawGameType == GameTypes.KAILUA.raw()) { gameTypeString = "Kailua"; + } else if (rawGameType == 4) { + gameTypeString = "Super Cannon"; + } else if (rawGameType == 5) { + gameTypeString = "Permissioned Super Cannon"; } else { gameTypeString = "Unknown"; } @@ -866,7 +965,7 @@ contract Deploy is Deployer { ), saltMixer: saltMixer, gasLimit: uint64(cfg.l2GenesisBlockGasLimit()), - disputeGameType: GameTypes.PERMISSIONED_CANNON, + disputeGameType: GameType.wrap(4), disputeAbsolutePrestate: Claim.wrap(bytes32(cfg.faultGameAbsolutePrestate())), disputeMaxGameDepth: cfg.faultGameMaxDepth(), disputeSplitDepth: cfg.faultGameSplitDepth(), diff --git a/packages/contracts-bedrock/snapshots/abi/SuperFaultDisputeGame.json b/packages/contracts-bedrock/snapshots/abi/SuperFaultDisputeGame.json index ec1c69edea9..8c72cb9c74c 100644 --- a/packages/contracts-bedrock/snapshots/abi/SuperFaultDisputeGame.json +++ b/packages/contracts-bedrock/snapshots/abi/SuperFaultDisputeGame.json @@ -147,46 +147,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "version", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "stateRoot", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "messagePasserStorageRoot", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "latestBlockhash", - "type": "bytes32" - } - ], - "internalType": "struct Types.OutputRootProof", - "name": "_outputRootProof", - "type": "tuple" - }, - { - "internalType": "bytes", - "name": "_headerRLP", - "type": "bytes" - } - ], - "name": "challengeRootL2Block", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [ { @@ -527,45 +487,6 @@ "stateMutability": "pure", "type": "function" }, - { - "inputs": [], - "name": "l2BlockNumberChallenged", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "l2BlockNumberChallenger", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "l2ChainId", - "outputs": [ - { - "internalType": "uint256", - "name": "l2ChainId_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "maxClockDuration", @@ -1003,11 +924,6 @@ "name": "AnchorRootNotFound", "type": "error" }, - { - "inputs": [], - "name": "BlockNumberMatches", - "type": "error" - }, { "inputs": [], "name": "BondTransferFailed", @@ -1043,21 +959,11 @@ "name": "ClockTimeExceeded", "type": "error" }, - { - "inputs": [], - "name": "ContentLengthMismatch", - "type": "error" - }, { "inputs": [], "name": "DuplicateStep", "type": "error" }, - { - "inputs": [], - "name": "EmptyItem", - "type": "error" - }, { "inputs": [], "name": "GameDepthExceeded", @@ -1098,36 +1004,16 @@ "name": "InvalidClockExtension", "type": "error" }, - { - "inputs": [], - "name": "InvalidDataRemainder", - "type": "error" - }, { "inputs": [], "name": "InvalidDisputedClaimIndex", "type": "error" }, - { - "inputs": [], - "name": "InvalidHeader", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidHeaderRLP", - "type": "error" - }, { "inputs": [], "name": "InvalidLocalIdent", "type": "error" }, - { - "inputs": [], - "name": "InvalidOutputRootProof", - "type": "error" - }, { "inputs": [], "name": "InvalidParent", @@ -1145,12 +1031,12 @@ }, { "inputs": [], - "name": "L2BlockNumberChallenged", + "name": "MaxDepthTooLarge", "type": "error" }, { "inputs": [], - "name": "MaxDepthTooLarge", + "name": "NoChainIdNeeded", "type": "error" }, { @@ -1170,7 +1056,7 @@ }, { "inputs": [], - "name": "UnexpectedList", + "name": "SuperFaultDisputeGameInvalidRootClaim", "type": "error" }, { @@ -1184,11 +1070,6 @@ "name": "UnexpectedRootClaim", "type": "error" }, - { - "inputs": [], - "name": "UnexpectedString", - "type": "error" - }, { "inputs": [], "name": "ValidStep", diff --git a/packages/contracts-bedrock/snapshots/abi/SuperPermissionedDisputeGame.json b/packages/contracts-bedrock/snapshots/abi/SuperPermissionedDisputeGame.json index b0e02ccfad1..02de35aca29 100644 --- a/packages/contracts-bedrock/snapshots/abi/SuperPermissionedDisputeGame.json +++ b/packages/contracts-bedrock/snapshots/abi/SuperPermissionedDisputeGame.json @@ -157,46 +157,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [ - { - "components": [ - { - "internalType": "bytes32", - "name": "version", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "stateRoot", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "messagePasserStorageRoot", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "latestBlockhash", - "type": "bytes32" - } - ], - "internalType": "struct Types.OutputRootProof", - "name": "_outputRootProof", - "type": "tuple" - }, - { - "internalType": "bytes", - "name": "_headerRLP", - "type": "bytes" - } - ], - "name": "challengeRootL2Block", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, { "inputs": [], "name": "challenger", @@ -550,45 +510,6 @@ "stateMutability": "pure", "type": "function" }, - { - "inputs": [], - "name": "l2BlockNumberChallenged", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "l2BlockNumberChallenger", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "l2ChainId", - "outputs": [ - { - "internalType": "uint256", - "name": "l2ChainId_", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "maxClockDuration", @@ -1044,11 +965,6 @@ "name": "BadAuth", "type": "error" }, - { - "inputs": [], - "name": "BlockNumberMatches", - "type": "error" - }, { "inputs": [], "name": "BondTransferFailed", @@ -1084,21 +1000,11 @@ "name": "ClockTimeExceeded", "type": "error" }, - { - "inputs": [], - "name": "ContentLengthMismatch", - "type": "error" - }, { "inputs": [], "name": "DuplicateStep", "type": "error" }, - { - "inputs": [], - "name": "EmptyItem", - "type": "error" - }, { "inputs": [], "name": "GameDepthExceeded", @@ -1139,36 +1045,16 @@ "name": "InvalidClockExtension", "type": "error" }, - { - "inputs": [], - "name": "InvalidDataRemainder", - "type": "error" - }, { "inputs": [], "name": "InvalidDisputedClaimIndex", "type": "error" }, - { - "inputs": [], - "name": "InvalidHeader", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidHeaderRLP", - "type": "error" - }, { "inputs": [], "name": "InvalidLocalIdent", "type": "error" }, - { - "inputs": [], - "name": "InvalidOutputRootProof", - "type": "error" - }, { "inputs": [], "name": "InvalidParent", @@ -1186,12 +1072,12 @@ }, { "inputs": [], - "name": "L2BlockNumberChallenged", + "name": "MaxDepthTooLarge", "type": "error" }, { "inputs": [], - "name": "MaxDepthTooLarge", + "name": "NoChainIdNeeded", "type": "error" }, { @@ -1211,7 +1097,7 @@ }, { "inputs": [], - "name": "UnexpectedList", + "name": "SuperFaultDisputeGameInvalidRootClaim", "type": "error" }, { @@ -1225,11 +1111,6 @@ "name": "UnexpectedRootClaim", "type": "error" }, - { - "inputs": [], - "name": "UnexpectedString", - "type": "error" - }, { "inputs": [], "name": "ValidStep", diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 937ad5c0926..42eacc880d1 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -180,12 +180,12 @@ "sourceCodeHash": "0xba32e6f35777426839a60e5556c09844e805eaabc14b9d3732cc64f2a99ce7fc" }, "src/dispute/SuperFaultDisputeGame.sol": { - "initCodeHash": "0xb8973b07e62acb901b231de2eceb9056372250b9d9f185202343b7cef44e3baf", - "sourceCodeHash": "0x9d7dec4a22d1d1ca49d3906d8391ec1607e1d22ec52251a418cc87672e92a45c" + "initCodeHash": "0x397d9eda98459859f30f170f0d3c6f4320d474ea36510ae7ece020800b80ba22", + "sourceCodeHash": "0xb08789200c909d24c8ebfbde9b9c55c50b9ebae2d21e761464c1b5abbbe59317" }, "src/dispute/SuperPermissionedDisputeGame.sol": { - "initCodeHash": "0xb3155c34a2a8dc700e70cf7f2a0a2fb3e495dc3a0f744ce5f8e01e3a018e3fee", - "sourceCodeHash": "0xd9683e93743e995f70a589169d700cf994aee7011112c8a8f2c3fce93372065f" + "initCodeHash": "0x9613232c1c11abee8782e2e9a5bb963923c73643d8c228e558f06082e8886f67", + "sourceCodeHash": "0xaefa8af211cee1b34063252f62020cd468175a2ab9cfc46b4e4cb03190e2d659" }, "src/legacy/DeployerWhitelist.sol": { "initCodeHash": "0x53099379ed48b87f027d55712dbdd1da7d7099925426eb0531da9c0012e02c29", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SuperFaultDisputeGame.json b/packages/contracts-bedrock/snapshots/storageLayout/SuperFaultDisputeGame.json index 7272c81a36a..62bd48ce754 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/SuperFaultDisputeGame.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/SuperFaultDisputeGame.json @@ -27,95 +27,81 @@ "slot": "0", "type": "bool" }, - { - "bytes": "1", - "label": "l2BlockNumberChallenged", - "offset": 18, - "slot": "0", - "type": "bool" - }, - { - "bytes": "20", - "label": "l2BlockNumberChallenger", - "offset": 0, - "slot": "1", - "type": "address" - }, { "bytes": "32", "label": "claimData", "offset": 0, - "slot": "2", + "slot": "1", "type": "struct SuperFaultDisputeGame.ClaimData[]" }, { "bytes": "32", "label": "normalModeCredit", "offset": 0, - "slot": "3", + "slot": "2", "type": "mapping(address => uint256)" }, { "bytes": "32", "label": "claims", "offset": 0, - "slot": "4", + "slot": "3", "type": "mapping(Hash => bool)" }, { "bytes": "32", "label": "subgames", "offset": 0, - "slot": "5", + "slot": "4", "type": "mapping(uint256 => uint256[])" }, { "bytes": "32", "label": "resolvedSubgames", "offset": 0, - "slot": "6", + "slot": "5", "type": "mapping(uint256 => bool)" }, { "bytes": "32", "label": "resolutionCheckpoints", "offset": 0, - "slot": "7", + "slot": "6", "type": "mapping(uint256 => struct SuperFaultDisputeGame.ResolutionCheckpoint)" }, { "bytes": "64", "label": "startingOutputRoot", "offset": 0, - "slot": "8", + "slot": "7", "type": "struct OutputRoot" }, { "bytes": "1", "label": "wasRespectedGameTypeWhenCreated", "offset": 0, - "slot": "10", + "slot": "9", "type": "bool" }, { "bytes": "32", "label": "refundModeCredit", "offset": 0, - "slot": "11", + "slot": "10", "type": "mapping(address => uint256)" }, { "bytes": "32", "label": "hasUnlockedCredit", "offset": 0, - "slot": "12", + "slot": "11", "type": "mapping(address => bool)" }, { "bytes": "1", "label": "bondDistributionMode", "offset": 0, - "slot": "13", + "slot": "12", "type": "enum BondDistributionMode" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SuperPermissionedDisputeGame.json b/packages/contracts-bedrock/snapshots/storageLayout/SuperPermissionedDisputeGame.json index 7272c81a36a..62bd48ce754 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/SuperPermissionedDisputeGame.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/SuperPermissionedDisputeGame.json @@ -27,95 +27,81 @@ "slot": "0", "type": "bool" }, - { - "bytes": "1", - "label": "l2BlockNumberChallenged", - "offset": 18, - "slot": "0", - "type": "bool" - }, - { - "bytes": "20", - "label": "l2BlockNumberChallenger", - "offset": 0, - "slot": "1", - "type": "address" - }, { "bytes": "32", "label": "claimData", "offset": 0, - "slot": "2", + "slot": "1", "type": "struct SuperFaultDisputeGame.ClaimData[]" }, { "bytes": "32", "label": "normalModeCredit", "offset": 0, - "slot": "3", + "slot": "2", "type": "mapping(address => uint256)" }, { "bytes": "32", "label": "claims", "offset": 0, - "slot": "4", + "slot": "3", "type": "mapping(Hash => bool)" }, { "bytes": "32", "label": "subgames", "offset": 0, - "slot": "5", + "slot": "4", "type": "mapping(uint256 => uint256[])" }, { "bytes": "32", "label": "resolvedSubgames", "offset": 0, - "slot": "6", + "slot": "5", "type": "mapping(uint256 => bool)" }, { "bytes": "32", "label": "resolutionCheckpoints", "offset": 0, - "slot": "7", + "slot": "6", "type": "mapping(uint256 => struct SuperFaultDisputeGame.ResolutionCheckpoint)" }, { "bytes": "64", "label": "startingOutputRoot", "offset": 0, - "slot": "8", + "slot": "7", "type": "struct OutputRoot" }, { "bytes": "1", "label": "wasRespectedGameTypeWhenCreated", "offset": 0, - "slot": "10", + "slot": "9", "type": "bool" }, { "bytes": "32", "label": "refundModeCredit", "offset": 0, - "slot": "11", + "slot": "10", "type": "mapping(address => uint256)" }, { "bytes": "32", "label": "hasUnlockedCredit", "offset": 0, - "slot": "12", + "slot": "11", "type": "mapping(address => bool)" }, { "bytes": "1", "label": "bondDistributionMode", "offset": 0, - "slot": "13", + "slot": "12", "type": "enum BondDistributionMode" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/dispute/SuperFaultDisputeGame.sol b/packages/contracts-bedrock/src/dispute/SuperFaultDisputeGame.sol index ac7df9e189d..bd96a4b4068 100644 --- a/packages/contracts-bedrock/src/dispute/SuperFaultDisputeGame.sol +++ b/packages/contracts-bedrock/src/dispute/SuperFaultDisputeGame.sol @@ -5,9 +5,6 @@ pragma solidity 0.8.15; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; import { FixedPointMathLib } from "@solady/utils/FixedPointMathLib.sol"; import { Clone } from "@solady/utils/Clone.sol"; -import { Types } from "src/libraries/Types.sol"; -import { Hashing } from "src/libraries/Hashing.sol"; -import { RLPReader } from "src/libraries/rlp/RLPReader.sol"; import { GameStatus, GameType, @@ -39,19 +36,15 @@ import { InvalidPrestate, ValidStep, GameDepthExceeded, - L2BlockNumberChallenged, InvalidDisputedClaimIndex, ClockTimeExceeded, DuplicateStep, CannotDefendRootClaim, IncorrectBondAmount, InvalidLocalIdent, - BlockNumberMatches, - InvalidHeaderRLP, ClockNotExpired, BondTransferFailed, NoCreditToClaim, - InvalidOutputRootProof, ClaimAboveSplit, GameNotFinalized, InvalidBondDistributionMode, @@ -69,6 +62,11 @@ import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; /// @title SuperFaultDisputeGame /// @notice An implementation of the `IFaultDisputeGame` interface for interop. contract SuperFaultDisputeGame is Clone, ISemver { + /// @dev Error to prevent initialization a dispute game with an actually valid, invalid state + error SuperFaultDisputeGameInvalidRootClaim(); + /// @dev Error to prevent passing a chainId to this dispute game + error NoChainIdNeeded(); + //////////////////////////////////////////////////////////////// // Structs // //////////////////////////////////////////////////////////////// @@ -128,6 +126,9 @@ contract SuperFaultDisputeGame is Clone, ISemver { // State Vars // //////////////////////////////////////////////////////////////// + /// @notice The constant for invalid root claims. + bytes32 internal constant INVALID_ROOT_CLAIM = keccak256("invalid"); + /// @notice The absolute prestate of the instruction trace. This is a constant that is defined /// by the program that is being used to execute the trace. Claim internal immutable ABSOLUTE_PRESTATE; @@ -154,9 +155,6 @@ contract SuperFaultDisputeGame is Clone, ISemver { /// @notice The anchor state registry. IAnchorStateRegistry internal immutable ANCHOR_STATE_REGISTRY; - /// @notice The chain ID of the L2 network this contract argues about. - uint256 internal immutable L2_CHAIN_ID; - /// @notice The duration of the clock extension. Will be doubled if the grandchild is the root claim of an execution /// trace bisection subgame. Duration internal immutable CLOCK_EXTENSION; @@ -164,15 +162,10 @@ contract SuperFaultDisputeGame is Clone, ISemver { /// @notice The global root claim's position is always at gindex 1. Position internal constant ROOT_POSITION = Position.wrap(1); - /// @notice The index of the block number in the RLP-encoded block header. - /// @dev Consensus encoding reference: - /// https://github.com/paradigmxyz/reth/blob/5f82993c23164ce8ccdc7bf3ae5085205383a5c8/crates/primitives/src/header.rs#L368 - uint256 internal constant HEADER_BLOCK_NUMBER_INDEX = 8; - /// @notice Semantic version. - /// @custom:semver 0.1.0-beta.0 + /// @custom:semver 0.1.0-beta.1 function version() public pure virtual returns (string memory) { - return "0.1.0-beta.0"; + return "0.1.0-beta.1"; } /// @notice The starting timestamp of the game @@ -187,13 +180,6 @@ contract SuperFaultDisputeGame is Clone, ISemver { /// @notice Flag for the `initialize` function to prevent re-initialization. bool internal initialized; - /// @notice Flag for whether or not the L2 block number claim has been invalidated via `challengeRootL2Block`. - bool public l2BlockNumberChallenged; - - /// @notice The challenger of the L2 block number claim. Should always be `address(0)` if `l2BlockNumberChallenged` - /// is `false`. Should be the address of the challenger if `l2BlockNumberChallenged` is `true`. - address public l2BlockNumberChallenger; - /// @notice An append-only array of all claims made during the dispute game. ClaimData[] public claimData; @@ -266,6 +252,8 @@ contract SuperFaultDisputeGame is Clone, ISemver { // OptimismPortal respected game type trick. if (_params.gameType.raw() == type(uint32).max) revert ReservedGameType(); + if (_params.l2ChainId != 0) revert NoChainIdNeeded(); + // Set up initial game state. GAME_TYPE = _params.gameType; ABSOLUTE_PRESTATE = _params.absolutePrestate; @@ -276,7 +264,6 @@ contract SuperFaultDisputeGame is Clone, ISemver { VM = _params.vm; WETH = _params.weth; ANCHOR_STATE_REGISTRY = _params.anchorStateRegistry; - L2_CHAIN_ID = _params.l2ChainId; } /// @notice Initializes the contract. @@ -302,6 +289,9 @@ contract SuperFaultDisputeGame is Clone, ISemver { // Should only happen if this is a new game type that hasn't been set up yet. if (root.raw() == bytes32(0)) revert AnchorRootNotFound(); + // Prevent initializing right away with an invalid claim state that is used as convention + if (rootClaim().raw() == INVALID_ROOT_CLAIM) revert SuperFaultDisputeGameInvalidRootClaim(); + // Set the starting output root. startingOutputRoot = OutputRoot({ l2BlockNumber: rootBlockNumber, root: root }); @@ -482,10 +472,6 @@ contract SuperFaultDisputeGame is Clone, ISemver { revert CannotDefendRootClaim(); } - // INVARIANT: No moves against the root claim can be made after it has been challenged with - // `challengeRootL2Block`.` - if (l2BlockNumberChallenged && _challengeIndex == 0) revert L2BlockNumberChallenged(); - // INVARIANT: A move can never surpass the `MAX_GAME_DEPTH`. The only option to counter a // claim at this depth is to perform a single instruction step on-chain via // the `step` function to prove that the state transition produces an unexpected @@ -611,20 +597,7 @@ contract SuperFaultDisputeGame is Clone, ISemver { // Load the disputed proposal's output root oracle.loadLocalData(_ident, uuid.raw(), disputed.raw(), 32, _partOffset); } else if (_ident == LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER) { - // Load the disputed proposal's L2 block number as a big-endian uint64 in the - // high order 8 bytes of the word. - - // We add the index at depth + 1 to the starting block number to get the disputed L2 - // block number. - uint256 l2Number = startingOutputRoot.l2BlockNumber + disputedPos.traceIndex(SPLIT_DEPTH) + 1; - - // Choose the minimum between the `l2BlockNumber` claim and the bisected-to L2 block number. - l2Number = l2Number < l2BlockNumber() ? l2Number : l2BlockNumber(); - - oracle.loadLocalData(_ident, uuid.raw(), bytes32(l2Number << 0xC0), 8, _partOffset); - } else if (_ident == LocalPreimageKey.CHAIN_ID) { - // Load the chain ID as a big-endian uint64 in the high order 8 bytes of the word. - oracle.loadLocalData(_ident, uuid.raw(), bytes32(L2_CHAIN_ID << 0xC0), 8, _partOffset); + oracle.loadLocalData(_ident, uuid.raw(), bytes32(l2BlockNumber() << 0xC0), 8, _partOffset); } else { revert InvalidLocalIdent(); } @@ -657,54 +630,6 @@ contract SuperFaultDisputeGame is Clone, ISemver { startingRootHash_ = startingOutputRoot.root; } - /// @notice Challenges the root L2 block number by providing the preimage of the output root and the L2 block header - /// and showing that the committed L2 block number is incorrect relative to the claimed L2 block number. - /// @param _outputRootProof The output root proof. - /// @param _headerRLP The RLP-encoded L2 block header. - function challengeRootL2Block( - Types.OutputRootProof calldata _outputRootProof, - bytes calldata _headerRLP - ) - external - { - // INVARIANT: Moves cannot be made unless the game is currently in progress. - if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress(); - - // The root L2 block claim can only be challenged once. - if (l2BlockNumberChallenged) revert L2BlockNumberChallenged(); - - // Verify the output root preimage. - if (Hashing.hashOutputRootProof(_outputRootProof) != rootClaim().raw()) revert InvalidOutputRootProof(); - - // Verify the block hash preimage. - if (keccak256(_headerRLP) != _outputRootProof.latestBlockhash) revert InvalidHeaderRLP(); - - // Decode the header RLP to find the number of the block. In the consensus encoding, the timestamp - // is the 9th element in the list that represents the block header. - RLPReader.RLPItem[] memory headerContents = RLPReader.readList(RLPReader.toRLPItem(_headerRLP)); - bytes memory rawBlockNumber = RLPReader.readBytes(headerContents[HEADER_BLOCK_NUMBER_INDEX]); - - // Sanity check the block number string length. - if (rawBlockNumber.length > 32) revert InvalidHeaderRLP(); - - // Convert the raw, left-aligned block number to a uint256 by aligning it as a big-endian - // number in the low-order bytes of a 32-byte word. - // - // SAFETY: The length of `rawBlockNumber` is checked above to ensure it is at most 32 bytes. - uint256 blockNumber; - assembly { - blockNumber := shr(shl(0x03, sub(0x20, mload(rawBlockNumber))), mload(add(rawBlockNumber, 0x20))) - } - - // Ensure the block number does not match the block number claimed in the dispute game. - if (blockNumber == l2BlockNumber()) revert BlockNumberMatches(); - - // Issue a special counter to the root claim. This counter will always win the root claim subgame, and receive - // the bond from the root claimant. - l2BlockNumberChallenger = msg.sender; - l2BlockNumberChallenged = true; - } - //////////////////////////////////////////////////////////////// // `IDisputeGame` impl // //////////////////////////////////////////////////////////////// @@ -824,21 +749,13 @@ contract SuperFaultDisputeGame is Clone, ISemver { resolvedSubgames[_claimIndex] = true; // Distribute the bond to the appropriate party. - if (_claimIndex == 0 && l2BlockNumberChallenged) { - // Special case: If the root claim has been challenged with the `challengeRootL2Block` function, - // the bond is always paid out to the issuer of that challenge. - address challenger = l2BlockNumberChallenger; - _distributeBond(challenger, subgameRootClaim); - subgameRootClaim.counteredBy = challenger; - } else { - // If the parent was not successfully countered, pay out the parent's bond to the claimant. - // If the parent was successfully countered, pay out the parent's bond to the challenger. - _distributeBond(countered == address(0) ? subgameRootClaim.claimant : countered, subgameRootClaim); + // If the parent was not successfully countered, pay out the parent's bond to the claimant. + // If the parent was successfully countered, pay out the parent's bond to the challenger. + _distributeBond(countered == address(0) ? subgameRootClaim.claimant : countered, subgameRootClaim); - // Once a subgame is resolved, we percolate the result up the DAG so subsequent calls to - // resolveClaim will not need to traverse this subgame. - subgameRootClaim.counteredBy = countered; - } + // Once a subgame is resolved, we percolate the result up the DAG so subsequent calls to + // resolveClaim will not need to traverse this subgame. + subgameRootClaim.counteredBy = countered; } } @@ -1119,11 +1036,6 @@ contract SuperFaultDisputeGame is Clone, ISemver { registry_ = ANCHOR_STATE_REGISTRY; } - /// @notice Returns the chain ID of the L2 network this contract argues about. - function l2ChainId() external view returns (uint256 l2ChainId_) { - l2ChainId_ = L2_CHAIN_ID; - } - //////////////////////////////////////////////////////////////// // HELPERS // //////////////////////////////////////////////////////////////// diff --git a/packages/contracts-bedrock/src/dispute/SuperPermissionedDisputeGame.sol b/packages/contracts-bedrock/src/dispute/SuperPermissionedDisputeGame.sol index e4305ddf646..1042408de74 100644 --- a/packages/contracts-bedrock/src/dispute/SuperPermissionedDisputeGame.sol +++ b/packages/contracts-bedrock/src/dispute/SuperPermissionedDisputeGame.sol @@ -33,9 +33,9 @@ contract SuperPermissionedDisputeGame is SuperFaultDisputeGame { } /// @notice Semantic version. - /// @custom:semver 0.1.0-beta.0 + /// @custom:semver 0.1.0-beta.1 function version() public pure override returns (string memory) { - return "0.1.0-beta.0"; + return "0.1.0-beta.1"; } /// @param _params Parameters for creating a new FaultDisputeGame. diff --git a/packages/contracts-bedrock/test/dispute/PermissionedDisputeGame.t.sol b/packages/contracts-bedrock/test/dispute/PermissionedDisputeGame.t.sol index 49c39c9f35c..46b8c1ca4b5 100644 --- a/packages/contracts-bedrock/test/dispute/PermissionedDisputeGame.t.sol +++ b/packages/contracts-bedrock/test/dispute/PermissionedDisputeGame.t.sol @@ -82,7 +82,7 @@ contract PermissionedDisputeGame_Init is DisputeGameFactory_Init { vm: _vm, weth: _weth, anchorStateRegistry: anchorStateRegistry, - l2ChainId: 10 + l2ChainId: 0 }), PROPOSER, CHALLENGER diff --git a/packages/contracts-bedrock/test/dispute/SuperFaultDisputeGame.t.sol b/packages/contracts-bedrock/test/dispute/SuperFaultDisputeGame.t.sol index 5e37770a74d..099a85e56c0 100644 --- a/packages/contracts-bedrock/test/dispute/SuperFaultDisputeGame.t.sol +++ b/packages/contracts-bedrock/test/dispute/SuperFaultDisputeGame.t.sol @@ -31,7 +31,7 @@ import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; contract SuperFaultDisputeGame_Init is DisputeGameFactory_Init { /// @dev The type of the game being tested. - GameType internal constant GAME_TYPE = GameType.wrap(0); + GameType internal constant GAME_TYPE = GameType.wrap(4); /// @dev The implementation of the game. IFaultDisputeGame internal gameImpl; @@ -85,7 +85,7 @@ contract SuperFaultDisputeGame_Init is DisputeGameFactory_Init { vm: _vm, weth: delayedWeth, anchorStateRegistry: anchorStateRegistry, - l2ChainId: 10 + l2ChainId: 0 }) ) ) @@ -96,6 +96,10 @@ contract SuperFaultDisputeGame_Init is DisputeGameFactory_Init { // Register the game implementation with the factory. disputeGameFactory.setImplementation(GAME_TYPE, gameImpl); uint256 bondAmount = disputeGameFactory.initBonds(GAME_TYPE); + + vm.prank(superchainConfig.guardian()); + optimismPortal2.setRespectedGameType(GAME_TYPE); + // Create a new game. gameProxy = IFaultDisputeGame( payable(address(disputeGameFactory.create{ value: bondAmount }(GAME_TYPE, rootClaim, extraData))) @@ -113,7 +117,7 @@ contract SuperFaultDisputeGame_Init is DisputeGameFactory_Init { assertEq(address(gameProxy.vm()), address(_vm)); // Label the proxy - vm.label(address(gameProxy), "FaultDisputeGame_Clone"); + vm.label(address(gameProxy), "SuperFaultDisputeGame_Clone"); } fallback() external payable { } @@ -173,7 +177,7 @@ contract SuperFaultDisputeGame_Test is SuperFaultDisputeGame_Init { _maxGameDepth = bound(_maxGameDepth, LibPosition.MAX_POSITION_BITLEN, type(uint256).max - 1); vm.expectRevert(MaxDepthTooLarge.selector); DeployUtils.create1({ - _name: "FaultDisputeGame", + _name: "SuperFaultDisputeGame", _args: DeployUtils.encodeConstructor( abi.encodeCall( IFaultDisputeGame.__constructor__, @@ -188,7 +192,7 @@ contract SuperFaultDisputeGame_Test is SuperFaultDisputeGame_Init { vm: alphabetVM, weth: IDelayedWETH(payable(address(0))), anchorStateRegistry: IAnchorStateRegistry(address(0)), - l2ChainId: 10 + l2ChainId: 0 }) ) ) @@ -217,7 +221,7 @@ contract SuperFaultDisputeGame_Test is SuperFaultDisputeGame_Init { vm.expectRevert(InvalidChallengePeriod.selector); DeployUtils.create1({ - _name: "FaultDisputeGame", + _name: "SuperFaultDisputeGame", _args: DeployUtils.encodeConstructor( abi.encodeCall( IFaultDisputeGame.__constructor__, @@ -232,7 +236,7 @@ contract SuperFaultDisputeGame_Test is SuperFaultDisputeGame_Init { vm: alphabetVM, weth: IDelayedWETH(payable(address(0))), anchorStateRegistry: IAnchorStateRegistry(address(0)), - l2ChainId: 10 + l2ChainId: 0 }) ) ) @@ -257,7 +261,7 @@ contract SuperFaultDisputeGame_Test is SuperFaultDisputeGame_Init { _splitDepth = bound(_splitDepth, maxGameDepth - 1, type(uint256).max); vm.expectRevert(InvalidSplitDepth.selector); DeployUtils.create1({ - _name: "FaultDisputeGame", + _name: "SuperFaultDisputeGame", _args: DeployUtils.encodeConstructor( abi.encodeCall( IFaultDisputeGame.__constructor__, @@ -272,7 +276,7 @@ contract SuperFaultDisputeGame_Test is SuperFaultDisputeGame_Init { vm: alphabetVM, weth: IDelayedWETH(payable(address(0))), anchorStateRegistry: IAnchorStateRegistry(address(0)), - l2ChainId: 10 + l2ChainId: 0 }) ) ) @@ -297,7 +301,7 @@ contract SuperFaultDisputeGame_Test is SuperFaultDisputeGame_Init { _splitDepth = bound(_splitDepth, 0, minSplitDepth - 1); vm.expectRevert(InvalidSplitDepth.selector); DeployUtils.create1({ - _name: "FaultDisputeGame", + _name: "SuperFaultDisputeGame", _args: DeployUtils.encodeConstructor( abi.encodeCall( IFaultDisputeGame.__constructor__, @@ -312,7 +316,7 @@ contract SuperFaultDisputeGame_Test is SuperFaultDisputeGame_Init { vm: alphabetVM, weth: IDelayedWETH(payable(address(0))), anchorStateRegistry: IAnchorStateRegistry(address(0)), - l2ChainId: 10 + l2ChainId: 0 }) ) ) @@ -345,7 +349,7 @@ contract SuperFaultDisputeGame_Test is SuperFaultDisputeGame_Init { vm.expectRevert(InvalidClockExtension.selector); DeployUtils.create1({ - _name: "FaultDisputeGame", + _name: "SuperFaultDisputeGame", _args: DeployUtils.encodeConstructor( abi.encodeCall( IFaultDisputeGame.__constructor__, @@ -360,7 +364,7 @@ contract SuperFaultDisputeGame_Test is SuperFaultDisputeGame_Init { vm: alphabetVM, weth: IDelayedWETH(payable(address(0))), anchorStateRegistry: IAnchorStateRegistry(address(0)), - l2ChainId: 10 + l2ChainId: 0 }) ) ) @@ -383,7 +387,7 @@ contract SuperFaultDisputeGame_Test is SuperFaultDisputeGame_Init { vm.expectRevert(ReservedGameType.selector); DeployUtils.create1({ - _name: "FaultDisputeGame", + _name: "SuperFaultDisputeGame", _args: DeployUtils.encodeConstructor( abi.encodeCall( IFaultDisputeGame.__constructor__, @@ -398,7 +402,7 @@ contract SuperFaultDisputeGame_Test is SuperFaultDisputeGame_Init { vm: alphabetVM, weth: IDelayedWETH(payable(address(0))), anchorStateRegistry: IAnchorStateRegistry(address(0)), - l2ChainId: 10 + l2ChainId: 0 }) ) ) @@ -475,6 +479,13 @@ contract SuperFaultDisputeGame_Test is SuperFaultDisputeGame_Init { assertEq(delayedWeth.balanceOf(address(gameProxy)), _value); } + /// @dev Tests that the game cannot be initialized with the reserved `keccak256("invalid") reserved root + function test_initialize_invalidRoot_reverts() public { + Claim claim = Claim.wrap(keccak256("invalid")); + vm.expectRevert(bytes4(keccak256("SuperFaultDisputeGameInvalidRootClaim()"))); + gameProxy = IFaultDisputeGame(payable(address(disputeGameFactory.create(GAME_TYPE, claim, extraData)))); + } + /// @dev Tests that the game cannot be initialized with extra data of the incorrect length (must be 32 bytes) function testFuzz_initialize_badExtraData_reverts(uint256 _extraDataLen) public { // The `DisputeGameFactory` will pack the root claim and the extra data into a single array, which is enforced @@ -970,181 +981,6 @@ contract SuperFaultDisputeGame_Test is SuperFaultDisputeGame_Init { gameProxy.attack{ value: bond }(disputed, 1, _dummyClaim()); } - /// @dev Tests that challenging the root claim's L2 block number by providing the real preimage of the output root - /// succeeds. - function testFuzz_challengeRootL2Block_succeeds( - bytes32 _storageRoot, - bytes32 _withdrawalRoot, - uint256 _l2BlockNumber - ) - public - { - _l2BlockNumber = bound(_l2BlockNumber, validL2BlockNumber, type(uint256).max - 1); - - (Types.OutputRootProof memory outputRootProof, bytes32 outputRoot, bytes memory headerRLP) = - _generateOutputRootProof(_storageRoot, _withdrawalRoot, abi.encodePacked(_l2BlockNumber)); - - // Create the dispute game with the output root at the wrong L2 block number. - uint256 wrongL2BlockNumber = bound(vm.randomUint(), _l2BlockNumber + 1, type(uint256).max); - IDisputeGame game = disputeGameFactory.create(GAME_TYPE, Claim.wrap(outputRoot), abi.encode(wrongL2BlockNumber)); - - // Challenge the L2 block number. - IFaultDisputeGame fdg = IFaultDisputeGame(address(game)); - fdg.challengeRootL2Block(outputRootProof, headerRLP); - - // Ensure that a duplicate challenge reverts. - vm.expectRevert(L2BlockNumberChallenged.selector); - fdg.challengeRootL2Block(outputRootProof, headerRLP); - - // Warp past the clocks, resolve the game. - vm.warp(block.timestamp + 3 days + 12 hours + 1); - fdg.resolveClaim(0, 0); - fdg.resolve(); - - // Ensure the challenge was successful. - assertEq(uint8(fdg.status()), uint8(GameStatus.CHALLENGER_WINS)); - assertTrue(fdg.l2BlockNumberChallenged()); - } - - /// @dev Tests that challenging the root claim's L2 block number by providing the real preimage of the output root - /// succeeds. Also, this claim should always receive the bond when there is another counter that is as far left - /// as possible. - function testFuzz_challengeRootL2Block_receivesBond_succeeds( - bytes32 _storageRoot, - bytes32 _withdrawalRoot, - uint256 _l2BlockNumber - ) - public - { - vm.deal(address(0xb0b), 1 ether); - _l2BlockNumber = bound(_l2BlockNumber, validL2BlockNumber, type(uint256).max - 1); - - (Types.OutputRootProof memory outputRootProof, bytes32 outputRoot, bytes memory headerRLP) = - _generateOutputRootProof(_storageRoot, _withdrawalRoot, abi.encodePacked(_l2BlockNumber)); - - // Create the dispute game with the output root at the wrong L2 block number. - disputeGameFactory.setInitBond(GAME_TYPE, 0.1 ether); - uint256 balanceBefore = address(this).balance; - _l2BlockNumber = bound(vm.randomUint(), _l2BlockNumber + 1, type(uint256).max); - IDisputeGame game = - disputeGameFactory.create{ value: 0.1 ether }(GAME_TYPE, Claim.wrap(outputRoot), abi.encode(_l2BlockNumber)); - IFaultDisputeGame fdg = IFaultDisputeGame(address(game)); - - // Attack the root as 0xb0b - uint256 bond = _getRequiredBond(0); - (,,,, Claim disputed,,) = fdg.claimData(0); - vm.prank(address(0xb0b)); - fdg.attack{ value: bond }(disputed, 0, Claim.wrap(0)); - - // Challenge the L2 block number as 0xace. This claim should receive the root claim's bond. - vm.prank(address(0xace)); - fdg.challengeRootL2Block(outputRootProof, headerRLP); - - // Warp past the clocks, resolve the game. - vm.warp(block.timestamp + 3 days + 12 hours + 1); - fdg.resolveClaim(1, 0); - fdg.resolveClaim(0, 0); - fdg.resolve(); - - // Ensure the challenge was successful. - assertEq(uint8(fdg.status()), uint8(GameStatus.CHALLENGER_WINS)); - - // Wait for finalization delay. - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Close the game. - fdg.closeGame(); - - // Claim credit once to trigger unlock period. - fdg.claimCredit(address(this)); - fdg.claimCredit(address(0xb0b)); - fdg.claimCredit(address(0xace)); - - // Wait for the withdrawal delay. - vm.warp(block.timestamp + delayedWeth.delay() + 1 seconds); - - // Claim credit - vm.expectRevert(NoCreditToClaim.selector); - fdg.claimCredit(address(this)); - fdg.claimCredit(address(0xb0b)); - fdg.claimCredit(address(0xace)); - - // Ensure that the party who challenged the L2 block number with the special move received the bond. - // - Root claim loses their bond - // - 0xace receives the root claim's bond - // - 0xb0b receives their bond back - assertEq(address(this).balance, balanceBefore - 0.1 ether); - assertEq(address(0xb0b).balance, 1 ether); - assertEq(address(0xace).balance, 0.1 ether); - } - - /// @dev Tests that challenging the root claim's L2 block number by providing the real preimage of the output root - /// never succeeds. - function testFuzz_challengeRootL2Block_rightBlockNumber_reverts( - bytes32 _storageRoot, - bytes32 _withdrawalRoot, - uint256 _l2BlockNumber - ) - public - { - _l2BlockNumber = bound(_l2BlockNumber, validL2BlockNumber, type(uint256).max); - - (Types.OutputRootProof memory outputRootProof, bytes32 outputRoot, bytes memory headerRLP) = - _generateOutputRootProof(_storageRoot, _withdrawalRoot, abi.encodePacked(_l2BlockNumber)); - - // Create the dispute game with the output root at the wrong L2 block number. - IDisputeGame game = disputeGameFactory.create(GAME_TYPE, Claim.wrap(outputRoot), abi.encode(_l2BlockNumber)); - - // Challenge the L2 block number. - IFaultDisputeGame fdg = IFaultDisputeGame(address(game)); - vm.expectRevert(BlockNumberMatches.selector); - fdg.challengeRootL2Block(outputRootProof, headerRLP); - - // Warp past the clocks, resolve the game. - vm.warp(block.timestamp + 3 days + 12 hours + 1); - fdg.resolveClaim(0, 0); - fdg.resolve(); - - // Ensure the challenge was successful. - assertEq(uint8(fdg.status()), uint8(GameStatus.DEFENDER_WINS)); - } - - /// @dev Tests that challenging the root claim's L2 block number with a bad output root proof reverts. - function test_challengeRootL2Block_badProof_reverts() public { - Types.OutputRootProof memory outputRootProof = - Types.OutputRootProof({ version: 0, stateRoot: 0, messagePasserStorageRoot: 0, latestBlockhash: 0 }); - - vm.expectRevert(InvalidOutputRootProof.selector); - gameProxy.challengeRootL2Block(outputRootProof, hex""); - } - - /// @dev Tests that challenging the root claim's L2 block number with a bad output root proof reverts. - function test_challengeRootL2Block_badHeaderRLP_reverts() public { - Types.OutputRootProof memory outputRootProof = - Types.OutputRootProof({ version: 0, stateRoot: 0, messagePasserStorageRoot: 0, latestBlockhash: 0 }); - bytes32 outputRoot = Hashing.hashOutputRootProof(outputRootProof); - - // Create the dispute game with the output root at the wrong L2 block number. - IDisputeGame game = disputeGameFactory.create(GAME_TYPE, Claim.wrap(outputRoot), abi.encode(validL2BlockNumber)); - IFaultDisputeGame fdg = IFaultDisputeGame(address(game)); - - vm.expectRevert(InvalidHeaderRLP.selector); - fdg.challengeRootL2Block(outputRootProof, hex""); - } - - /// @dev Tests that challenging the root claim's L2 block number with a bad output root proof reverts. - function test_challengeRootL2Block_badHeaderRLPBlockNumberLength_reverts() public { - (Types.OutputRootProof memory outputRootProof, bytes32 outputRoot,) = - _generateOutputRootProof(0, 0, new bytes(64)); - - // Create the dispute game with the output root at the wrong L2 block number. - IDisputeGame game = disputeGameFactory.create(GAME_TYPE, Claim.wrap(outputRoot), abi.encode(validL2BlockNumber)); - IFaultDisputeGame fdg = IFaultDisputeGame(address(game)); - - vm.expectRevert(InvalidHeaderRLP.selector); - fdg.challengeRootL2Block(outputRootProof, hex""); - } - /// @dev Tests that a claim cannot be stepped against twice. function test_step_duplicateStep_reverts() public { // Give the test contract some ether @@ -2142,15 +1978,14 @@ contract SuperFaultDisputeGame_Test is SuperFaultDisputeGame_Init { Position disputedPos = LibPosition.wrap(4, 0); // Expected local data - bytes32[5] memory data = [ + bytes32[4] memory data = [ gameProxy.l1Head().raw(), startingClaim, disputedClaim, - bytes32(validL2BlockNumber << 0xC0), - bytes32(gameProxy.l2ChainId() << 0xC0) + bytes32(uint256(gameProxy.l2BlockNumber()) << 0xC0) ]; - for (uint256 i = 1; i <= 5; i++) { + for (uint256 i = 1; i <= 4; i++) { uint256 expectedLen = i > 3 ? 8 : 32; bytes32 key = _getKey(i, keccak256(abi.encode(disputedClaim, disputedPos))); @@ -2193,15 +2028,14 @@ contract SuperFaultDisputeGame_Test is SuperFaultDisputeGame_Init { Position disputedPos = LibPosition.wrap(3, 0); // Expected local data - bytes32[5] memory data = [ + bytes32[4] memory data = [ gameProxy.l1Head().raw(), startingClaim, disputedClaim, - bytes32(validL2BlockNumber << 0xC0), - bytes32(gameProxy.l2ChainId() << 0xC0) + bytes32(uint256(gameProxy.l2BlockNumber()) << 0xC0) ]; - for (uint256 i = 1; i <= 5; i++) { + for (uint256 i = 1; i <= 4; i++) { uint256 expectedLen = i > 3 ? 8 : 32; bytes32 key = _getKey(i, keccak256(abi.encode(startingClaim, startingPos, disputedClaim, disputedPos))); @@ -2229,7 +2063,9 @@ contract SuperFaultDisputeGame_Test is SuperFaultDisputeGame_Init { // the leaves in our output bisection test tree, at SPLIT_DEPTH = 2 ** 2 IFaultDisputeGame game = IFaultDisputeGame( address( - disputeGameFactory.create(GAME_TYPE, Claim.wrap(bytes32(uint256(0xFF))), abi.encode(validL2BlockNumber)) + disputeGameFactory.create( + GAME_TYPE, Claim.wrap(bytes32(uint256(0xFF))), abi.encode(uint256(validL2BlockNumber)) + ) ) ); @@ -2266,7 +2102,7 @@ contract SuperFaultDisputeGame_Test is SuperFaultDisputeGame_Init { // Expected local data. This should be `l2BlockNumber`, and not the actual bisected-to block, // as we choose the minimum between the two. - bytes32 expectedNumber = bytes32(validL2BlockNumber << 0xC0); + bytes32 expectedNumber = bytes32(uint256(validL2BlockNumber << 0xC0)); uint256 expectedLen = 8; uint256 l2NumberIdent = LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER; @@ -2474,38 +2310,6 @@ contract SuperFaultDisputeGame_Test is SuperFaultDisputeGame_Init { assertEq(uint8(gameProxy.bondDistributionMode()), uint8(BondDistributionMode.NORMAL)); } - /// @dev Tests that closeGame called with any amount of gas either reverts (with OOG) or - /// updates the anchor state. This is specifically to verify that the try/catch inside - /// closeGame can't be called with just enough gas to OOG when calling the - /// AnchorStateRegistry but successfully execute the remainder of the function. - /// @param _gas Amount of gas to provide to closeGame. - function testFuzz_closeGame_canUpdateAnchorStateAndDoes_succeeds(uint256 _gas) public { - // Resolve and close the game first - vm.warp(block.timestamp + 3 days + 12 hours); - gameProxy.resolveClaim(0, 0); - gameProxy.resolve(); - - // Wait for finalization delay - vm.warp(block.timestamp + 3.5 days + 1 seconds); - - // Since providing *too* much gas isn't the issue here, bounding it to half the block gas - // limit is sufficient. We want to know that either (1) the function reverts or (2) the - // anchor state gets updated. If the function doesn't revert and the anchor state isn't - // updated then we have a problem. - _gas = bound(_gas, 0, block.gaslimit / 2); - - // The anchor state should not be the game proxy. - assert(address(gameProxy.anchorStateRegistry().anchorGame()) != address(gameProxy)); - - // Try closing the game. - try gameProxy.closeGame{ gas: _gas }() { - // If we got here, the function didn't revert, so the anchor state should have updated. - assert(address(gameProxy.anchorStateRegistry().anchorGame()) == address(gameProxy)); - } catch { - // Ok, function reverted. - } - } - /// @dev Helper to generate a mock RLP encoded header (with only a real block number) & an output root proof. function _generateOutputRootProof( bytes32 _storageRoot, @@ -2569,85 +2373,6 @@ contract SuperFaultDispute_1v1_Actors_Test is SuperFaultDisputeGame_Init { super.setUp(); } - /// @notice Fuzz test for a 1v1 output bisection dispute. - /// @dev The alphabet game has a constant status byte, and is not safe from someone being dishonest in - /// output bisection and then posting a correct execution trace bisection root claim. This test - /// does not cover this case (i.e. root claim of output bisection is dishonest, root claim of - /// execution trace bisection is made by the dishonest actor but is honest, honest actor cannot - /// attack it without risk of losing). - function testFuzz_outputBisection1v1honestRoot_succeeds(uint8 _divergeOutput, uint8 _divergeStep) public { - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - uint256 divergeAtOutput = bound(_divergeOutput, 0, 15); - uint256 divergeAtStep = bound(_divergeStep, 0, 7); - uint256 divergeStepOffset = (divergeAtOutput << 4) + divergeAtStep; - - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i >= divergeAtOutput ? 0xFF : i + 1; - } - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = i >= divergeStepOffset ? bytes1(uint8(0xFF)) : bytes1(uint8(i)); - } - - // Run the actor test - _actorTest({ - _rootClaim: 16, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.DEFENDER_WINS - }); - } - - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1honestRootGenesisAbsolutePrestate_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, consisting - // of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are from [2, 17] in this game. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i + 2; - } - // The dishonest trace covers all block -> block + 1 transitions, and is 256 bytes long, consisting - // of all set bits. - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = bytes1(0xFF); - } - - // Run the actor test - _actorTest({ - _rootClaim: 16, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.DEFENDER_WINS - }); - } - /// @notice Static unit test for a 1v1 output bisection dispute. function test_static_1v1dishonestRootGenesisAbsolutePrestate_succeeds() public { // The honest l2 outputs are from [1, 16] in this game. @@ -2830,43 +2555,6 @@ contract SuperFaultDispute_1v1_Actors_Test is SuperFaultDisputeGame_Init { }); } - /// @notice Static unit test for a 1v1 output bisection dispute. - function test_static_1v1correctAbsolutePrestate_succeeds() public { - // The honest l2 outputs are from [1, 16] in this game. - uint256[] memory honestL2Outputs = new uint256[](16); - for (uint256 i; i < honestL2Outputs.length; i++) { - honestL2Outputs[i] = i + 1; - } - // The honest trace covers all block -> block + 1 transitions, and is 256 bytes long, consisting - // of bytes [0, 255]. - bytes memory honestTrace = new bytes(256); - for (uint256 i; i < honestTrace.length; i++) { - honestTrace[i] = bytes1(uint8(i)); - } - - // The dishonest l2 outputs are half correct, half incorrect. - uint256[] memory dishonestL2Outputs = new uint256[](16); - for (uint256 i; i < dishonestL2Outputs.length; i++) { - dishonestL2Outputs[i] = i > 7 ? 0xFF : i + 1; - } - // The dishonest trace correct is half correct, half incorrect. - bytes memory dishonestTrace = new bytes(256); - for (uint256 i; i < dishonestTrace.length; i++) { - dishonestTrace[i] = i > 127 ? bytes1(0xFF) : bytes1(uint8(i)); - } - - // Run the actor test - _actorTest({ - _rootClaim: 16, - _absolutePrestateData: 0, - _honestTrace: honestTrace, - _honestL2Outputs: honestL2Outputs, - _dishonestTrace: dishonestTrace, - _dishonestL2Outputs: dishonestL2Outputs, - _expectedStatus: GameStatus.DEFENDER_WINS - }); - } - /// @notice Static unit test for a 1v1 output bisection dispute. function test_static_1v1dishonestAbsolutePrestate_succeeds() public { // The honest l2 outputs are from [1, 16] in this game. diff --git a/packages/contracts-bedrock/test/dispute/SuperPermissionedDisputeGame.t.sol b/packages/contracts-bedrock/test/dispute/SuperPermissionedDisputeGame.t.sol index e8f8ef920eb..096b2dd8dff 100644 --- a/packages/contracts-bedrock/test/dispute/SuperPermissionedDisputeGame.t.sol +++ b/packages/contracts-bedrock/test/dispute/SuperPermissionedDisputeGame.t.sol @@ -82,7 +82,7 @@ contract SuperPermissionedDisputeGame_Init is DisputeGameFactory_Init { vm: _vm, weth: _weth, anchorStateRegistry: anchorStateRegistry, - l2ChainId: 10 + l2ChainId: 0 }), PROPOSER, CHALLENGER diff --git a/packages/contracts-bedrock/test/universal/Specs.t.sol b/packages/contracts-bedrock/test/universal/Specs.t.sol index dfc902db05a..61e1c6a4a05 100644 --- a/packages/contracts-bedrock/test/universal/Specs.t.sol +++ b/packages/contracts-bedrock/test/universal/Specs.t.sol @@ -708,10 +708,6 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "SuperFaultDisputeGame", _sel: _getSel("anchorStateRegistry()") }); _addSpec({ _name: "SuperFaultDisputeGame", _sel: _getSel("attack(bytes32,uint256,bytes32)") }); _addSpec({ _name: "SuperFaultDisputeGame", _sel: _getSel("bondDistributionMode()") }); - _addSpec({ - _name: "SuperFaultDisputeGame", - _sel: _getSel("challengeRootL2Block((bytes32,bytes32,bytes32,bytes32),bytes)") - }); _addSpec({ _name: "SuperFaultDisputeGame", _sel: _getSel("claimCredit(address)") }); _addSpec({ _name: "SuperFaultDisputeGame", _sel: _getSel("claimData(uint256)") }); _addSpec({ _name: "SuperFaultDisputeGame", _sel: _getSel("claimDataLen()") }); @@ -731,9 +727,6 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "SuperFaultDisputeGame", _sel: _getSel("initialize()") }); _addSpec({ _name: "SuperFaultDisputeGame", _sel: _getSel("l1Head()") }); _addSpec({ _name: "SuperFaultDisputeGame", _sel: _getSel("l2BlockNumber()") }); - _addSpec({ _name: "SuperFaultDisputeGame", _sel: _getSel("l2BlockNumberChallenged()") }); - _addSpec({ _name: "SuperFaultDisputeGame", _sel: _getSel("l2BlockNumberChallenger()") }); - _addSpec({ _name: "SuperFaultDisputeGame", _sel: _getSel("l2ChainId()") }); _addSpec({ _name: "SuperFaultDisputeGame", _sel: _getSel("maxClockDuration()") }); _addSpec({ _name: "SuperFaultDisputeGame", _sel: _getSel("maxGameDepth()") }); _addSpec({ _name: "SuperFaultDisputeGame", _sel: _getSel("move(bytes32,uint256,bytes32,bool)") }); @@ -768,11 +761,6 @@ contract Specification_Test is CommonTest { _auth: Role.CHALLENGER }); _addSpec({ _name: "SuperPermissionedDisputeGame", _sel: _getSel("bondDistributionMode()") }); - _addSpec({ - _name: "SuperPermissionedDisputeGame", - _sel: _getSel("challengeRootL2Block((bytes32,bytes32,bytes32,bytes32),bytes)"), - _auth: Role.CHALLENGER - }); _addSpec({ _name: "SuperPermissionedDisputeGame", _sel: _getSel("challenger()") }); _addSpec({ _name: "SuperPermissionedDisputeGame", _sel: _getSel("claimCredit(address)") }); _addSpec({ _name: "SuperPermissionedDisputeGame", _sel: _getSel("claimData(uint256)") }); @@ -798,9 +786,6 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "SuperPermissionedDisputeGame", _sel: _getSel("initialize()") }); _addSpec({ _name: "SuperPermissionedDisputeGame", _sel: _getSel("l1Head()") }); _addSpec({ _name: "SuperPermissionedDisputeGame", _sel: _getSel("l2BlockNumber()") }); - _addSpec({ _name: "SuperPermissionedDisputeGame", _sel: _getSel("l2BlockNumberChallenged()") }); - _addSpec({ _name: "SuperPermissionedDisputeGame", _sel: _getSel("l2BlockNumberChallenger()") }); - _addSpec({ _name: "SuperPermissionedDisputeGame", _sel: _getSel("l2ChainId()") }); _addSpec({ _name: "SuperPermissionedDisputeGame", _sel: _getSel("maxClockDuration()") }); _addSpec({ _name: "SuperPermissionedDisputeGame", _sel: _getSel("maxGameDepth()") }); _addSpec({ From 8e6dae2dc00fee647c14b3bac7829e9f1db1f050 Mon Sep 17 00:00:00 2001 From: Axel Kingsley Date: Thu, 27 Feb 2025 15:15:52 -0600 Subject: [PATCH 033/130] op-supervisor: independent reset preparation (#14444) * interop: recovery draft * Create consistencyState ; Wire required Backend Functions * Full bisection ; block events during reset ; send reset signal * unit test and fixes * Cancel Reset if L1 Rewinds * lint * address comments ; fixes for tests * Fix Unit Tests * Add round-trip NodeID to Local Safe Updates (for ideal consistency tracking) * op-supervisor,op-node: fixes to handle interop startup and resets * refactor resetTracker ; fix E2E test * Address PR Comments --------- Co-authored-by: protolambda --- op-e2e/actions/helpers/l2_verifier.go | 2 +- op-e2e/actions/interop/dsl/dsl.go | 8 +- op-e2e/actions/interop/dsl/interop.go | 31 ++ .../actions/interop/emitter_contract_test.go | 21 +- op-e2e/actions/interop/interop_test.go | 30 +- op-e2e/actions/interop/reset_test.go | 6 +- op-node/rollup/driver/state.go | 17 +- op-node/rollup/engine/engine_controller.go | 53 +++ op-node/rollup/engine/engine_reset.go | 9 +- op-node/rollup/engine/events.go | 53 ++- op-node/rollup/engine/payload_success.go | 8 +- op-node/rollup/event.go | 10 +- op-node/rollup/interop/managed/api.go | 4 +- op-node/rollup/interop/managed/system.go | 49 ++- .../rollup/sequencing/sequencer_chaos_test.go | 8 +- op-node/rollup/status/status.go | 6 +- op-supervisor/supervisor/backend/backend.go | 20 ++ op-supervisor/supervisor/backend/db/anchor.go | 3 +- op-supervisor/supervisor/backend/db/db.go | 2 +- op-supervisor/supervisor/backend/db/query.go | 16 + op-supervisor/supervisor/backend/db/update.go | 10 +- .../backend/rewinder/rewinder_test.go | 2 +- .../supervisor/backend/superevents/events.go | 21 +- .../backend/syncnode/controller_test.go | 58 +++- .../supervisor/backend/syncnode/iface.go | 3 +- .../supervisor/backend/syncnode/node.go | 320 ++++++++---------- .../supervisor/backend/syncnode/node_test.go | 179 +++++----- .../supervisor/backend/syncnode/reset.go | 264 +++++++++++++++ .../supervisor/backend/syncnode/rpc.go | 4 +- 29 files changed, 817 insertions(+), 400 deletions(-) create mode 100644 op-supervisor/supervisor/backend/syncnode/reset.go diff --git a/op-e2e/actions/helpers/l2_verifier.go b/op-e2e/actions/helpers/l2_verifier.go index b2bd8a957ba..dd1724250f7 100644 --- a/op-e2e/actions/helpers/l2_verifier.go +++ b/op-e2e/actions/helpers/l2_verifier.go @@ -180,7 +180,7 @@ func NewL2Verifier(t Testing, log log.Logger, l1 derive.L1Fetcher, Log: log, Ctx: ctx, Drain: executor.Drain, - ManagedMode: false, + ManagedMode: managedMode, }, opts) sys.Register("engine", engine.NewEngDeriver(log, ctx, cfg, metrics, ec), opts) diff --git a/op-e2e/actions/interop/dsl/dsl.go b/op-e2e/actions/interop/dsl/dsl.go index de9e367ef89..6c5ca36a3c4 100644 --- a/op-e2e/actions/interop/dsl/dsl.go +++ b/op-e2e/actions/interop/dsl/dsl.go @@ -65,18 +65,12 @@ type InteropDSL struct { func NewInteropDSL(t helpers.Testing) *InteropDSL { setup := SetupInterop(t) actors := setup.CreateActors() + actors.PrepareChainState(t) t.Logf("ChainA: %v, ChainB: %v", actors.ChainA.ChainID, actors.ChainB.ChainID) allChains := []*Chain{actors.ChainA, actors.ChainB} - // Get all the initial events processed - for _, chain := range allChains { - chain.Sequencer.ActL2PipelineFull(t) - chain.Sequencer.SyncSupervisor(t) - } - actors.Supervisor.ProcessFull(t) - superRootSource, err := NewSuperRootSource( t.Ctx(), actors.ChainA.Sequencer.RollupClient(), diff --git a/op-e2e/actions/interop/dsl/interop.go b/op-e2e/actions/interop/dsl/interop.go index d4498a60d1a..92c8d7feb8e 100644 --- a/op-e2e/actions/interop/dsl/interop.go +++ b/op-e2e/actions/interop/dsl/interop.go @@ -68,6 +68,37 @@ type InteropActors struct { ChainB *Chain } +func (actors *InteropActors) PrepareChainState(t helpers.Testing) { + // Initialize both chain states + actors.ChainA.Sequencer.ActL2PipelineFull(t) + actors.ChainB.Sequencer.ActL2PipelineFull(t) + t.Log("Sequencers should initialize, and produce initial reset requests") + + // Process the anchor point + actors.Supervisor.ProcessFull(t) + t.Log("Supervisor should have anchor points now") + + // Sync supervisors, i.e. the reset request makes it to the supervisor now + actors.ChainA.Sequencer.SyncSupervisor(t) + actors.ChainB.Sequencer.SyncSupervisor(t) + t.Log("Supervisor has events now") + + // Pick up the reset request + actors.Supervisor.ProcessFull(t) + t.Log("Supervisor processed initial resets") + + // Process reset work + actors.ChainA.Sequencer.ActL2PipelineFull(t) + actors.ChainB.Sequencer.ActL2PipelineFull(t) + t.Log("Processed!") + + // Verify initial state + statusA := actors.ChainA.Sequencer.SyncStatus() + statusB := actors.ChainB.Sequencer.SyncStatus() + require.Equal(t, uint64(0), statusA.UnsafeL2.Number) + require.Equal(t, uint64(0), statusB.UnsafeL2.Number) +} + // messageExpiryTime is the time in seconds that a message will be valid for on the L2 chain. // At a 2 second block time, this should be small enough to cover all events buffered in the supervisor event queue. const messageExpiryTime = 120 // 2 minutes diff --git a/op-e2e/actions/interop/emitter_contract_test.go b/op-e2e/actions/interop/emitter_contract_test.go index 6f3c25a0cbc..756e5e84adf 100644 --- a/op-e2e/actions/interop/emitter_contract_test.go +++ b/op-e2e/actions/interop/emitter_contract_test.go @@ -43,7 +43,7 @@ func TestEmitterContract(gt *testing.T) { actors = is.CreateActors() aliceA = setupUser(t, is, actors.ChainA, 0) aliceB = setupUser(t, is, actors.ChainB, 0) - initializeChainState(t, actors) + actors.PrepareChainState(t) emitTx = initializeEmitterContractTest(t, aliceA, actors) } @@ -145,25 +145,6 @@ func idForTx(t helpers.Testing, tx *types.Transaction, srcChain *dsl.Chain) inbo } } -func initializeChainState(t helpers.Testing, actors *dsl.InteropActors) { - // Initialize both chain states - actors.ChainA.Sequencer.ActL2PipelineFull(t) - actors.ChainB.Sequencer.ActL2PipelineFull(t) - - // Sync supervisors - actors.ChainA.Sequencer.SyncSupervisor(t) - actors.ChainB.Sequencer.SyncSupervisor(t) - - // Verify initial state - statusA := actors.ChainA.Sequencer.SyncStatus() - statusB := actors.ChainB.Sequencer.SyncStatus() - require.Equal(t, uint64(0), statusA.UnsafeL2.Number) - require.Equal(t, uint64(0), statusB.UnsafeL2.Number) - - // Complete initial sync - actors.Supervisor.ProcessFull(t) -} - func initializeEmitterContractTest(t helpers.Testing, aliceA *userWithKeys, actors *dsl.InteropActors) *types.Transaction { // Deploy message contract and emit a log on ChainA // This issues two blocks to ChainA diff --git a/op-e2e/actions/interop/interop_test.go b/op-e2e/actions/interop/interop_test.go index 714fcf11140..9f4d5dd1031 100644 --- a/op-e2e/actions/interop/interop_test.go +++ b/op-e2e/actions/interop/interop_test.go @@ -25,10 +25,7 @@ func TestFullInterop(gt *testing.T) { is := dsl.SetupInterop(t) actors := is.CreateActors() - - // get both sequencers set up - actors.ChainA.Sequencer.ActL2PipelineFull(t) - actors.ChainB.Sequencer.ActL2PipelineFull(t) + actors.PrepareChainState(t) // sync the supervisor, handle initial events emitted by the nodes actors.ChainA.Sequencer.SyncSupervisor(t) @@ -168,10 +165,7 @@ func TestFinality(gt *testing.T) { testFinality := func(t helpers.StatefulTesting, extraBlocks int) { is := dsl.SetupInterop(t) actors := is.CreateActors() - - // set up a blank ChainA - actors.ChainA.Sequencer.ActL2PipelineFull(t) - actors.ChainA.Sequencer.SyncSupervisor(t) + actors.PrepareChainState(t) actors.Supervisor.ProcessFull(t) @@ -250,15 +244,7 @@ func TestInteropLocalSafeInvalidation(gt *testing.T) { is := dsl.SetupInterop(t) actors := is.CreateActors() - - // get both sequencers set up - actors.ChainA.Sequencer.ActL2PipelineFull(t) - actors.ChainB.Sequencer.ActL2PipelineFull(t) - - // sync the supervisor, handle initial events emitted by the nodes - actors.ChainA.Sequencer.SyncSupervisor(t) - actors.ChainB.Sequencer.SyncSupervisor(t) - actors.Supervisor.ProcessFull(t) + actors.PrepareChainState(t) genesisB := actors.ChainB.Sequencer.SyncStatus() @@ -376,15 +362,7 @@ func TestInteropCrossSafeDependencyDelay(gt *testing.T) { is := dsl.SetupInterop(t) actors := is.CreateActors() - - // get both sequencers set up - actors.ChainA.Sequencer.ActL2PipelineFull(t) - actors.ChainB.Sequencer.ActL2PipelineFull(t) - - // sync the supervisor, handle initial events emitted by the nodes - actors.ChainA.Sequencer.SyncSupervisor(t) - actors.ChainB.Sequencer.SyncSupervisor(t) - actors.Supervisor.ProcessFull(t) + actors.PrepareChainState(t) // We create a batch with some empty blocks before and after the cross-chain message, // so multiple L2 blocks are all derived from the same L1 block. diff --git a/op-e2e/actions/interop/reset_test.go b/op-e2e/actions/interop/reset_test.go index 58ae285f534..b7585bb5430 100644 --- a/op-e2e/actions/interop/reset_test.go +++ b/op-e2e/actions/interop/reset_test.go @@ -16,11 +16,7 @@ func TestReset(gt *testing.T) { is := dsl.SetupInterop(t) actors := is.CreateActors() - - // get both sequencers set up - // sync the supervisor, handle initial events emitted by the nodes - actors.ChainA.Sequencer.ActL2PipelineFull(t) - actors.ChainA.Sequencer.SyncSupervisor(t) + actors.PrepareChainState(t) // No blocks yet status := actors.ChainA.Sequencer.SyncStatus() diff --git a/op-node/rollup/driver/state.go b/op-node/rollup/driver/state.go index 67d0724b1c5..c0713ec8119 100644 --- a/op-node/rollup/driver/state.go +++ b/op-node/rollup/driver/state.go @@ -367,11 +367,11 @@ func (s *SyncDeriver) onEngineConfirmedReset(x engine.EngineResetConfirmedEvent) // and don't confirm the engine-reset with the derivation pipeline. // The pipeline will re-trigger a reset as necessary. if s.SafeHeadNotifs != nil { - if err := s.SafeHeadNotifs.SafeHeadReset(x.Safe); err != nil { - s.Log.Error("Failed to warn safe-head notifier of safe-head reset", "safe", x.Safe) + if err := s.SafeHeadNotifs.SafeHeadReset(x.CrossSafe); err != nil { + s.Log.Error("Failed to warn safe-head notifier of safe-head reset", "safe", x.CrossSafe) return } - if s.SafeHeadNotifs.Enabled() && x.Safe.ID() == s.Config.Genesis.L2 { + if s.SafeHeadNotifs.Enabled() && x.CrossSafe.ID() == s.Config.Genesis.L2 { // The rollup genesis block is always safe by definition. So if the pipeline resets this far back we know // we will process all safe head updates and can record genesis as always safe from L1 genesis. // Note that it is not safe to use cfg.Genesis.L1 here as it is the block immediately before the L2 genesis @@ -382,23 +382,20 @@ func (s *SyncDeriver) onEngineConfirmedReset(x engine.EngineResetConfirmedEvent) s.Log.Error("Failed to retrieve L1 genesis, cannot notify genesis as safe block", "err", err) return } - if err := s.SafeHeadNotifs.SafeHeadUpdated(x.Safe, l1Genesis.ID()); err != nil { + if err := s.SafeHeadNotifs.SafeHeadUpdated(x.CrossSafe, l1Genesis.ID()); err != nil { s.Log.Error("Failed to notify safe-head listener of safe-head", "err", err) return } } } + s.Log.Info("Confirming pipeline reset") s.Emitter.Emit(derive.ConfirmPipelineResetEvent{}) } func (s *SyncDeriver) onResetEvent(x rollup.ResetEvent) { if s.ManagedMode { - if errors.Is(x.Err, derive.ErrEngineResetReq) { - s.Log.Warn("Managed Mode is enabled, but engine reset is required", "err", x.Err) - s.Emitter.Emit(engine.ResetEngineRequestEvent{}) - } else { - s.Log.Warn("Encountered reset, waiting for op-supervisor to recover", "err", x.Err) - } + s.Log.Warn("Encountered reset in Managed Mode, waiting for op-supervisor", "err", x.Err) + // ManagedMode will pick up the ResetEvent return } // If the system corrupts, e.g. due to a reorg, simply reset it diff --git a/op-node/rollup/engine/engine_controller.go b/op-node/rollup/engine/engine_controller.go index 907238e84a9..67ea651c994 100644 --- a/op-node/rollup/engine/engine_controller.go +++ b/op-node/rollup/engine/engine_controller.go @@ -262,6 +262,56 @@ func (e *EngineController) checkForkchoiceUpdatedStatus(status eth.ExecutePayloa return status == eth.ExecutionValid } +// initializeUnknowns is important to give the op-node EngineController engine state. +// Pre-interop, the initial reset triggered a find-sync-start, and filled the forkchoice. +// This still happens, but now overrides what may be initialized here. +// Post-interop, the op-supervisor may diff the forkchoice state against the supervisor DB, +// to determine where to perform the initial reset to. +func (e *EngineController) initializeUnknowns(ctx context.Context) error { + if e.unsafeHead == (eth.L2BlockRef{}) { + ref, err := e.engine.L2BlockRefByLabel(ctx, eth.Unsafe) + if err != nil { + return fmt.Errorf("failed to load local-unsafe head: %w", err) + } + e.SetUnsafeHead(ref) + e.log.Info("Loaded initial local-unsafe block ref", "local_unsafe", ref) + } + var finalizedRef eth.L2BlockRef + if e.finalizedHead == (eth.L2BlockRef{}) { + var err error + finalizedRef, err = e.engine.L2BlockRefByLabel(ctx, eth.Finalized) + if err != nil { + return fmt.Errorf("failed to load finalized head: %w", err) + } + e.SetFinalizedHead(finalizedRef) + e.log.Info("Loaded initial finalized block ref", "finalized", finalizedRef) + } + if e.safeHead == (eth.L2BlockRef{}) { + ref, err := e.engine.L2BlockRefByLabel(ctx, eth.Safe) + if err != nil { + if errors.Is(err, ethereum.NotFound) { + // If the engine doesn't have a safe head, then we can use the finalized head + e.SetSafeHead(finalizedRef) + e.log.Info("Loaded initial cross-safe block from finalized", "cross_safe", finalizedRef) + } else { + return fmt.Errorf("failed to load cross-safe head: %w", err) + } + } else { + e.SetSafeHead(ref) + e.log.Info("Loaded initial cross-safe block ref", "cross_safe", ref) + } + } + if e.crossUnsafeHead == (eth.L2BlockRef{}) { + e.SetCrossUnsafeHead(e.safeHead) // preserve cross-safety, don't fall back to a non-cross safety level + e.log.Info("Set initial cross-unsafe block ref to match cross-safe", "cross_unsafe", e.safeHead) + } + if e.localSafeHead == (eth.L2BlockRef{}) { + e.SetLocalSafeHead(e.safeHead) + e.log.Info("Set initial local-safe block ref to match cross-safe", "local_safe", e.safeHead) + } + return nil +} + // TryUpdateEngine attempts to update the engine with the current forkchoice state of the rollup node, // this is a no-op if the nodes already agree on the forkchoice state. func (e *EngineController) TryUpdateEngine(ctx context.Context) error { @@ -271,6 +321,9 @@ func (e *EngineController) TryUpdateEngine(ctx context.Context) error { if e.IsEngineSyncing() { e.log.Warn("Attempting to update forkchoice state while EL syncing") } + if err := e.initializeUnknowns(ctx); err != nil { + return derive.NewTemporaryError(fmt.Errorf("cannot update engine until engine forkchoice is initialized: %w", err)) + } if e.unsafeHead.Number < e.finalizedHead.Number { err := fmt.Errorf("invalid forkchoice state, unsafe head %s is behind finalized head %s", e.unsafeHead, e.finalizedHead) e.emitter.Emit(rollup.CriticalErrorEvent{Err: err}) // make the node exit, things are very wrong. diff --git a/op-node/rollup/engine/engine_reset.go b/op-node/rollup/engine/engine_reset.go index 7574f9cf963..86228eb2090 100644 --- a/op-node/rollup/engine/engine_reset.go +++ b/op-node/rollup/engine/engine_reset.go @@ -14,6 +14,7 @@ import ( // ResetEngineRequestEvent requests the EngineResetDeriver to walk // the L2 chain backwards until it finds a plausible unsafe head, // and find an L2 safe block that is guaranteed to still be from the L1 chain. +// This event is not used in interop. type ResetEngineRequestEvent struct{} func (ev ResetEngineRequestEvent) String() string { @@ -56,9 +57,11 @@ func (d *EngineResetDeriver) OnEvent(ev event.Event) bool { return true } d.emitter.Emit(rollup.ForceResetEvent{ - Unsafe: result.Unsafe, - Safe: result.Safe, - Finalized: result.Finalized, + LocalUnsafe: result.Unsafe, + CrossUnsafe: result.Unsafe, + LocalSafe: result.Safe, + CrossSafe: result.Safe, + Finalized: result.Finalized, }) default: return false diff --git a/op-node/rollup/engine/events.go b/op-node/rollup/engine/events.go index d7f47cd23e1..2a5e2bfc5ad 100644 --- a/op-node/rollup/engine/events.go +++ b/op-node/rollup/engine/events.go @@ -267,7 +267,11 @@ func (ev TryUpdateEngineEvent) getBlockProcessingMetrics() []interface{} { } type EngineResetConfirmedEvent struct { - Unsafe, Safe, Finalized eth.L2BlockRef + LocalUnsafe eth.L2BlockRef + CrossUnsafe eth.L2BlockRef + LocalSafe eth.L2BlockRef + CrossSafe eth.L2BlockRef + Finalized eth.L2BlockRef } func (ev EngineResetConfirmedEvent) String() string { @@ -430,10 +434,22 @@ func (d *EngDeriver) OnEvent(ev event.Event) bool { // Time to apply the changes to the underlying engine d.emitter.Emit(TryUpdateEngineEvent{}) - log.Debug("Reset of Engine is completed", - "safeHead", x.Safe, "unsafe", x.Unsafe, "safe_timestamp", x.Safe.Time, - "unsafe_timestamp", x.Unsafe.Time) - d.emitter.Emit(EngineResetConfirmedEvent(x)) + v := EngineResetConfirmedEvent{ + LocalUnsafe: d.ec.LocalSafeL2Head(), + CrossUnsafe: d.ec.CrossUnsafeL2Head(), + LocalSafe: d.ec.LocalSafeL2Head(), + CrossSafe: d.ec.SafeL2Head(), + Finalized: d.ec.Finalized(), + } + // We do not emit the original event values, since those might not be set (optional attributes). + d.emitter.Emit(v) + d.log.Info("Reset of Engine is completed", + "local_unsafe", v.LocalUnsafe, + "cross_unsafe", v.CrossUnsafe, + "local_safe", v.LocalSafe, + "cross_safe", v.CrossSafe, + "finalized", v.Finalized, + ) case PromoteUnsafeEvent: // Backup unsafeHead when new block is not built on original unsafe head. if d.ec.unsafeHead.Number >= x.Ref.Number { @@ -570,24 +586,31 @@ func (d *EngDeriver) OnEvent(ev event.Event) bool { type ResetEngineControl interface { SetUnsafeHead(eth.L2BlockRef) + SetCrossUnsafeHead(ref eth.L2BlockRef) + SetLocalSafeHead(ref eth.L2BlockRef) SetSafeHead(eth.L2BlockRef) SetFinalizedHead(eth.L2BlockRef) - SetLocalSafeHead(ref eth.L2BlockRef) - SetCrossUnsafeHead(ref eth.L2BlockRef) SetBackupUnsafeL2Head(block eth.L2BlockRef, triggerReorg bool) SetPendingSafeL2Head(eth.L2BlockRef) } -// ForceEngineReset is not to be used. The op-program needs it for now, until event processing is adopted there. func ForceEngineReset(ec ResetEngineControl, x rollup.ForceResetEvent) { - // if the unsafe head is not provided, do not override the existing unsafe head - if x.Unsafe != (eth.L2BlockRef{}) { - ec.SetUnsafeHead(x.Unsafe) + // local-unsafe is an optional attribute, empty to preserve the existing latest chain + if x.LocalUnsafe != (eth.L2BlockRef{}) { + ec.SetUnsafeHead(x.LocalUnsafe) } - ec.SetLocalSafeHead(x.Safe) - ec.SetPendingSafeL2Head(x.Safe) + // cross-safe is fine to revert back, it does not affect engine logic, just sync-status + ec.SetCrossUnsafeHead(x.CrossUnsafe) + + // derivation continues at local-safe point + ec.SetLocalSafeHead(x.LocalSafe) + ec.SetPendingSafeL2Head(x.LocalSafe) + + // "safe" in RPC terms is cross-safe + ec.SetSafeHead(x.CrossSafe) + + // finalized head ec.SetFinalizedHead(x.Finalized) - ec.SetSafeHead(x.Safe) - ec.SetCrossUnsafeHead(x.Safe) + ec.SetBackupUnsafeL2Head(eth.L2BlockRef{}, false) } diff --git a/op-node/rollup/engine/payload_success.go b/op-node/rollup/engine/payload_success.go index 433acb8915b..e7454168acc 100644 --- a/op-node/rollup/engine/payload_success.go +++ b/op-node/rollup/engine/payload_success.go @@ -29,9 +29,11 @@ func (eq *EngDeriver) onPayloadSuccess(ev PayloadSuccessEvent) { // Change the engine state to make the replacement block the cross-safe head of the chain, // And continue syncing from there. eq.emitter.Emit(rollup.ForceResetEvent{ - Unsafe: ev.Ref, - Safe: ev.Ref, - Finalized: eq.ec.Finalized(), + LocalUnsafe: ev.Ref, + CrossUnsafe: ev.Ref, + LocalSafe: ev.Ref, + CrossSafe: ev.Ref, + Finalized: eq.ec.Finalized(), }) eq.emitter.Emit(InteropReplacedBlockEvent{ Envelope: ev.Envelope, diff --git a/op-node/rollup/event.go b/op-node/rollup/event.go index 6b4b8c068cd..6e0fe81eb5d 100644 --- a/op-node/rollup/event.go +++ b/op-node/rollup/event.go @@ -40,9 +40,15 @@ func (ev ResetEvent) String() string { return "reset-event" } -// ForceResetEvent forces a reset to a specific unsafe/safe/finalized starting point. +// ForceResetEvent forces a reset to a specific local-unsafe/local-safe/finalized starting point. +// Resets may override local-unsafe, to reset the very end of the chain. +// Resets may override local-safe, since post-interop we need the local-safe block derivation to continue. +// Pre-interop both local and cross values should be set the same. type ForceResetEvent struct { - Unsafe, Safe, Finalized eth.L2BlockRef + // LocalUnsafe is optional: the existing chain local-unsafe head will be preserved if this field is zeroed. + LocalUnsafe eth.L2BlockRef + + CrossUnsafe, LocalSafe, CrossSafe, Finalized eth.L2BlockRef } func (ev ForceResetEvent) String() string { diff --git a/op-node/rollup/interop/managed/api.go b/op-node/rollup/interop/managed/api.go index 03de8aa2a88..a949162146d 100644 --- a/op-node/rollup/interop/managed/api.go +++ b/op-node/rollup/interop/managed/api.go @@ -43,8 +43,8 @@ func (ib *InteropAPI) AnchorPoint(ctx context.Context) (supervisortypes.DerivedB return ib.backend.AnchorPoint(ctx) } -func (ib *InteropAPI) Reset(ctx context.Context, unsafe, safe, finalized eth.BlockID) error { - return ib.backend.Reset(ctx, unsafe, safe, finalized) +func (ib *InteropAPI) Reset(ctx context.Context, lUnsafe, xUnsafe, lSafe, xSafe, finalized eth.BlockID) error { + return ib.backend.Reset(ctx, lUnsafe, xUnsafe, lSafe, xSafe, finalized) } func (ib *InteropAPI) FetchReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) { diff --git a/op-node/rollup/interop/managed/system.go b/op-node/rollup/interop/managed/system.go index f5238f40bb5..a02503b2963 100644 --- a/op-node/rollup/interop/managed/system.go +++ b/op-node/rollup/interop/managed/system.go @@ -253,10 +253,19 @@ const ( ConflictingBlockRPCErrCode = -39002 ) -func (m *ManagedMode) Reset(ctx context.Context, unsafe, safe, finalized eth.BlockID) error { - logger := m.log.New("unsafe", unsafe, "safe", safe, "finalized", finalized) - logger.Info("Received reset request", "unsafe", unsafe, "safe", safe, "finalized", finalized) - +func (m *ManagedMode) Reset(ctx context.Context, lUnsafe, xUnsafe, lSafe, xSafe, finalized eth.BlockID) error { + logger := m.log.New( + "localUnsafe", lUnsafe, + "crossUnsafe", xUnsafe, + "localSafe", lSafe, + "crossSafe", xSafe, + "finalized", finalized) + logger.Info("Received reset request", + "localUnsafe", lUnsafe, + "crossUnsafe", xUnsafe, + "localSafe", lSafe, + "crossSafe", xSafe, + "finalized", finalized) verify := func(ref eth.BlockID, name string) (eth.L2BlockRef, error) { result, err := m.l2.L2BlockRefByNumber(ctx, ref.Number) if err != nil { @@ -285,26 +294,42 @@ func (m *ManagedMode) Reset(ctx context.Context, unsafe, safe, finalized eth.Blo return result, nil } - // unsafeRef is always unused, as it is either - // - invalid (does not match, and therefore cannot be used for reset) - // - valid, in which case we will use the full unsafe chain for reset - _, err := verify(unsafe, "unsafe") + // verify all provided references + _, err := verify(lUnsafe, "unsafe") + if err != nil { + logger.Error("Cannot reset, local-unsafe block not known") + return err + } + xUnsafeRef, err := verify(xUnsafe, "cross-unsafe") + if err != nil { + logger.Error("Cannot reset, cross-safe block not known") + return err + } + lSafeRef, err := verify(lSafe, "safe") if err != nil { + logger.Error("Cannot reset, local-safe block not known") return err } - safeRef, err := verify(safe, "safe") + xSafeRef, err := verify(xSafe, "cross-safe") if err != nil { + logger.Error("Cannot reset, cross-safe block not known") return err } finalizedRef, err := verify(finalized, "finalized") if err != nil { + logger.Error("Cannot reset, finalized block not known") return err } m.emitter.Emit(rollup.ForceResetEvent{ - Unsafe: eth.L2BlockRef{}, - Safe: safeRef, - Finalized: finalizedRef, + // Unsafe is not provided, because it is never considered for reset. + // it is either invalid, in which case we cannot reset to it, + // or valid, in which case we reset to the full chain. + LocalUnsafe: eth.L2BlockRef{}, + CrossUnsafe: xUnsafeRef, + LocalSafe: lSafeRef, + CrossSafe: xSafeRef, + Finalized: finalizedRef, }) return nil } diff --git a/op-node/rollup/sequencing/sequencer_chaos_test.go b/op-node/rollup/sequencing/sequencer_chaos_test.go index 93ba254c1b5..e4bc55154d0 100644 --- a/op-node/rollup/sequencing/sequencer_chaos_test.go +++ b/op-node/rollup/sequencing/sequencer_chaos_test.go @@ -107,9 +107,11 @@ func (c *ChaoticEngine) OnEvent(ev event.Event) bool { c.currentPayloadInfo = eth.PayloadInfo{} c.currentAttributes = nil c.emitter.Emit(engine.EngineResetConfirmedEvent{ - Unsafe: c.unsafe, - Safe: c.safe, - Finalized: c.finalized, + LocalUnsafe: c.unsafe, + CrossUnsafe: c.unsafe, + LocalSafe: c.safe, + CrossSafe: c.safe, + Finalized: c.finalized, }) case engine.BuildInvalidEvent: // Engine translates the internal BuildInvalidEvent event diff --git a/op-node/rollup/status/status.go b/op-node/rollup/status/status.go index 26e9ddbc219..9937a547311 100644 --- a/op-node/rollup/status/status.go +++ b/op-node/rollup/status/status.go @@ -118,8 +118,10 @@ func (st *StatusTracker) OnEvent(ev event.Event) bool { st.data.SafeL2 = eth.L2BlockRef{} st.data.CurrentL1 = eth.L1BlockRef{} case engine.EngineResetConfirmedEvent: - st.data.UnsafeL2 = x.Unsafe - st.data.SafeL2 = x.Safe + st.data.UnsafeL2 = x.LocalUnsafe + st.data.CrossUnsafeL2 = x.CrossUnsafe + st.data.LocalSafeL2 = x.LocalSafe + st.data.SafeL2 = x.CrossSafe st.data.FinalizedL2 = x.Finalized default: // other events do not affect the sync status return false diff --git a/op-supervisor/supervisor/backend/backend.go b/op-supervisor/supervisor/backend/backend.go index d06e266fbc2..e13ccd4a7d2 100644 --- a/op-supervisor/supervisor/backend/backend.go +++ b/op-supervisor/supervisor/backend/backend.go @@ -560,6 +560,14 @@ func (su *SupervisorBackend) SafeDerivedAt(ctx context.Context, chainID eth.Chai return v.ID(), nil } +func (su *SupervisorBackend) FindSealedBlock(ctx context.Context, chainID eth.ChainID, number uint64) (eth.BlockID, error) { + seal, err := su.chainDBs.FindSealedBlock(chainID, number) + if err != nil { + return eth.BlockID{}, err + } + return seal.ID(), nil +} + // AllSafeDerivedAt returns the last derived block for each chain, from the given L1 block func (su *SupervisorBackend) AllSafeDerivedAt(ctx context.Context, source eth.BlockID) (map[eth.ChainID]eth.BlockID, error) { chains := su.depSet.Chains() @@ -586,6 +594,18 @@ func (su *SupervisorBackend) FinalizedL1() eth.BlockRef { return su.chainDBs.FinalizedL1() } +func (su *SupervisorBackend) IsLocalUnsafe(ctx context.Context, chainID eth.ChainID, block eth.BlockID) error { + return su.chainDBs.IsLocalUnsafe(chainID, block) +} + +func (su *SupervisorBackend) IsCrossSafe(ctx context.Context, chainID eth.ChainID, block eth.BlockID) error { + return su.chainDBs.IsCrossSafe(chainID, block) +} + +func (su *SupervisorBackend) IsLocalSafe(ctx context.Context, chainID eth.ChainID, block eth.BlockID) error { + return su.chainDBs.IsLocalSafe(chainID, block) +} + func (su *SupervisorBackend) CrossDerivedToSource(ctx context.Context, chainID eth.ChainID, derived eth.BlockID) (source eth.BlockRef, err error) { v, err := su.chainDBs.CrossDerivedToSourceRef(chainID, derived) if err != nil { diff --git a/op-supervisor/supervisor/backend/db/anchor.go b/op-supervisor/supervisor/backend/db/anchor.go index e862039ff47..1e07ee6fbaa 100644 --- a/op-supervisor/supervisor/backend/db/anchor.go +++ b/op-supervisor/supervisor/backend/db/anchor.go @@ -58,7 +58,8 @@ func (db *ChainsDB) maybeInitSafeDB(id eth.ChainID, anchor types.DerivedBlockRef if err := db.initializedUpdateCrossSafe(id, anchor.Source, anchor.Derived); err != nil { return err } - db.initializedUpdateLocalSafe(id, anchor.Source, anchor.Derived) + // "anchor" is not a node, so failure to update won't be caught by any SyncNode + db.initializedUpdateLocalSafe(id, anchor.Source, anchor.Derived, "anchor") } else if err != nil { return fmt.Errorf("failed to check if chain database is initialized: %w", err) } else { diff --git a/op-supervisor/supervisor/backend/db/db.go b/op-supervisor/supervisor/backend/db/db.go index 13cd9ad306a..e7b471bd8ea 100644 --- a/op-supervisor/supervisor/backend/db/db.go +++ b/op-supervisor/supervisor/backend/db/db.go @@ -152,7 +152,7 @@ func (db *ChainsDB) OnEvent(ev event.Event) bool { "chain", x.ChainID, "derived", x.Anchor.Derived, "source", x.Anchor.Source) db.initFromAnchor(x.ChainID, x.Anchor) case superevents.LocalDerivedEvent: - db.UpdateLocalSafe(x.ChainID, x.Derived.Source, x.Derived.Derived) + db.UpdateLocalSafe(x.ChainID, x.Derived.Source, x.Derived.Derived, x.NodeID) case superevents.FinalizedL1RequestEvent: db.onFinalizedL1(x.FinalizedL1) case superevents.ReplaceBlockEvent: diff --git a/op-supervisor/supervisor/backend/db/query.go b/op-supervisor/supervisor/backend/db/query.go index af1e3549c80..a103ef2f863 100644 --- a/op-supervisor/supervisor/backend/db/query.go +++ b/op-supervisor/supervisor/backend/db/query.go @@ -76,6 +76,22 @@ func (db *ChainsDB) IsLocalUnsafe(chainID eth.ChainID, block eth.BlockID) error return nil } +func (db *ChainsDB) IsCrossSafe(chainID eth.ChainID, block eth.BlockID) error { + xdb, ok := db.crossDBs.Get(chainID) + if !ok { + return types.ErrUnknownChain + } + return xdb.ContainsDerived(block) +} + +func (db *ChainsDB) IsLocalSafe(chainID eth.ChainID, block eth.BlockID) error { + ldb, ok := db.localDBs.Get(chainID) + if !ok { + return types.ErrUnknownChain + } + return ldb.ContainsDerived(block) +} + func (db *ChainsDB) SafeDerivedAt(chainID eth.ChainID, source eth.BlockID) (types.BlockSeal, error) { lDB, ok := db.localDBs.Get(chainID) if !ok { diff --git a/op-supervisor/supervisor/backend/db/update.go b/op-supervisor/supervisor/backend/db/update.go index 90e036be67e..b1866007391 100644 --- a/op-supervisor/supervisor/backend/db/update.go +++ b/op-supervisor/supervisor/backend/db/update.go @@ -82,16 +82,16 @@ func (db *ChainsDB) Rewind(chain eth.ChainID, headBlock eth.BlockID) error { // UpdateLocalSafe updates the local-safe database with the given source and lastDerived blocks. // It wraps an inner function, blocking the call if the database is not initialized. -func (db *ChainsDB) UpdateLocalSafe(chain eth.ChainID, source eth.BlockRef, lastDerived eth.BlockRef) { +func (db *ChainsDB) UpdateLocalSafe(chain eth.ChainID, source eth.BlockRef, lastDerived eth.BlockRef, nodeId string) { logger := db.logger.New("chain", chain, "source", source, "lastDerived", lastDerived) if !db.isInitialized(chain) { logger.Error("cannot UpdateLocalSafe on uninitialized database", "chain", chain) return } - db.initializedUpdateLocalSafe(chain, source, lastDerived) + db.initializedUpdateLocalSafe(chain, source, lastDerived, nodeId) } -func (db *ChainsDB) initializedUpdateLocalSafe(chain eth.ChainID, source eth.BlockRef, lastDerived eth.BlockRef) { +func (db *ChainsDB) initializedUpdateLocalSafe(chain eth.ChainID, source eth.BlockRef, lastDerived eth.BlockRef, nodeId string) { logger := db.logger.New("chain", chain, "ource", source, "lastDerived", lastDerived) localDB, ok := db.localDBs.Get(chain) if !ok { @@ -105,10 +105,10 @@ func (db *ChainsDB) initializedUpdateLocalSafe(chain eth.ChainID, source eth.Blo return } logger.Warn("Failed to update local safe", "err", err) - db.emitter.Emit(superevents.LocalSafeOutOfSyncEvent{ + db.emitter.Emit(superevents.UpdateLocalSafeFailedEvent{ ChainID: chain, - L1Ref: source, Err: err, + NodeID: nodeId, }) return } diff --git a/op-supervisor/supervisor/backend/rewinder/rewinder_test.go b/op-supervisor/supervisor/backend/rewinder/rewinder_test.go index 692a3354687..fce81faf992 100644 --- a/op-supervisor/supervisor/backend/rewinder/rewinder_test.go +++ b/op-supervisor/supervisor/backend/rewinder/rewinder_test.go @@ -851,7 +851,7 @@ func (s *testSetup) makeBlockSafe(chainID eth.ChainID, block eth.L2BlockRef, l1B Number: block.Number, Time: block.Time, ParentHash: block.ParentHash, - }) + }, "test") if makeCrossSafe { require.NoError(s.t, s.chainsDB.UpdateCrossUnsafe(chainID, types.BlockSeal{ diff --git a/op-supervisor/supervisor/backend/superevents/events.go b/op-supervisor/supervisor/backend/superevents/events.go index 7b8b7a41d37..9c13dcf1eca 100644 --- a/op-supervisor/supervisor/backend/superevents/events.go +++ b/op-supervisor/supervisor/backend/superevents/events.go @@ -91,16 +91,6 @@ func (ev FinalizedL2UpdateEvent) String() string { return "finalized-l2-update" } -type LocalSafeOutOfSyncEvent struct { - ChainID eth.ChainID - L1Ref eth.BlockRef - Err error -} - -func (ev LocalSafeOutOfSyncEvent) String() string { - return "local-safe-out-of-sync" -} - type LocalUnsafeReceivedEvent struct { ChainID eth.ChainID NewLocalUnsafe eth.BlockRef @@ -113,6 +103,7 @@ func (ev LocalUnsafeReceivedEvent) String() string { type LocalDerivedEvent struct { ChainID eth.ChainID Derived types.DerivedBlockRefPair + NodeID string } func (ev LocalDerivedEvent) String() string { @@ -170,3 +161,13 @@ type ChainRewoundEvent struct { func (ev ChainRewoundEvent) String() string { return "chain-rewound" } + +type UpdateLocalSafeFailedEvent struct { + ChainID eth.ChainID + Err error + NodeID string +} + +func (ev UpdateLocalSafeFailedEvent) String() string { + return "update-local-safe-failed" +} diff --git a/op-supervisor/supervisor/backend/syncnode/controller_test.go b/op-supervisor/supervisor/backend/syncnode/controller_test.go index 9755ab94691..eb5935f23e9 100644 --- a/op-supervisor/supervisor/backend/syncnode/controller_test.go +++ b/op-supervisor/supervisor/backend/syncnode/controller_test.go @@ -25,6 +25,7 @@ type mockSyncControl struct { updateCrossUnsafeFn func(ctx context.Context, derived eth.BlockID) error updateFinalizedFn func(ctx context.Context, id eth.BlockID) error pullEventFn func(ctx context.Context) (*types.ManagedEvent, error) + blockRefByNumFn func(ctx context.Context, number uint64) (eth.BlockRef, error) subscribeEvents gethevent.FeedOf[*types.ManagedEvent] } @@ -47,9 +48,9 @@ func (m *mockSyncControl) ProvideL1(ctx context.Context, ref eth.BlockRef) error return nil } -func (m *mockSyncControl) Reset(ctx context.Context, unsafe, safe, finalized eth.BlockID) error { +func (m *mockSyncControl) Reset(ctx context.Context, lUnsafe, xUnsafe, lSafe, xSafe, finalized eth.BlockID) error { if m.resetFn != nil { - return m.resetFn(ctx, unsafe, safe, finalized) + return m.resetFn(ctx, lUnsafe, lSafe, finalized) } return nil } @@ -86,6 +87,13 @@ func (m *mockSyncControl) UpdateFinalized(ctx context.Context, id eth.BlockID) e return nil } +func (m *mockSyncControl) BlockRefByNumber(ctx context.Context, number uint64) (eth.BlockRef, error) { + if m.blockRefByNumFn != nil { + return m.blockRefByNumFn(ctx, number) + } + return eth.BlockRef{}, nil +} + func (m *mockSyncControl) String() string { return "mock" } @@ -93,10 +101,26 @@ func (m *mockSyncControl) String() string { var _ SyncControl = (*mockSyncControl)(nil) type mockBackend struct { - safeDerivedAtFn func(ctx context.Context, chainID eth.ChainID, source eth.BlockID) (eth.BlockID, error) + localSafeFn func(ctx context.Context, chainID eth.ChainID) (pair types.DerivedIDPair, err error) + finalizedFn func(ctx context.Context, chainID eth.ChainID) (eth.BlockID, error) + safeDerivedAtFn func(ctx context.Context, chainID eth.ChainID, source eth.BlockID) (eth.BlockID, error) + findSealedBlockFn func(ctx context.Context, chainID eth.ChainID, num uint64) (eth.BlockID, error) + isLocalSafeFn func(ctx context.Context, chainID eth.ChainID, blockID eth.BlockID) error + isCrossSafeFn func(ctx context.Context, chainID eth.ChainID, blockID eth.BlockID) error + isLocalUnsafeFn func(ctx context.Context, chainID eth.ChainID, blockID eth.BlockID) error +} + +func (m *mockBackend) FindSealedBlock(ctx context.Context, chainID eth.ChainID, num uint64) (eth.BlockID, error) { + if m.findSealedBlockFn != nil { + return m.findSealedBlockFn(ctx, chainID, num) + } + return eth.BlockID{}, nil } func (m *mockBackend) LocalSafe(ctx context.Context, chainID eth.ChainID) (pair types.DerivedIDPair, err error) { + if m.localSafeFn != nil { + return m.localSafeFn(ctx, chainID) + } return types.DerivedIDPair{}, nil } @@ -108,6 +132,27 @@ func (m *mockBackend) LocalUnsafe(ctx context.Context, chainID eth.ChainID) (eth return eth.BlockID{}, nil } +func (m *mockBackend) IsLocalSafe(ctx context.Context, chainID eth.ChainID, blockID eth.BlockID) error { + if m.isLocalSafeFn != nil { + return m.isLocalSafeFn(ctx, chainID, blockID) + } + return nil +} + +func (m *mockBackend) IsCrossSafe(ctx context.Context, chainID eth.ChainID, blockID eth.BlockID) error { + if m.isCrossSafeFn != nil { + return m.isCrossSafeFn(ctx, chainID, blockID) + } + return nil +} + +func (m *mockBackend) IsLocalUnsafe(ctx context.Context, chainID eth.ChainID, blockID eth.BlockID) error { + if m.isLocalUnsafeFn != nil { + return m.isLocalUnsafeFn(ctx, chainID, blockID) + } + return nil +} + func (m *mockBackend) SafeDerivedAt(ctx context.Context, chainID eth.ChainID, source eth.BlockID) (derived eth.BlockID, err error) { if m.safeDerivedAtFn != nil { return m.safeDerivedAtFn(ctx, chainID, source) @@ -116,6 +161,9 @@ func (m *mockBackend) SafeDerivedAt(ctx context.Context, chainID eth.ChainID, so } func (m *mockBackend) Finalized(ctx context.Context, chainID eth.ChainID) (eth.BlockID, error) { + if m.finalizedFn != nil { + return m.finalizedFn(ctx, chainID) + } return eth.BlockID{}, nil } @@ -123,6 +171,10 @@ func (m *mockBackend) L1BlockRefByNumber(ctx context.Context, number uint64) (et return eth.L1BlockRef{}, nil } +func (m *mockBackend) CrossUnsafe(ctx context.Context, chainID eth.ChainID) (eth.BlockID, error) { + return eth.BlockID{}, nil +} + var _ backend = (*mockBackend)(nil) func sampleDepSet(t *testing.T) depset.DependencySet { diff --git a/op-supervisor/supervisor/backend/syncnode/iface.go b/op-supervisor/supervisor/backend/syncnode/iface.go index a8970a67d6a..0fc1f833da3 100644 --- a/op-supervisor/supervisor/backend/syncnode/iface.go +++ b/op-supervisor/supervisor/backend/syncnode/iface.go @@ -36,6 +36,7 @@ type SyncSource interface { type SyncControl interface { SubscribeEvents(ctx context.Context, c chan *types.ManagedEvent) (ethereum.Subscription, error) PullEvent(ctx context.Context) (*types.ManagedEvent, error) + BlockRefByNumber(ctx context.Context, number uint64) (eth.BlockRef, error) UpdateCrossUnsafe(ctx context.Context, id eth.BlockID) error UpdateCrossSafe(ctx context.Context, derived eth.BlockID, source eth.BlockID) error @@ -43,7 +44,7 @@ type SyncControl interface { InvalidateBlock(ctx context.Context, seal types.BlockSeal) error - Reset(ctx context.Context, unsafe, safe, finalized eth.BlockID) error + Reset(ctx context.Context, lUnsafe, xUnsafe, lSafe, xSafe, finalized eth.BlockID) error ProvideL1(ctx context.Context, nextL1 eth.BlockRef) error AnchorPoint(ctx context.Context) (types.DerivedBlockRefPair, error) diff --git a/op-supervisor/supervisor/backend/syncnode/node.go b/op-supervisor/supervisor/backend/syncnode/node.go index e577e8ffc40..f99957f2509 100644 --- a/op-supervisor/supervisor/backend/syncnode/node.go +++ b/op-supervisor/supervisor/backend/syncnode/node.go @@ -3,10 +3,9 @@ package syncnode import ( "context" "errors" - "fmt" "io" - "strings" "sync" + "sync/atomic" "time" "github.com/ethereum-optimism/optimism/op-service/rpc" @@ -23,11 +22,17 @@ import ( ) type backend interface { - LocalSafe(ctx context.Context, chainID eth.ChainID) (pair types.DerivedIDPair, err error) LocalUnsafe(ctx context.Context, chainID eth.ChainID) (eth.BlockID, error) + CrossUnsafe(ctx context.Context, chainID eth.ChainID) (eth.BlockID, error) + LocalSafe(ctx context.Context, chainID eth.ChainID) (pair types.DerivedIDPair, err error) CrossSafe(ctx context.Context, chainID eth.ChainID) (pair types.DerivedIDPair, err error) - SafeDerivedAt(ctx context.Context, chainID eth.ChainID, source eth.BlockID) (derived eth.BlockID, err error) Finalized(ctx context.Context, chainID eth.ChainID) (eth.BlockID, error) + + FindSealedBlock(ctx context.Context, chainID eth.ChainID, number uint64) (eth.BlockID, error) + IsLocalSafe(ctx context.Context, chainID eth.ChainID, block eth.BlockID) error + IsCrossSafe(ctx context.Context, chainID eth.ChainID, block eth.BlockID) error + IsLocalUnsafe(ctx context.Context, chainID eth.ChainID, block eth.BlockID) error + SafeDerivedAt(ctx context.Context, chainID eth.ChainID, source eth.BlockID) (derived eth.BlockID, err error) L1BlockRefByNumber(ctx context.Context, number uint64) (eth.L1BlockRef, error) } @@ -57,6 +62,11 @@ type ManagedNode struct { ctx context.Context cancel context.CancelFunc wg sync.WaitGroup + + lastNodeLocalUnsafe eth.BlockID + lastNodeLocalSafe eth.BlockID + + resetTracker *resetTracker } var _ event.AttachEmitter = (*ManagedNode)(nil) @@ -72,6 +82,12 @@ func NewManagedNode(log log.Logger, id eth.ChainID, node SyncControl, backend ba ctx: ctx, cancel: cancel, } + m.resetTracker = &resetTracker{ + managed: m, + synchronous: noSubscribe, + cancelling: &atomic.Bool{}, + resetting: &atomic.Bool{}, + } if !noSubscribe { m.SubscribeToNodeEvents() } @@ -84,7 +100,22 @@ func (m *ManagedNode) AttachEmitter(em event.Emitter) { } func (m *ManagedNode) OnEvent(ev event.Event) bool { + // if we're resetting, ignore all events + if m.resetTracker.isResetting() { + // even if we are resetting, cancel the reset if the L1 rewinds + if _, ok := ev.(superevents.ChainRewoundEvent); ok { + m.resetTracker.cancelReset() + } + m.log.Debug("Ignoring event during ongoing reset", "event", ev) + return false + } switch x := ev.(type) { + case superevents.UpdateLocalSafeFailedEvent: + if x.ChainID != m.chainID || + x.NodeID != m.Node.String() { + return false + } + m.onUpdateLocalSafeFailed(x) case superevents.InvalidateLocalSafeEvent: if x.ChainID != m.chainID { return false @@ -105,16 +136,10 @@ func (m *ManagedNode) OnEvent(ev event.Event) bool { return false } m.onFinalizedL2(x.FinalizedL2) - case superevents.LocalSafeOutOfSyncEvent: - if x.ChainID != m.chainID { - return false - } - m.resetFromError(x.Err, x.L1Ref) case superevents.ChainRewoundEvent: if x.ChainID != m.chainID { return false } - m.sendReset() default: return false } @@ -202,6 +227,10 @@ func (m *ManagedNode) PullEvents(ctx context.Context) (pulledAny bool, err error } func (m *ManagedNode) onNodeEvent(ev *types.ManagedEvent) { + if m.resetTracker.isResetting() { + m.log.Debug("Ignoring event during ongoing reset", "event", ev) + return + } if ev == nil { m.log.Warn("Received nil event") return @@ -226,15 +255,42 @@ func (m *ManagedNode) onNodeEvent(ev *types.ManagedEvent) { } } +// onResetEvent handles a reset event from the node func (m *ManagedNode) onResetEvent(errStr string) { m.log.Warn("Node sent us a reset error", "err", errStr) - if strings.Contains(errStr, "cannot continue derivation until Engine has been reset") { - // TODO - return + m.resetFullRange() +} + +func (m *ManagedNode) onUpdateLocalSafeFailed(ev superevents.UpdateLocalSafeFailedEvent) { + switch { + case errors.Is(ev.Err, types.ErrConflict): + m.log.Warn("DB indicated a conflict, checking consistency") + m.resetIfInconsistent() + case errors.Is(ev.Err, types.ErrFuture): + m.log.Warn("DB indicated an update is in the future, checking if node is ahead") + m.resetIfAhead() + } +} + +// OnResetReady handles a reset-ready event from the supervisor +// once the supervisor has determined the reset target by bisecting the search range +func (m *ManagedNode) OnResetReady(lUnsafe, xUnsafe, lSafe, xSafe, finalized eth.BlockID) { + m.log.Info("Reset ready event received", + "localUnsafe", lUnsafe, + "crossUnsafe", xUnsafe, + "localSafe", lSafe, + "crossSafe", xSafe, + "finalized", finalized) + ctx, cancel := context.WithTimeout(m.ctx, nodeTimeout) + defer cancel() + // whether the reset passes or fails, this ongoing reset is done + m.resetTracker.endReset() + if err := m.Node.Reset(ctx, + lUnsafe, xUnsafe, + lSafe, xSafe, + finalized); err != nil { + m.log.Error("Failed to reset node", "err", err) } - // Try and restore the safe head of the op-supervisor. - // The node will abort the reset until we find a block that is known. - m.sendReset() } func (m *ManagedNode) onCrossUnsafeUpdate(seal types.BlockSeal) { @@ -279,6 +335,8 @@ func (m *ManagedNode) onUnsafeBlock(unsafeRef eth.BlockRef) { ChainID: m.chainID, NewLocalUnsafe: unsafeRef, }) + m.lastNodeLocalUnsafe = unsafeRef.ID() + m.resetIfInconsistent() } func (m *ManagedNode) onDerivationUpdate(pair types.DerivedBlockRefPair) { @@ -287,7 +345,10 @@ func (m *ManagedNode) onDerivationUpdate(pair types.DerivedBlockRefPair) { m.emitter.Emit(superevents.LocalDerivedEvent{ ChainID: m.chainID, Derived: pair, + NodeID: m.Node.String(), }) + m.lastNodeLocalSafe = pair.Derived.ID() + m.resetIfInconsistent() } func (m *ManagedNode) onDerivationOriginUpdate(origin eth.BlockRef) { @@ -298,164 +359,6 @@ func (m *ManagedNode) onDerivationOriginUpdate(origin eth.BlockRef) { }) } -// resetFromError considers an incoming error signal, and an optional L1 block reference, -// and calls specific reset handling, or passes the call along to the default reset. -func (m *ManagedNode) resetFromError(errSignal error, l1Ref eth.BlockRef) { - switch { - case errors.Is(errSignal, types.ErrConflict): - // conflicts must be resolved via walkback - if err := m.walkback(l1Ref); err != nil { - m.log.Warn("Failed to walkback", "l1Ref", l1Ref, "err", err) - } - case errors.Is(errSignal, types.ErrOutOfOrder): - // if the out of order signal shows the node is far enough behind, - // push a reset to attempt to get the node to tip more quickly. - if m.farBehind(l1Ref.ID()) { - m.log.Warn("Node is far behind and should be reset", "l1Ref", l1Ref, "err", errSignal) - // m.sendReset() - } else { - // otherwise, ignore the out of order signal, the node is near enough to the tip. - m.log.Warn("Node is behind, ignoring", "l1Ref", l1Ref, "err", errSignal) - } - case errors.Is(errSignal, types.ErrFuture): - // if the node is in the future, we need to reset it back to the tip of the supervisor - m.sendReset() - } -} - -// farBehindThreshold is the heuristic threshold for determining if the node is far behind. -var farBehindThreshold = uint64(20) - -// farBehind checks if the node is far behind the given reference block. -// it a heuristic to determine if the node is far behind and should be reset. -func (m *ManagedNode) farBehind(ref eth.BlockID) bool { - ctx, cancel := context.WithTimeout(m.ctx, internalTimeout) - defer cancel() - latest, err := m.backend.LocalSafe(ctx, m.chainID) - if err != nil { - m.log.Warn("Failed to retrieve local-safe", "err", err) - return false - } - // can't be far behind if the latest is lower than the threshold already - if latest.Source.Number < uint64(farBehindThreshold) { - return false - } - // if even after pushing the latest back by the threshold, - // the ref is still behind, then we are far behind. - return ref.Number < latest.Source.Number-farBehindThreshold -} - -func (m *ManagedNode) walkback(l1Ref eth.L1BlockRef) error { - ctx, cancel := context.WithTimeout(m.ctx, internalTimeout) - defer cancel() - - u, err := m.backend.LocalUnsafe(ctx, m.chainID) - if err != nil { - return fmt.Errorf("failed to retrieve local-unsafe: %w", err) - } - f, err := m.backend.Finalized(ctx, m.chainID) - if err != nil { - if errors.Is(err, types.ErrFuture) { - f = eth.BlockID{Number: 0} - } else { - return fmt.Errorf("failed to retrieve finalized: %w", err) - } - } - if err := m.resolveConflict(ctx, l1Ref, u, f); err != nil { - return fmt.Errorf("failed to resolve conflict: %w", err) - } - return nil -} - -func (m *ManagedNode) sendReset() { - ctx, cancel := context.WithTimeout(m.ctx, internalTimeout) - defer cancel() - - u, err := m.backend.LocalUnsafe(ctx, m.chainID) - if err != nil { - m.log.Warn("Failed to retrieve local-unsafe", "err", err) - return - } - s, err := m.backend.CrossSafe(ctx, m.chainID) - if err != nil { - m.log.Warn("Failed to retrieve cross-safe", "err", err) - return - } - f, err := m.backend.Finalized(ctx, m.chainID) - if err != nil { - if errors.Is(err, types.ErrFuture) { - f = eth.BlockID{Number: 0} - } else { - m.log.Warn("Failed to retrieve finalized", "err", err) - return - } - } - - if err := m.Node.Reset(ctx, u, s.Derived, f); err != nil { - m.log.Warn("Node failed to reset", "err", err) - return - } -} - -// resolveConflict attempts to reset the node to a valid state when a conflict is detected. -// It first tries using the latest safe block, and if that fails, walks back block by block -// until it finds a common ancestor or reaches the finalized block. -func (m *ManagedNode) resolveConflict(ctx context.Context, l1Ref eth.BlockRef, u eth.BlockID, f eth.BlockID) error { - // First try to reset to the last known safe block - s, err := m.backend.SafeDerivedAt(ctx, m.chainID, l1Ref.ID()) - if err != nil { - return fmt.Errorf("failed to retrieve safe block for %v: %w", l1Ref.ID(), err) - } - - // Helper to attempt a reset and classify the error - tryReset := func(safe eth.BlockID) (resolved bool, needsWalkback bool, err error) { - m.log.Debug("Attempting reset", "unsafe", u, "safe", safe, "finalized", f) - if err := m.Node.Reset(ctx, u, safe, f); err == nil { - return true, false, nil - } else { - var rpcErr *gethrpc.JsonError - if errors.As(err, &rpcErr) && (rpcErr.Code == blockNotFoundRPCErrCode || rpcErr.Code == conflictingBlockRPCErrCode) { - return false, true, err - } - return false, false, err - } - } - - // Try initial reset - resolved, needsWalkback, err := tryReset(s) - if resolved { - return nil - } - if !needsWalkback { - return fmt.Errorf("error during reset: %w", err) - } - - // Walk back one block at a time looking for a common ancestor - currentBlock := s.Number - for i := 0; i < maxWalkBackAttempts; i++ { - currentBlock-- - if currentBlock <= f.Number { - return fmt.Errorf("reached finalized block %d without finding common ancestor", f.Number) - } - - safe, err := m.backend.SafeDerivedAt(ctx, m.chainID, eth.BlockID{Number: currentBlock}) - if err != nil { - return fmt.Errorf("failed to retrieve safe block %d: %w", currentBlock, err) - } - - resolved, _, err := tryReset(safe) - if resolved { - return nil - } - // Continue walking back on walkable errors, otherwise return the error - var rpcErr *gethrpc.JsonError - if !errors.As(err, &rpcErr) || (rpcErr.Code != blockNotFoundRPCErrCode && rpcErr.Code != conflictingBlockRPCErrCode) { - return fmt.Errorf("error during reset at block %d: %w", currentBlock, err) - } - } - return fmt.Errorf("exceeded maximum walk-back attempts (%d)", maxWalkBackAttempts) -} - func (m *ManagedNode) onExhaustL1Event(completed types.DerivedBlockRefPair) { m.log.Info("Node completed syncing", "l2", completed.Derived, "l1", completed.Source) @@ -516,3 +419,72 @@ func (m *ManagedNode) Close() error { } return nil } + +// resetIfInconsistent checks if the node is consistent with the logs db +// and initiates a bisection based reset preparation if it is +func (m *ManagedNode) resetIfInconsistent() { + ctx, cancel := context.WithTimeout(m.ctx, internalTimeout) + defer cancel() + + var last eth.BlockID + + // check if the last unsafe block we saw is consistent with the logs db + err := m.backend.IsLocalUnsafe(ctx, m.chainID, m.lastNodeLocalUnsafe) + if errors.Is(err, types.ErrConflict) { + m.log.Warn("local unsafe block is inconsistent with logs db. Initiating reset", + "lastUnsafeblock", m.lastNodeLocalUnsafe, + "err", err) + last = m.lastNodeLocalUnsafe + } + + // check if the last safe block we saw is consistent with the local safe db + err = m.backend.IsLocalSafe(ctx, m.chainID, m.lastNodeLocalSafe) + if errors.Is(err, types.ErrConflict) { + m.log.Warn("local safe block is inconsistent with logs db. Initiating reset", + "lastSafeblock", m.lastNodeLocalSafe, + "err", err) + last = m.lastNodeLocalSafe + } + + // there is inconsistency. begin the reset process + if last != (eth.BlockID{}) { + m.resetTracker.beginBisectionReset(last) + } else { + m.log.Debug("no inconsistency found") + } +} + +// resetIfAhead checks if the node is ahead of the logs db +// and initiates a bisection based reset preparation if it is +func (m *ManagedNode) resetIfAhead() { + ctx, cancel := context.WithTimeout(m.ctx, internalTimeout) + defer cancel() + + // get the last local safe block + lastDBLocalSafe, err := m.backend.LocalSafe(ctx, m.chainID) + if err != nil { + m.log.Error("failed to get last local safe block", "err", err) + return + } + // if the node is ahead of the logs db, initiate a reset + // with the end of the range being the last safe block in the db + if m.lastNodeLocalSafe.Number > lastDBLocalSafe.Derived.Number { + m.log.Warn("local safe block on node is ahead of logs db. Initiating reset", + "lastNodeLocalSafe", m.lastNodeLocalSafe, + "lastDBLocalSafe", lastDBLocalSafe.Derived) + m.resetTracker.beginBisectionReset(lastDBLocalSafe.Derived) + } +} + +// resetFullRange resets the node using the last block in the db +// as the end of the range to search for the last consistent block +func (m *ManagedNode) resetFullRange() { + internalCtx, iCancel := context.WithTimeout(m.ctx, internalTimeout) + defer iCancel() + dbLast, err := m.backend.LocalUnsafe(internalCtx, m.chainID) + if err != nil { + m.log.Error("failed to get last local unsafe block", "err", err) + return + } + m.resetTracker.beginBisectionReset(dbLast) +} diff --git a/op-supervisor/supervisor/backend/syncnode/node_test.go b/op-supervisor/supervisor/backend/syncnode/node_test.go index 573d5ca80bb..be656999688 100644 --- a/op-supervisor/supervisor/backend/syncnode/node_test.go +++ b/op-supervisor/supervisor/backend/syncnode/node_test.go @@ -2,7 +2,6 @@ package syncnode import ( "context" - "fmt" "testing" "time" @@ -11,8 +10,8 @@ import ( "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/superevents" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" - gethrpc "github.com/ethereum/go-ethereum/rpc" "github.com/stretchr/testify/require" ) @@ -93,97 +92,95 @@ func TestEventResponse(t *testing.T) { }, 4*time.Second, 250*time.Millisecond) } -func TestResetConflict(t *testing.T) { +func TestPrepareReset(t *testing.T) { chainID := eth.ChainIDFromUInt64(1) - logger := testlog.Logger(t, log.LvlDebug) - - tests := []struct { - name string - resetErrors []error - expectAttempts int - expectError bool - l1RefNum uint64 - finalizedNum uint64 - }{ - { - name: "succeeds_first_try", - resetErrors: []error{nil}, - expectAttempts: 1, - expectError: false, - l1RefNum: 100, - finalizedNum: 50, - }, - { - name: "walks_back_on_block_not_found", - resetErrors: []error{ - &gethrpc.JsonError{Code: blockNotFoundRPCErrCode}, - &gethrpc.JsonError{Code: blockNotFoundRPCErrCode}, - nil, - }, - expectAttempts: 3, - expectError: false, - l1RefNum: 100, - finalizedNum: 50, - }, - { - name: "handles_finalized_boundary", - resetErrors: []error{ - &gethrpc.JsonError{Code: blockNotFoundRPCErrCode}, - }, - expectAttempts: 1, - expectError: true, - l1RefNum: 100, - finalizedNum: 99, - }, - { - name: "stops_after_max_attempts_exceeded", - resetErrors: func() []error { - // Generate more errors than we allow attempts for - errors := make([]error, maxWalkBackAttempts+100) - for i := range errors { - errors[i] = &gethrpc.JsonError{Code: blockNotFoundRPCErrCode} - } - return errors - }(), - // We expect the max number of attempts to be made, plus one for the initial attempt - expectAttempts: maxWalkBackAttempts + 1, - expectError: true, - l1RefNum: 1000, - finalizedNum: 1, - }, + logger := testlog.Logger(t, log.LvlInfo) + syncCtrl := &mockSyncControl{} + backend := &mockBackend{} + + ex := event.NewGlobalSynchronous(context.Background()) + eventSys := event.NewSystem(logger, ex) + + mon := &eventMonitor{} + eventSys.Register("monitor", mon, event.DefaultRegisterOpts()) + + node := NewManagedNode(logger, chainID, syncCtrl, backend, false) + eventSys.Register("node", node, event.DefaultRegisterOpts()) + + // mock: return a block of the same number as requested + syncCtrl.blockRefByNumFn = func(ctx context.Context, number uint64) (eth.BlockRef, error) { + return eth.BlockRef{Number: number, Hash: common.Hash{0xaa}}, nil } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - resetAttempts := 0 - ctrl := &mockSyncControl{ - resetFn: func(ctx context.Context, unsafe, safe, finalized eth.BlockID) error { - resetAttempts++ - if resetAttempts > len(tc.resetErrors) { - return fmt.Errorf("unexpected reset attempt %d", resetAttempts) - } - return tc.resetErrors[resetAttempts-1] - }, - } - backend := &mockBackend{ - safeDerivedAtFn: func(ctx context.Context, chainID eth.ChainID, source eth.BlockID) (eth.BlockID, error) { - return eth.BlockID{Number: source.Number}, nil - }, - } - - node := NewManagedNode(logger, chainID, ctrl, backend, true) - l1Ref := eth.BlockRef{Number: tc.l1RefNum} - unsafe := eth.BlockID{Number: tc.l1RefNum + 100} - finalized := eth.BlockID{Number: tc.finalizedNum} - - err := node.resolveConflict(context.Background(), l1Ref, unsafe, finalized) - - require.Equal(t, tc.expectAttempts, resetAttempts, "incorrect number of reset attempts") - if tc.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) - } - }) + // mock: control whether the blocks appear valid or not + var pivot uint64 + backend.isLocalUnsafeFn = func(ctx context.Context, chainID eth.ChainID, id eth.BlockID) error { + if id.Number > uint64(pivot) { + return types.ErrConflict + } + return nil + } + + // mock: record the reset signal given to the node + var unsafe, safe, finalized eth.BlockID + var resetCalled int + syncCtrl.resetFn = func(ctx context.Context, u, s, f eth.BlockID) error { + unsafe = u + safe = s + finalized = f + resetCalled++ + return nil } + + // test that the bisection finds the correct block, + // anywhere inside the min-max range + min, max := uint64(1), uint64(235) + for i := min; i < max; i++ { + node.resetTracker.a = eth.BlockID{Number: min, Hash: common.Hash{0xaa}} + node.resetTracker.z = eth.BlockID{Number: max} + pivot = i + node.resetTracker.bisectToTarget() + require.Equal(t, i, unsafe.Number) + require.Equal(t, uint64(0), safe.Number) + require.Equal(t, uint64(0), finalized.Number) + } + + // test that when the end of range (z) is known to the node, + // the reset request is made with the end of the range as the safe block + for i := min; i < max; i++ { + node.resetTracker.a = eth.BlockID{Number: min} + node.resetTracker.z = eth.BlockID{Number: max, Hash: common.Hash{0xaa}} + pivot = 0 + node.resetTracker.bisectToTarget() + require.Equal(t, max, unsafe.Number) + } + + // mock: return local safe and finalized blocks which are *ahead* of the pivot + backend.localSafeFn = func(ctx context.Context, chainID eth.ChainID) (types.DerivedIDPair, error) { + return types.DerivedIDPair{ + Derived: eth.BlockID{Number: pivot + 1}, + }, nil + } + backend.finalizedFn = func(ctx context.Context, chainID eth.ChainID) (eth.BlockID, error) { + return eth.BlockID{Number: pivot + 1}, nil + } + // test that the bisection finds the correct block, + // AND that the safe and finalized blocks are updated to match the unsafe block + for i := min; i < max; i++ { + node.resetTracker.a = eth.BlockID{Number: min, Hash: common.Hash{0xaa}} + node.resetTracker.z = eth.BlockID{Number: max} + pivot = i + node.resetTracker.bisectToTarget() + require.Equal(t, i, unsafe.Number) + require.Equal(t, i, safe.Number) + require.Equal(t, i, finalized.Number) + } + + // test that the reset function is not called if start of the range (a) is unknown + resetCount := resetCalled + node.resetTracker.a = eth.BlockID{Number: 0, Hash: common.Hash{0xbb}} + node.resetTracker.z = eth.BlockID{Number: max} + pivot = 40 + node.resetTracker.bisectToTarget() + require.Equal(t, resetCount, resetCalled) } diff --git a/op-supervisor/supervisor/backend/syncnode/reset.go b/op-supervisor/supervisor/backend/syncnode/reset.go new file mode 100644 index 00000000000..0d73fd41875 --- /dev/null +++ b/op-supervisor/supervisor/backend/syncnode/reset.go @@ -0,0 +1,264 @@ +package syncnode + +import ( + "context" + "errors" + "sync/atomic" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum/go-ethereum" +) + +// resetTracker manages a bisection +// between consistent and inconsistent blocks +// and is used to prepare a reset request +// which is sent to the managed node +type resetTracker struct { + a eth.BlockID + z eth.BlockID + + synchronous bool + resetting *atomic.Bool + cancelling *atomic.Bool + + managed *ManagedNode +} + +// init initializes the reset tracker with +// empty start and end of range, and no reset in progress +func (t *resetTracker) init() { + t.resetting.Store(true) + t.cancelling.Store(false) + t.a = eth.BlockID{} + t.z = eth.BlockID{} +} + +// beginBisectionReset initializes the reset tracker +// and starts the bisection process at the given block +// which will lead to a reset request +func (t *resetTracker) beginBisectionReset(z eth.BlockID) { + t.managed.log.Info("beginning reset", "endOfRange", z) + // only one reset can be in progress at a time + if t.resetting.Load() { + return + } + // initialize the reset tracker + t.init() + t.z = z + // action tests may prefer to run the managed node totally synchronously + if t.synchronous { + t.bisectToTarget() + } else { + go t.bisectToTarget() + } +} + +// endReset signals that the reset is over +func (t *resetTracker) endReset() { + t.resetting.Store(false) + t.cancelling.Store(false) +} + +// isResetting returns true if a reset is in progress +func (t *resetTracker) isResetting() bool { + return t.resetting.Load() +} + +// cancelReset signals that the ongoing reset should be cancelled +// it is not guaranteed that the reset will be cancelled immediately +func (t *resetTracker) cancelReset() { + t.cancelling.Store(true) +} + +// bisectToTarget prepares the reset by bisecting the search range until the last consistent block is found. +// it then calls resetHeadsFromTarget to trigger the reset on the node. +func (t *resetTracker) bisectToTarget() { + nodeCtx, nCancel := context.WithTimeout(t.managed.ctx, nodeTimeout) + defer nCancel() + internalCtx, iCancel := context.WithTimeout(t.managed.ctx, internalTimeout) + defer iCancel() + + // initialize the start of the range if it is empty + if t.a == (eth.BlockID{}) { + t.managed.log.Debug("start of range is empty, finding the first block") + var err error + t.a, err = t.managed.backend.FindSealedBlock(internalCtx, t.managed.chainID, 0) + if err != nil { + t.managed.log.Error("failed to initialize start of bisection range", "err", err) + t.endReset() + return + } + } + + // before starting bisection, check if z is already consistent (i.e. the node is ahead but otherwise consistent) + nodeZ, err := t.managed.Node.BlockRefByNumber(nodeCtx, t.z.Number) + // if z is already consistent, we can skip the bisection + // and move straight to a targeted reset + if err == nil && nodeZ.ID() == t.z { + t.resetHeadsFromTarget(t.z) + return + } + + // before starting bisection, check if a is inconsistent (i.e. the node has no common reference point) + // if the first block in the range can't be found or is inconsistent, we can't do a reset + nodeA, err := t.managed.Node.BlockRefByNumber(nodeCtx, t.a.Number) + if err != nil { + t.managed.log.Error("failed to get block at start of range. cannot reset node", "err", err) + t.endReset() + return + } + if nodeA.ID() != t.a { + t.managed.log.Error("start of range is inconsistent with logs db. cannot reset node", + "a", t.a, + "block", nodeA.ID()) + t.endReset() + return + } + + // repeatedly bisect the range until the last consistent block is found + for { + if t.cancelling.Load() { + t.managed.log.Debug("reset cancelled") + t.endReset() + return + } + if t.a.Number >= t.z.Number { + t.managed.log.Debug("reset target converged. Resetting to start of range", "a", t.a, "z", t.z) + t.resetHeadsFromTarget(t.a) + return + } + if t.a.Number+1 == t.z.Number { + break + } + err := t.bisect() + if err != nil { + t.managed.log.Error("failed to bisect recovery range. cannot reset node", "err", err) + t.endReset() + return + } + } + // the bisection is now complete. a is the last consistent block, and z is the first inconsistent block + t.resetHeadsFromTarget(t.a) +} + +// bisect halves the search range of the ongoing reset to narrow down +// where the reset will target. It bisects the range and constrains either +// the start or the end of the range, based on the consistency of the midpoint +// with the logs db. +func (t *resetTracker) bisect() error { + internalCtx, iCancel := context.WithTimeout(t.managed.ctx, internalTimeout) + defer iCancel() + nodeCtx, nCancel := context.WithTimeout(t.managed.ctx, nodeTimeout) + defer nCancel() + + // attempt to get the block at the midpoint of the range + i := (t.a.Number + t.z.Number) / 2 + nodeIRef, err := t.managed.Node.BlockRefByNumber(nodeCtx, i) + if err != nil { + // if the block is not known to the node, it is defacto inconsistent + if errors.Is(err, ethereum.NotFound) { + t.managed.log.Trace("midpoint of range is not known to node. pulling back end of range", "i", i) + t.z = eth.BlockID{Number: i} + return nil + } else { + t.managed.log.Error("failed to get block at midpoint of range. cannot reset node", "err", err) + } + } + + // check if the block at i is consistent with the logs db + // and update the search range accordingly + nodeI := nodeIRef.ID() + err = t.managed.backend.IsLocalUnsafe(internalCtx, t.managed.chainID, nodeI) + if err != nil { + t.managed.log.Trace("midpoint of range is inconsistent with logs db. pulling back end of range", "i", i) + t.z = nodeI + } else { + t.managed.log.Trace("midpoint of range is consistent with logs db. pushing up start of range", "i", i) + t.a = nodeI + } + return nil +} + +// resetHeadsFromTarget takes a target block and identifies the correct +// unsafe, safe, and finalized blocks to target for the reset. +// It then triggers the reset on the node. +func (t *resetTracker) resetHeadsFromTarget(target eth.BlockID) { + internalCtx, iCancel := context.WithTimeout(t.managed.ctx, internalTimeout) + defer iCancel() + + // if the target is empty, no reset can be done + if target == (eth.BlockID{}) { + t.managed.log.Error("no reset target found. cannot reset node") + t.endReset() + return + } + + t.managed.log.Info("reset target identified", "target", target) + var lUnsafe, xUnsafe, lSafe, xSafe, finalized eth.BlockID + + // the unsafe block is always the last block we found to be consistent + lUnsafe = target + + // all other blocks are either the last consistent block, or the last block in the db, whichever is earlier + // cross unsafe + lastXUnsafe, err := t.managed.backend.CrossUnsafe(internalCtx, t.managed.chainID) + if err != nil { + t.managed.log.Error("failed to get last cross unsafe block. cancelling reset", "err", err) + t.endReset() + return + } + if lastXUnsafe.Number < target.Number { + xUnsafe = lastXUnsafe + } else { + xUnsafe = target + } + // local safe + lastLSafe, err := t.managed.backend.LocalSafe(internalCtx, t.managed.chainID) + if err != nil { + t.managed.log.Error("failed to get last safe block. cancelling reset", "err", err) + t.endReset() + return + } + if lastLSafe.Derived.Number < target.Number { + lSafe = lastLSafe.Derived + } else { + lSafe = target + } + // cross safe + lastXSafe, err := t.managed.backend.CrossSafe(internalCtx, t.managed.chainID) + if err != nil { + t.managed.log.Error("failed to get last cross safe block. cancelling reset", "err", err) + t.endReset() + return + } + if lastXSafe.Derived.Number < target.Number { + xSafe = lastXSafe.Derived + } else { + xSafe = target + } + // finalized + lastFinalized, err := t.managed.backend.Finalized(internalCtx, t.managed.chainID) + if errors.Is(err, types.ErrFuture) { + t.managed.log.Warn("finalized block is not yet known", "err", err) + lastFinalized = eth.BlockID{} + } else if err != nil { + t.managed.log.Error("failed to get last finalized block. cancelling reset", "err", err) + t.endReset() + return + } + if lastFinalized.Number < target.Number { + finalized = lastFinalized + } else { + finalized = target + } + + // trigger the reset + t.managed.log.Info("triggering reset on node", + "localUnsafe", lUnsafe, + "crossUnsafe", xUnsafe, + "localSafe", lSafe, + "crossSafe", xSafe, + "finalized", finalized) + t.managed.OnResetReady(lUnsafe, xUnsafe, lSafe, xSafe, finalized) +} diff --git a/op-supervisor/supervisor/backend/syncnode/rpc.go b/op-supervisor/supervisor/backend/syncnode/rpc.go index 526c352bdc2..8d2c33196ea 100644 --- a/op-supervisor/supervisor/backend/syncnode/rpc.go +++ b/op-supervisor/supervisor/backend/syncnode/rpc.go @@ -126,8 +126,8 @@ func (rs *RPCSyncNode) InvalidateBlock(ctx context.Context, seal types.BlockSeal return rs.cl.CallContext(ctx, nil, "interop_invalidateBlock", seal) } -func (rs *RPCSyncNode) Reset(ctx context.Context, unsafe, safe, finalized eth.BlockID) error { - return rs.cl.CallContext(ctx, nil, "interop_reset", unsafe, safe, finalized) +func (rs *RPCSyncNode) Reset(ctx context.Context, lUnsafe, xUnsafe, lSafe, xSafe, finalized eth.BlockID) error { + return rs.cl.CallContext(ctx, nil, "interop_reset", lUnsafe, xUnsafe, lSafe, xSafe, finalized) } func (rs *RPCSyncNode) ProvideL1(ctx context.Context, nextL1 eth.BlockRef) error { From 1dce8162c3853ef80f2c3c5e0206e99b5d9bfed9 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Fri, 28 Feb 2025 10:09:47 +1000 Subject: [PATCH 034/130] op-program, op-challenger: Reduce number of steps per timestamp to 128 (#14572) * op-program, op-challenger: Reduce number of steps per timestamp to 128 * Fix disputeed trace index. --- .../game/fault/trace/super/provider.go | 2 +- .../game/fault/trace/super/provider_test.go | 2 +- op-e2e/actions/interop/proofs_test.go | 73 ++++++++++--------- op-program/client/interop/interop.go | 2 +- 4 files changed, 42 insertions(+), 37 deletions(-) diff --git a/op-challenger/game/fault/trace/super/provider.go b/op-challenger/game/fault/trace/super/provider.go index ad6cf8af657..e38ea6f1cad 100644 --- a/op-challenger/game/fault/trace/super/provider.go +++ b/op-challenger/game/fault/trace/super/provider.go @@ -24,7 +24,7 @@ var ( ) const ( - StepsPerTimestamp = 1024 + StepsPerTimestamp = 128 ) type PreimagePrestateProvider interface { diff --git a/op-challenger/game/fault/trace/super/provider_test.go b/op-challenger/game/fault/trace/super/provider_test.go index 3016a830ab6..87ee3b115df 100644 --- a/op-challenger/game/fault/trace/super/provider_test.go +++ b/op-challenger/game/fault/trace/super/provider_test.go @@ -311,7 +311,7 @@ func TestComputeStep(t *testing.T) { } else { require.Equal(t, prevTimestamp+1, timestamp, "Incorrect timestamp at trace index %d", traceIndex) require.Zero(t, step, "Incorrect step at trace index %d", traceIndex) - require.Equal(t, uint64(1023), prevStep, "Should only loop back to step 0 after the consolidation step") + require.Equal(t, uint64(StepsPerTimestamp-1), prevStep, "Should only loop back to step 0 after the consolidation step") } prevTimestamp = timestamp prevStep = step diff --git a/op-e2e/actions/interop/proofs_test.go b/op-e2e/actions/interop/proofs_test.go index 27aaa22c65a..6c4cfb0b447 100644 --- a/op-e2e/actions/interop/proofs_test.go +++ b/op-e2e/actions/interop/proofs_test.go @@ -22,6 +22,11 @@ import ( "github.com/stretchr/testify/require" ) +const ( + stepsPerTimestamp = 128 + consolidateStep = stepsPerTimestamp - 1 +) + func TestInteropFaultProofs_TraceExtensionActivation(gt *testing.T) { t := helpers.NewDefaultTesting(gt) system := dsl.NewInteropDSL(t) @@ -37,7 +42,7 @@ func TestInteropFaultProofs_TraceExtensionActivation(gt *testing.T) { agreedClaim := system.Outputs.SuperRoot(endTimestamp).Marshal() disputedClaim := system.Outputs.TransitionState(endTimestamp, 1, system.Outputs.OptimisticBlockAtTimestamp(system.Actors.ChainA, endTimestamp+1)).Marshal() - disputedTraceIndex := int64(1024) + disputedTraceIndex := int64(stepsPerTimestamp) tests := []*transitionTest{ { name: "CorrectlyDidNotActivate", @@ -115,16 +120,16 @@ func TestInteropFaultProofs_ConsolidateValidCrossChainMessage(gt *testing.T) { tests := []*transitionTest{ { name: "Consolidate-AllValid", - agreedClaim: paddingStep(1023), + agreedClaim: paddingStep(consolidateStep), disputedClaim: end.Marshal(), - disputedTraceIndex: 1023, + disputedTraceIndex: consolidateStep, expectValid: true, }, { name: "Consolidate-AllValid-InvalidNoChange", - agreedClaim: paddingStep(1023), - disputedClaim: paddingStep(1023), - disputedTraceIndex: 1023, + agreedClaim: paddingStep(consolidateStep), + disputedClaim: paddingStep(consolidateStep), + disputedTraceIndex: consolidateStep, expectValid: false, }, } @@ -236,9 +241,9 @@ func TestInteropFaultProofs(gt *testing.T) { }, { name: "LastPaddingStep", - agreedClaim: paddingStep(1022), - disputedClaim: paddingStep(1023), - disputedTraceIndex: 1022, + agreedClaim: paddingStep(consolidateStep - 1), + disputedClaim: paddingStep(consolidateStep), + disputedTraceIndex: consolidateStep - 1, expectValid: true, }, { @@ -252,7 +257,7 @@ func TestInteropFaultProofs(gt *testing.T) { system.Outputs.OptimisticBlockAtTimestamp(actors.ChainA, endTimestamp+1), ).Marshal(), proposalTimestamp: endTimestamp + 100, - disputedTraceIndex: 1024, + disputedTraceIndex: consolidateStep + 1, expectValid: true, }, { @@ -269,7 +274,7 @@ func TestInteropFaultProofs(gt *testing.T) { system.Outputs.OptimisticBlockAtTimestamp(actors.ChainB, endTimestamp+1), ).Marshal(), proposalTimestamp: endTimestamp + 100, - disputedTraceIndex: 1025, + disputedTraceIndex: consolidateStep + 2, expectValid: true, }, { @@ -277,7 +282,7 @@ func TestInteropFaultProofs(gt *testing.T) { // Expect to transition to invalid because the unsafe head is reached but challenger needs to handle // not having any data at the next timestamp because the chain doesn't extend that far. name: "DisputeTimestampAfterChainHeadConsolidate", - agreedClaim: system.Outputs.TransitionState(endTimestamp, 1023, + agreedClaim: system.Outputs.TransitionState(endTimestamp, consolidateStep, system.Outputs.OptimisticBlockAtTimestamp(actors.ChainA, endTimestamp+1), system.Outputs.OptimisticBlockAtTimestamp(actors.ChainB, endTimestamp+1), ).Marshal(), @@ -285,7 +290,7 @@ func TestInteropFaultProofs(gt *testing.T) { // It will have an incremented timestamp but the same chain output roots disputedClaim: system.Outputs.SuperRoot(endTimestamp + 1).Marshal(), proposalTimestamp: endTimestamp + 100, - disputedTraceIndex: 2047, + disputedTraceIndex: 2*stepsPerTimestamp - 1, expectValid: true, }, { @@ -297,7 +302,7 @@ func TestInteropFaultProofs(gt *testing.T) { // Timestamp has advanced enough to expect the next block now, but it doesn't exit so transition to invalid disputedClaim: interop.InvalidTransition, proposalTimestamp: endTimestamp + 100, - disputedTraceIndex: 2048, + disputedTraceIndex: 2 * stepsPerTimestamp, expectValid: true, }, { @@ -306,7 +311,7 @@ func TestInteropFaultProofs(gt *testing.T) { agreedClaim: interop.InvalidTransition, disputedClaim: interop.InvalidTransition, proposalTimestamp: endTimestamp + 100, - disputedTraceIndex: 3071, + disputedTraceIndex: 4*stepsPerTimestamp - 1, expectValid: true, }, { @@ -315,7 +320,7 @@ func TestInteropFaultProofs(gt *testing.T) { agreedClaim: interop.InvalidTransition, disputedClaim: interop.InvalidTransition, proposalTimestamp: endTimestamp + 100, - disputedTraceIndex: 3072, + disputedTraceIndex: 4*stepsPerTimestamp + 1, expectValid: true, }, @@ -423,16 +428,16 @@ func TestInteropFaultProofs_Cycle(gt *testing.T) { tests := []*transitionTest{ { name: "Consolidate-AllValid", - agreedClaim: paddingStep(1023), + agreedClaim: paddingStep(consolidateStep), disputedClaim: end.Marshal(), - disputedTraceIndex: 1023, + disputedTraceIndex: consolidateStep, expectValid: true, }, { name: "Consolidate-AllValid-InvalidNoChange", - agreedClaim: paddingStep(1023), - disputedClaim: paddingStep(1023), - disputedTraceIndex: 1023, + agreedClaim: paddingStep(consolidateStep), + disputedClaim: paddingStep(consolidateStep), + disputedTraceIndex: consolidateStep, expectValid: false, }, } @@ -491,7 +496,7 @@ func TestInteropFaultProofs_CascadeInvalidBlock(gt *testing.T) { startTimestamp := endTimestamp - 1 optimisticEnd := system.Outputs.SuperRoot(endTimestamp) - preConsolidation := system.Outputs.TransitionState(startTimestamp, 1023, + preConsolidation := system.Outputs.TransitionState(startTimestamp, consolidateStep, system.Outputs.OptimisticBlockAtTimestamp(actors.ChainA, endTimestamp), system.Outputs.OptimisticBlockAtTimestamp(actors.ChainB, endTimestamp), ).Marshal() @@ -510,7 +515,7 @@ func TestInteropFaultProofs_CascadeInvalidBlock(gt *testing.T) { name: "Consolidate-ExpectInvalidPendingBlock", agreedClaim: preConsolidation, disputedClaim: optimisticEnd.Marshal(), - disputedTraceIndex: 1023, + disputedTraceIndex: consolidateStep, expectValid: false, // TODO(#14306): Support cascading re-orgs in op-program skipProgram: true, @@ -520,7 +525,7 @@ func TestInteropFaultProofs_CascadeInvalidBlock(gt *testing.T) { name: "Consolidate-ReplaceInvalidBlocks", agreedClaim: preConsolidation, disputedClaim: crossSafeEnd.Marshal(), - disputedTraceIndex: 1023, + disputedTraceIndex: consolidateStep, expectValid: true, // TODO(#14306): Support cascading re-orgs in op-program skipProgram: true, @@ -572,7 +577,7 @@ func TestInteropFaultProofs_MessageExpiry(gt *testing.T) { startTimestamp := endTimestamp - 1 optimisticEnd := system.Outputs.SuperRoot(endTimestamp) - preConsolidation := system.Outputs.TransitionState(startTimestamp, 1023, + preConsolidation := system.Outputs.TransitionState(startTimestamp, consolidateStep, system.Outputs.OptimisticBlockAtTimestamp(actors.ChainA, endTimestamp), system.Outputs.OptimisticBlockAtTimestamp(actors.ChainB, endTimestamp), ).Marshal() @@ -588,14 +593,14 @@ func TestInteropFaultProofs_MessageExpiry(gt *testing.T) { name: "Consolidate-ExpectInvalidPendingBlock", agreedClaim: preConsolidation, disputedClaim: optimisticEnd.Marshal(), - disputedTraceIndex: 1023, + disputedTraceIndex: consolidateStep, expectValid: false, }, { name: "Consolidate-ReplaceInvalidBlocks", agreedClaim: preConsolidation, disputedClaim: crossSafeEnd.Marshal(), - disputedTraceIndex: 1023, + disputedTraceIndex: consolidateStep, expectValid: true, }, } @@ -705,23 +710,23 @@ func TestInteropFaultProofsInvalidBlock(gt *testing.T) { }, { name: "LastPaddingStep", - agreedClaim: paddingStep(1022), - disputedClaim: paddingStep(1023), - disputedTraceIndex: 1022, + agreedClaim: paddingStep(consolidateStep - 1), + disputedClaim: paddingStep(consolidateStep), + disputedTraceIndex: consolidateStep - 1, expectValid: true, }, { name: "Consolidate-ExpectInvalidPendingBlock", - agreedClaim: paddingStep(1023), + agreedClaim: paddingStep(consolidateStep), disputedClaim: end.Marshal(), - disputedTraceIndex: 1023, + disputedTraceIndex: consolidateStep, expectValid: false, }, { name: "Consolidate-ReplaceInvalidBlock", - agreedClaim: paddingStep(1023), + agreedClaim: paddingStep(consolidateStep), disputedClaim: crossSafeSuperRootEnd.Marshal(), - disputedTraceIndex: 1023, + disputedTraceIndex: consolidateStep, expectValid: true, }, { diff --git a/op-program/client/interop/interop.go b/op-program/client/interop/interop.go index 311a284de64..3f5be634a48 100644 --- a/op-program/client/interop/interop.go +++ b/op-program/client/interop/interop.go @@ -29,7 +29,7 @@ var ( ) const ( - ConsolidateStep = 1023 + ConsolidateStep = 127 ) type taskExecutor interface { From e274a154fa20e4e3a0d1e9553cc8169772c488b1 Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 28 Feb 2025 15:44:59 +0100 Subject: [PATCH 035/130] op-test-sequencer: initial service scaffolding (#14270) * op-test-sequencer: initial service framework * op-test-sequencer: readme and justfile --- op-test-sequencer/.gitignore | 1 + op-test-sequencer/README.md | 32 +++ op-test-sequencer/cmd/main.go | 60 +++++ op-test-sequencer/cmd/main_test.go | 86 +++++++ op-test-sequencer/config/config.go | 41 ++++ op-test-sequencer/config/config_test.go | 12 + op-test-sequencer/flags/flags.go | 76 +++++++ op-test-sequencer/flags/flags_test.go | 89 ++++++++ op-test-sequencer/justfile | 21 ++ op-test-sequencer/metrics/metricer.go | 6 + op-test-sequencer/metrics/metrics.go | 66 ++++++ op-test-sequencer/metrics/noop.go | 9 + .../sequencer/backend/backend.go | 49 ++++ .../sequencer/backend/backend_test.go | 27 +++ op-test-sequencer/sequencer/backend/mock.go | 21 ++ op-test-sequencer/sequencer/entrypoint.go | 39 ++++ .../sequencer/frontend/frontend.go | 15 ++ op-test-sequencer/sequencer/service.go | 211 ++++++++++++++++++ op-test-sequencer/sequencer/service_test.go | 77 +++++++ 19 files changed, 938 insertions(+) create mode 100644 op-test-sequencer/.gitignore create mode 100644 op-test-sequencer/README.md create mode 100644 op-test-sequencer/cmd/main.go create mode 100644 op-test-sequencer/cmd/main_test.go create mode 100644 op-test-sequencer/config/config.go create mode 100644 op-test-sequencer/config/config_test.go create mode 100644 op-test-sequencer/flags/flags.go create mode 100644 op-test-sequencer/flags/flags_test.go create mode 100644 op-test-sequencer/justfile create mode 100644 op-test-sequencer/metrics/metricer.go create mode 100644 op-test-sequencer/metrics/metrics.go create mode 100644 op-test-sequencer/metrics/noop.go create mode 100644 op-test-sequencer/sequencer/backend/backend.go create mode 100644 op-test-sequencer/sequencer/backend/backend_test.go create mode 100644 op-test-sequencer/sequencer/backend/mock.go create mode 100644 op-test-sequencer/sequencer/entrypoint.go create mode 100644 op-test-sequencer/sequencer/frontend/frontend.go create mode 100644 op-test-sequencer/sequencer/service.go create mode 100644 op-test-sequencer/sequencer/service_test.go diff --git a/op-test-sequencer/.gitignore b/op-test-sequencer/.gitignore new file mode 100644 index 00000000000..ba077a4031a --- /dev/null +++ b/op-test-sequencer/.gitignore @@ -0,0 +1 @@ +bin diff --git a/op-test-sequencer/README.md b/op-test-sequencer/README.md new file mode 100644 index 00000000000..bc59cc161da --- /dev/null +++ b/op-test-sequencer/README.md @@ -0,0 +1,32 @@ +# op-test-sequencer + +This is a test service for block sequencing. +This service is in active development. + +## Usage + +### Build from source + +```bash +# from op-test-sequencer dir: +just op-test-sequencer +./bin/op-test-sequencer --help +``` + +### Run from source + +```bash +# from op-test-sequencer dir: +go run ./cmd --help +``` + +### Build docker image + +Not available yet. + +## Overview + +This service is in active development. + +See [design doc](https://github.com/ethereum-optimism/design-docs/blob/main/protocol/test-sequencing.md) +for design considerations. diff --git a/op-test-sequencer/cmd/main.go b/op-test-sequencer/cmd/main.go new file mode 100644 index 00000000000..bd054ff1f71 --- /dev/null +++ b/op-test-sequencer/cmd/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "context" + "io" + "os" + + "github.com/urfave/cli/v2" + + "github.com/ethereum/go-ethereum/log" + + opservice "github.com/ethereum-optimism/optimism/op-service" + "github.com/ethereum-optimism/optimism/op-service/cliapp" + "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum-optimism/optimism/op-service/metrics/doc" + "github.com/ethereum-optimism/optimism/op-test-sequencer/config" + "github.com/ethereum-optimism/optimism/op-test-sequencer/flags" + "github.com/ethereum-optimism/optimism/op-test-sequencer/metrics" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer" +) + +var ( + Version = "v0.0.0" + GitCommit = "" + GitDate = "" +) + +func main() { + ctx := ctxinterrupt.WithSignalWaiterMain(context.Background()) + err := run(ctx, os.Stdout, os.Stderr, os.Args, fromConfig) + if err != nil { + log.Crit("Application failed", "message", err) + } +} + +func run(ctx context.Context, w io.Writer, ew io.Writer, args []string, fn sequencer.MainFn) error { + oplog.SetupDefaults() + + app := cli.NewApp() + app.Writer = w + app.ErrWriter = ew + app.Flags = cliapp.ProtectFlags(flags.Flags) + app.Version = opservice.FormatVersion(Version, GitCommit, GitDate, "") + app.Name = "op-test-sequencer" + app.Usage = "op-test-sequencer sequences blocks" + app.Description = "op-test-sequencer sequences blocks" + app.Action = cliapp.LifecycleCmd(sequencer.Main(app.Version, fn)) + app.Commands = []*cli.Command{ + { + Name: "doc", + Subcommands: doc.NewSubcommands(metrics.NewMetrics("default")), + }, + } + return app.RunContext(ctx, args) +} + +func fromConfig(ctx context.Context, cfg *config.Config, logger log.Logger) (cliapp.Lifecycle, error) { + return sequencer.FromConfig(ctx, cfg, logger) +} diff --git a/op-test-sequencer/cmd/main_test.go b/op-test-sequencer/cmd/main_test.go new file mode 100644 index 00000000000..04875d34a9b --- /dev/null +++ b/op-test-sequencer/cmd/main_test.go @@ -0,0 +1,86 @@ +package main + +import ( + "context" + "errors" + "fmt" + "io" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/cliapp" + "github.com/ethereum-optimism/optimism/op-test-sequencer/config" +) + +func TestLogLevel(t *testing.T) { + t.Run("RejectInvalid", func(t *testing.T) { + verifyArgsInvalid(t, "unknown level: foo", addRequiredArgs("--log.level=foo")) + }) + + for _, lvl := range []string{"trace", "debug", "info", "error", "crit"} { + lvl := lvl + t.Run("AcceptValid_"+lvl, func(t *testing.T) { + logger, _, err := dryRunWithArgs(addRequiredArgs("--log.level", lvl)) + require.NoError(t, err) + require.NotNil(t, logger) + }) + } +} + +func TestMockRun(t *testing.T) { + t.Run("Valid", func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgs("--mock-run")) + require.Equal(t, true, cfg.MockRun) + }) +} + +func verifyArgsInvalid(t *testing.T, messageContains string, cliArgs []string) { + _, _, err := dryRunWithArgs(cliArgs) + require.ErrorContains(t, err, messageContains) +} + +func configForArgs(t *testing.T, cliArgs []string) *config.Config { + _, cfg, err := dryRunWithArgs(cliArgs) + require.NoError(t, err) + return cfg +} + +func dryRunWithArgs(cliArgs []string) (log.Logger, *config.Config, error) { + cfg := new(config.Config) + var logger log.Logger + fullArgs := append([]string{"op-test-sequencer"}, cliArgs...) + testErr := errors.New("dry-run") + err := run(context.Background(), io.Discard, io.Discard, fullArgs, func(ctx context.Context, config *config.Config, log log.Logger) (cliapp.Lifecycle, error) { + logger = log + cfg = config + return nil, testErr + }) + if errors.Is(err, testErr) { // expected error + err = nil + } + return logger, cfg, err +} + +func addRequiredArgs(args ...string) []string { + req := requiredArgs() + combined := toArgList(req) + return append(combined, args...) +} + +func toArgList(req map[string]string) []string { + var combined []string + for name, value := range req { + combined = append(combined, fmt.Sprintf("%s=%s", name, value)) + } + return combined +} + +func requiredArgs() map[string]string { + args := map[string]string{ + //"--example": "todo", + } + return args +} diff --git a/op-test-sequencer/config/config.go b/op-test-sequencer/config/config.go new file mode 100644 index 00000000000..469c182bd7c --- /dev/null +++ b/op-test-sequencer/config/config.go @@ -0,0 +1,41 @@ +package config + +import ( + "errors" + + oplog "github.com/ethereum-optimism/optimism/op-service/log" + opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" + "github.com/ethereum-optimism/optimism/op-service/oppprof" + oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" +) + +type Config struct { + Version string + + LogConfig oplog.CLIConfig + MetricsConfig opmetrics.CLIConfig + PprofConfig oppprof.CLIConfig + RPC oprpc.CLIConfig + + JWTSecretPath string + + MockRun bool +} + +func (c *Config) Check() error { + var result error + result = errors.Join(result, c.MetricsConfig.Check()) + result = errors.Join(result, c.PprofConfig.Check()) + result = errors.Join(result, c.RPC.Check()) + return result +} + +func DefaultCLIConfig() *Config { + return &Config{ + Version: "dev", + LogConfig: oplog.DefaultCLIConfig(), + MetricsConfig: opmetrics.DefaultCLIConfig(), + PprofConfig: oppprof.DefaultCLIConfig(), + RPC: oprpc.DefaultCLIConfig(), + } +} diff --git a/op-test-sequencer/config/config_test.go b/op-test-sequencer/config/config_test.go new file mode 100644 index 00000000000..38482a7184d --- /dev/null +++ b/op-test-sequencer/config/config_test.go @@ -0,0 +1,12 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDefaultConfig(t *testing.T) { + cfg := DefaultCLIConfig() + require.NoError(t, cfg.Check()) +} diff --git a/op-test-sequencer/flags/flags.go b/op-test-sequencer/flags/flags.go new file mode 100644 index 00000000000..55abb82e6a9 --- /dev/null +++ b/op-test-sequencer/flags/flags.go @@ -0,0 +1,76 @@ +package flags + +import ( + "fmt" + + "github.com/urfave/cli/v2" + + opservice "github.com/ethereum-optimism/optimism/op-service" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" + "github.com/ethereum-optimism/optimism/op-service/oppprof" + oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" + "github.com/ethereum-optimism/optimism/op-test-sequencer/config" +) + +const EnvVarPrefix = "OP_TEST_SEQUENCER" + +func prefixEnvVars(name string) []string { + return opservice.PrefixEnvVar(EnvVarPrefix, name) +} + +var ( + RPCJWTSecret = &cli.StringFlag{ + Name: "rpc.jwt-secret", + Usage: "Path to JWT secret key for sequencer admin RPC.", + EnvVars: prefixEnvVars("RPC_JWT_SECRET"), + TakesFile: true, + } + MockRunFlag = &cli.BoolFlag{ + Name: "mock-run", + Usage: "Mock run, no actual backend used, just presenting the service", + EnvVars: prefixEnvVars("MOCK_RUN"), + Hidden: true, // this is for testing only + } +) + +var requiredFlags = []cli.Flag{} + +var optionalFlags = []cli.Flag{ + RPCJWTSecret, + MockRunFlag, +} + +func init() { + optionalFlags = append(optionalFlags, oprpc.CLIFlags(EnvVarPrefix)...) + optionalFlags = append(optionalFlags, oplog.CLIFlags(EnvVarPrefix)...) + optionalFlags = append(optionalFlags, opmetrics.CLIFlags(EnvVarPrefix)...) + optionalFlags = append(optionalFlags, oppprof.CLIFlags(EnvVarPrefix)...) + + Flags = append(Flags, requiredFlags...) + Flags = append(Flags, optionalFlags...) +} + +// Flags contains the list of configuration options available to the binary. +var Flags []cli.Flag + +func CheckRequired(ctx *cli.Context) error { + for _, f := range requiredFlags { + if !ctx.IsSet(f.Names()[0]) { + return fmt.Errorf("flag %s is required", f.Names()[0]) + } + } + return nil +} + +func ConfigFromCLI(ctx *cli.Context, version string) *config.Config { + return &config.Config{ + Version: version, + LogConfig: oplog.ReadCLIConfig(ctx), + MetricsConfig: opmetrics.ReadCLIConfig(ctx), + PprofConfig: oppprof.ReadCLIConfig(ctx), + RPC: oprpc.ReadCLIConfig(ctx), + MockRun: ctx.Bool(MockRunFlag.Name), + JWTSecretPath: ctx.Path(RPCJWTSecret.Name), + } +} diff --git a/op-test-sequencer/flags/flags_test.go b/op-test-sequencer/flags/flags_test.go new file mode 100644 index 00000000000..0f890184633 --- /dev/null +++ b/op-test-sequencer/flags/flags_test.go @@ -0,0 +1,89 @@ +package flags + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" + "github.com/urfave/cli/v2" + + opservice "github.com/ethereum-optimism/optimism/op-service" +) + +// TestOptionalFlagsDontSetRequired asserts that all flags deemed optional set +// the Required field to false. +func TestOptionalFlagsDontSetRequired(t *testing.T) { + for _, flag := range optionalFlags { + reqFlag, ok := flag.(cli.RequiredFlag) + require.True(t, ok) + require.False(t, reqFlag.IsRequired()) + } +} + +// TestUniqueFlags asserts that all flag names are unique, to avoid accidental conflicts between the many flags. +func TestUniqueFlags(t *testing.T) { + seenCLI := make(map[string]struct{}) + for _, flag := range Flags { + for _, name := range flag.Names() { + if _, ok := seenCLI[name]; ok { + t.Errorf("duplicate flag %s", name) + continue + } + seenCLI[name] = struct{}{} + } + } +} + +// TestBetaFlags test that all flags starting with "beta." have "BETA_" in the env var, and vice versa. +func TestBetaFlags(t *testing.T) { + for _, flag := range Flags { + envFlag, ok := flag.(interface { + GetEnvVars() []string + }) + if !ok || len(envFlag.GetEnvVars()) == 0 { // skip flags without env-var support + continue + } + name := flag.Names()[0] + envName := envFlag.GetEnvVars()[0] + if strings.HasPrefix(name, "beta.") { + require.Contains(t, envName, "BETA_", "%q flag must contain BETA in env var to match \"beta.\" flag name", name) + } + if strings.Contains(envName, "BETA_") { + require.True(t, strings.HasPrefix(name, "beta."), "%q flag must start with \"beta.\" in flag name to match \"BETA_\" env var", name) + } + } +} + +func TestHasEnvVar(t *testing.T) { + for _, flag := range Flags { + flag := flag + flagName := flag.Names()[0] + + t.Run(flagName, func(t *testing.T) { + envFlagGetter, ok := flag.(interface { + GetEnvVars() []string + }) + envFlags := envFlagGetter.GetEnvVars() + require.True(t, ok, "must be able to cast the flag to an EnvVar interface") + require.Equal(t, 1, len(envFlags), "flags should have exactly one env var") + }) + } +} + +func TestEnvVarFormat(t *testing.T) { + for _, flag := range Flags { + flag := flag + flagName := flag.Names()[0] + + t.Run(flagName, func(t *testing.T) { + envFlagGetter, ok := flag.(interface { + GetEnvVars() []string + }) + envFlags := envFlagGetter.GetEnvVars() + require.True(t, ok, "must be able to cast the flag to an EnvVar interface") + require.Equal(t, 1, len(envFlags), "flags should have exactly one env var") + expectedEnvVar := opservice.FlagNameToEnvVarName(flagName, "OP_TEST_SEQUENCER") + require.Equal(t, expectedEnvVar, envFlags[0]) + }) + } +} diff --git a/op-test-sequencer/justfile b/op-test-sequencer/justfile new file mode 100644 index 00000000000..cbd70ac1940 --- /dev/null +++ b/op-test-sequencer/justfile @@ -0,0 +1,21 @@ +import '../justfiles/go.just' + +# Build ldflags string +_LDFLAGSSTRING := "'" + trim( + "-X main.GitCommit=" + GITCOMMIT + " " + \ + "-X main.GitDate=" + GITDATE + " " + \ + "-X main.Version=" + VERSION + " " + \ + "-X main.Meta=" + VERSION_META + " " + \ + "") + "'" + +BINARY := "./bin/op-test-sequencer" + +# Build op-test-sequencer binary +op-test-sequencer: (go_build BINARY "./cmd" "-ldflags" _LDFLAGSSTRING) + +# Clean build artifacts +clean: + rm -f {{BINARY}} + +# Run tests +test: (go_test "./...") diff --git a/op-test-sequencer/metrics/metricer.go b/op-test-sequencer/metrics/metricer.go new file mode 100644 index 00000000000..47ac9c23378 --- /dev/null +++ b/op-test-sequencer/metrics/metricer.go @@ -0,0 +1,6 @@ +package metrics + +type Metricer interface { + RecordInfo(version string) + RecordUp() +} diff --git a/op-test-sequencer/metrics/metrics.go b/op-test-sequencer/metrics/metrics.go new file mode 100644 index 00000000000..946ad148c66 --- /dev/null +++ b/op-test-sequencer/metrics/metrics.go @@ -0,0 +1,66 @@ +package metrics + +import ( + opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" + "github.com/prometheus/client_golang/prometheus" +) + +const Namespace = "op_test_sequencer" + +type Metrics struct { + ns string + registry *prometheus.Registry + factory opmetrics.Factory + + info prometheus.GaugeVec + up prometheus.Gauge +} + +var _ Metricer = (*Metrics)(nil) + +func NewMetrics(procName string) *Metrics { + if procName == "" { + procName = "default" + } + ns := Namespace + "_" + procName + + registry := opmetrics.NewRegistry() + factory := opmetrics.With(registry) + + return &Metrics{ + ns: ns, + registry: registry, + factory: factory, + + info: *factory.NewGaugeVec(prometheus.GaugeOpts{ + Namespace: ns, + Name: "info", + Help: "Pseudo-metric tracking version and config info", + }, []string{ + "version", + }), + up: factory.NewGauge(prometheus.GaugeOpts{ + Namespace: ns, + Name: "up", + Help: "1 if the op-test-sequencer has finished starting up", + }), + } +} + +func (m *Metrics) Registry() *prometheus.Registry { + return m.registry +} + +func (m *Metrics) Document() []opmetrics.DocumentedMetric { + return m.factory.Document() +} + +// RecordInfo sets a pseudo-metric that contains versioning and config info. +func (m *Metrics) RecordInfo(version string) { + m.info.WithLabelValues(version).Set(1) +} + +// RecordUp sets the up metric to 1. +func (m *Metrics) RecordUp() { + m.up.Set(1) +} diff --git a/op-test-sequencer/metrics/noop.go b/op-test-sequencer/metrics/noop.go new file mode 100644 index 00000000000..82f1f41d3f6 --- /dev/null +++ b/op-test-sequencer/metrics/noop.go @@ -0,0 +1,9 @@ +package metrics + +type NoopMetrics struct{} + +func (n NoopMetrics) RecordInfo(version string) {} + +func (n NoopMetrics) RecordUp() {} + +var _ Metricer = NoopMetrics{} diff --git a/op-test-sequencer/sequencer/backend/backend.go b/op-test-sequencer/sequencer/backend/backend.go new file mode 100644 index 00000000000..05828bf279a --- /dev/null +++ b/op-test-sequencer/sequencer/backend/backend.go @@ -0,0 +1,49 @@ +package backend + +import ( + "context" + "errors" + "sync/atomic" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/metrics" +) + +var ( + errAlreadyStarted = errors.New("already started") + errAlreadyStopped = errors.New("already stopped") +) + +type Backend struct { + started atomic.Bool + logger log.Logger + m metrics.Metricer +} + +func NewBackend(log log.Logger, m metrics.Metricer) *Backend { + return &Backend{ + logger: log, + m: m, + } +} + +func (ba *Backend) Start(ctx context.Context) error { + if !ba.started.CompareAndSwap(false, true) { + return errAlreadyStarted + } + ba.logger.Info("Starting sequencer backend") + return nil +} + +func (ba *Backend) Stop(ctx context.Context) error { + if !ba.started.CompareAndSwap(true, false) { + return errAlreadyStopped + } + ba.logger.Info("Stopping sequencer backend") + return nil +} + +func (ba *Backend) Hello(ctx context.Context, name string) (string, error) { + return "hello " + name + "!", nil +} diff --git a/op-test-sequencer/sequencer/backend/backend_test.go b/op-test-sequencer/sequencer/backend/backend_test.go new file mode 100644 index 00000000000..2b4c79fc2dd --- /dev/null +++ b/op-test-sequencer/sequencer/backend/backend_test.go @@ -0,0 +1,27 @@ +package backend + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-test-sequencer/metrics" +) + +func TestBackend(t *testing.T) { + logger := testlog.Logger(t, log.LevelWarn) + b := NewBackend(logger, metrics.NoopMetrics{}) + require.NoError(t, b.Start(context.Background())) + require.ErrorIs(t, b.Start(context.Background()), errAlreadyStarted) + + result, err := b.Hello(context.Background(), "alice") + require.NoError(t, err) + require.Contains(t, result, "alice") + + require.NoError(t, b.Stop(context.Background())) + require.ErrorIs(t, b.Stop(context.Background()), errAlreadyStopped) +} diff --git a/op-test-sequencer/sequencer/backend/mock.go b/op-test-sequencer/sequencer/backend/mock.go new file mode 100644 index 00000000000..9b0818f66a6 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/mock.go @@ -0,0 +1,21 @@ +package backend + +import "context" + +type MockBackend struct{} + +func NewMockBackend() *MockBackend { + return &MockBackend{} +} + +func (ba *MockBackend) Start(ctx context.Context) error { + return nil +} + +func (ba *MockBackend) Stop(ctx context.Context) error { + return nil +} + +func (ba *MockBackend) Hello(ctx context.Context, name string) (string, error) { + return "hello " + name + "!", nil +} diff --git a/op-test-sequencer/sequencer/entrypoint.go b/op-test-sequencer/sequencer/entrypoint.go new file mode 100644 index 00000000000..248add453a6 --- /dev/null +++ b/op-test-sequencer/sequencer/entrypoint.go @@ -0,0 +1,39 @@ +package sequencer + +import ( + "context" + "fmt" + + "github.com/urfave/cli/v2" + + "github.com/ethereum/go-ethereum/log" + + opservice "github.com/ethereum-optimism/optimism/op-service" + "github.com/ethereum-optimism/optimism/op-service/cliapp" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum-optimism/optimism/op-test-sequencer/config" + "github.com/ethereum-optimism/optimism/op-test-sequencer/flags" +) + +type MainFn func(ctx context.Context, cfg *config.Config, logger log.Logger) (cliapp.Lifecycle, error) + +// Main is the entrypoint into the service. +// This method returns a cliapp.LifecycleAction, to create an op-service CLI-lifecycle-managed service with. +func Main(version string, fn MainFn) cliapp.LifecycleAction { + return func(cliCtx *cli.Context, closeApp context.CancelCauseFunc) (cliapp.Lifecycle, error) { + if err := flags.CheckRequired(cliCtx); err != nil { + return nil, err + } + cfg := flags.ConfigFromCLI(cliCtx, version) + if err := cfg.Check(); err != nil { + return nil, fmt.Errorf("invalid CLI flags: %w", err) + } + + l := oplog.NewLogger(oplog.AppOut(cliCtx), cfg.LogConfig) + oplog.SetGlobalLogHandler(l.Handler()) + opservice.ValidateEnvVars(flags.EnvVarPrefix, flags.Flags, l) + + l.Info("Initializing op-test-sequencer") + return fn(cliCtx.Context, cfg, l) + } +} diff --git a/op-test-sequencer/sequencer/frontend/frontend.go b/op-test-sequencer/sequencer/frontend/frontend.go new file mode 100644 index 00000000000..b9dc094a379 --- /dev/null +++ b/op-test-sequencer/sequencer/frontend/frontend.go @@ -0,0 +1,15 @@ +package frontend + +import "context" + +type Backend interface { + Hello(ctx context.Context, name string) (string, error) +} + +type AdminFrontend struct { + Backend Backend +} + +func (af *AdminFrontend) Hello(ctx context.Context, name string) (string, error) { + return af.Backend.Hello(ctx, name) +} diff --git a/op-test-sequencer/sequencer/service.go b/op-test-sequencer/sequencer/service.go new file mode 100644 index 00000000000..be2515b3622 --- /dev/null +++ b/op-test-sequencer/sequencer/service.go @@ -0,0 +1,211 @@ +package sequencer + +import ( + "context" + "errors" + "fmt" + "sync/atomic" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/ethereum-optimism/optimism/op-service/cliapp" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/httputil" + opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" + "github.com/ethereum-optimism/optimism/op-service/oppprof" + oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" + "github.com/ethereum-optimism/optimism/op-test-sequencer/config" + "github.com/ethereum-optimism/optimism/op-test-sequencer/metrics" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/frontend" +) + +type serviceBackend interface { + frontend.Backend + Start(ctx context.Context) error + Stop(ctx context.Context) error +} + +var _ serviceBackend = (*backend.Backend)(nil) +var _ serviceBackend = (*backend.MockBackend)(nil) + +type Service struct { + closing atomic.Bool + + log log.Logger + + backend serviceBackend + + metrics metrics.Metricer + pprofService *oppprof.Service + metricsSrv *httputil.HTTPServer + rpcServer *oprpc.Server + jwtSecret eth.Bytes32 +} + +var _ cliapp.Lifecycle = (*Service)(nil) + +func FromConfig(ctx context.Context, cfg *config.Config, logger log.Logger) (*Service, error) { + su := &Service{log: logger} + if err := su.initFromCLIConfig(ctx, cfg); err != nil { + return nil, errors.Join(err, su.Stop(ctx)) // try to clean up our failed initialization attempt + } + return su, nil +} + +func (s *Service) initFromCLIConfig(ctx context.Context, cfg *config.Config) error { + s.initMetrics(cfg) + if err := s.initPProf(cfg); err != nil { + return fmt.Errorf("failed to start PProf server: %w", err) + } + if err := s.initMetricsServer(cfg); err != nil { + return fmt.Errorf("failed to start Metrics server: %w", err) + } + if err := s.initBackend(ctx, cfg); err != nil { + return fmt.Errorf("failed to start backend: %w", err) + } + if err := s.initRPCServer(cfg); err != nil { + return fmt.Errorf("failed to start RPC server: %w", err) + } + return nil +} + +func (s *Service) initMetrics(cfg *config.Config) { + if cfg.MetricsConfig.Enabled { + procName := "default" + s.metrics = metrics.NewMetrics(procName) + s.metrics.RecordInfo(cfg.Version) + } else { + s.metrics = metrics.NoopMetrics{} + } +} + +func (s *Service) initPProf(cfg *config.Config) error { + s.pprofService = oppprof.New( + cfg.PprofConfig.ListenEnabled, + cfg.PprofConfig.ListenAddr, + cfg.PprofConfig.ListenPort, + cfg.PprofConfig.ProfileType, + cfg.PprofConfig.ProfileDir, + cfg.PprofConfig.ProfileFilename, + ) + + if err := s.pprofService.Start(); err != nil { + return fmt.Errorf("failed to start pprof service: %w", err) + } + + return nil +} + +func (s *Service) initMetricsServer(cfg *config.Config) error { + if !cfg.MetricsConfig.Enabled { + s.log.Info("Metrics disabled") + return nil + } + m, ok := s.metrics.(opmetrics.RegistryMetricer) + if !ok { + return fmt.Errorf("metrics were enabled, but metricer %T does not expose registry for metrics-server", s.metrics) + } + s.log.Debug("Starting metrics server", "addr", cfg.MetricsConfig.ListenAddr, "port", cfg.MetricsConfig.ListenPort) + metricsSrv, err := opmetrics.StartServer(m.Registry(), cfg.MetricsConfig.ListenAddr, cfg.MetricsConfig.ListenPort) + if err != nil { + return fmt.Errorf("failed to start metrics server: %w", err) + } + s.log.Info("Started metrics server", "addr", metricsSrv.Addr()) + s.metricsSrv = metricsSrv + return nil +} + +func (s *Service) initBackend(ctx context.Context, cfg *config.Config) error { + if cfg.MockRun { + s.backend = backend.NewMockBackend() + return nil + } + s.backend = backend.NewBackend(s.log, s.metrics) + return nil +} + +func (s *Service) initRPCServer(cfg *config.Config) error { + secret, err := oprpc.ObtainJWTSecret(s.log, cfg.JWTSecretPath, true) + if err != nil { + return err + } + server := oprpc.NewServer( + cfg.RPC.ListenAddr, + cfg.RPC.ListenPort, + cfg.Version, + oprpc.WithLogger(s.log), + oprpc.WithJWTSecret(secret[:]), + ) + if cfg.RPC.EnableAdmin { + s.log.Info("Admin RPC enabled") + server.AddAPI(rpc.API{ + Namespace: "admin", + Service: &frontend.AdminFrontend{Backend: s.backend}, + Authenticated: true, + }) + } + s.jwtSecret = secret + s.rpcServer = server + return nil +} + +func (s *Service) Start(ctx context.Context) error { + s.log.Info("Starting JSON-RPC server") + if err := s.rpcServer.Start(); err != nil { + return fmt.Errorf("unable to start RPC server: %w", err) + } + + if err := s.backend.Start(ctx); err != nil { + return fmt.Errorf("unable to start backend: %w", err) + } + + s.metrics.RecordUp() + s.log.Info("JSON-RPC Server started", "endpoint", s.rpcServer.Endpoint()) + return nil +} + +func (s *Service) Stop(ctx context.Context) error { + if !s.closing.CompareAndSwap(false, true) { + s.log.Warn("Already closing") + return nil // already closing + } + s.log.Info("Stopping JSON-RPC server") + var result error + if s.rpcServer != nil { + if err := s.rpcServer.Stop(); err != nil { + result = errors.Join(result, fmt.Errorf("failed to stop RPC server: %w", err)) + } + } + s.log.Info("Stopped RPC Server") + if s.backend != nil { + if err := s.backend.Stop(ctx); err != nil { + result = errors.Join(result, fmt.Errorf("failed to close backend: %w", err)) + } + } + s.log.Info("Stopped Backend") + if s.pprofService != nil { + if err := s.pprofService.Stop(ctx); err != nil { + result = errors.Join(result, fmt.Errorf("failed to stop PProf server: %w", err)) + } + } + s.log.Info("Stopped PProf") + if s.metricsSrv != nil { + if err := s.metricsSrv.Stop(ctx); err != nil { + result = errors.Join(result, fmt.Errorf("failed to stop metrics server: %w", err)) + } + } + s.log.Info("JSON-RPC server stopped") + return result +} + +func (s *Service) Stopped() bool { + return s.closing.Load() +} + +func (s *Service) RPC() string { + // the RPC endpoint is assumed to be HTTP + // TODO(#11032): make this flexible for ws if the server supports it + return "http://" + s.rpcServer.Endpoint() +} diff --git a/op-test-sequencer/sequencer/service_test.go b/op-test-sequencer/sequencer/service_test.go new file mode 100644 index 00000000000..e61ce120052 --- /dev/null +++ b/op-test-sequencer/sequencer/service_test.go @@ -0,0 +1,77 @@ +package sequencer + +import ( + "context" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + gn "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/ethereum-optimism/optimism/op-service/client" + oplog "github.com/ethereum-optimism/optimism/op-service/log" + opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" + "github.com/ethereum-optimism/optimism/op-service/oppprof" + oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-test-sequencer/config" +) + +func TestService(t *testing.T) { + cfg := &config.Config{ + Version: "", + LogConfig: oplog.CLIConfig{ + Level: log.LevelError, + Color: false, + Format: oplog.FormatLogFmt, + }, + MetricsConfig: opmetrics.CLIConfig{ + Enabled: true, + ListenAddr: "127.0.0.1", + ListenPort: 0, // pick a port automatically + }, + PprofConfig: oppprof.CLIConfig{ + ListenEnabled: true, + ListenAddr: "127.0.0.1", + ListenPort: 0, // pick a port automatically + ProfileType: "", + ProfileDir: "", + ProfileFilename: "", + }, + RPC: oprpc.CLIConfig{ + ListenAddr: "127.0.0.1", + ListenPort: 0, // pick a port automatically + EnableAdmin: true, + }, + MockRun: true, + JWTSecretPath: filepath.Join(t.TempDir(), "test_jwt_secret.txt"), + } + logger := testlog.Logger(t, log.LevelError) + s, err := FromConfig(context.Background(), cfg, logger) + require.NoError(t, err) + require.NoError(t, s.Start(context.Background()), "start service") + // run some RPC tests against the service with the mock backend + { + endpoint := "http://" + s.rpcServer.Endpoint() + t.Logf("dialing %s", endpoint) + opts := []client.RPCOption{ + client.WithFixedDialBackoff(time.Second * 5), + client.WithDialAttempts(4), + client.WithGethRPCOptions(rpc.WithHTTPAuth(gn.NewJWTAuth(s.jwtSecret))), + } + cl, err := client.NewRPC(context.Background(), logger, endpoint, opts...) + require.NoError(t, err) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + var dest string + err = cl.CallContext(ctx, &dest, "admin_hello", "foobar") + cancel() + require.NoError(t, err) + require.Equal(t, "hello foobar!", dest, "expecting mock result") + cl.Close() + } + require.NoError(t, s.Stop(context.Background()), "stop service") +} From cd6f74cdddcb12ff4f7ae1285d3ec74fcd4711a7 Mon Sep 17 00:00:00 2001 From: Sebastian Stammler Date: Fri, 28 Feb 2025 16:52:52 +0100 Subject: [PATCH 036/130] ci: only tag finalized Docker images as 'latest' (#14582) --- ops/scripts/ci-docker-tag-op-stack-release.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ops/scripts/ci-docker-tag-op-stack-release.sh b/ops/scripts/ci-docker-tag-op-stack-release.sh index c8dfde1fae2..41840f5d6eb 100755 --- a/ops/scripts/ci-docker-tag-op-stack-release.sh +++ b/ops/scripts/ci-docker-tag-op-stack-release.sh @@ -35,8 +35,8 @@ fi echo "Tagging $SOURCE_IMAGE_TAG with '$IMAGE_TAG'" gcloud container images add-tag -q "$SOURCE_IMAGE_TAG" "$TARGET_IMAGE_TAG" -# Do not tag with latest if the release is a release candidate. -if [[ "$IMAGE_TAG" == *"rc"* ]]; then +# Only tag finalized releases with 'latest' +if ! [[ "$IMAGE_TAG" =~ v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "Not tagging with 'latest' because the release is a release candidate." exit 0 fi From 6aa2ca54d32a69b7095677d26967515d5824ed3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= <38806121+alcueca@users.noreply.github.com> Date: Fri, 28 Feb 2025 16:07:25 +0000 Subject: [PATCH 037/130] Match OPCM version with monorepo release version (#14455) * docs: We were still discussing some points about versioning * fix: `isRC` is automatically set to false * fix: reverted the versioning rules after the first change * feat: explain why we have individual contract versioning * feat: notes on deprecating `version()` * docs: clearer branch denominations * docs: when to remove `version()` Co-authored-by: Matt Solomon --------- Co-authored-by: alcueca Co-authored-by: Matt Solomon --- packages/contracts-bedrock/meta/VERSIONING.md | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/contracts-bedrock/meta/VERSIONING.md b/packages/contracts-bedrock/meta/VERSIONING.md index c1acacd9158..836eff618e9 100644 --- a/packages/contracts-bedrock/meta/VERSIONING.md +++ b/packages/contracts-bedrock/meta/VERSIONING.md @@ -31,15 +31,17 @@ Version increments follow the [style guide rules](./STYLE_GUIDE.md#versioning) f ## Individual Contract Versioning +Individual contract versioning allows us to uniquely identify which version of a contract from the develop branch corresponds to each deployed contract instance. + Versioning for individual contracts works as follows: -- A contract is only `X.Y.Z` on `develop` if it has been governance approved. If it's `X.Y.Z` before that, it must be on a branch. More on this below. +- A contract on develop always has a version of X.Y.Z, regardless of whether is has been governance approved and meets our security bar. This DOES NOT indicate these contracts are always safe for production use. More on this below. - For contracts with feature-specific changes, a `+feature-name` identifier must be appended to the version number. See the [Smart Contract Feature Development](https://github.com/ethereum-optimism/design-docs/blob/main/smart-contract-feature-development.md) design document to learn more. - When making changes to a contract, always bump to the lowest possible version based on the specific change you are making. We do not want to e.g. optimistically bump to a major version, because protocol development sequencing may change unexpectedly. Use these examples to know how to bump the version: - - Example 1: A contract is currently on `1.2.3`. + - Example 1: A contract is currently on `1.2.3` on `develop` and you are working on a new feature on your `feature` branch off `develop`. - We don't yet know when the next release of this contract will be. However, you are simply fixing typos in comments so you bump the version to `1.2.4`. - - The next PR made to that same contract clarifies some comments, but we haven't merged to `develop` yet. The version is still `1.2.4`. - - The next PR introduces a breaking change, which bumps the version from `1.2.4` to `2.0.0`. + - The next commit to the `feature` branch clarifies some comments. We only consider the aggregated `feature` changes with regards to `develop` when determining the version, so we stay at `1.2.4`. + - The next commit to the `feature` branch introduces a breaking change, which bumps the version from `1.2.4` to `2.0.0`. - Example 2: A contract is currently on `2.4.7`. - We know the next release of this contract will be a breaking change. Regardless, as you start development by fixing typos in comments, bump the version to `2.4.8`. This is because we may end up putting out a release before the breaking change is added. - Once you start working on the breaking change, bump the version to `3.0.0`. @@ -51,6 +53,14 @@ Versioning is enforced by CI checks: Note: Previously, the versioning scheme included `-beta.n` and `-rc.n` qualifiers. These are no longer used to reduce the amount of work required to execute this versioning system. +## Deprecating Individual Contract Versioning + +Individual contract versioning could be deprecated when the following conditions are met: + +1. Every OPCM instance is registered in the superchain registry +2. All contracts are implemented as either proxies or concrete singletons, allowing verification of governance approval through the `OPCM.Implementations` struct +3. We have validated with engineering teams (such as the fault proofs team) and ecosystem partners (such as L2Beat) that removing `version()` functions would not negatively impact their workflows + ## Monorepo Contracts Release Versioning Versioning for monorepo releases works as follows: @@ -67,11 +77,11 @@ Versioning for monorepo releases works as follows: ## Optimism Contracts Manager (OPCM) Versioning -The [OPCM](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L1/OPContractsManager.sol) is the contract that manages the deployment of all contracts on L1. Its version is the same as the [associated monorepo contracts release](./VERSIONING.md#monorepo-contracts-release-versioning). +The [OPCM](https://github.com/ethereum-optimism/optimism/blob/main/packages/contracts-bedrock/src/L1/OPContractsManager.sol) is the contract that manages the deployment of all contracts on L1. The `OPCM` is the source of truth for the contracts that belong in a release, available as on-chain addresses by querying [the `getImplementations` function](https://github.com/ethereum-optimism/optimism/blob/4c8764f0453e141555846d8c9dd2af9edbc1d014/packages/contracts-bedrock/src/L1/OPContractsManager.sol#L1061). -When developing a new version of the OPCM, [the `isRC` flag](https://github.com/ethereum-optimism/optimism/blob/4c8764f0453e141555846d8c9dd2af9edbc1d014/packages/contracts-bedrock/src/L1/OPContractsManager.sol#L181) must be set to `true` to indicate that the OPCM is in a release candidate state. The flag [will be set to `false`](https://github.com/ethereum-optimism/optimism/blob/4c8764f0453e141555846d8c9dd2af9edbc1d014/packages/contracts-bedrock/src/L1/OPContractsManager.sol#L453) when the OPCM is deployed to L1 using the `upgrade` function. +When developing a new release of the contracts, [the `isRC` flag](https://github.com/ethereum-optimism/optimism/blob/4c8764f0453e141555846d8c9dd2af9edbc1d014/packages/contracts-bedrock/src/L1/OPContractsManager.sol#L181) must be set to `true` to indicate that the OPCM refers to a release candidate. The flag [is automatically set to `false`](https://github.com/ethereum-optimism/optimism/blob/4c8764f0453e141555846d8c9dd2af9edbc1d014/packages/contracts-bedrock/src/L1/OPContractsManager.sol#L453) the first time the OPCM `upgrade` method is invoked from governance's Upgrade Controller Safe. This Safe is a 2/2 held by the Security Council and Optimism Foundation. ## Release Process @@ -129,3 +139,4 @@ Now there are two scenarios for the PR that merges the release branch back into - In practice, this one unlikely to occur when using inheritance for feature development, as specified in [Smart Contract Feature Development](https://github.com/ethereum-optimism/design-docs/blob/main/smart-contract-feature-development.md) architecture. It's more likely that (1) is the case, and we merge the version change into the base contract. This flow also provides a dedicated branch for each release, making it easy to deploy a patch or bug fix, regardless of other changes that may have occurred on develop since the release. + From 3f56e4b5135ad210dc9f3be1df6241a0253be522 Mon Sep 17 00:00:00 2001 From: Tyler Smith Date: Fri, 28 Feb 2025 10:35:34 -0800 Subject: [PATCH 038/130] interop: Recursive hazard detection (#14453) * refactor,tweak: Add new HazardSet type and add recursive hazards. * tweak: Update op-program consolidate.go to use new HazardSet. * cleanup: Move map lookup into if. * tests,cleanup: Use existing mock dependency set. * fix * tests,cleanup: Improve and cleanup HazardSet tests. * op-program: Fix log index of executing messages in block * fix: Re-add message expiry check from 1d05a2a. * cleanup: Remove repeated log. * tweak: Remove hazard recursion limit. * docs: Update comment in (un)safe update tests to be accurate. * tests: Update test comments and restore missing cycle tests. * cleanup: Remove line break from fn sig. * fix: Resolve merge issues in consolidate.go. * fix: Use new message expiry window getter in consolidate.go. * tests: Remove first skip in CascadeInvalidBlock. * refactor: Cleanup HazardSet.build and split checks out into helpers. * fix: Re-remove legacy expiry check after merge conflict. --------- Co-authored-by: inphi --- op-e2e/actions/interop/proofs_test.go | 6 +- op-program/client/interop/consolidate.go | 32 +- .../supervisor/backend/cross/cycle.go | 11 +- .../supervisor/backend/cross/cycle_test.go | 27 +- .../supervisor/backend/cross/hazard_set.go | 192 +++++ .../backend/cross/hazard_set_test.go | 807 ++++++++++++++++++ .../supervisor/backend/cross/safe_frontier.go | 4 +- .../backend/cross/safe_frontier_test.go | 16 +- .../supervisor/backend/cross/safe_start.go | 111 +-- .../backend/cross/safe_start_test.go | 152 ++-- .../supervisor/backend/cross/safe_update.go | 18 +- .../backend/cross/safe_update_test.go | 4 +- .../backend/cross/unsafe_frontier.go | 6 +- .../backend/cross/unsafe_frontier_test.go | 24 +- .../supervisor/backend/cross/unsafe_start.go | 109 +-- .../backend/cross/unsafe_start_test.go | 137 +-- .../supervisor/backend/cross/unsafe_update.go | 6 +- .../backend/cross/unsafe_update_test.go | 18 +- 18 files changed, 1302 insertions(+), 378 deletions(-) create mode 100644 op-supervisor/supervisor/backend/cross/hazard_set.go create mode 100644 op-supervisor/supervisor/backend/cross/hazard_set_test.go diff --git a/op-e2e/actions/interop/proofs_test.go b/op-e2e/actions/interop/proofs_test.go index 6c4cfb0b447..9164ef75fcd 100644 --- a/op-e2e/actions/interop/proofs_test.go +++ b/op-e2e/actions/interop/proofs_test.go @@ -367,8 +367,6 @@ func TestInteropFaultProofs(gt *testing.T) { func TestInteropFaultProofs_Cycle(gt *testing.T) { t := helpers.NewDefaultTesting(gt) - // TODO(#14425): Handle cyclic valid messages - t.Skip("Cyclic valid messages does not work") system := dsl.NewInteropDSL(t) actors := system.Actors @@ -446,8 +444,6 @@ func TestInteropFaultProofs_Cycle(gt *testing.T) { func TestInteropFaultProofs_CascadeInvalidBlock(gt *testing.T) { t := helpers.NewDefaultTesting(gt) - // TODO(#14307): Support cascading invalidation in op-supervisor - t.Skip("Cascading invalidation not yet working") system := dsl.NewInteropDSL(t) @@ -483,7 +479,7 @@ func TestInteropFaultProofs_CascadeInvalidBlock(gt *testing.T) { // as an invalid message. system.AddL2Block(actors.ChainA, dsl.WithL2BlockTransactions(system.InboxContract.Execute(alice, chainBInitTx)), - // Block becomes cross-unsafe because the init msg is currently present, but it should not become cross-safe. + dsl.WithL1BlockCrossUnsafe(), ) chainAExecTx := system.InboxContract.LastTransaction() chainAExecTx.CheckIncluded() diff --git a/op-program/client/interop/consolidate.go b/op-program/client/interop/consolidate.go index 09eb78357aa..141a7205245 100644 --- a/op-program/client/interop/consolidate.go +++ b/op-program/client/interop/consolidate.go @@ -20,22 +20,23 @@ import ( "github.com/ethereum/go-ethereum/log" ) -func ReceiptsToExecutingMessages(depset depset.ChainIndexFromID, receipts ethtypes.Receipts) ([]*supervisortypes.ExecutingMessage, uint32, error) { - var execMsgs []*supervisortypes.ExecutingMessage - var logCount uint32 +// ReceiptsToExecutingMessages returns the executing messages in the receipts indexed by their position in the log. +func ReceiptsToExecutingMessages(depset depset.ChainIndexFromID, receipts ethtypes.Receipts) (map[uint32]*supervisortypes.ExecutingMessage, uint32, error) { + execMsgs := make(map[uint32]*supervisortypes.ExecutingMessage) + var curr uint32 for _, rcpt := range receipts { - logCount += uint32(len(rcpt.Logs)) for _, l := range rcpt.Logs { execMsg, err := processors.DecodeExecutingMessageLog(l, depset) if err != nil { return nil, 0, err } if execMsg != nil { - execMsgs = append(execMsgs, execMsg) + execMsgs[curr] = execMsg } + curr++ } } - return execMsgs, logCount, nil + return execMsgs, curr, nil } func fetchAgreedBlockHashes(oracle l2.Oracle, superRoot *eth.SuperV1) ([]common.Hash, error) { @@ -101,7 +102,7 @@ func RunConsolidation( Number: optimisticBlock.NumberU64(), Timestamp: optimisticBlock.Time(), } - if err := checkHazards(deps, candidate, chain.ChainID, execMsgs); err != nil { + if err := checkHazards(logger, deps, candidate, chain.ChainID, execMsgs); err != nil { if !isInvalidMessageError(err) { return eth.Bytes32{}, err } @@ -157,13 +158,8 @@ type ConsolidateCheckDeps interface { cross.UnsafeStartDeps } -func checkHazards( - deps ConsolidateCheckDeps, - candidate supervisortypes.BlockSeal, - chainID eth.ChainID, - execMsgs []*supervisortypes.ExecutingMessage, -) error { - hazards, err := cross.CrossUnsafeHazards(deps, chainID, candidate, execMsgs) +func checkHazards(logger log.Logger, deps ConsolidateCheckDeps, candidate supervisortypes.BlockSeal, chainID eth.ChainID, execMsgs map[uint32]*supervisortypes.ExecutingMessage) error { + hazards, err := cross.CrossUnsafeHazards(deps, logger, chainID, candidate) if err != nil { return err } @@ -284,15 +280,11 @@ func (d *consolidateCheckDeps) OpenBlock( Number: block.NumberU64(), } _, receipts := d.oracle.ReceiptsByBlockHash(block.Hash(), chainID) - execs, logCount, err := ReceiptsToExecutingMessages(d.depset, receipts) + execMsgs, logCount, err = ReceiptsToExecutingMessages(d.depset, receipts) if err != nil { return eth.BlockRef{}, 0, nil, err } - execMsgs = make(map[uint32]*supervisortypes.ExecutingMessage, len(execs)) - for _, exec := range execs { - execMsgs[exec.LogIdx] = exec - } - return ref, uint32(logCount), execMsgs, nil + return ref, logCount, execMsgs, nil } func (d *consolidateCheckDeps) DependencySet() depset.DependencySet { diff --git a/op-supervisor/supervisor/backend/cross/cycle.go b/op-supervisor/supervisor/backend/cross/cycle.go index 1ac2e9c9a80..91aae79f681 100644 --- a/op-supervisor/supervisor/backend/cross/cycle.go +++ b/op-supervisor/supervisor/backend/cross/cycle.go @@ -69,7 +69,7 @@ func (g *graph) addEdge(from, to node) { // succeeds if and only if a graph is acyclic. // // Returns nil if no cycles are found or ErrCycle if a cycle is detected. -func HazardCycleChecks(depSet depset.ChainIDFromIndex, d CycleCheckDeps, inTimestamp uint64, hazards map[types.ChainIndex]types.BlockSeal) error { +func HazardCycleChecks(depSet depset.ChainIDFromIndex, d CycleCheckDeps, inTimestamp uint64, hazards *HazardSet) error { g, err := buildGraph(depSet, d, inTimestamp, hazards) if err != nil { return err @@ -82,7 +82,7 @@ func HazardCycleChecks(depSet depset.ChainIDFromIndex, d CycleCheckDeps, inTimes // Returns: // - map of chain index to its log count // - map of chain index to map of log index to executing message (nil if doesn't exist or ignored) -func gatherLogs(depSet depset.ChainIDFromIndex, d CycleCheckDeps, inTimestamp uint64, hazards map[types.ChainIndex]types.BlockSeal) ( +func gatherLogs(depSet depset.ChainIDFromIndex, d CycleCheckDeps, inTimestamp uint64, hazards *HazardSet) ( map[types.ChainIndex]uint32, map[types.ChainIndex]map[uint32]*types.ExecutingMessage, error, @@ -90,7 +90,7 @@ func gatherLogs(depSet depset.ChainIDFromIndex, d CycleCheckDeps, inTimestamp ui logCounts := make(map[types.ChainIndex]uint32) execMsgs := make(map[types.ChainIndex]map[uint32]*types.ExecutingMessage) - for hazardChainIndex, hazardBlock := range hazards { + for hazardChainIndex, hazardBlock := range hazards.Entries() { hazardChainID, err := depSet.ChainIDFromIndex(hazardChainIndex) if err != nil { return nil, nil, err @@ -130,7 +130,7 @@ func gatherLogs(depSet depset.ChainIDFromIndex, d CycleCheckDeps, inTimestamp ui } // buildGraph constructs a dependency graph from the hazard blocks. -func buildGraph(depSet depset.ChainIDFromIndex, d CycleCheckDeps, inTimestamp uint64, hazards map[types.ChainIndex]types.BlockSeal) (*graph, error) { +func buildGraph(depSet depset.ChainIDFromIndex, d CycleCheckDeps, inTimestamp uint64, hazards *HazardSet) (*graph, error) { g := &graph{ inDegree0: make(map[node]struct{}), inDegreeNon0: make(map[node]uint32), @@ -165,10 +165,11 @@ func buildGraph(depSet depset.ChainIDFromIndex, d CycleCheckDeps, inTimestamp ui } // Add edges for executing messages to their initiating messages + hazardEntries := hazards.Entries() for hazardChainIndex, msgs := range execMsgs { for execLogIdx, m := range msgs { // Error if the chain is unknown - if _, ok := hazards[m.Chain]; !ok { + if _, ok := hazardEntries[m.Chain]; !ok { return nil, ErrExecMsgUnknownChain } diff --git a/op-supervisor/supervisor/backend/cross/cycle_test.go b/op-supervisor/supervisor/backend/cross/cycle_test.go index b702c84f8e6..2481ae1720f 100644 --- a/op-supervisor/supervisor/backend/cross/cycle_test.go +++ b/op-supervisor/supervisor/backend/cross/cycle_test.go @@ -101,7 +101,7 @@ func runHazardCycleChecksTestCase(t *testing.T, tc hazardCycleChecksTestCase) { depSet.mapping[index] = eth.ChainIDFromUInt64(uint64(index)) } // Run the test - err := HazardCycleChecks(depSet, deps, 100, hazards) + err := HazardCycleChecks(depSet, deps, 100, NewHazardSetFromEntries(hazards)) // No error expected if tc.expectErr == nil { @@ -188,6 +188,31 @@ func TestHazardCycleChecksFailures(t *testing.T) { expectErr: errors.New("tried to open block"), msg: "expected error due to block mismatch", }, + { + name: "multiple blocks with messages", + chainBlocks: map[string]chainBlockDef{ + "1": { + logCount: 2, + messages: map[uint32]*types.ExecutingMessage{ + 0: execMsg("2", 0), + 1: execMsg("2", 1), + }, + }, + "2": { + logCount: 2, + messages: map[uint32]*types.ExecutingMessage{ + 0: execMsg("1", 0), + 1: execMsg("1", 1), + }, + }, + }, + hazards: map[types.ChainIndex]types.BlockSeal{ + 1: {Number: 1}, + 2: {Number: 1}, + }, + expectErr: ErrCycle, + msg: "expected cycle error with multiple blocks and messages", + }, { name: "invalid log index error", chainBlocks: map[string]chainBlockDef{ diff --git a/op-supervisor/supervisor/backend/cross/hazard_set.go b/op-supervisor/supervisor/backend/cross/hazard_set.go new file mode 100644 index 00000000000..c13e99b2a42 --- /dev/null +++ b/op-supervisor/supervisor/backend/cross/hazard_set.go @@ -0,0 +1,192 @@ +package cross + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +type HazardDeps interface { + Contains(chain eth.ChainID, query types.ContainsQuery) (types.BlockSeal, error) + DependencySet() depset.DependencySet + IsCrossValidBlock(chainID eth.ChainID, block eth.BlockID) error + OpenBlock(chainID eth.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) +} + +// HazardSet tracks blocks that must be checked before a candidate can be promoted +type HazardSet struct { + entries map[types.ChainIndex]types.BlockSeal +} + +// NewHazardSet creates a new HazardSet with the given dependencies and initial block +func NewHazardSet(deps HazardDeps, logger log.Logger, chainID eth.ChainID, block types.BlockSeal) (*HazardSet, error) { + if deps == nil { + return nil, fmt.Errorf("hazard dependencies cannot be nil") + } + h := &HazardSet{ + entries: make(map[types.ChainIndex]types.BlockSeal), + } + logger.Debug("Building new HazardSet", "chainID", chainID, "block", block) + if err := h.build(deps, logger, chainID, block); err != nil { + return nil, fmt.Errorf("failed to build hazard set: %w", err) + } + logger.Debug("Successfully built HazardSet", "chainID", chainID, "block", block) + return h, nil +} + +func NewHazardSetFromEntries(entries map[types.ChainIndex]types.BlockSeal) *HazardSet { + return &HazardSet{entries: entries} +} + +// potentialHazard represents a block that needs to be processed for hazards +type potentialHazard struct { + chainID eth.ChainID + block types.BlockSeal +} + +// checkChainCanExecute verifies that a chain can execute messages at a given timestamp. +// If there are any executing messages, then the chain must be able to execute at the timestamp. +func (h *HazardSet) checkChainCanExecute(depSet depset.DependencySet, chainID eth.ChainID, block types.BlockSeal, execMsgs map[uint32]*types.ExecutingMessage) error { + if len(execMsgs) > 0 { + if ok, err := depSet.CanExecuteAt(chainID, block.Timestamp); err != nil { + return fmt.Errorf("cannot check message execution of block %s (chain %s): %w", block, chainID, err) + } else if !ok { + return fmt.Errorf("cannot execute messages in block %s (chain %s): %w", block, chainID, types.ErrConflict) + } + } + return nil +} + +// checkChainCanInitiate verifies that a chain can initiate messages at a given timestamp. +// The chain must be able to initiate at the timestamp of the message we're referencing. +func (h *HazardSet) checkChainCanInitiate(depSet depset.DependencySet, initChainID eth.ChainID, candidate types.BlockSeal, msg *types.ExecutingMessage) error { + if ok, err := depSet.CanInitiateAt(initChainID, msg.Timestamp); err != nil { + return fmt.Errorf("cannot check message initiation of msg %s (chain %s): %w", msg, initChainID, err) + } else if !ok { + return fmt.Errorf("cannot allow initiating message %s (chain %s): %w", msg, initChainID, types.ErrConflict) + } + return nil +} + +// checkMessageWithOlderTimestamp handles messages from past blocks. +// It ensures non-cyclic ordering relative to other messages. +func (h *HazardSet) checkMessageWithOlderTimestamp(deps HazardDeps, msg *types.ExecutingMessage, initChainID eth.ChainID, includedIn types.BlockSeal, candidateTimestamp uint64) error { + if err := deps.IsCrossValidBlock(initChainID, includedIn.ID()); err != nil { + return fmt.Errorf("msg %s included in non-cross-safe block %s: %w", msg, includedIn, err) + } + // Run expiry window invariant check *after* verifying that the message is non-conflicting. + expiresAt := msg.Timestamp + deps.DependencySet().MessageExpiryWindow() + if expiresAt < candidateTimestamp { + return fmt.Errorf("timestamp of message %s (chain %s) has expired: %d < %d: %w", msg, initChainID, expiresAt, candidateTimestamp, types.ErrConflict) + } + return nil +} + +// checkMessageWithCurrentTimestamp handles messages from the same time as the candidate block. +// We have to inspect ordering of individual log events to ensure non-cyclic cross-chain message ordering. +// And since we may have back-and-forth messaging, we cannot wait till the initiating side is cross-safe. +// Thus check that it was included in a local-safe block, and then proceed with transitive block checks, +// to ensure the local block we depend on is becoming cross-safe also. +// Also returns a boolean indicating if the message already exists in the hazard set. +func (h *HazardSet) checkMessageWithCurrentTimestamp(msg *types.ExecutingMessage, initChainID eth.ChainID, includedIn types.BlockSeal) (bool, error) { + existing, ok := h.entries[msg.Chain] + if ok { + if existing.ID() != includedIn.ID() { + return true, fmt.Errorf("found dependency on %s (chain %d), but already depend on %s", includedIn, initChainID, existing) + } + } + return ok, nil +} + +// build adds a block to the hazard set and recursively adds any blocks that it depends on. +// Warning for future: If we have sub-second distinct blocks (different block number), +// we need to increase precision on the above timestamp invariant. +// Otherwise a local block can depend on a future local block of the same chain, +// simply by pulling in a block of another chain, +// which then depends on a block of the original chain, +// all with the same timestamp, without message cycles. +func (h *HazardSet) build(deps HazardDeps, logger log.Logger, chainID eth.ChainID, block types.BlockSeal) error { + depSet := deps.DependencySet() + stack := []potentialHazard{{chainID: chainID, block: block}} + + for len(stack) > 0 { + next := stack[len(stack)-1] + stack = stack[:len(stack)-1] + candidate := next.block + destChainID := next.chainID + logger.Debug("Processing block for hazards", "chainID", destChainID, "block", candidate) + + // Get the block and ensure it's allowed to execute messages. + opened, _, execMsgs, err := deps.OpenBlock(destChainID, candidate.Number) + if err != nil { + return fmt.Errorf("failed to open block: %w", err) + } + if opened.ID() != candidate.ID() { + return fmt.Errorf("unsafe L2 DB has %s, but candidate cross-safe was %s: %w", opened, candidate, types.ErrConflict) + } + if err := h.checkChainCanExecute(depSet, destChainID, candidate, execMsgs); err != nil { + return err + } + + for _, msg := range execMsgs { + logger.Debug("Processing message", "chainID", destChainID, "block", candidate, "msg", msg) + + // Get the source chain, ensure it's allowed to initiate messages, and contains the initiating message. + srcChainID, err := depSet.ChainIDFromIndex(msg.Chain) + if err != nil { + if errors.Is(err, types.ErrUnknownChain) { + err = fmt.Errorf("msg %s may not execute from unknown chain %s: %w", msg, msg.Chain, types.ErrConflict) + } + return err + } + if err := h.checkChainCanInitiate(depSet, srcChainID, candidate, msg); err != nil { + return err + } + includedIn, err := deps.Contains(srcChainID, + types.ContainsQuery{ + Timestamp: msg.Timestamp, + BlockNum: msg.BlockNum, + LogIdx: msg.LogIdx, + LogHash: msg.Hash, + }) + if err != nil { + return fmt.Errorf("executing msg %s failed inclusion check: %w", msg, err) + } + + if msg.Timestamp < candidate.Timestamp { + if err := h.checkMessageWithOlderTimestamp(deps, msg, srcChainID, includedIn, candidate.Timestamp); err != nil { + return err + } + } else if msg.Timestamp == candidate.Timestamp { + exists, err := h.checkMessageWithCurrentTimestamp(msg, srcChainID, includedIn) + if err != nil { + return err + } + + if !exists { + logger.Debug("Adding block to the hazard set", "chainID", srcChainID, "block", includedIn) + h.entries[msg.Chain] = includedIn + stack = append(stack, potentialHazard{ + chainID: srcChainID, + block: includedIn, + }) + } + } else { + return fmt.Errorf("executing message %s in %s breaks timestamp invariant", msg, candidate) + } + } + } + return nil +} + +func (h *HazardSet) Entries() map[types.ChainIndex]types.BlockSeal { + if h == nil { + return nil + } + return h.entries +} diff --git a/op-supervisor/supervisor/backend/cross/hazard_set_test.go b/op-supervisor/supervisor/backend/cross/hazard_set_test.go new file mode 100644 index 00000000000..3596a36f2ab --- /dev/null +++ b/op-supervisor/supervisor/backend/cross/hazard_set_test.go @@ -0,0 +1,807 @@ +package cross + +import ( + "encoding/binary" + "fmt" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) + +func TestHazardSet_Build(t *testing.T) { + vectors := []testVector{ + { + name: "Empty Message List", + blocks: []blockDef{ + makeBlock(0, 100, 1), + }, + expected: map[types.ChainIndex]types.BlockSeal{}, + }, + { + name: "Single Dependency", + blocks: []blockDef{ + makeBlock(0, 100, 1, makeMessage(1, 100, 1, 1)), + makeBlock(1, 100, 1), // Referenced block from chain 1 + }, + expected: map[types.ChainIndex]types.BlockSeal{ + 1: makeBlockSeal(1, 100, 1), + }, + }, + { + name: "Multiple Messages Same Block", + blocks: []blockDef{ + makeBlock(0, 100, 1, makeMessage(1, 100, 1, 1), makeMessage(1, 100, 1, 2)), + makeBlock(1, 100, 1), + }, + expected: map[types.ChainIndex]types.BlockSeal{ + 1: makeBlockSeal(1, 100, 1), + }, + }, + { + name: "Multiple Messages Different Blocks Different Chains", + blocks: []blockDef{ + makeBlock(0, 100, 1, makeMessage(1, 100, 1, 1), makeMessage(2, 100, 1, 1)), + makeBlock(1, 100, 1), + makeBlock(2, 100, 1), + }, + expected: map[types.ChainIndex]types.BlockSeal{ + 1: makeBlockSeal(1, 100, 1), + 2: makeBlockSeal(1, 100, 2), + }, + }, + { + name: "Multiple Messages Different Blocks Same Chain", + blocks: []blockDef{ + makeBlock(0, 100, 1, makeMessage(1, 100, 1, 1), makeMessage(1, 100, 2, 1)), + makeBlock(1, 100, 1), + makeBlock(1, 100, 2), + }, + expectErr: fmt.Errorf("already depend on"), + }, + { + name: "Hazards Across Multiple Chains", + blocks: []blockDef{ + makeBlock(0, 100, 1, makeMessage(1, 100, 1, 1), makeMessage(2, 100, 1, 1)), + makeBlock(1, 100, 1), + makeBlock(2, 100, 1), + }, + expected: map[types.ChainIndex]types.BlockSeal{ + 1: makeBlockSeal(1, 100, 1), + 2: makeBlockSeal(1, 100, 2), + }, + }, + { + name: "Recursive Hazards", + blocks: []blockDef{ + makeBlock(0, 100, 1, makeMessage(1, 100, 1, 1)), + makeBlock(1, 100, 1, makeMessage(2, 100, 1, 1)), + makeBlock(2, 100, 1), + }, + expected: map[types.ChainIndex]types.BlockSeal{ + 1: makeBlockSeal(1, 100, 1), + 2: makeBlockSeal(1, 100, 2), + }, + }, + { + name: "Recursive Hazards - Missing Intermediate Block", + blocks: []blockDef{ + makeBlock(0, 100, 1, makeMessage(1, 100, 1, 1)), + // Block 1 in Chain 1 is missing + makeBlock(2, 100, 1), + }, + expectErr: types.ErrFuture, + }, + { + name: "Invalid Timestamp - Future Message", + blocks: []blockDef{ + makeBlock(0, 100, 1, makeMessage(1, 200, 1, 1)), // Message timestamp > block timestamp + makeBlock(1, 100, 1), + }, + expectErr: fmt.Errorf("breaks timestamp invariant"), + }, + { + name: "Invalid Timestamp - Zero", + blocks: []blockDef{ + makeBlock(0, 100, 1, makeMessage(1, 0, 1, 1)), + makeBlock(1, 100, 1), + }, + expectErr: types.ErrFuture, + }, + { + name: "Missing Block - Message References Non-existent Block", + blocks: []blockDef{ + makeBlock(0, 100, 1, makeMessage(1, 100, 999, 1)), // Block 999 doesn't exist + }, + expectErr: types.ErrFuture, + }, + { + name: "Missing Block - Chain Break", + blocks: []blockDef{ + makeBlock(0, 100, 1, makeMessage(1, 100, 1, 1)), + makeBlock(1, 100, 1, makeMessage(2, 100, 1, 1)), // Message references block in chain 2 that doesn't exist + }, + expectErr: types.ErrFuture, + }, + { + name: "Invalid Block Number - Zero", + blocks: []blockDef{ + makeBlock(0, 100, 1, makeMessage(1, 100, 0, 1)), // Invalid block number + }, + expectErr: types.ErrFuture, + }, + { + name: "Recursive Hazards - Diamond Pattern", + blocks: []blockDef{ + makeBlock(0, 100, 1, makeMessage(1, 100, 1, 1), makeMessage(2, 100, 1, 1)), + makeBlock(1, 100, 1, makeMessage(3, 100, 1, 1)), + makeBlock(2, 100, 1, makeMessage(3, 100, 1, 1)), + makeBlock(3, 100, 1), + }, + expected: map[types.ChainIndex]types.BlockSeal{ + 1: makeBlockSeal(1, 100, 1), + 2: makeBlockSeal(1, 100, 2), + 3: makeBlockSeal(1, 100, 3), + }, + }, + { + name: "Multiple Independent Chains", + blocks: []blockDef{ + // Chain 0 -> (Chain 1 & Chain 2) + makeBlock(0, 100, 1, makeMessage(1, 100, 1, 1), makeMessage(2, 100, 1, 1)), + + // Chain 1 -> Chain 3 + makeBlock(1, 100, 1, makeMessage(3, 100, 1, 1)), + + // Chain 2 -> Chain 4 + makeBlock(2, 100, 1, makeMessage(4, 100, 1, 1)), + + // No dependencies + makeBlock(3, 100, 1), + makeBlock(4, 100, 1), + }, + expected: map[types.ChainIndex]types.BlockSeal{ + 1: makeBlockSeal(1, 100, 1), + 2: makeBlockSeal(1, 100, 2), + 3: makeBlockSeal(1, 100, 3), + 4: makeBlockSeal(1, 100, 4), + }, + }, + { + name: "Already Processed Block", + blocks: []blockDef{ + makeBlock(0, 100, 1, makeMessage(1, 100, 1, 1), makeMessage(2, 100, 1, 1)), + // Both chain 1 and 2 reference chain 3 + makeBlock(1, 100, 1, makeMessage(3, 100, 1, 1)), + makeBlock(2, 100, 1, makeMessage(3, 100, 1, 1)), + makeBlock(3, 100, 1), + }, + expected: map[types.ChainIndex]types.BlockSeal{ + 1: makeBlockSeal(1, 100, 1), + 2: makeBlockSeal(1, 100, 2), + 3: makeBlockSeal(1, 100, 3), + }, + }, + } + + for _, tc := range vectors { + t.Run(tc.name, func(t *testing.T) { + logger := newTestLogger(t) + + deps := newMockHazardDeps(t, tc) + if tc.verifyBlockFn != nil { + deps.verifyBlockFn = tc.verifyBlockFn + } + + // Create HazardSet with first block + if len(tc.blocks) == 0 { + t.Fatal("test case must have at least one block") + } + firstBlock := tc.blocks[0] + seal := types.BlockSeal{ + Number: firstBlock.number, + Timestamp: firstBlock.timestamp, + Hash: firstBlock.hash, + } + chainID := eth.ChainIDFromUInt64(uint64(firstBlock.chain)) + + hs, err := NewHazardSet(deps, logger, chainID, seal) + if tc.expectErr != nil { + t.Log("error creating hazard set", "block", firstBlock, "error", err) + require.Error(t, err, "expected error %s, got %v", tc.expectErr, err) + require.Contains(t, err.Error(), tc.expectErr.Error(), "expected error %s, got %v", tc.expectErr, err) + return + } + require.NoError(t, err) + require.Equal(t, tc.expected, hs.Entries()) + }) + } +} + +func TestHazardSet_CrossValidBlocks(t *testing.T) { + require := require.New(t) + logger := newTestLogger(t) + depSet := &hazardMockDependencySet{} + + // Helper function to create block hash + makeBlockHash := func(chainID eth.ChainID, num uint64) common.Hash { + var hash common.Hash + // Put the number in the first 8 bytes (consistent format) + binary.BigEndian.PutUint64(hash[:8], num) + // Use the chain ID to distinguish chains + hash[15] = byte(chainID[0]) // Use first byte of chainID + return hash + } + + // Define a struct for test blocks + type testBlockDef struct { + chain eth.ChainID + num uint64 + timestamp uint64 + msgs []struct { + Chain types.ChainIndex + BlockNum uint64 + LogIndex uint32 + Timestamp uint64 + } + } + + // Helper function to create a block map + makeBlockMap := func(blocks []testBlockDef) map[blockKey]blockDef { + blockMap := make(map[blockKey]blockDef) + for _, block := range blocks { + // Extract chain index from chain ID + chainIndex, err := depSet.ChainIndexFromID(block.chain) + if err != nil { + // Use a fallback if error + chainIndex = types.ChainIndex(block.chain[0]) + } + + key := blockKey{ + chain: chainIndex, + number: block.num, + } + + // Convert messages to the expected format + messages := make([]*types.ExecutingMessage, 0, len(block.msgs)) + for _, msg := range block.msgs { + messages = append(messages, &types.ExecutingMessage{ + Chain: msg.Chain, + BlockNum: msg.BlockNum, + LogIdx: msg.LogIndex, + Timestamp: msg.Timestamp, + }) + } + + // Use the provided timestamp or default to 100 + timestamp := block.timestamp + if timestamp == 0 { + timestamp = 100 + } + + blockMap[key] = blockDef{ + chain: chainIndex, + number: block.num, + timestamp: timestamp, + hash: makeBlockHash(block.chain, block.num), + messages: messages, + } + } + return blockMap + } + + // Define test vectors and the expected results for each chain + vectors := []struct { + name string + blocks []testBlockDef + verifyBlockFn func(chainID eth.ChainID, block eth.BlockID) error + expectedHazards map[types.ChainIndex]types.BlockSeal + expectedErr error + }{ + { + name: "Valid: Block Is Cross-Valid", + blocks: []testBlockDef{ + { + chain: eth.ChainIDFromUInt64(0), + num: 10, + timestamp: 100, + msgs: []struct { + Chain types.ChainIndex + BlockNum uint64 + LogIndex uint32 + Timestamp uint64 + }{ + { + Chain: 1, + BlockNum: 5, + LogIndex: 1, + Timestamp: 100, + }, + }, + }, + { + chain: eth.ChainIDFromUInt64(1), + num: 5, + timestamp: 100, + }, + }, + expectedHazards: map[types.ChainIndex]types.BlockSeal{ + 1: { + Hash: makeBlockHash(eth.ChainIDFromUInt64(1), 5), + Number: 5, + Timestamp: 100, + }, + }, + verifyBlockFn: func(chainID eth.ChainID, block eth.BlockID) error { + return fmt.Errorf("block %s is not cross-valid", block) + }, + }, + { + name: "Invalid: Block Is Not Cross-Valid", + blocks: []testBlockDef{ + { + chain: eth.ChainIDFromUInt64(0), + num: 10, + timestamp: 100, + msgs: []struct { + Chain types.ChainIndex + BlockNum uint64 + LogIndex uint32 + Timestamp uint64 + }{ + { + Chain: 1, + BlockNum: 5, + LogIndex: 1, + Timestamp: 100, + }, + }, + }, + { + chain: eth.ChainIDFromUInt64(1), + num: 5, + timestamp: 100, + }, + }, + expectedHazards: map[types.ChainIndex]types.BlockSeal{ + 1: { + Hash: makeBlockHash(eth.ChainIDFromUInt64(1), 5), + Number: 5, + Timestamp: 100, + }, + }, + verifyBlockFn: func(chainID eth.ChainID, block eth.BlockID) error { + // No blocks are considered cross-valid + return fmt.Errorf("block %s is not cross-valid", block) + }, + }, + { + name: "Mix: Some Blocks Are Cross-Valid", + blocks: []testBlockDef{ + { + chain: eth.ChainIDFromUInt64(0), + num: 10, + timestamp: 100, + msgs: []struct { + Chain types.ChainIndex + BlockNum uint64 + LogIndex uint32 + Timestamp uint64 + }{ + { + Chain: 1, + BlockNum: 5, + LogIndex: 1, + Timestamp: 100, + }, + { + Chain: 2, + BlockNum: 5, + LogIndex: 1, + Timestamp: 100, + }, + }, + }, + { + chain: eth.ChainIDFromUInt64(1), + num: 5, + timestamp: 100, + }, + { + chain: eth.ChainIDFromUInt64(2), + num: 5, + timestamp: 100, + }, + }, + expectedHazards: map[types.ChainIndex]types.BlockSeal{ + 1: { + Hash: makeBlockHash(eth.ChainIDFromUInt64(1), 5), + Number: 5, + Timestamp: 100, + }, + 2: { + Hash: makeBlockHash(eth.ChainIDFromUInt64(2), 5), + Number: 5, + Timestamp: 100, + }, + }, + verifyBlockFn: func(chainID eth.ChainID, block eth.BlockID) error { + // Cross-validation check only applies to dependent blocks when hazards are being built + return fmt.Errorf("block %s is not cross-valid", block) + }, + }, + { + name: "Error: Verification Error", + blocks: []testBlockDef{ + { + chain: eth.ChainIDFromUInt64(0), + num: 10, + timestamp: 200, + msgs: []struct { + Chain types.ChainIndex + BlockNum uint64 + LogIndex uint32 + Timestamp uint64 + }{ + { + Chain: 1, + BlockNum: 5, + LogIndex: 1, + Timestamp: 100, + }, + }, + }, + { + chain: eth.ChainIDFromUInt64(1), + num: 5, + timestamp: 100, + }, + }, + expectedErr: fmt.Errorf("failed to build hazard set: msg ExecMsg(chainIndex: 1, block: 5, log: 1, time: 100, logHash: 0x0000000000000000000000000000000000000000000000000000000000000000) included in non-cross-safe block BlockSeal(hash:0x0000000000000005000000000000000100000000000000000000000000000000, number:5, time:100): block 0x0000000000000005000000000000000100000000000000000000000000000000:5 (chain 1) is not cross-valid: verification database error"), + verifyBlockFn: func(chainID eth.ChainID, block eth.BlockID) error { + return fmt.Errorf("verification database error") + }, + }, + { + name: "Recursive: Chain 1 Cross-Valid", + blocks: []testBlockDef{ + { + chain: eth.ChainIDFromUInt64(0), + num: 10, + timestamp: 100, + msgs: []struct { + Chain types.ChainIndex + BlockNum uint64 + LogIndex uint32 + Timestamp uint64 + }{ + { + Chain: 1, + BlockNum: 5, + LogIndex: 1, + Timestamp: 100, + }, + }, + }, + { + chain: eth.ChainIDFromUInt64(1), + num: 5, + timestamp: 100, + msgs: []struct { + Chain types.ChainIndex + BlockNum uint64 + LogIndex uint32 + Timestamp uint64 + }{ + { + Chain: 2, + BlockNum: 5, + LogIndex: 1, + Timestamp: 100, + }, + }, + }, + { + chain: eth.ChainIDFromUInt64(2), + num: 5, + timestamp: 100, + }, + }, + expectedHazards: map[types.ChainIndex]types.BlockSeal{ + 1: { + Hash: makeBlockHash(eth.ChainIDFromUInt64(1), 5), + Number: 5, + Timestamp: 100, + }, + 2: { + Hash: makeBlockHash(eth.ChainIDFromUInt64(2), 5), + Number: 5, + Timestamp: 100, + }, + }, + verifyBlockFn: func(chainID eth.ChainID, block eth.BlockID) error { + // Cross-validation only skips later additions + return fmt.Errorf("block %s is not cross-valid", block) + }, + }, + { + name: "Recursive: Chain 2 Cross-Valid", + blocks: []testBlockDef{ + { + chain: eth.ChainIDFromUInt64(0), + num: 10, + timestamp: 100, + msgs: []struct { + Chain types.ChainIndex + BlockNum uint64 + LogIndex uint32 + Timestamp uint64 + }{ + { + Chain: 1, + BlockNum: 5, + LogIndex: 1, + Timestamp: 50, // Use a lower timestamp to trigger IsCrossValidBlock check + }, + }, + }, + { + chain: eth.ChainIDFromUInt64(1), + num: 5, + timestamp: 100, + msgs: []struct { + Chain types.ChainIndex + BlockNum uint64 + LogIndex uint32 + Timestamp uint64 + }{ + { + Chain: 2, + BlockNum: 5, + LogIndex: 1, + Timestamp: 50, // Use a lower timestamp to trigger IsCrossValidBlock check + }, + }, + }, + { + chain: eth.ChainIDFromUInt64(2), + num: 5, + timestamp: 100, + }, + }, + // This test expects to fail because Chain 1 is not cross-valid + expectedErr: fmt.Errorf("is not cross-valid"), + }, + } + + for _, tc := range vectors { + t.Run(tc.name, func(t *testing.T) { + // Create dependency mock + mockDeps := &mockHazardDeps{ + logger: logger, + deps: depSet, + blockMap: makeBlockMap(tc.blocks), + verifyBlockFn: tc.verifyBlockFn, + } + + // Create the HazardSet for the first block (candidate block) + firstBlock := tc.blocks[0] + candidateSeal := types.BlockSeal{ + Hash: makeBlockHash(firstBlock.chain, firstBlock.num), + Number: firstBlock.num, + Timestamp: firstBlock.timestamp, + } + + // Create the HazardSet - this should recursively build the entire set + hs, err := NewHazardSet(mockDeps, logger, firstBlock.chain, candidateSeal) + if tc.expectedErr != nil { + require.Error(err) + require.Contains(err.Error(), tc.expectedErr.Error()) + return + } + require.NoError(err) + require.NotNil(hs) + + // Verify the hazard set entries match the expected hazards + require.Equal(tc.expectedHazards, hs.Entries()) + }) + } +} + +// mockHazardDeps implements HazardDeps for testing +type mockHazardDeps struct { + logger log.Logger + containsFn func(chain eth.ChainID, query types.ContainsQuery) (types.BlockSeal, error) + verifyBlockFn func(chainID eth.ChainID, block eth.BlockID) error + openBlockFn func(chainID eth.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) + deps depset.DependencySet + blockMap map[blockKey]blockDef +} + +func (m *mockHazardDeps) Contains(chain eth.ChainID, query types.ContainsQuery) (types.BlockSeal, error) { + if m.containsFn != nil { + return m.containsFn(chain, query) + } + // Look up the block in our test data + chainIndex, err := m.ChainIndexFromID(chain) + if err != nil { + return types.BlockSeal{}, err + } + + // Validate timestamp is greater than 0 + if query.Timestamp == 0 { + return types.BlockSeal{}, fmt.Errorf("failed to check if message exists: block not found: %w", types.ErrFuture) + } + + key := blockKey{ + chain: chainIndex, + number: query.BlockNum, + } + if block, ok := m.blockMap[key]; ok { + // Check timestamp invariant + if query.Timestamp > block.timestamp { + return types.BlockSeal{}, fmt.Errorf("message timestamp %d breaks timestamp invariant with block timestamp %d", query.Timestamp, block.timestamp) + } + return types.BlockSeal{ + Number: block.number, + Timestamp: block.timestamp, + Hash: block.hash, + }, nil + } + return types.BlockSeal{}, fmt.Errorf("failed to check if message exists: block not found: %w", types.ErrFuture) +} + +func (m *mockHazardDeps) IsCrossValidBlock(chainID eth.ChainID, block eth.BlockID) error { + if m.verifyBlockFn != nil { + err := m.verifyBlockFn(chainID, block) + if err != nil { + // Format a clear error message that includes both the block and chain information + // This ensures errors are properly identified and propagated + chainIdx, _ := m.deps.ChainIndexFromID(chainID) + return fmt.Errorf("block %s (chain %d) is not cross-valid: %w", block, chainIdx, err) + } + return nil + } + // By default, blocks are not cross-valid + return fmt.Errorf("block %s is not cross-valid", block) +} + +func (m *mockHazardDeps) OpenBlock(chainID eth.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + if m.openBlockFn != nil { + return m.openBlockFn(chainID, blockNum) + } + // Look up the block in our test data + chainIndex, err := m.ChainIndexFromID(chainID) + if err != nil { + return eth.BlockRef{}, 0, nil, fmt.Errorf("failed to get chain index: %w", err) + } + key := blockKey{ + chain: chainIndex, + number: blockNum, + } + if block, ok := m.blockMap[key]; ok { + // Convert messages slice to map + msgMap := make(map[uint32]*types.ExecutingMessage) + for i, msg := range block.messages { + msgMap[uint32(i)] = msg + } + m.Logger().Debug("Opening block", "chainID", chainID, "blockNum", blockNum, "hash", block.hash.String()[:6]+".."+block.hash.String()[60:]) + return eth.BlockRef{ + Hash: block.hash, + Number: block.number, + Time: block.timestamp, + }, uint32(len(block.messages)), msgMap, nil + } + return eth.BlockRef{}, 0, nil, types.ErrFuture +} + +func (m *mockHazardDeps) DependencySet() depset.DependencySet { + return m.deps +} + +func (m *mockHazardDeps) ChainIndexFromID(id eth.ChainID) (types.ChainIndex, error) { + return m.deps.ChainIndexFromID(id) +} + +func (m *mockHazardDeps) Logger() log.Logger { + return m.logger +} + +func makeBlock(chain types.ChainIndex, timestamp, number uint64, messages ...*types.ExecutingMessage) blockDef { + return blockDef{ + number: number, + timestamp: timestamp, + chain: chain, + hash: common.Hash{byte(chain), byte(number)}, // Deterministic hash based on chain and number + messages: messages, + } +} + +func makeMessage(chain types.ChainIndex, timestamp, blockNum uint64, logIdx uint32) *types.ExecutingMessage { + return &types.ExecutingMessage{ + Chain: chain, + BlockNum: blockNum, + Timestamp: timestamp, + LogIdx: logIdx, + } +} + +func makeBlockSeal(number, timestamp uint64, chain types.ChainIndex) types.BlockSeal { + return types.BlockSeal{ + Number: number, + Timestamp: timestamp, + Hash: common.Hash{byte(chain), byte(number)}, // Match block hash generation + } +} + +type testVector struct { + name string + blocks []blockDef + expected map[types.ChainIndex]types.BlockSeal + expectErr error + verifyBlockFn func(chainID eth.ChainID, block eth.BlockID) error +} + +type blockDef struct { + number uint64 + timestamp uint64 + hash common.Hash + chain types.ChainIndex + messages []*types.ExecutingMessage +} + +func newMockHazardDeps(t *testing.T, tc testVector) *mockHazardDeps { + t.Helper() + + // Create a map of all blocks for quick lookup + blockMap := make(map[blockKey]blockDef) + for _, block := range tc.blocks { + key := blockKey{ + chain: block.chain, + number: block.number, + } + blockMap[key] = block + } + + mock := &mockHazardDeps{ + logger: newTestLogger(t), + deps: &hazardMockDependencySet{}, + blockMap: blockMap, + } + + // Set the verifyBlockFn if provided in the test vector + if tc.verifyBlockFn != nil { + mock.verifyBlockFn = tc.verifyBlockFn + } + + return mock +} + +// hazardMockDependencySet wraps mockDependencySet and makes it easier to test +// with 0 chainIndex values. +type hazardMockDependencySet struct { + mockDependencySet +} + +func (m hazardMockDependencySet) ChainIDFromIndex(idx types.ChainIndex) (eth.ChainID, error) { + return eth.ChainIDFromUInt64(uint64(idx)), nil +} + +func (m hazardMockDependencySet) ChainIndexFromID(id eth.ChainID) (types.ChainIndex, error) { + return types.ChainIndex(id[0]), nil +} + +type blockKey struct { + chain types.ChainIndex + number uint64 +} + +func newTestLogger(t *testing.T) log.Logger { + return testlog.Logger(t, log.LevelDebug) +} diff --git a/op-supervisor/supervisor/backend/cross/safe_frontier.go b/op-supervisor/supervisor/backend/cross/safe_frontier.go index 7922ff3fd55..94f4d5666cc 100644 --- a/op-supervisor/supervisor/backend/cross/safe_frontier.go +++ b/op-supervisor/supervisor/backend/cross/safe_frontier.go @@ -21,9 +21,9 @@ type SafeFrontierCheckDeps interface { // - already cross-safe. // - the first (if not first: local blocks to verify before proceeding) // local-safe block, after the cross-safe block. -func HazardSafeFrontierChecks(d SafeFrontierCheckDeps, inL1Source eth.BlockID, hazards map[types.ChainIndex]types.BlockSeal) error { +func HazardSafeFrontierChecks(d SafeFrontierCheckDeps, inL1Source eth.BlockID, hazards *HazardSet) error { depSet := d.DependencySet() - for hazardChainIndex, hazardBlock := range hazards { + for hazardChainIndex, hazardBlock := range hazards.Entries() { hazardChainID, err := depSet.ChainIDFromIndex(hazardChainIndex) if err != nil { if errors.Is(err, types.ErrUnknownChain) { diff --git a/op-supervisor/supervisor/backend/cross/safe_frontier_test.go b/op-supervisor/supervisor/backend/cross/safe_frontier_test.go index 157b7ed8602..f5ed64fd020 100644 --- a/op-supervisor/supervisor/backend/cross/safe_frontier_test.go +++ b/op-supervisor/supervisor/backend/cross/safe_frontier_test.go @@ -18,7 +18,7 @@ func TestHazardSafeFrontierChecks(t *testing.T) { hazards := map[types.ChainIndex]types.BlockSeal{} // when there are no hazards, // no work is done, and no error is returned - err := HazardSafeFrontierChecks(sfcd, l1Source, hazards) + err := HazardSafeFrontierChecks(sfcd, l1Source, NewHazardSetFromEntries(hazards)) require.NoError(t, err) }) t.Run("unknown chain", func(t *testing.T) { @@ -33,7 +33,7 @@ func TestHazardSafeFrontierChecks(t *testing.T) { hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {}} // when there is one hazard, and ChainIDFromIndex returns ErrUnknownChain, // an error is returned as a ErrConflict - err := HazardSafeFrontierChecks(sfcd, l1Source, hazards) + err := HazardSafeFrontierChecks(sfcd, l1Source, NewHazardSetFromEntries(hazards)) require.ErrorIs(t, err, types.ErrConflict) }) t.Run("initSource in scope", func(t *testing.T) { @@ -46,7 +46,7 @@ func TestHazardSafeFrontierChecks(t *testing.T) { // when there is one hazard, and CrossSource returns a BlockSeal within scope // (ie the hazard's block number is less than or equal to the source block number), // no error is returned - err := HazardSafeFrontierChecks(sfcd, l1Source, hazards) + err := HazardSafeFrontierChecks(sfcd, l1Source, NewHazardSetFromEntries(hazards)) require.NoError(t, err) }) t.Run("initSource out of scope", func(t *testing.T) { @@ -59,7 +59,7 @@ func TestHazardSafeFrontierChecks(t *testing.T) { // when there is one hazard, and CrossSource returns a BlockSeal out of scope // (ie the hazard's block number is greater than the source block number), // an error is returned as a ErrOutOfScope - err := HazardSafeFrontierChecks(sfcd, l1Source, hazards) + err := HazardSafeFrontierChecks(sfcd, l1Source, NewHazardSetFromEntries(hazards)) require.ErrorIs(t, err, types.ErrOutOfScope) }) t.Run("errFuture: candidate cross safe failure", func(t *testing.T) { @@ -78,7 +78,7 @@ func TestHazardSafeFrontierChecks(t *testing.T) { // when there is one hazard, and CrossSource returns an ErrFuture, // and CandidateCrossSafe returns an error, // the error from CandidateCrossSafe is returned - err := HazardSafeFrontierChecks(sfcd, l1Source, hazards) + err := HazardSafeFrontierChecks(sfcd, l1Source, NewHazardSetFromEntries(hazards)) require.ErrorContains(t, err, "some error") }) t.Run("errFuture: expected block does not match candidate", func(t *testing.T) { @@ -98,7 +98,7 @@ func TestHazardSafeFrontierChecks(t *testing.T) { // and CandidateCrossSafe returns a candidate that does not match the hazard, // (ie the candidate's block number is the same as the hazard's block number, but the hashes are different), // an error is returned as a ErrConflict - err := HazardSafeFrontierChecks(sfcd, l1Source, hazards) + err := HazardSafeFrontierChecks(sfcd, l1Source, NewHazardSetFromEntries(hazards)) require.ErrorIs(t, err, types.ErrConflict) }) t.Run("errFuture: local-safe hazard out of scope", func(t *testing.T) { @@ -117,7 +117,7 @@ func TestHazardSafeFrontierChecks(t *testing.T) { // when there is one hazard, and CrossSource returns an ErrFuture, // and the initSource is out of scope, // an error is returned as a ErrOutOfScope - err := HazardSafeFrontierChecks(sfcd, l1Source, hazards) + err := HazardSafeFrontierChecks(sfcd, l1Source, NewHazardSetFromEntries(hazards)) require.ErrorIs(t, err, types.ErrOutOfScope) }) t.Run("CrossSource Error", func(t *testing.T) { @@ -136,7 +136,7 @@ func TestHazardSafeFrontierChecks(t *testing.T) { // when there is one hazard, and CrossSource returns an ErrFuture, // and the initSource is out of scope, // an error is returned as a ErrOutOfScope - err := HazardSafeFrontierChecks(sfcd, l1Source, hazards) + err := HazardSafeFrontierChecks(sfcd, l1Source, NewHazardSetFromEntries(hazards)) require.ErrorContains(t, err, "some error") }) } diff --git a/op-supervisor/supervisor/backend/cross/safe_start.go b/op-supervisor/supervisor/backend/cross/safe_start.go index f4b5061d007..0b65a1cdca6 100644 --- a/op-supervisor/supervisor/backend/cross/safe_start.go +++ b/op-supervisor/supervisor/backend/cross/safe_start.go @@ -1,12 +1,12 @@ package cross import ( - "errors" "fmt" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum/go-ethereum/log" ) type SafeStartDeps interface { @@ -15,104 +15,31 @@ type SafeStartDeps interface { CrossDerivedToSource(chainID eth.ChainID, derived eth.BlockID) (source types.BlockSeal, err error) DependencySet() depset.DependencySet + + OpenBlock(chainID eth.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) } // CrossSafeHazards checks if the given messages all exist and pass invariants. // It returns a hazard-set: if any intra-block messaging happened, // these hazard blocks have to be verified. -func CrossSafeHazards(d SafeStartDeps, chainID eth.ChainID, inL1Source eth.BlockID, - candidate types.BlockSeal, execMsgs []*types.ExecutingMessage) (hazards map[types.ChainIndex]types.BlockSeal, err error) { - - hazards = make(map[types.ChainIndex]types.BlockSeal) - - // Warning for future: If we have sub-second distinct blocks (different block number), - // we need to increase precision on the above timestamp invariant. - // Otherwise a local block can depend on a future local block of the same chain, - // simply by pulling in a block of another chain, - // which then depends on a block of the original chain, - // all with the same timestamp, without message cycles. +func CrossSafeHazards(d SafeStartDeps, logger log.Logger, chainID eth.ChainID, inL1Source eth.BlockID, candidate types.BlockSeal) (*HazardSet, error) { + safeDeps := &SafeHazardDeps{SafeStartDeps: d, inL1Source: inL1Source} + return NewHazardSet(safeDeps, logger, chainID, candidate) +} - depSet := d.DependencySet() +type SafeHazardDeps struct { + SafeStartDeps + inL1Source eth.BlockID +} - if len(execMsgs) > 0 { - if ok, err := depSet.CanExecuteAt(chainID, candidate.Timestamp); err != nil { - return nil, fmt.Errorf("cannot check message execution of block %s (chain %s): %w", candidate, chainID, err) - } else if !ok { - return nil, fmt.Errorf("cannot execute messages in block %s (chain %s): %w", candidate, chainID, types.ErrConflict) - } +func (d *SafeHazardDeps) IsCrossValidBlock(chainID eth.ChainID, derived eth.BlockID) error { + initSource, err := d.CrossDerivedToSource(chainID, derived) + if err != nil { + return fmt.Errorf("non-cross-safe block %s: %w", derived, err) } - - // check all executing messages - for _, msg := range execMsgs { - initChainID, err := depSet.ChainIDFromIndex(msg.Chain) - if err != nil { - if errors.Is(err, types.ErrUnknownChain) { - err = fmt.Errorf("msg %s may not execute from unknown chain %s: %w", msg, msg.Chain, types.ErrConflict) - } - return nil, err - } - if ok, err := depSet.CanInitiateAt(initChainID, msg.Timestamp); err != nil { - return nil, fmt.Errorf("cannot check message initiation of msg %s (chain %s): %w", msg, chainID, err) - } else if !ok { - return nil, fmt.Errorf("cannot allow initiating message %s (chain %s): %w", msg, chainID, types.ErrConflict) - } - if msg.Timestamp < candidate.Timestamp { - // If timestamp is older: invariant ensures non-cyclic ordering relative to other messages. - // Check that the block that they are included in is cross-safe already. - includedIn, err := d.Contains(initChainID, - types.ContainsQuery{ - Timestamp: msg.Timestamp, - BlockNum: msg.BlockNum, - LogIdx: msg.LogIdx, - LogHash: msg.Hash, - }) - if err != nil { - return nil, fmt.Errorf("executing msg %s failed check: %w", msg, err) - } - initSource, err := d.CrossDerivedToSource(initChainID, includedIn.ID()) - if err != nil { - return nil, fmt.Errorf("msg %s included in non-cross-safe block %s: %w", msg, includedIn, err) - } - if initSource.Number > inL1Source.Number { - return nil, fmt.Errorf("msg %s was included in block %s derived from %s which is not in cross-safe scope %s: %w", - msg, includedIn, initSource, inL1Source, types.ErrOutOfScope) - } - // Run expiry window invariant check *after* verifying that the message is non-conflicting. - if msg.Timestamp+depSet.MessageExpiryWindow() < candidate.Timestamp { - return nil, fmt.Errorf("timestamp of message %s (chain %s) has expired: %d < %d: %w", msg, chainID, msg.Timestamp+depSet.MessageExpiryWindow(), candidate.Timestamp, types.ErrConflict) - } - } else if msg.Timestamp == candidate.Timestamp { - // If timestamp is equal: we have to inspect ordering of individual - // log events to ensure non-cyclic cross-chain message ordering. - // And since we may have back-and-forth messaging, we cannot wait till the initiating side is cross-safe. - // Thus check that it was included in a local-safe block, - // and then proceed with transitive block checks, - // to ensure the local block we depend on is becoming cross-safe also. - includedIn, err := d.Contains(initChainID, - types.ContainsQuery{ - Timestamp: msg.Timestamp, - BlockNum: msg.BlockNum, - LogIdx: msg.LogIdx, - LogHash: msg.Hash, - }) - if err != nil { - return nil, fmt.Errorf("executing msg %s failed check: %w", msg, err) - } - // As a hazard block, it will be checked to be included in a cross-safe block, - // or right after a cross-safe block in a local-safe block, in HazardSafeFrontierChecks. - if existing, ok := hazards[msg.Chain]; ok { - if existing != includedIn { - return nil, fmt.Errorf("found dependency on %s (chain %d), but already depend on %s", includedIn, initChainID, chainID) - } - } else { - // Mark it as hazard block - hazards[msg.Chain] = includedIn - } - } else { - // Timestamp invariant is broken: executing message tries to execute future block. - // The predeploy inbox contract should not have allowed this executing message through. - return nil, fmt.Errorf("executing message %s in %s breaks timestamp invariant", msg, candidate) - } + if initSource.Number > d.inL1Source.Number { + return fmt.Errorf("block %s derived from %s which is not in cross-safe scope %s: %w", + derived, initSource, d.inL1Source, types.ErrOutOfScope) } - return hazards, nil + return nil } diff --git a/op-supervisor/supervisor/backend/cross/safe_start_test.go b/op-supervisor/supervisor/backend/cross/safe_start_test.go index fb6ae7dd812..388064f80f0 100644 --- a/op-supervisor/supervisor/backend/cross/safe_start_test.go +++ b/op-supervisor/supervisor/backend/cross/safe_start_test.go @@ -17,12 +17,11 @@ func TestCrossSafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) inL1Source := eth.BlockID{} candidate := types.BlockSeal{} - execMsgs := []*types.ExecutingMessage{} // when there are no execMsgs, // no work is done, and no error is returned - hazards, err := CrossSafeHazards(ssd, chainID, inL1Source, candidate, execMsgs) + hazards, err := CrossSafeHazards(ssd, newTestLogger(t), chainID, inL1Source, candidate) require.NoError(t, err) - require.Empty(t, hazards) + require.Empty(t, hazards.Entries()) }) t.Run("CanExecuteAt returns false", func(t *testing.T) { ssd := &mockSafeStartDeps{} @@ -34,10 +33,10 @@ func TestCrossSafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) inL1Source := eth.BlockID{} candidate := types.BlockSeal{} - execMsgs := []*types.ExecutingMessage{{}} + ssd.openBlockFn = newOpenBlockFn(&types.ExecutingMessage{}) // when there is one execMsg, and CanExecuteAt returns false, // no work is done and an error is returned - hazards, err := CrossSafeHazards(ssd, chainID, inL1Source, candidate, execMsgs) + hazards, err := CrossSafeHazards(ssd, newTestLogger(t), chainID, inL1Source, candidate) require.ErrorIs(t, err, types.ErrConflict) require.Empty(t, hazards) }) @@ -51,10 +50,10 @@ func TestCrossSafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) inL1Source := eth.BlockID{} candidate := types.BlockSeal{} - execMsgs := []*types.ExecutingMessage{{}} + ssd.openBlockFn = newOpenBlockFn(&types.ExecutingMessage{}) // when there is one execMsg, and CanExecuteAt returns false, // no work is done and an error is returned - hazards, err := CrossSafeHazards(ssd, chainID, inL1Source, candidate, execMsgs) + hazards, err := CrossSafeHazards(ssd, newTestLogger(t), chainID, inL1Source, candidate) require.ErrorContains(t, err, "some error") require.Empty(t, hazards) }) @@ -68,10 +67,10 @@ func TestCrossSafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) inL1Source := eth.BlockID{} candidate := types.BlockSeal{} - execMsgs := []*types.ExecutingMessage{{}} + ssd.openBlockFn = newOpenBlockFn(&types.ExecutingMessage{}) // when there is one execMsg, and ChainIDFromIndex returns ErrUnknownChain, // an error is returned as a ErrConflict - hazards, err := CrossSafeHazards(ssd, chainID, inL1Source, candidate, execMsgs) + hazards, err := CrossSafeHazards(ssd, newTestLogger(t), chainID, inL1Source, candidate) require.ErrorIs(t, err, types.ErrConflict) require.Empty(t, hazards) }) @@ -85,10 +84,10 @@ func TestCrossSafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) inL1Source := eth.BlockID{} candidate := types.BlockSeal{} - execMsgs := []*types.ExecutingMessage{{}} + ssd.openBlockFn = newOpenBlockFn(&types.ExecutingMessage{}) // when there is one execMsg, and ChainIDFromIndex returns some other error, // the error is returned - hazards, err := CrossSafeHazards(ssd, chainID, inL1Source, candidate, execMsgs) + hazards, err := CrossSafeHazards(ssd, newTestLogger(t), chainID, inL1Source, candidate) require.ErrorContains(t, err, "some error") require.Empty(t, hazards) }) @@ -102,10 +101,10 @@ func TestCrossSafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) inL1Source := eth.BlockID{} candidate := types.BlockSeal{} - execMsgs := []*types.ExecutingMessage{{}} + ssd.openBlockFn = newOpenBlockFn(&types.ExecutingMessage{}) // when there is one execMsg, and CanInitiateAt returns false, // the error is returned as a ErrConflict - hazards, err := CrossSafeHazards(ssd, chainID, inL1Source, candidate, execMsgs) + hazards, err := CrossSafeHazards(ssd, newTestLogger(t), chainID, inL1Source, candidate) require.ErrorIs(t, err, types.ErrConflict) require.Empty(t, hazards) }) @@ -119,10 +118,10 @@ func TestCrossSafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) inL1Source := eth.BlockID{} candidate := types.BlockSeal{} - execMsgs := []*types.ExecutingMessage{{}} + ssd.openBlockFn = newOpenBlockFn(&types.ExecutingMessage{}) // when there is one execMsg, and CanInitiateAt returns an error, // the error is returned - hazards, err := CrossSafeHazards(ssd, chainID, inL1Source, candidate, execMsgs) + hazards, err := CrossSafeHazards(ssd, newTestLogger(t), chainID, inL1Source, candidate) require.ErrorContains(t, err, "some error") require.Empty(t, hazards) }) @@ -132,11 +131,12 @@ func TestCrossSafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) inL1Source := eth.BlockID{} candidate := types.BlockSeal{Timestamp: 2} + ssd.candidate = candidate em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 10} - execMsgs := []*types.ExecutingMessage{em1} + ssd.openBlockFn = newOpenBlockFn(em1) // when there is one execMsg, and the timestamp is greater than the candidate, // an error is returned - hazards, err := CrossSafeHazards(ssd, chainID, inL1Source, candidate, execMsgs) + hazards, err := CrossSafeHazards(ssd, newTestLogger(t), chainID, inL1Source, candidate) require.ErrorContains(t, err, "breaks timestamp invariant") require.Empty(t, hazards) }) @@ -149,40 +149,56 @@ func TestCrossSafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) inL1Source := eth.BlockID{} candidate := types.BlockSeal{Timestamp: 2} + ssd.candidate = candidate em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 2} - execMsgs := []*types.ExecutingMessage{em1} - // when there is one execMsg, and the timetamp is equal to the candidate, + ssd.openBlockFn = newOpenBlockFn(em1) + // when there is one execMsg, and the timestamp is equal to the candidate, // and check returns an error, // that error is returned - hazards, err := CrossSafeHazards(ssd, chainID, inL1Source, candidate, execMsgs) + hazards, err := CrossSafeHazards(ssd, newTestLogger(t), chainID, inL1Source, candidate) require.ErrorContains(t, err, "some error") require.Empty(t, hazards) }) t.Run("timestamp is equal, same hazard twice", func(t *testing.T) { ssd := &mockSafeStartDeps{} - sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x02})} + sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x02}), Timestamp: 2} ssd.checkFn = func() (includedIn types.BlockSeal, err error) { return sampleBlockSeal, nil } ssd.deps = mockDependencySet{} chainID := eth.ChainIDFromUInt64(0) inL1Source := eth.BlockID{} - candidate := types.BlockSeal{Timestamp: 2} + candidate := types.BlockSeal{Number: 0, Hash: common.Hash{}, Timestamp: 2} + ssd.candidate = candidate em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 2} em2 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 2} - execMsgs := []*types.ExecutingMessage{em1, em2} + ssd.openBlockFn = func(chainID eth.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + if blockNum == candidate.Number { + return eth.BlockRef{ + Hash: candidate.Hash, + Time: candidate.Timestamp, + Number: candidate.Number, + }, 2, map[uint32]*types.ExecutingMessage{0: em1, 1: em2}, nil + } + return eth.BlockRef{ + Hash: sampleBlockSeal.Hash, + Time: sampleBlockSeal.Timestamp, + Number: sampleBlockSeal.Number, + }, 2, map[uint32]*types.ExecutingMessage{0: em1, 1: em2}, nil + } // when there are two execMsgs, and both are equal time to the candidate, // and check returns the same includedIn for both // they load the hazards once, and return no error - hazards, err := CrossSafeHazards(ssd, chainID, inL1Source, candidate, execMsgs) + hazards, err := CrossSafeHazards(ssd, newTestLogger(t), chainID, inL1Source, candidate) require.NoError(t, err) - require.Equal(t, hazards, map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): sampleBlockSeal}) + require.Equal(t, map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): sampleBlockSeal}, hazards.Entries()) }) t.Run("timestamp is equal, different hazards", func(t *testing.T) { + logger := newTestLogger(t) ssd := &mockSafeStartDeps{} // set the check function to return a different BlockSeal for the second call - sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x02})} - sampleBlockSeal2 := types.BlockSeal{Number: 333, Hash: common.BytesToHash([]byte{0x22})} + sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x02}), Timestamp: 2} + sampleBlockSeal2 := types.BlockSeal{Number: 333, Hash: common.BytesToHash([]byte{0x22}), Timestamp: 2} calls := 0 ssd.checkFn = func() (includedIn types.BlockSeal, err error) { defer func() { calls++ }() @@ -195,17 +211,19 @@ func TestCrossSafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) inL1Source := eth.BlockID{} candidate := types.BlockSeal{Timestamp: 2} + ssd.candidate = candidate em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 2} em2 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 2} - execMsgs := []*types.ExecutingMessage{em1, em2} + ssd.openBlockFn = newOpenBlockFn(em1, em2) // when there are two execMsgs, and both are equal time to the candidate, // and check returns different includedIn for the two, // an error is returned - hazards, err := CrossSafeHazards(ssd, chainID, inL1Source, candidate, execMsgs) + hazards, err := CrossSafeHazards(ssd, logger, chainID, inL1Source, candidate) require.ErrorContains(t, err, "but already depend on") require.Empty(t, hazards) }) t.Run("timestamp is less, check returns error", func(t *testing.T) { + logger := newTestLogger(t) ssd := &mockSafeStartDeps{} ssd.checkFn = func() (includedIn types.BlockSeal, err error) { return types.BlockSeal{}, errors.New("some error") @@ -214,18 +232,20 @@ func TestCrossSafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) inL1Source := eth.BlockID{} candidate := types.BlockSeal{Timestamp: 2} + ssd.candidate = candidate em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} - execMsgs := []*types.ExecutingMessage{em1} + ssd.openBlockFn = newOpenBlockFn(em1) // when there is one execMsg, and the timestamp is less than the candidate, // and check returns an error, // that error is returned - hazards, err := CrossSafeHazards(ssd, chainID, inL1Source, candidate, execMsgs) + hazards, err := CrossSafeHazards(ssd, logger, chainID, inL1Source, candidate) require.ErrorContains(t, err, "some error") require.Empty(t, hazards) }) t.Run("timestamp is less, DerivedToSource returns error", func(t *testing.T) { + logger := newTestLogger(t) ssd := &mockSafeStartDeps{} - sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x02})} + sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x02}), Timestamp: 2} ssd.checkFn = func() (includedIn types.BlockSeal, err error) { return sampleBlockSeal, nil } @@ -236,16 +256,18 @@ func TestCrossSafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) inL1Source := eth.BlockID{} candidate := types.BlockSeal{Timestamp: 2} + ssd.candidate = candidate em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} - execMsgs := []*types.ExecutingMessage{em1} + ssd.openBlockFn = newOpenBlockFn(em1) // when there is one execMsg, and the timestamp is less than the candidate, // and DerivedToSource returns aan error, // that error is returned - hazards, err := CrossSafeHazards(ssd, chainID, inL1Source, candidate, execMsgs) + hazards, err := CrossSafeHazards(ssd, logger, chainID, inL1Source, candidate) require.ErrorContains(t, err, "some error") require.Empty(t, hazards) }) t.Run("timestamp is less, DerivedToSource Number is greater", func(t *testing.T) { + logger := newTestLogger(t) ssd := &mockSafeStartDeps{} sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x02})} ssd.checkFn = func() (includedIn types.BlockSeal, err error) { @@ -259,22 +281,21 @@ func TestCrossSafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) inL1Source := eth.BlockID{} candidate := types.BlockSeal{Timestamp: 2} + ssd.candidate = candidate em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} - execMsgs := []*types.ExecutingMessage{em1} + ssd.openBlockFn = newOpenBlockFn(em1) // when there is one execMsg, and the timestamp is less than the candidate, // and DerivedToSource returns a BlockSeal with a greater Number than the inL1Source, // an error is returned as a ErrOutOfScope - hazards, err := CrossSafeHazards(ssd, chainID, inL1Source, candidate, execMsgs) + hazards, err := CrossSafeHazards(ssd, logger, chainID, inL1Source, candidate) require.ErrorIs(t, err, types.ErrOutOfScope) require.Empty(t, hazards) }) t.Run("timestamp is less, DerivedToSource Number less", func(t *testing.T) { + logger := newTestLogger(t) ssd := &mockSafeStartDeps{} - sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x02})} - ssd.checkFn = func() (includedIn types.BlockSeal, err error) { - return sampleBlockSeal, nil - } - sampleSource := types.BlockSeal{Number: 1, Hash: common.BytesToHash([]byte{0x03})} + // Don't need checkFn since we want no hazards + sampleSource := types.BlockSeal{Number: 1, Hash: common.BytesToHash([]byte{0x03}), Timestamp: 3} ssd.derivedToSrcFn = func() (source types.BlockSeal, err error) { return sampleSource, nil } @@ -282,68 +303,69 @@ func TestCrossSafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) inL1Source := eth.BlockID{Number: 10} candidate := types.BlockSeal{Timestamp: 2} + ssd.candidate = candidate em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} - execMsgs := []*types.ExecutingMessage{em1} + ssd.openBlockFn = newOpenBlockFn(em1) // when there is one execMsg, and the timestamp is less than the candidate, // and DerivedToSource returns a BlockSeal with a smaller Number than the inL1Source, // no error is returned - hazards, err := CrossSafeHazards(ssd, chainID, inL1Source, candidate, execMsgs) + hazards, err := CrossSafeHazards(ssd, logger, chainID, inL1Source, candidate) require.NoError(t, err) - require.Empty(t, hazards) + require.Empty(t, hazards.Entries()) }) t.Run("timestamp is less, DerivedToSource Number equal", func(t *testing.T) { + logger := newTestLogger(t) ssd := &mockSafeStartDeps{} - sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x02})} - ssd.checkFn = func() (includedIn types.BlockSeal, err error) { - return sampleBlockSeal, nil - } - sampleSource := types.BlockSeal{Number: 1, Hash: common.BytesToHash([]byte{0x03})} + // Don't need checkFn since we want no hazards ssd.derivedToSrcFn = func() (source types.BlockSeal, err error) { - return sampleSource, nil + return types.BlockSeal{Number: 1, Hash: common.BytesToHash([]byte{0x03}), Timestamp: 1}, nil } ssd.deps = mockDependencySet{} chainID := eth.ChainIDFromUInt64(0) inL1Source := eth.BlockID{Number: 1} candidate := types.BlockSeal{Timestamp: 2} + ssd.candidate = candidate em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} - execMsgs := []*types.ExecutingMessage{em1} + ssd.openBlockFn = newOpenBlockFn(em1) // when there is one execMsg, and the timestamp is less than the candidate, // and DerivedToSource returns a BlockSeal with a equal to the Number of inL1Source, // no error is returned - hazards, err := CrossSafeHazards(ssd, chainID, inL1Source, candidate, execMsgs) + hazards, err := CrossSafeHazards(ssd, logger, chainID, inL1Source, candidate) require.NoError(t, err) - require.Empty(t, hazards) + require.Empty(t, hazards.Entries()) }) t.Run("message expiry", func(t *testing.T) { + logger := newTestLogger(t) ssd := &mockSafeStartDeps{} ssd.deps.messageExpiryWindow = 10 chainID := eth.ChainIDFromUInt64(0) inL1Source := eth.BlockID{Number: 1} candidate := types.BlockSeal{Timestamp: 12} em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} - execMsgs := []*types.ExecutingMessage{em1} + ssd.openBlockFn = newOpenBlockFn(em1) // when there is one execMsg, and the timestamp is less than the candidate, // and DerivedToSource returns a BlockSeal with a equal to the Number of inL1Source, // no error is returned - hazards, err := CrossSafeHazards(ssd, chainID, inL1Source, candidate, execMsgs) + hazards, err := CrossSafeHazards(ssd, logger, chainID, inL1Source, candidate) require.ErrorIs(t, err, types.ErrConflict) require.ErrorContains(t, err, "has expired") - require.Empty(t, hazards) + require.Empty(t, hazards.Entries()) }) t.Run("message close to expiry", func(t *testing.T) { + logger := newTestLogger(t) ssd := &mockSafeStartDeps{} ssd.deps.messageExpiryWindow = 10 chainID := eth.ChainIDFromUInt64(0) inL1Source := eth.BlockID{Number: 1} candidate := types.BlockSeal{Timestamp: 11} em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} - execMsgs := []*types.ExecutingMessage{em1} + ssd.openBlockFn = newOpenBlockFn(em1) // when there is one execMsg, and the timestamp is less than the candidate, // and DerivedToSource returns a BlockSeal with a equal to the Number of inL1Source, // no error is returned - hazards, err := CrossSafeHazards(ssd, chainID, inL1Source, candidate, execMsgs) + hazards, err := CrossSafeHazards(ssd, logger, chainID, inL1Source, candidate) require.NoError(t, err) - require.Empty(t, hazards) + require.Empty(t, hazards.Entries()) }) } @@ -351,9 +373,11 @@ type mockSafeStartDeps struct { deps mockDependencySet checkFn func() (includedIn types.BlockSeal, err error) derivedToSrcFn func() (source types.BlockSeal, err error) + openBlockFn func(chainID eth.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) + candidate types.BlockSeal } -func (m *mockSafeStartDeps) Contains(chain eth.ChainID, q types.ContainsQuery) (includedIn types.BlockSeal, err error) { +func (m *mockSafeStartDeps) Contains(chain eth.ChainID, query types.ContainsQuery) (includedIn types.BlockSeal, err error) { if m.checkFn != nil { return m.checkFn() } @@ -370,3 +394,11 @@ func (m *mockSafeStartDeps) CrossDerivedToSource(chainID eth.ChainID, derived et func (m *mockSafeStartDeps) DependencySet() depset.DependencySet { return m.deps } + +func (m *mockSafeStartDeps) OpenBlock(chainID eth.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + if m.openBlockFn != nil { + return m.openBlockFn(chainID, blockNum) + } + execMsgs = make(map[uint32]*types.ExecutingMessage) + return eth.BlockRef{Time: m.candidate.Timestamp}, uint32(len(execMsgs)), execMsgs, nil +} diff --git a/op-supervisor/supervisor/backend/cross/safe_update.go b/op-supervisor/supervisor/backend/cross/safe_update.go index 18350c60df5..f942c851c5b 100644 --- a/op-supervisor/supervisor/backend/cross/safe_update.go +++ b/op-supervisor/supervisor/backend/cross/safe_update.go @@ -92,14 +92,8 @@ func scopedCrossSafeUpdate(logger log.Logger, chainID eth.ChainID, d CrossSafeDe return candidate, fmt.Errorf("failed to determine candidate block for cross-safe: %w", err) } logger.Debug("Candidate cross-safe", "scope", candidate.Source, "candidate", candidate.Derived) - opened, _, execMsgs, err := d.OpenBlock(chainID, candidate.Derived.Number) - if err != nil { - return candidate, fmt.Errorf("failed to open block %s: %w", candidate.Derived, err) - } - if opened.ID() != candidate.Derived.ID() { - return candidate, fmt.Errorf("unsafe L2 DB has %s, but candidate cross-safe was %s: %w", opened, candidate.Derived, types.ErrConflict) - } - hazards, err := CrossSafeHazards(d, chainID, candidate.Source.ID(), types.BlockSealFromRef(opened), sliceOfExecMsgs(execMsgs)) + + hazards, err := CrossSafeHazards(d, logger, chainID, candidate.Source.ID(), types.BlockSealFromRef(candidate.Derived)) if err != nil { return candidate, fmt.Errorf("failed to determine dependencies of cross-safe candidate %s: %w", candidate.Derived, err) } @@ -117,14 +111,6 @@ func scopedCrossSafeUpdate(logger log.Logger, chainID eth.ChainID, d CrossSafeDe return candidate, nil } -func sliceOfExecMsgs(execMsgs map[uint32]*types.ExecutingMessage) []*types.ExecutingMessage { - msgs := make([]*types.ExecutingMessage, 0, len(execMsgs)) - for _, msg := range execMsgs { - msgs = append(msgs, msg) - } - return msgs -} - type CrossSafeWorker struct { logger log.Logger chainID eth.ChainID diff --git a/op-supervisor/supervisor/backend/cross/safe_update_test.go b/op-supervisor/supervisor/backend/cross/safe_update_test.go index 4b9f2f03e42..af783e52c67 100644 --- a/op-supervisor/supervisor/backend/cross/safe_update_test.go +++ b/op-supervisor/supervisor/backend/cross/safe_update_test.go @@ -368,10 +368,10 @@ func TestScopedCrossSafeUpdate(t *testing.T) { count := 0 csd.deps = mockDependencySet{} // cause CrossSafeHazards to return an error by making ChainIDFromIndex return an error - // but only on the second call (which will be used by HazardSafeFrontierChecks) + // but only on the third call (which will be used by HazardSafeFrontierChecks) csd.deps.chainIDFromIndexfn = func() (eth.ChainID, error) { defer func() { count++ }() - if count == 0 { + if count < 2 { return eth.ChainID{}, nil } return eth.ChainID{}, errors.New("some error") diff --git a/op-supervisor/supervisor/backend/cross/unsafe_frontier.go b/op-supervisor/supervisor/backend/cross/unsafe_frontier.go index af51e8c08c4..f7eaecf4c2c 100644 --- a/op-supervisor/supervisor/backend/cross/unsafe_frontier.go +++ b/op-supervisor/supervisor/backend/cross/unsafe_frontier.go @@ -22,13 +22,13 @@ type UnsafeFrontierCheckDeps interface { // - already cross-unsafe. // - the first (if not first: local blocks to verify before proceeding) // local-unsafe block, after the cross-unsafe block. -func HazardUnsafeFrontierChecks(d UnsafeFrontierCheckDeps, hazards map[types.ChainIndex]types.BlockSeal) error { +func HazardUnsafeFrontierChecks(d UnsafeFrontierCheckDeps, hazards *HazardSet) error { depSet := d.DependencySet() - for hazardChainIndex, hazardBlock := range hazards { + for hazardChainIndex, hazardBlock := range hazards.Entries() { hazardChainID, err := depSet.ChainIDFromIndex(hazardChainIndex) if err != nil { if errors.Is(err, types.ErrUnknownChain) { - err = fmt.Errorf("cannot cross-unsafe verify block %s of unknown chain index %s: %w", hazardBlock, hazardChainIndex, types.ErrConflict) + err = fmt.Errorf("cannot cross-safe verify block %s of unknown chain index %s: %w", hazardBlock, hazardChainIndex, types.ErrConflict) } return err } diff --git a/op-supervisor/supervisor/backend/cross/unsafe_frontier_test.go b/op-supervisor/supervisor/backend/cross/unsafe_frontier_test.go index 247af8a70fc..df4080f7018 100644 --- a/op-supervisor/supervisor/backend/cross/unsafe_frontier_test.go +++ b/op-supervisor/supervisor/backend/cross/unsafe_frontier_test.go @@ -17,7 +17,7 @@ func TestHazardUnsafeFrontierChecks(t *testing.T) { hazards := map[types.ChainIndex]types.BlockSeal{} // when there are no hazards, // no work is done, and no error is returned - err := HazardUnsafeFrontierChecks(ufcd, hazards) + err := HazardUnsafeFrontierChecks(ufcd, NewHazardSetFromEntries(hazards)) require.NoError(t, err) }) t.Run("unknown chain", func(t *testing.T) { @@ -28,40 +28,40 @@ func TestHazardUnsafeFrontierChecks(t *testing.T) { }, }, } - hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {}} + hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {Number: 0}} // when there is one hazard, and ChainIDFromIndex returns ErrUnknownChain, // an error is returned as a ErrConflict - err := HazardUnsafeFrontierChecks(ufcd, hazards) + err := HazardUnsafeFrontierChecks(ufcd, NewHazardSetFromEntries(hazards)) require.ErrorIs(t, err, types.ErrConflict) }) t.Run("is cross unsafe", func(t *testing.T) { ufcd := &mockUnsafeFrontierCheckDeps{} - hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {}} + hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {Number: 0}} ufcd.isCrossUnsafe = nil // when there is one hazard, and IsCrossUnsafe returns nil (no error) // no error is returned - err := HazardUnsafeFrontierChecks(ufcd, hazards) + err := HazardUnsafeFrontierChecks(ufcd, NewHazardSetFromEntries(hazards)) require.NoError(t, err) }) t.Run("errFuture: is not local unsafe", func(t *testing.T) { ufcd := &mockUnsafeFrontierCheckDeps{} - hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {}} + hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {Number: 0}} ufcd.isCrossUnsafe = types.ErrFuture ufcd.isLocalUnsafe = errors.New("some error") // when there is one hazard, and IsCrossUnsafe returns an ErrFuture, // and IsLocalUnsafe returns an error, // the error from IsLocalUnsafe is (wrapped and) returned - err := HazardUnsafeFrontierChecks(ufcd, hazards) + err := HazardUnsafeFrontierChecks(ufcd, NewHazardSetFromEntries(hazards)) require.ErrorContains(t, err, "some error") }) t.Run("errFuture: genesis block", func(t *testing.T) { ufcd := &mockUnsafeFrontierCheckDeps{} - hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {}} + hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {Number: 0}} ufcd.isCrossUnsafe = types.ErrFuture // when there is one hazard, and IsCrossUnsafe returns an ErrFuture, // BUT the hazard's block number is 0, // no error is returned - err := HazardUnsafeFrontierChecks(ufcd, hazards) + err := HazardUnsafeFrontierChecks(ufcd, NewHazardSetFromEntries(hazards)) require.NoError(t, err) }) t.Run("errFuture: error getting parent block", func(t *testing.T) { @@ -74,7 +74,7 @@ func TestHazardUnsafeFrontierChecks(t *testing.T) { // when there is one hazard, and IsCrossUnsafe returns an ErrFuture, // and there is an error getting the parent block, // the error from ParentBlock is (wrapped and) returned - err := HazardUnsafeFrontierChecks(ufcd, hazards) + err := HazardUnsafeFrontierChecks(ufcd, NewHazardSetFromEntries(hazards)) require.ErrorContains(t, err, "some error") }) t.Run("errFuture: parent block is not cross unsafe", func(t *testing.T) { @@ -89,7 +89,7 @@ func TestHazardUnsafeFrontierChecks(t *testing.T) { // when there is one hazard, and IsCrossUnsafe returns an ErrFuture, // and the parent block is not cross unsafe, // the error from IsCrossUnsafe is (wrapped and) returned - err := HazardUnsafeFrontierChecks(ufcd, hazards) + err := HazardUnsafeFrontierChecks(ufcd, NewHazardSetFromEntries(hazards)) require.ErrorContains(t, err, "not cross unsafe!") }) t.Run("IsCrossUnsafe Error", func(t *testing.T) { @@ -98,7 +98,7 @@ func TestHazardUnsafeFrontierChecks(t *testing.T) { ufcd.isCrossUnsafe = errors.New("some error") // when there is one hazard, and IsCrossUnsafe returns an error, // the error from IsCrossUnsafe is (wrapped and) returned - err := HazardUnsafeFrontierChecks(ufcd, hazards) + err := HazardUnsafeFrontierChecks(ufcd, NewHazardSetFromEntries(hazards)) require.ErrorContains(t, err, "some error") }) } diff --git a/op-supervisor/supervisor/backend/cross/unsafe_start.go b/op-supervisor/supervisor/backend/cross/unsafe_start.go index d90e78f3862..5e7a79b2bc3 100644 --- a/op-supervisor/supervisor/backend/cross/unsafe_start.go +++ b/op-supervisor/supervisor/backend/cross/unsafe_start.go @@ -1,12 +1,12 @@ package cross import ( - "errors" "fmt" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" + "github.com/ethereum/go-ethereum/log" ) type UnsafeStartDeps interface { @@ -15,103 +15,28 @@ type UnsafeStartDeps interface { IsCrossUnsafe(chainID eth.ChainID, block eth.BlockID) error DependencySet() depset.DependencySet + + OpenBlock(chainID eth.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) } // CrossUnsafeHazards checks if the given messages all exist and pass invariants. // It returns a hazard-set: if any intra-block messaging happened, // these hazard blocks have to be verified. -func CrossUnsafeHazards(d UnsafeStartDeps, chainID eth.ChainID, - candidate types.BlockSeal, execMsgs []*types.ExecutingMessage) (hazards map[types.ChainIndex]types.BlockSeal, err error) { - - hazards = make(map[types.ChainIndex]types.BlockSeal) - - // Warning for future: If we have sub-second distinct blocks (different block number), - // we need to increase precision on the above timestamp invariant. - // Otherwise a local block can depend on a future local block of the same chain, - // simply by pulling in a block of another chain, - // which then depends on a block of the original chain, - // all with the same timestamp, without message cycles. - - depSet := d.DependencySet() - - if len(execMsgs) > 0 { - if ok, err := depSet.CanExecuteAt(chainID, candidate.Timestamp); err != nil { - return nil, fmt.Errorf("cannot check message execution of block %s (chain %s): %w", candidate, chainID, err) - } else if !ok { - return nil, fmt.Errorf("cannot execute messages in block %s (chain %s): %w", candidate, chainID, types.ErrConflict) - } - } +func CrossUnsafeHazards(d UnsafeStartDeps, logger log.Logger, chainID eth.ChainID, + candidate types.BlockSeal) (*HazardSet, error) { + unsafeDeps := &UnsafeHazardDeps{UnsafeStartDeps: d} + return NewHazardSet(unsafeDeps, logger, chainID, candidate) +} - // check all executing messages - for _, msg := range execMsgs { - initChainID, err := depSet.ChainIDFromIndex(msg.Chain) - if err != nil { - if errors.Is(err, types.ErrUnknownChain) { - err = fmt.Errorf("msg %s may not execute from unknown chain %s: %w", msg, msg.Chain, types.ErrConflict) - } - return nil, err - } - if ok, err := depSet.CanInitiateAt(initChainID, msg.Timestamp); err != nil { - return nil, fmt.Errorf("cannot check message initiation of msg %s (chain %s): %w", msg, chainID, err) - } else if !ok { - return nil, fmt.Errorf("cannot allow initiating message %s (chain %s): %w", msg, chainID, types.ErrConflict) - } - if msg.Timestamp < candidate.Timestamp { - // If timestamp is older: invariant ensures non-cyclic ordering relative to other messages. - // Check that the block that they are included in is cross-safe already. - includedIn, err := d.Contains(initChainID, - types.ContainsQuery{ - Timestamp: msg.Timestamp, - BlockNum: msg.BlockNum, - LogIdx: msg.LogIdx, - LogHash: msg.Hash, - }) - if err != nil { - return nil, fmt.Errorf("executing msg %s failed check: %w", msg, err) - } - if err := d.IsCrossUnsafe(initChainID, includedIn.ID()); err != nil { - return nil, fmt.Errorf("msg %s included in non-cross-unsafe block %s: %w", msg, includedIn, err) - } - if includedIn.Timestamp != msg.Timestamp { - return nil, fmt.Errorf("executing msg %s exists, but has different timestamp than block %s: %w", msg, includedIn, types.ErrConflict) - } - // Run expiry window invariant check *after* verifying that the message is non-conflicting. - if msg.Timestamp+depSet.MessageExpiryWindow() < candidate.Timestamp { - return nil, fmt.Errorf("timestamp of message %s (chain %s) has expired: %d < %d: %w", msg, chainID, msg.Timestamp+depSet.MessageExpiryWindow(), candidate.Timestamp, types.ErrConflict) - } - } else if msg.Timestamp == candidate.Timestamp { - // If timestamp is equal: we have to inspect ordering of individual - // log events to ensure non-cyclic cross-chain message ordering. - // And since we may have back-and-forth messaging, we cannot wait till the initiating side is cross-unsafe. - // Thus check that it was included in a local-unsafe block, - // and then proceed with transitive block checks, - // to ensure the local block we depend on is becoming cross-unsafe also. - includedIn, err := d.Contains(initChainID, - types.ContainsQuery{ - Timestamp: msg.Timestamp, - BlockNum: msg.BlockNum, - LogIdx: msg.LogIdx, - LogHash: msg.Hash, - }) - if err != nil { - return nil, fmt.Errorf("executing msg %s failed check: %w", msg, err) - } +// UnsafeHazardDeps adapts UnsafeStartDeps to HazardDeps +type UnsafeHazardDeps struct { + UnsafeStartDeps +} - // As a hazard block, it will be checked to be included in a cross-unsafe block, - // or right after a cross-unsafe block, in HazardUnsafeFrontierChecks. - if existing, ok := hazards[msg.Chain]; ok { - if existing != includedIn { - return nil, fmt.Errorf("found dependency on %s (chain %d), but already depend on %s", includedIn, initChainID, chainID) - } - } else { - // Mark it as hazard block - hazards[msg.Chain] = includedIn - } - } else { - // Timestamp invariant is broken: executing message tries to execute future block. - // The predeploy inbox contract should not have allowed this executing message through. - return nil, fmt.Errorf("executing message %s in %s breaks timestamp invariant", msg, candidate) - } +// VerifyBlock implements HazardDeps by checking cross-unsafe status +func (d *UnsafeHazardDeps) IsCrossValidBlock(chainID eth.ChainID, block eth.BlockID) error { + if err := d.IsCrossUnsafe(chainID, block); err != nil { + return fmt.Errorf("block %s is not cross-unsafe: %w", block, err) } - return hazards, nil + return nil } diff --git a/op-supervisor/supervisor/backend/cross/unsafe_start_test.go b/op-supervisor/supervisor/backend/cross/unsafe_start_test.go index 962c9fba9e7..3bde6d49532 100644 --- a/op-supervisor/supervisor/backend/cross/unsafe_start_test.go +++ b/op-supervisor/supervisor/backend/cross/unsafe_start_test.go @@ -16,12 +16,11 @@ func TestCrossUnsafeHazards(t *testing.T) { usd := &mockUnsafeStartDeps{} chainID := eth.ChainIDFromUInt64(0) candidate := types.BlockSeal{} - execMsgs := []*types.ExecutingMessage{} // when there are no execMsgs, // no work is done, and no error is returned - hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + hazards, err := CrossUnsafeHazards(usd, newTestLogger(t), chainID, candidate) require.NoError(t, err) - require.Empty(t, hazards) + require.Empty(t, hazards.Entries()) }) t.Run("CanExecuteAt returns false", func(t *testing.T) { usd := &mockUnsafeStartDeps{} @@ -32,12 +31,12 @@ func TestCrossUnsafeHazards(t *testing.T) { } chainID := eth.ChainIDFromUInt64(0) candidate := types.BlockSeal{} - execMsgs := []*types.ExecutingMessage{{}} + usd.openBlockFn = newOpenBlockFn(&types.ExecutingMessage{}) // when there is one execMsg, and CanExecuteAt returns false, // no work is done and an error is returned - hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + hazards, err := CrossUnsafeHazards(usd, newTestLogger(t), chainID, candidate) require.ErrorIs(t, err, types.ErrConflict) - require.Empty(t, hazards) + require.Empty(t, hazards.Entries()) }) t.Run("CanExecuteAt returns error", func(t *testing.T) { usd := &mockUnsafeStartDeps{} @@ -48,12 +47,12 @@ func TestCrossUnsafeHazards(t *testing.T) { } chainID := eth.ChainIDFromUInt64(0) candidate := types.BlockSeal{} - execMsgs := []*types.ExecutingMessage{{}} + usd.openBlockFn = newOpenBlockFn(&types.ExecutingMessage{}) // when there is one execMsg, and CanExecuteAt returns false, // no work is done and an error is returned - hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + hazards, err := CrossUnsafeHazards(usd, newTestLogger(t), chainID, candidate) require.ErrorContains(t, err, "some error") - require.Empty(t, hazards) + require.Empty(t, hazards.Entries()) }) t.Run("unknown chain", func(t *testing.T) { usd := &mockUnsafeStartDeps{} @@ -64,12 +63,12 @@ func TestCrossUnsafeHazards(t *testing.T) { } chainID := eth.ChainIDFromUInt64(0) candidate := types.BlockSeal{} - execMsgs := []*types.ExecutingMessage{{}} + usd.openBlockFn = newOpenBlockFn(&types.ExecutingMessage{}) // when there is one execMsg, and ChainIDFromIndex returns ErrUnknownChain, // an error is returned as a ErrConflict - hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + hazards, err := CrossUnsafeHazards(usd, newTestLogger(t), chainID, candidate) require.ErrorIs(t, err, types.ErrConflict) - require.Empty(t, hazards) + require.Empty(t, hazards.Entries()) }) t.Run("ChainIDFromUInt64 returns error", func(t *testing.T) { usd := &mockUnsafeStartDeps{} @@ -80,12 +79,12 @@ func TestCrossUnsafeHazards(t *testing.T) { } chainID := eth.ChainIDFromUInt64(0) candidate := types.BlockSeal{} - execMsgs := []*types.ExecutingMessage{{}} + usd.openBlockFn = newOpenBlockFn(&types.ExecutingMessage{}) // when there is one execMsg, and ChainIDFromIndex returns some other error, // the error is returned - hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + hazards, err := CrossUnsafeHazards(usd, newTestLogger(t), chainID, candidate) require.ErrorContains(t, err, "some error") - require.Empty(t, hazards) + require.Empty(t, hazards.Entries()) }) t.Run("CanInitiateAt returns false", func(t *testing.T) { usd := &mockUnsafeStartDeps{} @@ -96,12 +95,12 @@ func TestCrossUnsafeHazards(t *testing.T) { } chainID := eth.ChainIDFromUInt64(0) candidate := types.BlockSeal{} - execMsgs := []*types.ExecutingMessage{{}} + usd.openBlockFn = newOpenBlockFn(&types.ExecutingMessage{}) // when there is one execMsg, and CanInitiateAt returns false, // the error is returned as a ErrConflict - hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + hazards, err := CrossUnsafeHazards(usd, newTestLogger(t), chainID, candidate) require.ErrorIs(t, err, types.ErrConflict) - require.Empty(t, hazards) + require.Empty(t, hazards.Entries()) }) t.Run("CanInitiateAt returns error", func(t *testing.T) { usd := &mockUnsafeStartDeps{} @@ -112,12 +111,12 @@ func TestCrossUnsafeHazards(t *testing.T) { } chainID := eth.ChainIDFromUInt64(0) candidate := types.BlockSeal{} - execMsgs := []*types.ExecutingMessage{{}} + usd.openBlockFn = newOpenBlockFn(&types.ExecutingMessage{}) // when there is one execMsg, and CanInitiateAt returns an error, // the error is returned - hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + hazards, err := CrossUnsafeHazards(usd, newTestLogger(t), chainID, candidate) require.ErrorContains(t, err, "some error") - require.Empty(t, hazards) + require.Empty(t, hazards.Entries()) }) t.Run("timestamp is greater than candidate", func(t *testing.T) { usd := &mockUnsafeStartDeps{} @@ -125,12 +124,12 @@ func TestCrossUnsafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) candidate := types.BlockSeal{Timestamp: 2} em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 10} - execMsgs := []*types.ExecutingMessage{em1} + usd.openBlockFn = newOpenBlockFn(em1) // when there is one execMsg, and the timestamp is greater than the candidate, // an error is returned - hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + hazards, err := CrossUnsafeHazards(usd, newTestLogger(t), chainID, candidate) require.ErrorContains(t, err, "breaks timestamp invariant") - require.Empty(t, hazards) + require.Empty(t, hazards.Entries()) }) t.Run("timestamp is equal, Check returns error", func(t *testing.T) { usd := &mockUnsafeStartDeps{} @@ -141,32 +140,45 @@ func TestCrossUnsafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) candidate := types.BlockSeal{Timestamp: 2} em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 2} - execMsgs := []*types.ExecutingMessage{em1} - // when there is one execMsg, and the timetamp is equal to the candidate, + usd.openBlockFn = newOpenBlockFn(em1) + // when there is one execMsg, and the timestamp is equal to the candidate, // and check returns an error, // that error is returned - hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + hazards, err := CrossUnsafeHazards(usd, newTestLogger(t), chainID, candidate) require.ErrorContains(t, err, "some error") - require.Empty(t, hazards) + require.Empty(t, hazards.Entries()) }) t.Run("timestamp is equal, same hazard twice", func(t *testing.T) { usd := &mockUnsafeStartDeps{} - sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x02})} + sampleBlockSeal := types.BlockSeal{Number: 3, Hash: common.BytesToHash([]byte{0x03}), Timestamp: 1} usd.checkFn = func() (includedIn types.BlockSeal, err error) { return sampleBlockSeal, nil } usd.deps = mockDependencySet{} chainID := eth.ChainIDFromUInt64(0) - candidate := types.BlockSeal{Timestamp: 2} + candidate := types.BlockSeal{Hash: common.BytesToHash([]byte{0x04}), Number: 4, Timestamp: 2} em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 2} em2 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 2} - execMsgs := []*types.ExecutingMessage{em1, em2} + usd.openBlockFn = func(chainID eth.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + if blockNum == 4 { + return eth.BlockRef{ + Hash: candidate.Hash, + Number: candidate.Number, + Time: candidate.Timestamp, + }, 2, map[uint32]*types.ExecutingMessage{0: em1, 1: em2}, nil + } + return eth.BlockRef{ + Hash: sampleBlockSeal.Hash, + Number: sampleBlockSeal.Number, + Time: sampleBlockSeal.Timestamp, + }, 0, nil, nil + } // when there are two execMsgs, and both are equal time to the candidate, // and check returns the same includedIn for both // they load the hazards once, and return no error - hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + hazards, err := CrossUnsafeHazards(usd, newTestLogger(t), chainID, candidate) require.NoError(t, err) - require.Equal(t, hazards, map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): sampleBlockSeal}) + require.Equal(t, map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): sampleBlockSeal}, hazards.Entries()) }) t.Run("timestamp is equal, different hazards", func(t *testing.T) { usd := &mockUnsafeStartDeps{} @@ -186,13 +198,13 @@ func TestCrossUnsafeHazards(t *testing.T) { candidate := types.BlockSeal{Timestamp: 2} em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 2} em2 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 2} - execMsgs := []*types.ExecutingMessage{em1, em2} + usd.openBlockFn = newOpenBlockFn(em1, em2) // when there are two execMsgs, and both are equal time to the candidate, // and check returns different includedIn for the two, // an error is returned - hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + hazards, err := CrossUnsafeHazards(usd, newTestLogger(t), chainID, candidate) require.ErrorContains(t, err, "but already depend on") - require.Empty(t, hazards) + require.Empty(t, hazards.Entries()) }) t.Run("timestamp is less, check returns error", func(t *testing.T) { usd := &mockUnsafeStartDeps{} @@ -203,13 +215,13 @@ func TestCrossUnsafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) candidate := types.BlockSeal{Timestamp: 2} em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} - execMsgs := []*types.ExecutingMessage{em1} + usd.openBlockFn = newOpenBlockFn(em1) // when there is one execMsg, and the timestamp is less than the candidate, // and check returns an error, // that error is returned - hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + hazards, err := CrossUnsafeHazards(usd, newTestLogger(t), chainID, candidate) require.ErrorContains(t, err, "some error") - require.Empty(t, hazards) + require.Empty(t, hazards.Entries()) }) t.Run("timestamp is less, IsCrossUnsafe returns error", func(t *testing.T) { usd := &mockUnsafeStartDeps{} @@ -224,13 +236,13 @@ func TestCrossUnsafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) candidate := types.BlockSeal{Timestamp: 2} em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} - execMsgs := []*types.ExecutingMessage{em1} + usd.openBlockFn = newOpenBlockFn(em1) // when there is one execMsg, and the timestamp is less than the candidate, // and IsCrossUnsafe returns an error, // that error is returned - hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + hazards, err := CrossUnsafeHazards(usd, newTestLogger(t), chainID, candidate) require.ErrorContains(t, err, "some error") - require.Empty(t, hazards) + require.Empty(t, hazards.Entries()) }) t.Run("timestamp is less, IsCrossUnsafe", func(t *testing.T) { usd := &mockUnsafeStartDeps{} @@ -245,15 +257,16 @@ func TestCrossUnsafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) candidate := types.BlockSeal{Timestamp: 2} em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 0} - execMsgs := []*types.ExecutingMessage{em1} + usd.openBlockFn = newOpenBlockFn(em1) // when there is one execMsg, and the timestamp is less than the candidate, // and IsCrossUnsafe returns no error, // no error is returned - hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + hazards, err := CrossUnsafeHazards(usd, newTestLogger(t), chainID, candidate) require.NoError(t, err) - require.Empty(t, hazards) + require.Empty(t, hazards.Entries()) }) t.Run("message expiry", func(t *testing.T) { + logger := newTestLogger(t) usd := &mockUnsafeStartDeps{} usd.deps.messageExpiryWindow = 10 sampleBlockSeal := types.BlockSeal{Timestamp: 1} @@ -263,15 +276,16 @@ func TestCrossUnsafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) candidate := types.BlockSeal{Timestamp: 12} em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} - execMsgs := []*types.ExecutingMessage{em1} + usd.openBlockFn = newOpenBlockFn(em1) // when there is one execMsg that has just expired, // ErrExpired is returned - hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + hazards, err := CrossUnsafeHazards(usd, logger, chainID, candidate) require.ErrorIs(t, err, types.ErrConflict) require.ErrorContains(t, err, "has expired") - require.Empty(t, hazards) + require.Empty(t, hazards.Entries()) }) t.Run("message near expiry", func(t *testing.T) { + logger := newTestLogger(t) usd := &mockUnsafeStartDeps{} usd.deps.messageExpiryWindow = 10 sampleBlockSeal := types.BlockSeal{Timestamp: 1} @@ -281,11 +295,11 @@ func TestCrossUnsafeHazards(t *testing.T) { chainID := eth.ChainIDFromUInt64(0) candidate := types.BlockSeal{Timestamp: 11} em1 := &types.ExecutingMessage{Chain: types.ChainIndex(0), Timestamp: 1} - execMsgs := []*types.ExecutingMessage{em1} + usd.openBlockFn = newOpenBlockFn(em1) // when there is one execMsg that is near expiry, then no error is returned - hazards, err := CrossUnsafeHazards(usd, chainID, candidate, execMsgs) + hazards, err := CrossUnsafeHazards(usd, logger, chainID, candidate) require.NoError(t, err) - require.Empty(t, hazards) + require.Empty(t, hazards.Entries()) }) } @@ -293,6 +307,7 @@ type mockUnsafeStartDeps struct { deps mockDependencySet checkFn func() (includedIn types.BlockSeal, err error) isCrossUnsafeFn func() error + openBlockFn func(chainID eth.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) } func (m *mockUnsafeStartDeps) Contains(chain eth.ChainID, q types.ContainsQuery) (includedIn types.BlockSeal, err error) { @@ -312,3 +327,23 @@ func (m *mockUnsafeStartDeps) IsCrossUnsafe(chainID eth.ChainID, derived eth.Blo func (m *mockUnsafeStartDeps) DependencySet() depset.DependencySet { return m.deps } + +func (m *mockUnsafeStartDeps) OpenBlock(chainID eth.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + if m.openBlockFn != nil { + return m.openBlockFn(chainID, blockNum) + } + // Default implementation returns block with matching timestamp to avoid invariant errors + // Return timestamp matching the candidate timestamp + execMsgs = make(map[uint32]*types.ExecutingMessage) + return eth.BlockRef{Number: blockNum}, uint32(len(execMsgs)), execMsgs, nil +} + +func newOpenBlockFn(ems ...*types.ExecutingMessage) func(chainID eth.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { + execMsgs := make(map[uint32]*types.ExecutingMessage) + for i, em := range ems { + execMsgs[uint32(i)] = em + } + return func(chainID eth.ChainID, blockNum uint64) (eth.BlockRef, uint32, map[uint32]*types.ExecutingMessage, error) { + return eth.BlockRef{}, uint32(len(execMsgs)), execMsgs, nil + } +} diff --git a/op-supervisor/supervisor/backend/cross/unsafe_update.go b/op-supervisor/supervisor/backend/cross/unsafe_update.go index 095b4e58b56..2550142acf8 100644 --- a/op-supervisor/supervisor/backend/cross/unsafe_update.go +++ b/op-supervisor/supervisor/backend/cross/unsafe_update.go @@ -25,7 +25,6 @@ type CrossUnsafeDeps interface { func CrossUnsafeUpdate(logger log.Logger, chainID eth.ChainID, d CrossUnsafeDeps) error { var candidate types.BlockSeal - var execMsgs []*types.ExecutingMessage // fetch cross-head to determine next cross-unsafe candidate if crossUnsafe, err := d.CrossUnsafe(chainID); err != nil { @@ -39,7 +38,7 @@ func CrossUnsafeUpdate(logger log.Logger, chainID eth.ChainID, d CrossUnsafeDeps } else { // Open block N+1: this is a local-unsafe block, // just after cross-safe, that can be promoted if it passes the dependency checks. - bl, _, msgs, err := d.OpenBlock(chainID, crossUnsafe.Number+1) + bl, _, _, err := d.OpenBlock(chainID, crossUnsafe.Number+1) if err != nil { return fmt.Errorf("failed to open block %d: %w", crossUnsafe.Number+1, err) } @@ -47,10 +46,9 @@ func CrossUnsafeUpdate(logger log.Logger, chainID eth.ChainID, d CrossUnsafeDeps return fmt.Errorf("cannot use block %s, it does not build on cross-unsafe block %s: %w", bl, crossUnsafe, types.ErrConflict) } candidate = types.BlockSealFromRef(bl) - execMsgs = sliceOfExecMsgs(msgs) } - hazards, err := CrossUnsafeHazards(d, chainID, candidate, execMsgs) + hazards, err := CrossUnsafeHazards(d, logger, chainID, candidate) if err != nil { // TODO(#11693): reorgs can be detected by checking if the error is ErrConflict, // missing data is identified by ErrFuture, diff --git a/op-supervisor/supervisor/backend/cross/unsafe_update_test.go b/op-supervisor/supervisor/backend/cross/unsafe_update_test.go index 6371fc598e8..5c774357367 100644 --- a/op-supervisor/supervisor/backend/cross/unsafe_update_test.go +++ b/op-supervisor/supervisor/backend/cross/unsafe_update_test.go @@ -110,11 +110,11 @@ func TestCrossUnsafeUpdate(t *testing.T) { } usd.deps = mockDependencySet{} count := 0 - // make HazardUnsafeFrontierChecks return an error by failing the second ChainIDFromIndex call - // (the first one is in CrossSafeHazards) + // make HazardUnsafeFrontierChecks return an error by failing the third ChainIDFromIndex call + // (the first two are in CrossSafeHazards) usd.deps.chainIDFromIndexfn = func() (eth.ChainID, error) { defer func() { count++ }() - if count == 1 { + if count < 2 { return eth.ChainID{}, errors.New("some error") } return eth.ChainID{}, nil @@ -156,11 +156,19 @@ func TestCrossUnsafeUpdate(t *testing.T) { usd.crossUnsafeFn = func(chainID eth.ChainID) (types.BlockSeal, error) { return crossUnsafe, nil } - bl := eth.BlockRef{ParentHash: common.Hash{0x01}, Time: 1} + bl := eth.BlockRef{ParentHash: common.Hash{0x01}, Time: 1, Number: 1} em1 := &types.ExecutingMessage{Timestamp: 1} usd.openBlockFn = func(chainID eth.ChainID, blockNum uint64) (ref eth.BlockRef, logCount uint32, execMsgs map[uint32]*types.ExecutingMessage, err error) { // include one executing message to ensure one hazard is returned - return bl, 2, map[uint32]*types.ExecutingMessage{1: em1}, nil + if blockNum == 1 { + return bl, 2, map[uint32]*types.ExecutingMessage{1: em1}, nil + } + return eth.BlockRef{ + Hash: crossUnsafe.Hash, + }, 0, nil, nil + } + usd.checkFn = func(chainID eth.ChainID, blockNum uint64, timestamp uint64, logIdx uint32, logHash common.Hash) (types.BlockSeal, error) { + return crossUnsafe, nil } usd.deps = mockDependencySet{} var updatingChainID eth.ChainID From 2f15b04a426ab692b1545c272babfef703c506d4 Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 28 Feb 2025 19:53:01 +0100 Subject: [PATCH 039/130] op-service: check if TLS is enabled and move middleware (#14571) * op-service: check if TLS is enabled * op-service: apply RPC middlewares globally * op-service: apply RPC middlewares before RPC but after health * op-service: test health before middleware --- op-service/httputil/server.go | 2 +- op-service/rpc/handler.go | 8 +++--- op-service/rpc/server_test.go | 51 +++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/op-service/httputil/server.go b/op-service/httputil/server.go index 727201bc7cc..b17d5f80175 100644 --- a/op-service/httputil/server.go +++ b/op-service/httputil/server.go @@ -74,7 +74,7 @@ func (s *HTTPServer) Start() error { }, } - if s.config.tls != nil { + if s.config.tls != nil && s.config.tls.CLIConfig.Enabled { srv.TLSConfig = s.config.tls.Config } diff --git a/op-service/rpc/handler.go b/op-service/rpc/handler.go index 40563a25ad3..361dce26f1d 100644 --- a/op-service/rpc/handler.go +++ b/op-service/rpc/handler.go @@ -148,13 +148,9 @@ func (b *Handler) AddRPC(route string) error { // default to 404 not-found handler = http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { - b.log.Info("oh no!") http.NotFound(writer, request) }) - // Health endpoint is lowest priority. - handler = b.newHealthMiddleware(handler) - // serve RPC on configured RPC path (but not on arbitrary paths) handler = b.newHttpRPCMiddleware(srv, handler) @@ -167,6 +163,10 @@ func (b *Handler) AddRPC(route string) error { for _, middleware := range b.middlewares { handler = middleware(handler) } + + // Health endpoint applies before user middleware + handler = b.newHealthMiddleware(handler) + b.rpcRoutes[route] = srv b.mux.Handle(route+"/", http.StripPrefix(route+"/", handler)) diff --git a/op-service/rpc/server_test.go b/op-service/rpc/server_test.go index 900ebbba26d..8774f936422 100644 --- a/op-service/rpc/server_test.go +++ b/op-service/rpc/server_test.go @@ -114,6 +114,57 @@ func testServer(t *testing.T, endpoint string, appVersion string, namespace stri }) } +// TestUserMiddlewareBeforeHealth tests that the health endpoint is always available, in front of user-middleware. +func TestUserMiddlewareBeforeHealth(t *testing.T) { + appVersion := "test" + logger := testlog.Logger(t, log.LevelTrace) + server := ServerFromConfig(&ServerConfig{ + HttpOptions: nil, + RpcOptions: []Option{ + WithLogger(logger), + WithWebsocketEnabled(), + WithMiddleware(func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusTeapot) + }) + }), + }, + Host: "127.0.0.1", + Port: 0, + AppVersion: appVersion, + }) + server.AddAPI(rpc.API{ + Namespace: "test", + Service: new(testAPI), + }) + require.NoError(t, server.Start(), "must start") + + t.Cleanup(func() { + err := server.Stop() + if err != nil { + panic(err) + } + }) + + t.Run("does not support other GET /foobar", func(t *testing.T) { + res, err := http.Get(server.httpServer.HTTPEndpoint() + "/foobar") + require.NoError(t, err) + defer res.Body.Close() + require.Equal(t, http.StatusTeapot, res.StatusCode) + }) + + t.Run("supports GET /healthz", func(t *testing.T) { + res, err := http.Get(server.httpServer.HTTPEndpoint() + "/healthz") + require.NoError(t, err) + defer res.Body.Close() + require.Equal(t, http.StatusOK, res.StatusCode) + body, err := io.ReadAll(res.Body) + require.NoError(t, err) + require.EqualValues(t, fmt.Sprintf("{\"version\":\"%s\"}\n", appVersion), string(body)) + }) + +} + func TestAuthServer(t *testing.T) { secret := [32]byte{0: 4} badSecret := [32]byte{0: 5} From e40278366454c5f186bad115c8c2396038d169e3 Mon Sep 17 00:00:00 2001 From: smartcontracts Date: Fri, 28 Feb 2025 14:23:52 -0600 Subject: [PATCH 040/130] feat: bytecode verification script (#14589) Adds a script that makes it easy to verify that the bytecode you have in a local artifact matches the bytecode of a contract on a live network. --- .circleci/config.yml | 1 + .../scripts/verify/verify-bytecode/main.go | 513 ++++++++++++++ .../verify/verify-bytecode/main_test.go | 646 ++++++++++++++++++ 3 files changed, 1160 insertions(+) create mode 100644 packages/contracts-bedrock/scripts/verify/verify-bytecode/main.go create mode 100644 packages/contracts-bedrock/scripts/verify/verify-bytecode/main_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index ac858b59b79..ebc84ee53f9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1508,6 +1508,7 @@ workflows: op-e2e/actions op-e2e/faultproofs packages/contracts-bedrock/scripts/checks + packages/contracts-bedrock/scripts/verify op-dripper requires: - contracts-bedrock-build diff --git a/packages/contracts-bedrock/scripts/verify/verify-bytecode/main.go b/packages/contracts-bedrock/scripts/verify/verify-bytecode/main.go new file mode 100644 index 00000000000..9115bf6742e --- /dev/null +++ b/packages/contracts-bedrock/scripts/verify/verify-bytecode/main.go @@ -0,0 +1,513 @@ +package main + +import ( + "context" + "encoding/hex" + "encoding/json" + "flag" + "fmt" + "os" + "strconv" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/fatih/color" +) + +// ImmutableReference represents an immutable reference in the contract bytecode +type ImmutableReference struct { + Offset int + Length int + Value string +} + +// BytecodeDifference represents a difference between expected and actual bytecode +type BytecodeDifference struct { + Start int + Length int + Expected string + Actual string + InImmutable bool + ImmutableName string +} + +// currentDiff is a helper struct for tracking differences during comparison +type currentDiff struct { + Start int + Expected []string + Actual []string + InImmutable bool + ImmutableName string +} + +func main() { + // Parse command line arguments + address := flag.String("address", "", "Contract address to check") + artifactPath := flag.String("artifact", "", "Path to the contract artifact JSON file") + rpcURL := flag.String("rpc", "", "RPC URL for the network") + flag.Parse() + + if *rpcURL == "" { + color.Red("Error: RPC URL is required") + flag.Usage() + os.Exit(1) + } + + color.Cyan("Comparing contract at %s with artifact %s", *address, *artifactPath) + + // Load the artifact + artifact, err := loadArtifact(*artifactPath) + if err != nil { + color.Red("Error loading artifact: %v", err) + os.Exit(1) + } + + // Get expected bytecode from artifact + expectedBytecode, err := getDeployedBytecode(artifact) + if err != nil { + color.Red("Error: %v", err) + os.Exit(1) + } + + // Get immutable references + immutableRefs, err := getImmutableReferences(artifact) + if err != nil { + color.Red("Error: %v", err) + os.Exit(1) + } + + // Get actual bytecode from the network + actualBytecode, err := getOnchainBytecode(*address, *rpcURL) + if err != nil { + color.Red("Error: %v", err) + os.Exit(1) + } + + // Find differences + differences, err := findDifferences(expectedBytecode, actualBytecode, immutableRefs) + if err != nil { + color.Red("Error: %v", err) + os.Exit(1) + } + + // Print results + printDifferences(differences, immutableRefs) + + // Exit with error code if there are non-immutable differences + for _, diff := range differences { + if !diff.InImmutable { + os.Exit(1) + } + } + + color.Green("✓ Contract bytecode matches the artifact (accounting for immutable references).") +} + +func loadArtifact(path string) (map[string]any, error) { + if path == "" { + return nil, fmt.Errorf("artifact path is required") + } + + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed to read artifact file: %w", err) + } + + var artifact map[string]any + if err := json.Unmarshal(data, &artifact); err != nil { + return nil, fmt.Errorf("failed to parse JSON: %w", err) + } + + return artifact, nil +} + +func getDeployedBytecode(artifact map[string]any) (string, error) { + // Check for Forge/Foundry artifact format + if deployedBytecode, ok := artifact["deployedBytecode"].(map[string]any); ok { + if object, ok := deployedBytecode["object"].(string); ok { + return object, nil + } + } + + // Check for standard artifact formats + if deployedBytecode, ok := artifact["deployedBytecode"].(string); ok { + return deployedBytecode, nil + } + + // Check for bytecode field + if bytecode, ok := artifact["bytecode"].(map[string]any); ok { + if object, ok := bytecode["object"].(string); ok { + return object, nil + } + } else if bytecode, ok := artifact["bytecode"].(string); ok { + return bytecode, nil + } + + return "", fmt.Errorf("could not find deployedBytecode in artifact") +} + +func getVariableNameFromAST(artifact map[string]any, varID string) string { + // Remove any prefix from the ID (sometimes IDs are prefixed with a path) + cleanID := varID + if strings.Contains(varID, ":") { + parts := strings.Split(varID, ":") + cleanID = parts[len(parts)-1] + } + + // Try to convert to int + idInt, err := strconv.Atoi(cleanID) + if err != nil { + return varID + } + + // Try to find the AST node + if ast, ok := artifact["ast"].(map[string]any); ok { + // Recursively search for the node with matching ID + name := findNodeName(ast, idInt) + if name != "" { + return name + } + } + + // Fallback to using the ID if we can't find the name + return varID +} + +func findNodeName(node any, targetID int) string { + switch n := node.(type) { + case map[string]any: + // Check if this is the node we're looking for + if id, ok := n["id"].(float64); ok && int(id) == targetID { + if name, ok := n["name"].(string); ok { + return name + } + } + + // Recursively search in all child nodes + for _, value := range n { + result := findNodeName(value, targetID) + if result != "" { + return result + } + } + case []any: + // Search in list items + for _, item := range n { + result := findNodeName(item, targetID) + if result != "" { + return result + } + } + } + return "" +} + +func getImmutableReferences(artifact map[string]any) (map[string][]ImmutableReference, error) { + references := make(map[string][]ImmutableReference) + + var immutableRefs map[string]any + + // Handle Forge/Foundry artifact format + if deployedBytecode, ok := artifact["deployedBytecode"].(map[string]any); ok { + if refs, ok := deployedBytecode["immutableReferences"].(map[string]any); ok { + immutableRefs = refs + } else { + return references, nil // No immutable references found + } + } else if refs, ok := artifact["immutableReferences"].(map[string]any); ok { + // Handle standard artifact format + immutableRefs = refs + } else { + return references, nil // No immutable references found + } + + // Process the references + for varID, refs := range immutableRefs { + // Get the variable name from AST + varName := getVariableNameFromAST(artifact, varID) + references[varName] = []ImmutableReference{} + + refsList, ok := refs.([]any) + if !ok { + continue + } + + for _, ref := range refsList { + var start, length int + + // Handle different formats of immutable references + if refMap, ok := ref.(map[string]any); ok { + if startVal, ok := refMap["start"].(float64); ok { + start = int(startVal) + } + if lengthVal, ok := refMap["length"].(float64); ok { + length = int(lengthVal) + } + } else if refArray, ok := ref.([]any); ok && len(refArray) >= 2 { + // Some formats use [start, length] array + if startVal, ok := refArray[0].(float64); ok { + start = int(startVal) + } + if lengthVal, ok := refArray[1].(float64); ok { + length = int(lengthVal) + } + } else { + color.Yellow("Warning: Unrecognized immutable reference format: %v", ref) + continue + } + + references[varName] = append(references[varName], ImmutableReference{ + Offset: start, + Length: length, + Value: "", + }) + } + } + + return references, nil +} + +func getOnchainBytecode(address string, rpcURL string) (string, error) { + if address == "" { + return "", fmt.Errorf("contract address is required") + } + + client, err := ethclient.Dial(rpcURL) + if err != nil { + return "", fmt.Errorf("failed to connect to RPC at %s: %w", rpcURL, err) + } + + code, err := client.CodeAt(context.Background(), common.HexToAddress(address), nil) + if err != nil { + return "", fmt.Errorf("failed to get code at address %s: %w", address, err) + } + + if len(code) == 0 { + return "", fmt.Errorf("no code found at address %s", address) + } + + return "0x" + hex.EncodeToString(code), nil +} + +func isInImmutableReference( + position int, + immutableRefs map[string][]ImmutableReference, +) (bool, string, *ImmutableReference) { + for varName, refs := range immutableRefs { + for i := range refs { + ref := &refs[i] + if ref.Offset <= position && position < ref.Offset+ref.Length { + return true, varName, ref + } + } + } + return false, "", nil +} + +func findDifferences( + expectedBytecode string, + actualBytecode string, + immutableRefs map[string][]ImmutableReference, +) ([]BytecodeDifference, error) { + // Remove '0x' prefix if present + expected := strings.TrimPrefix(expectedBytecode, "0x") + actual := strings.TrimPrefix(actualBytecode, "0x") + + // Convert to bytes for comparison + expectedBytes, err := hex.DecodeString(expected) + if err != nil { + return nil, fmt.Errorf("failed to decode expected bytecode: %w", err) + } + + actualBytes, err := hex.DecodeString(actual) + if err != nil { + return nil, fmt.Errorf("failed to decode actual bytecode: %w", err) + } + + // Check length differences + if len(expectedBytes) != len(actualBytes) { + color.Yellow("Warning: Bytecode length mismatch. Expected: %d, Actual: %d", + len(expectedBytes), len(actualBytes)) + } + + // Use the shorter length for comparison + compareLength := min(len(expectedBytes), len(actualBytes)) + + // Initialize all immutable reference values + for _, refs := range immutableRefs { + for i := range refs { + refs[i].Value = "" + } + } + + differences := []BytecodeDifference{} + var currDiff *currentDiff = nil + + for i := 0; i < compareLength; i++ { + inImmutable, varName, ref := isInImmutableReference(i, immutableRefs) + + // If we're in an immutable reference, collect the value + if inImmutable && ref != nil { + // Add this byte to the immutable value + ref.Value += fmt.Sprintf("%02x", actualBytes[i]) + + // If bytes differ and we're in an immutable reference, that's expected + if expectedBytes[i] != actualBytes[i] { + if currDiff == nil { + currDiff = ¤tDiff{ + Start: i, + Expected: []string{}, + Actual: []string{}, + InImmutable: true, + ImmutableName: varName, + } + } else if !currDiff.InImmutable { + // We were tracking a non-immutable diff, finish it and start a new one + differences = append(differences, BytecodeDifference{ + Start: currDiff.Start, + Length: len(currDiff.Expected), + Expected: strings.Join(currDiff.Expected, ""), + Actual: strings.Join(currDiff.Actual, ""), + InImmutable: currDiff.InImmutable, + ImmutableName: currDiff.ImmutableName, + }) + currDiff = ¤tDiff{ + Start: i, + Expected: []string{}, + Actual: []string{}, + InImmutable: true, + ImmutableName: varName, + } + } + + currDiff.Expected = append(currDiff.Expected, fmt.Sprintf("%02x", expectedBytes[i])) + currDiff.Actual = append(currDiff.Actual, fmt.Sprintf("%02x", actualBytes[i])) + } else if currDiff != nil && currDiff.InImmutable { + // End of a difference section within an immutable reference + differences = append(differences, BytecodeDifference{ + Start: currDiff.Start, + Length: len(currDiff.Expected), + Expected: strings.Join(currDiff.Expected, ""), + Actual: strings.Join(currDiff.Actual, ""), + InImmutable: currDiff.InImmutable, + ImmutableName: currDiff.ImmutableName, + }) + currDiff = nil + } + } else { + // Not in an immutable reference - any difference is an error + if expectedBytes[i] != actualBytes[i] { + if currDiff == nil { + currDiff = ¤tDiff{ + Start: i, + Expected: []string{}, + Actual: []string{}, + InImmutable: false, + ImmutableName: "", + } + } else if currDiff.InImmutable { + // We were tracking an immutable diff, finish it and start a new one + differences = append(differences, BytecodeDifference{ + Start: currDiff.Start, + Length: len(currDiff.Expected), + Expected: strings.Join(currDiff.Expected, ""), + Actual: strings.Join(currDiff.Actual, ""), + InImmutable: currDiff.InImmutable, + ImmutableName: currDiff.ImmutableName, + }) + currDiff = ¤tDiff{ + Start: i, + Expected: []string{}, + Actual: []string{}, + InImmutable: false, + ImmutableName: "", + } + } + + currDiff.Expected = append(currDiff.Expected, fmt.Sprintf("%02x", expectedBytes[i])) + currDiff.Actual = append(currDiff.Actual, fmt.Sprintf("%02x", actualBytes[i])) + } else if currDiff != nil && !currDiff.InImmutable { + // End of a difference section outside immutable reference + differences = append(differences, BytecodeDifference{ + Start: currDiff.Start, + Length: len(currDiff.Expected), + Expected: strings.Join(currDiff.Expected, ""), + Actual: strings.Join(currDiff.Actual, ""), + InImmutable: currDiff.InImmutable, + ImmutableName: currDiff.ImmutableName, + }) + currDiff = nil + } + } + } + + // Don't forget the last difference if we reached the end + if currDiff != nil { + differences = append(differences, BytecodeDifference{ + Start: currDiff.Start, + Length: len(currDiff.Expected), + Expected: strings.Join(currDiff.Expected, ""), + Actual: strings.Join(currDiff.Actual, ""), + InImmutable: currDiff.InImmutable, + ImmutableName: currDiff.ImmutableName, + }) + } + + return differences, nil +} + +func printDifferences( + differences []BytecodeDifference, + immutableRefs map[string][]ImmutableReference, +) { + // Separate immutable and non-immutable differences + var nonImmutableDiffs []BytecodeDifference + var immutableDiffs []BytecodeDifference + + for _, diff := range differences { + if diff.InImmutable { + immutableDiffs = append(immutableDiffs, diff) + } else { + nonImmutableDiffs = append(nonImmutableDiffs, diff) + } + } + + // Print summary + color.Cyan("\n=== Bytecode Comparison Summary ===") + fmt.Printf("Total differences: %d\n", len(differences)) + fmt.Printf(" - In immutable references: %d\n", len(immutableDiffs)) + fmt.Printf(" - In code: %d\n", len(nonImmutableDiffs)) + + // Print non-immutable differences (these are errors) + if len(nonImmutableDiffs) > 0 { + color.Red("\n=== Unexpected Differences in Code ===") + for _, diff := range nonImmutableDiffs { + color.Red("Position %d-%d:", diff.Start, diff.Start+diff.Length-1) + fmt.Printf(" Expected: 0x%s\n", diff.Expected) + fmt.Printf(" Actual: 0x%s\n", diff.Actual) + } + color.Red("\n⚠️ The contract bytecode does not match the artifact!") + } else { + color.Green("\n✓ No unexpected differences in code.") + } + + // Print immutable references + color.Cyan("\n=== Immutable References ===") + if len(immutableRefs) == 0 { + fmt.Println("No immutable references found in the artifact.") + } else { + for varName, refs := range immutableRefs { + color.Yellow("\n%s:", varName) + for i, ref := range refs { + if ref.Value != "" { + fmt.Printf(" [%d] Offset: %d, Length: %d\n", i, ref.Offset, ref.Length) + fmt.Printf(" Value: 0x%s\n", ref.Value) + } else { + fmt.Printf(" [%d] Offset: %d, Length: %d\n", i, ref.Offset, ref.Length) + fmt.Printf(" Value: (not modified)\n") + } + } + } + } +} diff --git a/packages/contracts-bedrock/scripts/verify/verify-bytecode/main_test.go b/packages/contracts-bedrock/scripts/verify/verify-bytecode/main_test.go new file mode 100644 index 00000000000..bc07b31a836 --- /dev/null +++ b/packages/contracts-bedrock/scripts/verify/verify-bytecode/main_test.go @@ -0,0 +1,646 @@ +package main + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLoadArtifact(t *testing.T) { + // Create a temporary artifact file + tempDir := t.TempDir() + artifactPath := filepath.Join(tempDir, "artifact.json") + + // Test case 1: Valid artifact + validArtifact := map[string]interface{}{ + "deployedBytecode": map[string]interface{}{ + "object": "0x1234", + }, + } + artifactJSON, err := json.Marshal(validArtifact) + require.NoError(t, err) + err = os.WriteFile(artifactPath, artifactJSON, 0644) + require.NoError(t, err) + + artifact, err := loadArtifact(artifactPath) + require.NoError(t, err) + assert.Equal(t, "0x1234", artifact["deployedBytecode"].(map[string]interface{})["object"]) + + // Test case 2: Empty path + _, err = loadArtifact("") + assert.Error(t, err) + assert.Contains(t, err.Error(), "artifact path is required") + + // Test case 3: Non-existent file + _, err = loadArtifact(filepath.Join(tempDir, "nonexistent.json")) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to read artifact file") + + // Test case 4: Invalid JSON + err = os.WriteFile(artifactPath, []byte("invalid json"), 0644) + require.NoError(t, err) + _, err = loadArtifact(artifactPath) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse JSON") +} + +func TestGetDeployedBytecode(t *testing.T) { + tests := []struct { + name string + artifact map[string]interface{} + want string + wantErr bool + }{ + { + name: "Forge/Foundry format", + artifact: map[string]interface{}{ + "deployedBytecode": map[string]interface{}{ + "object": "0x1234", + }, + }, + want: "0x1234", + wantErr: false, + }, + { + name: "Standard format with string", + artifact: map[string]interface{}{ + "deployedBytecode": "0x5678", + }, + want: "0x5678", + wantErr: false, + }, + { + name: "Bytecode object format", + artifact: map[string]interface{}{ + "bytecode": map[string]interface{}{ + "object": "0xabcd", + }, + }, + want: "0xabcd", + wantErr: false, + }, + { + name: "Bytecode string format", + artifact: map[string]interface{}{ + "bytecode": "0xef01", + }, + want: "0xef01", + wantErr: false, + }, + { + name: "No bytecode", + artifact: map[string]interface{}{}, + want: "", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getDeployedBytecode(tt.artifact) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + } + }) + } +} + +func TestGetVariableNameFromAST(t *testing.T) { + tests := []struct { + name string + artifact map[string]interface{} + varID string + want string + }{ + { + name: "Find variable by ID", + artifact: map[string]interface{}{ + "ast": map[string]interface{}{ + "nodes": []interface{}{ + map[string]interface{}{ + "id": float64(123), + "name": "testVar", + }, + }, + }, + }, + varID: "123", + want: "testVar", + }, + { + name: "Find variable with path prefix", + artifact: map[string]interface{}{ + "ast": map[string]interface{}{ + "nodes": []interface{}{ + map[string]interface{}{ + "id": float64(456), + "name": "prefixedVar", + }, + }, + }, + }, + varID: "path:to:456", + want: "prefixedVar", + }, + { + name: "Variable not found", + artifact: map[string]interface{}{ + "ast": map[string]interface{}{ + "nodes": []interface{}{ + map[string]interface{}{ + "id": float64(789), + "name": "otherVar", + }, + }, + }, + }, + varID: "999", + want: "999", // Returns the ID if not found + }, + { + name: "Non-numeric ID", + artifact: map[string]interface{}{ + "ast": map[string]interface{}{ + "nodes": []interface{}{}, + }, + }, + varID: "abc", + want: "abc", // Returns the ID if not numeric + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := getVariableNameFromAST(tt.artifact, tt.varID) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestFindNodeName(t *testing.T) { + tests := []struct { + name string + node interface{} + targetID int + want string + }{ + { + name: "Find node in map", + node: map[string]interface{}{ + "id": float64(123), + "name": "testNode", + }, + targetID: 123, + want: "testNode", + }, + { + name: "Find node in nested map", + node: map[string]interface{}{ + "child": map[string]interface{}{ + "id": float64(456), + "name": "nestedNode", + }, + }, + targetID: 456, + want: "nestedNode", + }, + { + name: "Find node in array", + node: map[string]interface{}{ + "children": []interface{}{ + map[string]interface{}{ + "id": float64(789), + "name": "arrayNode", + }, + }, + }, + targetID: 789, + want: "arrayNode", + }, + { + name: "Node not found", + node: map[string]interface{}{ + "id": float64(111), + "name": "wrongNode", + }, + targetID: 999, + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := findNodeName(tt.node, tt.targetID) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestGetImmutableReferences(t *testing.T) { + tests := []struct { + name string + artifact map[string]interface{} + want map[string][]ImmutableReference + wantLen int + }{ + { + name: "Forge/Foundry format", + artifact: map[string]interface{}{ + "deployedBytecode": map[string]interface{}{ + "immutableReferences": map[string]interface{}{ + "123": []interface{}{ + map[string]interface{}{ + "start": float64(10), + "length": float64(32), + }, + }, + }, + }, + "ast": map[string]interface{}{ + "nodes": []interface{}{ + map[string]interface{}{ + "id": float64(123), + "name": "testVar", + }, + }, + }, + }, + want: map[string][]ImmutableReference{ + "testVar": { + { + Offset: 10, + Length: 32, + Value: "", + }, + }, + }, + wantLen: 1, + }, + { + name: "Standard format", + artifact: map[string]interface{}{ + "immutableReferences": map[string]interface{}{ + "456": []interface{}{ + []interface{}{float64(20), float64(16)}, + }, + }, + "ast": map[string]interface{}{ + "nodes": []interface{}{ + map[string]interface{}{ + "id": float64(456), + "name": "anotherVar", + }, + }, + }, + }, + want: map[string][]ImmutableReference{ + "anotherVar": { + { + Offset: 20, + Length: 16, + Value: "", + }, + }, + }, + wantLen: 1, + }, + { + name: "No immutable references", + artifact: map[string]interface{}{ + "deployedBytecode": map[string]interface{}{}, + }, + want: map[string][]ImmutableReference{}, + wantLen: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getImmutableReferences(tt.artifact) + assert.NoError(t, err) + assert.Equal(t, tt.wantLen, len(got)) + + // Check specific values for non-empty cases + if tt.wantLen > 0 { + for k, v := range tt.want { + assert.Contains(t, got, k) + assert.Equal(t, v[0].Offset, got[k][0].Offset) + assert.Equal(t, v[0].Length, got[k][0].Length) + } + } + }) + } +} + +func TestIsInImmutableReference(t *testing.T) { + immutableRefs := map[string][]ImmutableReference{ + "var1": { + {Offset: 10, Length: 5, Value: ""}, + }, + "var2": { + {Offset: 20, Length: 10, Value: ""}, + {Offset: 40, Length: 5, Value: ""}, + }, + } + + tests := []struct { + name string + position int + wantIn bool + wantVarName string + wantRef bool + }{ + { + name: "Inside first variable", + position: 12, + wantIn: true, + wantVarName: "var1", + wantRef: true, + }, + { + name: "At start of first variable", + position: 10, + wantIn: true, + wantVarName: "var1", + wantRef: true, + }, + { + name: "At end of first variable (exclusive)", + position: 15, + wantIn: false, + wantVarName: "", + wantRef: false, + }, + { + name: "Inside second variable, first reference", + position: 25, + wantIn: true, + wantVarName: "var2", + wantRef: true, + }, + { + name: "Inside second variable, second reference", + position: 42, + wantIn: true, + wantVarName: "var2", + wantRef: true, + }, + { + name: "Outside any variable", + position: 30, + wantIn: false, + wantVarName: "", + wantRef: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + inImmutable, varName, ref := isInImmutableReference(tt.position, immutableRefs) + assert.Equal(t, tt.wantIn, inImmutable) + assert.Equal(t, tt.wantVarName, varName) + if tt.wantRef { + assert.NotNil(t, ref) + } else { + assert.Nil(t, ref) + } + }) + } +} + +func TestFindDifferences(t *testing.T) { + tests := []struct { + name string + expectedBytecode string + actualBytecode string + immutableRefs map[string][]ImmutableReference + wantDiffs int + wantImmutable int + wantErr bool + }{ + { + name: "No differences", + expectedBytecode: "0x1234567890abcdef", + actualBytecode: "0x1234567890abcdef", + immutableRefs: map[string][]ImmutableReference{}, + wantDiffs: 0, + wantImmutable: 0, + wantErr: false, + }, + { + name: "Difference in immutable reference", + expectedBytecode: "0x1234000000abcdef", + actualBytecode: "0x1234fffffeabcdef", + immutableRefs: map[string][]ImmutableReference{ + "testVar": { + {Offset: 2, Length: 3, Value: ""}, + }, + }, + wantDiffs: 1, + wantImmutable: 1, + wantErr: false, + }, + { + name: "Difference outside immutable reference", + expectedBytecode: "0x1234567890abcdef", + actualBytecode: "0x1234567890abcdee", // Last byte different + immutableRefs: map[string][]ImmutableReference{}, + wantDiffs: 1, + wantImmutable: 0, + wantErr: false, + }, + { + name: "Multiple differences", + expectedBytecode: "0x1234000000abcdef", + actualBytecode: "0x1234fffffeabcdee", // Immutable and non-immutable differences + immutableRefs: map[string][]ImmutableReference{ + "testVar": { + {Offset: 2, Length: 3, Value: ""}, + }, + }, + wantDiffs: 2, + wantImmutable: 1, + wantErr: false, + }, + { + name: "Invalid expected bytecode", + expectedBytecode: "0xZZZZ", + actualBytecode: "0x1234", + immutableRefs: map[string][]ImmutableReference{}, + wantDiffs: 0, + wantImmutable: 0, + wantErr: true, + }, + { + name: "Invalid actual bytecode", + expectedBytecode: "0x1234", + actualBytecode: "0xZZZZ", + immutableRefs: map[string][]ImmutableReference{}, + wantDiffs: 0, + wantImmutable: 0, + wantErr: true, + }, + { + name: "Different lengths", + expectedBytecode: "0x1234", + actualBytecode: "0x123456", + immutableRefs: map[string][]ImmutableReference{}, + wantDiffs: 0, // No differences in the common part + wantImmutable: 0, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + diffs, err := findDifferences(tt.expectedBytecode, tt.actualBytecode, tt.immutableRefs) + + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.wantDiffs, len(diffs)) + + // Count immutable differences + immutableCount := 0 + for _, diff := range diffs { + if diff.InImmutable { + immutableCount++ + } + } + assert.Equal(t, tt.wantImmutable, immutableCount) + }) + } +} + +func TestFindDifferencesDetailed(t *testing.T) { + // Test with specific bytecode patterns to verify exact difference detection + expected := "0x1234567890abcdef" + actual := "0x1234FF7890abFFef" + + immutableRefs := map[string][]ImmutableReference{ + "testVar": { + {Offset: 2, Length: 1, Value: ""}, // Covers the "FF" difference + }, + } + + diffs, err := findDifferences(expected, actual, immutableRefs) + require.NoError(t, err) + + // Should find 2 differences: one in immutable ref, one outside + assert.Equal(t, 2, len(diffs)) + + // First difference should be in immutable reference + assert.True(t, diffs[0].InImmutable) + assert.Equal(t, "testVar", diffs[0].ImmutableName) + assert.Equal(t, 2, diffs[0].Start) // 0-based index after 0x prefix + assert.Equal(t, 1, diffs[0].Length) + assert.Equal(t, "56", diffs[0].Expected) + assert.Equal(t, "ff", diffs[0].Actual) + + // Second difference should be outside immutable reference + assert.False(t, diffs[1].InImmutable) + assert.Equal(t, "", diffs[1].ImmutableName) + assert.Equal(t, 6, diffs[1].Start) // 0-based index after 0x prefix + assert.Equal(t, 1, diffs[1].Length) + assert.Equal(t, "cd", diffs[1].Expected) + assert.Equal(t, "ff", diffs[1].Actual) + + // Check that immutable reference value was captured + assert.Equal(t, "ff", immutableRefs["testVar"][0].Value) +} + +// TestPrintDifferences doesn't test the actual output (which goes to stdout) +// but ensures the function doesn't panic with various inputs +func TestPrintDifferences(t *testing.T) { + differences := []BytecodeDifference{ + { + Start: 10, + Length: 2, + Expected: "1234", + Actual: "5678", + InImmutable: true, + ImmutableName: "testVar", + }, + { + Start: 20, + Length: 1, + Expected: "ab", + Actual: "cd", + InImmutable: false, + ImmutableName: "", + }, + } + + immutableRefs := map[string][]ImmutableReference{ + "testVar": { + {Offset: 10, Length: 2, Value: "5678"}, + }, + } + + // This should not panic + printDifferences(differences, immutableRefs) + + // Test with empty differences + printDifferences([]BytecodeDifference{}, immutableRefs) + + // Test with empty immutable references + printDifferences(differences, map[string][]ImmutableReference{}) +} + +// Test handling of bytecode with and without 0x prefix +func TestBytecodePrefix(t *testing.T) { + expected := "0x1234" + actual := "1234" // No prefix + + diffs, err := findDifferences(expected, actual, map[string][]ImmutableReference{}) + require.NoError(t, err) + assert.Equal(t, 0, len(diffs), "Should handle different prefixes correctly") + + // Test the reverse + diffs, err = findDifferences(actual, expected, map[string][]ImmutableReference{}) + require.NoError(t, err) + assert.Equal(t, 0, len(diffs), "Should handle different prefixes correctly") +} + +// Test consecutive differences are properly grouped +func TestConsecutiveDifferences(t *testing.T) { + expected := "0x123456789a" + actual := "0x12FFFF789a" // Two consecutive bytes different + + diffs, err := findDifferences(expected, actual, map[string][]ImmutableReference{}) + require.NoError(t, err) + + // Should group consecutive differences + assert.Equal(t, 1, len(diffs), "Consecutive differences should be grouped") + assert.Equal(t, 2, diffs[0].Length, "Difference should span 2 bytes") + assert.Equal(t, "3456", diffs[0].Expected) + assert.Equal(t, "ffff", diffs[0].Actual) +} + +// Test with empty bytecode +func TestEmptyBytecode(t *testing.T) { + _, err := findDifferences("0x", "0x", map[string][]ImmutableReference{}) + assert.NoError(t, err, "Should handle empty bytecode") + + _, err = findDifferences("", "", map[string][]ImmutableReference{}) + assert.NoError(t, err, "Should handle empty bytecode without prefix") +} + +// Test with invalid hex characters +func TestInvalidHex(t *testing.T) { + _, err := findDifferences("0x123Z", "0x1234", map[string][]ImmutableReference{}) + assert.Error(t, err, "Should detect invalid hex in expected bytecode") + + _, err = findDifferences("0x1234", "0x123Z", map[string][]ImmutableReference{}) + assert.Error(t, err, "Should detect invalid hex in actual bytecode") +} From 414f40210168431758f05ae96d3c30972febeca8 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Sat, 1 Mar 2025 09:25:46 +1000 Subject: [PATCH 041/130] op-e2e: Write jwt.secret to a temp dir instead of into the source code tree. (#14575) --- op-e2e/.gitignore | 1 - op-e2e/interop/supersystem_l2.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/op-e2e/.gitignore b/op-e2e/.gitignore index 0eccf16b621..7db44382a78 100644 --- a/op-e2e/.gitignore +++ b/op-e2e/.gitignore @@ -1,2 +1 @@ external_*/shim -interop/jwt.secret diff --git a/op-e2e/interop/supersystem_l2.go b/op-e2e/interop/supersystem_l2.go index ea96d68b283..7eb94c2ed70 100644 --- a/op-e2e/interop/supersystem_l2.go +++ b/op-e2e/interop/supersystem_l2.go @@ -167,7 +167,7 @@ func (s *interopE2ESystem) newNodeForL2( //SupervisorAddr: s.supervisor.RPC(), RPCAddr: "127.0.0.1", RPCPort: 0, - RPCJwtSecretPath: "jwt.secret", + RPCJwtSecretPath: s.t.TempDir() + "/jwt.secret", }, P2P: nil, // disabled P2P setup for now L1EpochPollInterval: time.Second * 2, From 4ba2eb00eafc3d7de2c8ceb6fd83913a8c0a2c0d Mon Sep 17 00:00:00 2001 From: ControlCplusControlV <44706811+ControlCplusControlV@users.noreply.github.com> Date: Fri, 28 Feb 2025 20:52:04 -0500 Subject: [PATCH 042/130] default to the correct gameType (#14591) --- packages/contracts-bedrock/scripts/deploy/Deploy.s.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index ca927105ad9..f4638d20801 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -965,7 +965,7 @@ contract Deploy is Deployer { ), saltMixer: saltMixer, gasLimit: uint64(cfg.l2GenesisBlockGasLimit()), - disputeGameType: GameType.wrap(4), + disputeGameType: GameTypes.PERMISSIONED_CANNON, disputeAbsolutePrestate: Claim.wrap(bytes32(cfg.faultGameAbsolutePrestate())), disputeMaxGameDepth: cfg.faultGameMaxDepth(), disputeSplitDepth: cfg.faultGameSplitDepth(), From d8b84ae072ee4b0312c38d335b3f9345324a59f8 Mon Sep 17 00:00:00 2001 From: Tyler Smith Date: Mon, 3 Mar 2025 02:37:02 -0800 Subject: [PATCH 043/130] Interop: Rewind tests (#14289) * interop,fix: Treat missing L1 block as non-canonical. * interop,tests: Add more Rewinder test cases. * interop,tests: Remove unncessary error return. --- .../supervisor/backend/rewinder/rewinder.go | 10 +- .../backend/rewinder/rewinder_test.go | 691 +++++++++++++++++- 2 files changed, 696 insertions(+), 5 deletions(-) diff --git a/op-supervisor/supervisor/backend/rewinder/rewinder.go b/op-supervisor/supervisor/backend/rewinder/rewinder.go index 8a41892aa24..69fe84ad164 100644 --- a/op-supervisor/supervisor/backend/rewinder/rewinder.go +++ b/op-supervisor/supervisor/backend/rewinder/rewinder.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/log" "github.com/ethereum-optimism/optimism/op-node/rollup/event" @@ -157,7 +158,7 @@ func (r *Rewinder) rewindL1ChainIfReorged(chainID eth.ChainID, newTip eth.BlockI // Get the canonical L1 block at our local head's height canonicalL1, err := r.l1Node.L1BlockRefByNumber(context.Background(), localSafeL1.Number) - if err != nil { + if err != nil && !errors.Is(err, ethereum.NotFound) { return fmt.Errorf("failed to get canonical L1 block at height %d: %w", localSafeL1.Number, err) } @@ -189,9 +190,14 @@ func (r *Rewinder) rewindL1ChainIfReorged(chainID eth.ChainID, newTip eth.BlockI currentL1 := localSafeL1.ID() for currentL1.Number >= finalizedL1.Number { // Get the canonical L1 block at this height from the node + // If it's not found we'll continue through the loop and try the previous block remoteL1, err := r.l1Node.L1BlockRefByNumber(context.Background(), currentL1.Number) if err != nil { - return fmt.Errorf("failed to get L1 block at height %d: %w", currentL1.Number, err) + if errors.Is(err, ethereum.NotFound) { + r.log.Debug("no L1 block at height", "chain", chainID, "height", currentL1.Number) + } else { + return fmt.Errorf("failed to get L1 block at height %d: %w", currentL1.Number, err) + } } // If hashes match, we found the common ancestor diff --git a/op-supervisor/supervisor/backend/rewinder/rewinder_test.go b/op-supervisor/supervisor/backend/rewinder/rewinder_test.go index fce81faf992..19353fe0ca7 100644 --- a/op-supervisor/supervisor/backend/rewinder/rewinder_test.go +++ b/op-supervisor/supervisor/backend/rewinder/rewinder_test.go @@ -7,6 +7,7 @@ import ( "path/filepath" "testing" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/require" @@ -735,6 +736,671 @@ func TestRewindL1PastCrossSafe(t *testing.T) { s.verifyHeads(chainID, block2.ID(), "should have rewound to block2") } +// TestRewindL1GenesisOnlyL2 tests rewinder behavior with only a genesis block. +func TestRewindL1GenesisOnlyL2(t *testing.T) { + s := setupTestChain(t) + defer s.Close() + + chainID := eth.ChainID{1} + chain := s.chains[chainID] + s.chainsDB.ForceInitialized(chainID) // force init for test + + // Create only genesis block + genesis := eth.L2BlockRef{ + Hash: common.HexToHash("0x1110"), + Number: 0, + ParentHash: common.Hash{}, + Time: 1000, + L1Origin: eth.BlockID{Hash: common.HexToHash("0xaaa0"), Number: 0}, + SequenceNumber: 0, + } + + // Setup sync node with genesis block + chain.setupSyncNodeBlocks(genesis) + + // Setup L1 blocks + l1Genesis := eth.BlockRef{ + Hash: common.HexToHash("0xaaa0"), + Number: 0, + Time: 900, + } + + l1GenesisB := eth.BlockRef{ + Hash: common.HexToHash("0xbbb0"), + Number: 0, + Time: 900, + } + + chain.l1Node.blocks[l1Genesis.Number] = l1Genesis + + // Seal genesis block + s.sealBlocks(chainID, genesis) + + // Create rewinder with all dependencies + i := New(s.logger, s.chainsDB, chain.l1Node) + i.AttachEmitter(&mockEmitter{}) + + // Make genesis block derived from l1Genesis and make it safe + s.makeBlockSafe(chainID, genesis, l1Genesis, true) + + // Set genesis L1 block as finalized + s.chainsDB.OnEvent(superevents.FinalizedL1RequestEvent{ + FinalizedL1: l1Genesis, + }) + + s.verifyHeads(chainID, genesis.ID(), "should have genesis as head") + + // Try L1 reorg at genesis by replacing with l1GenesisB + chain.l1Node.blocks[l1GenesisB.Number] = l1GenesisB + + // Trigger L1 reorg + i.OnEvent(superevents.RewindL1Event{ + IncomingBlock: l1GenesisB.ID(), + }) + + s.verifyHeads(chainID, genesis.ID(), "should still have genesis as head after L1 reorg attempt") + + // Try LocalDerived event with same genesis block + i.OnEvent(superevents.LocalSafeUpdateEvent{ + ChainID: chainID, + NewLocalSafe: types.DerivedBlockSealPair{ + Source: types.BlockSeal{ + Hash: l1GenesisB.Hash, + Number: l1GenesisB.Number, + }, + Derived: types.BlockSeal{ + Hash: genesis.Hash, + Number: genesis.Number, + }, + }, + }) + + s.verifyHeads(chainID, genesis.ID(), "should still have genesis as head after LocalDerived event") +} + +// TestRewindL1NoL2Impact tests L1 reorgs that don't affect L2 blocks. +func TestRewindL1NoL2Impact(t *testing.T) { + s := setupTestChain(t) + defer s.Close() + + chainID := eth.ChainID{1} + chain := s.chains[chainID] + s.chainsDB.ForceInitialized(chainID) // force init for test + + // Create L1 blocks + l1Block0 := eth.BlockRef{ + Hash: common.HexToHash("0xaaa0"), + Number: 0, + Time: 900, + } + l1Block1 := eth.BlockRef{ + Hash: common.HexToHash("0xaaa1"), + Number: 1, + Time: 912, + ParentHash: l1Block0.Hash, + } + l1Block2A := eth.BlockRef{ + Hash: common.HexToHash("0xaaa2"), + Number: 2, + Time: 924, + ParentHash: l1Block1.Hash, + } + chain.l1Node.blocks[l1Block0.Number] = l1Block0 + chain.l1Node.blocks[l1Block1.Number] = l1Block1 + chain.l1Node.blocks[l1Block2A.Number] = l1Block2A + + // Create L2 blocks, all derived from the same l1Block0 + genesis := eth.L2BlockRef{ + Hash: common.HexToHash("0x1110"), + Number: 0, + ParentHash: common.Hash{}, + Time: 1000, + L1Origin: l1Block0.ID(), + SequenceNumber: 0, + } + block1 := eth.L2BlockRef{ + Hash: common.HexToHash("0x1111"), + Number: 1, + ParentHash: genesis.Hash, + Time: 1002, + L1Origin: l1Block0.ID(), + SequenceNumber: 1, + } + block2 := eth.L2BlockRef{ + Hash: common.HexToHash("0x1112"), + Number: 2, + ParentHash: block1.Hash, + Time: 1004, + L1Origin: l1Block0.ID(), + SequenceNumber: 2, + } + + // Setup sync node with blocks + chain.setupSyncNodeBlocks(genesis, block1, block2) + + // Seal all blocks + s.sealBlocks(chainID, genesis, block1, block2) + + // Make all blocks safe and derived from l1Block0 + s.makeBlockSafe(chainID, genesis, l1Block0, true) + s.makeBlockSafe(chainID, block1, l1Block0, true) + s.makeBlockSafe(chainID, block2, l1Block0, true) + + // Create rewinder with all dependencies + i := New(s.logger, s.chainsDB, chain.l1Node) + i.AttachEmitter(&mockEmitter{}) + + // Verify heads are at block2 + s.verifyHeads(chainID, block2.ID(), "should have block2 as latest sealed block") + + // Replace l1Block2A with l1Block2B (causes L1 reorg at height 2) + l1Block2B := eth.BlockRef{ + Hash: common.HexToHash("0xbbb2"), + Number: 2, + Time: 924, + ParentHash: l1Block1.Hash, + } + chain.l1Node.blocks[l1Block2B.Number] = l1Block2B + + // Trigger L1 reorg + i.OnEvent(superevents.RewindL1Event{ + IncomingBlock: l1Block2B.ID(), + }) + + // Verify no rewind occurred since L2 blocks all derive from l1Block0 + s.verifyHeads(chainID, block2.ID(), "should still have block2 as latest sealed block") +} + +// TestRewindL1SingleBlockL2Impact tests L1 reorgs that affect a single L2 block. +func TestRewindL1SingleBlockL2Impact(t *testing.T) { + s := setupTestChain(t) + defer s.Close() + + chainID := eth.ChainID{1} + chain := s.chains[chainID] + s.chainsDB.ForceInitialized(chainID) // force init for test + + // Create L1 blocks + l1Block0 := eth.BlockRef{ + Hash: common.HexToHash("0xaaa0"), + Number: 0, + Time: 900, + } + l1Block1 := eth.BlockRef{ + Hash: common.HexToHash("0xaaa1"), + Number: 1, + Time: 912, + ParentHash: l1Block0.Hash, + } + l1Block2A := eth.BlockRef{ + Hash: common.HexToHash("0xaaa2"), + Number: 2, + Time: 924, + ParentHash: l1Block1.Hash, + } + chain.l1Node.blocks[l1Block0.Number] = l1Block0 + chain.l1Node.blocks[l1Block1.Number] = l1Block1 + chain.l1Node.blocks[l1Block2A.Number] = l1Block2A + + // Create L2 blocks with l1 origins + genesis := eth.L2BlockRef{ + Hash: common.HexToHash("0x1110"), + Number: 0, + ParentHash: common.Hash{}, + Time: 1000, + L1Origin: l1Block0.ID(), + SequenceNumber: 0, + } + block1 := eth.L2BlockRef{ + Hash: common.HexToHash("0x1111"), + Number: 1, + ParentHash: genesis.Hash, + Time: 1002, + L1Origin: l1Block0.ID(), + SequenceNumber: 1, + } + block2 := eth.L2BlockRef{ + Hash: common.HexToHash("0x1112"), + Number: 2, + ParentHash: block1.Hash, + Time: 1004, + L1Origin: l1Block1.ID(), + SequenceNumber: 0, + } + block3 := eth.L2BlockRef{ + Hash: common.HexToHash("0x1113"), + Number: 3, + ParentHash: block2.Hash, + Time: 1006, + L1Origin: l1Block1.ID(), + SequenceNumber: 1, + } + block4 := eth.L2BlockRef{ + Hash: common.HexToHash("0x1114"), + Number: 4, + ParentHash: block3.Hash, + Time: 1008, + L1Origin: l1Block2A.ID(), + SequenceNumber: 0, + } + + // Setup sync node with blocks + chain.setupSyncNodeBlocks(genesis, block1, block2, block3, block4) + + // Seal all blocks + s.sealBlocks(chainID, genesis, block1, block2, block3, block4) + + // Make all blocks safe + s.makeBlockSafe(chainID, genesis, l1Block0, true) + s.makeBlockSafe(chainID, block1, l1Block0, true) + s.makeBlockSafe(chainID, block1, l1Block1, true) // Bump scope + s.makeBlockSafe(chainID, block2, l1Block1, true) + s.makeBlockSafe(chainID, block3, l1Block1, true) + s.makeBlockSafe(chainID, block3, l1Block2A, true) // Bump scope + s.makeBlockSafe(chainID, block4, l1Block2A, true) + + // Create rewinder with all dependencies + i := New(s.logger, s.chainsDB, chain.l1Node) + i.AttachEmitter(&mockEmitter{}) + + // Verify heads are at block4 + s.verifyHeads(chainID, block4.ID(), "should have block4 as latest sealed block") + + // Replace l1Block2A with l1Block2B (causes L1 reorg at height 2) + l1Block2B := eth.BlockRef{ + Hash: common.HexToHash("0xbbb2"), + Number: 2, + Time: 924, + ParentHash: l1Block1.Hash, + } + chain.l1Node.blocks[l1Block2B.Number] = l1Block2B + + // Trigger L1 reorg + i.OnEvent(superevents.RewindL1Event{ + IncomingBlock: l1Block2B.ID(), + }) + + // Verify we rewound to block3 since it's derived from l1Block1 which is still canonical + s.verifyHeads(chainID, block3.ID(), "should have rewound to block3") +} + +// TestL1RewindDeepL2Impact tests L1 reorgs affecting multiple L2 blocks. +func TestRewindL1DeepL2Impact(t *testing.T) { + s := setupTestChain(t) + defer s.Close() + + chainID := eth.ChainID{1} + chain := s.chains[chainID] + s.chainsDB.ForceInitialized(chainID) // force init for test + + // Create blocks 0-120 + numBlocks := 120 + var l1Blocks []eth.BlockRef + var l2Blocks []eth.L2BlockRef + + // Create L1 blocks first (one per L2 block for simplicity) + for i := 0; i < numBlocks; i++ { + l1Block := eth.BlockRef{ + Hash: common.HexToHash(fmt.Sprintf("0xaaa%d", i)), + Number: uint64(i), + Time: 900 + uint64(i)*12, + } + if i > 0 { + l1Block.ParentHash = l1Blocks[i-1].Hash + } + l1Blocks = append(l1Blocks, l1Block) + chain.l1Node.blocks[uint64(i)] = l1Block + } + + // Create L2 blocks, each derived from corresponding L1 block + for i := 0; i < numBlocks; i++ { + l2Block := eth.L2BlockRef{ + Hash: common.HexToHash(fmt.Sprintf("0x%d", i)), + Number: uint64(i), + Time: 1000 + uint64(i)*2, + L1Origin: l1Blocks[i].ID(), + SequenceNumber: 0, + } + if i > 0 { + l2Block.ParentHash = l2Blocks[i-1].Hash + } + l2Blocks = append(l2Blocks, l2Block) + } + + // Setup sync node with all blocks + chain.setupSyncNodeBlocks(l2Blocks...) + + // Seal all blocks + for _, block := range l2Blocks { + s.sealBlocks(chainID, block) + } + + // Make genesis safe and derived from L1 genesis + s.makeBlockSafe(chainID, l2Blocks[0], l1Blocks[0], true) + + // Set genesis L1 block as finalized + s.chainsDB.OnEvent(superevents.FinalizedL1RequestEvent{ + FinalizedL1: l1Blocks[0], + }) + + // Make blocks up to 119 safe + for i := 1; i < numBlocks; i++ { + // First bump scope by linking the previous L2 block to the new L1 block + s.makeBlockSafe(chainID, l2Blocks[i-1], l1Blocks[i], true) // Bump scope + // Then add the new L2 block + s.makeBlockSafe(chainID, l2Blocks[i], l1Blocks[i], true) + } + + // Create rewinder with all dependencies + i := New(s.logger, s.chainsDB, chain.l1Node) + i.AttachEmitter(&mockEmitter{}) + + // Verify latest L2 block is at height 119 + s.verifyHeads(chainID, l2Blocks[numBlocks-1].ID(), "should have latest L2 block at height 119") + + // Create a divergent L1 block at height 20 + l1Block20B := eth.BlockRef{ + Hash: common.HexToHash("0xbbb20"), + Number: 20, + Time: 900 + 20*12, + ParentHash: l1Blocks[19].Hash, + } + chain.l1Node.blocks[20] = l1Block20B + + // Trigger L1 reorg + chain.l1Node.reorg(t, l1Block20B) + i.OnEvent(superevents.RewindL1Event{ + IncomingBlock: l1Block20B.ID(), + }) + + // Verify we rewound to block 19 since it's the last block derived from a canonical L1 block + s.verifyHeads(chainID, l2Blocks[19].ID(), "should have rewound to block 19") +} + +// TestRewindL2LocalDerivationUnsafeMismatch tests handling of mismatched unsafe blocks. +func TestRewindL2LocalDerivationUnsafeMismatch(t *testing.T) { + s := setupTestChain(t) + defer s.Close() + + chainID := eth.ChainID{1} + chain := s.chains[chainID] + s.chainsDB.ForceInitialized(chainID) // force init for test + + // Create L1 blocks + l1Block0 := eth.BlockRef{ + Hash: common.HexToHash("0xaaa0"), + Number: 0, + Time: 900, + } + l1Block1 := eth.BlockRef{ + Hash: common.HexToHash("0xaaa1"), + Number: 1, + Time: 912, + ParentHash: l1Block0.Hash, + } + l1Block2 := eth.BlockRef{ + Hash: common.HexToHash("0xaaa2"), + Number: 2, + Time: 924, + ParentHash: l1Block1.Hash, + } + l1Block3 := eth.BlockRef{ + Hash: common.HexToHash("0xaaa3"), + Number: 3, + Time: 936, + ParentHash: l1Block2.Hash, + } + chain.l1Node.blocks[l1Block0.Number] = l1Block0 + chain.l1Node.blocks[l1Block1.Number] = l1Block1 + chain.l1Node.blocks[l1Block2.Number] = l1Block2 + chain.l1Node.blocks[l1Block3.Number] = l1Block3 + + // Create L2 blocks + genesis := eth.L2BlockRef{ + Hash: common.HexToHash("0x1110"), + Number: 0, + ParentHash: common.Hash{}, + Time: 1000, + L1Origin: l1Block0.ID(), + SequenceNumber: 0, + } + block1 := eth.L2BlockRef{ + Hash: common.HexToHash("0x1111"), + Number: 1, + ParentHash: genesis.Hash, + Time: 1002, + L1Origin: l1Block1.ID(), + SequenceNumber: 0, + } + block2 := eth.L2BlockRef{ + Hash: common.HexToHash("0x1112"), + Number: 2, + ParentHash: block1.Hash, + Time: 1004, + L1Origin: l1Block2.ID(), + SequenceNumber: 0, + } + block3A := eth.L2BlockRef{ + Hash: common.HexToHash("0x1113a"), + Number: 3, + ParentHash: block2.Hash, + Time: 1006, + L1Origin: l1Block3.ID(), + SequenceNumber: 0, + } + block3B := eth.L2BlockRef{ + Hash: common.HexToHash("0x1113b"), + Number: 3, + ParentHash: block2.Hash, + Time: 1006, + L1Origin: l1Block3.ID(), // Same L1 origin but different hash + SequenceNumber: 0, + } + + // Setup sync node with all blocks + chain.setupSyncNodeBlocks(genesis, block1, block2, block3A, block3B) + + // Seal blocks up to block3A + s.sealBlocks(chainID, genesis, block1, block2, block3A) + + // Make blocks up to block2 safe + s.makeBlockSafe(chainID, genesis, l1Block0, true) + s.makeBlockSafe(chainID, genesis, l1Block1, true) // Bump scope + s.makeBlockSafe(chainID, block1, l1Block1, true) + s.makeBlockSafe(chainID, block1, l1Block2, true) // Bump scope + s.makeBlockSafe(chainID, block2, l1Block2, true) + + // Create rewinder with all dependencies + i := New(s.logger, s.chainsDB, chain.l1Node) + i.AttachEmitter(&mockEmitter{}) + + // Verify block3A is the latest sealed block but not safe + s.verifyLogsHead(chainID, block3A.ID(), "should have block3A as latest sealed block") + s.verifyLocalSafe(chainID, block2.ID(), "block2 should be local-safe") + s.verifyCrossSafe(chainID, block2.ID(), "block2 should be cross-safe") + + // Simulate receiving a LocalDerivedEvent for block3B + i.OnEvent(superevents.LocalSafeUpdateEvent{ + ChainID: chainID, + NewLocalSafe: types.DerivedBlockSealPair{ + Source: types.BlockSeal{ + Hash: l1Block3.Hash, + Number: l1Block3.Number, + }, + Derived: types.BlockSeal{ + Hash: block3B.Hash, + Number: block3B.Number, + }, + }, + }) + + // Verify rewound to block2 since it's the common ancestor + s.verifyLogsHead(chainID, block2.ID(), "should have rewound to block2") + s.verifyLocalSafe(chainID, block2.ID(), "block2 should still be local-safe") + s.verifyCrossSafe(chainID, block2.ID(), "block2 should still be cross-safe") + + // Add block3B to chain + s.sealBlocks(chainID, block3B) + + // Verify now on new chain + s.verifyLogsHead(chainID, block3B.ID(), "should be on block3B") +} + +// TestRewindL2LocalDerivationSafeMismatch tests handling of mismatched safe blocks. +func TestRewindL2LocalDerivationSafeMismatch(t *testing.T) { + s := setupTestChain(t) + defer s.Close() + + chainID := eth.ChainID{1} + chain := s.chains[chainID] + s.chainsDB.ForceInitialized(chainID) // force init for test + + // Create L1 blocks + l1Block0 := eth.BlockRef{ + Hash: common.HexToHash("0xaaa0"), + Number: 0, + Time: 900, + } + l1Block1 := eth.BlockRef{ + Hash: common.HexToHash("0xaaa1"), + Number: 1, + Time: 912, + ParentHash: l1Block0.Hash, + } + l1Block2 := eth.BlockRef{ + Hash: common.HexToHash("0xaaa2"), + Number: 2, + Time: 924, + ParentHash: l1Block1.Hash, + } + l1Block3A := eth.BlockRef{ + Hash: common.HexToHash("0xaaa3"), + Number: 3, + Time: 936, + ParentHash: l1Block2.Hash, + } + chain.l1Node.blocks[l1Block0.Number] = l1Block0 + chain.l1Node.blocks[l1Block1.Number] = l1Block1 + chain.l1Node.blocks[l1Block2.Number] = l1Block2 + chain.l1Node.blocks[l1Block3A.Number] = l1Block3A + + // Create L2 blocks + genesis := eth.L2BlockRef{ + Hash: common.HexToHash("0x1110"), + Number: 0, + ParentHash: common.Hash{}, + Time: 1000, + L1Origin: l1Block0.ID(), + SequenceNumber: 0, + } + block1 := eth.L2BlockRef{ + Hash: common.HexToHash("0x1111"), + Number: 1, + ParentHash: genesis.Hash, + Time: 1002, + L1Origin: l1Block1.ID(), + SequenceNumber: 0, + } + block2 := eth.L2BlockRef{ + Hash: common.HexToHash("0x1112"), + Number: 2, + ParentHash: block1.Hash, + Time: 1004, + L1Origin: l1Block2.ID(), + SequenceNumber: 0, + } + block3A := eth.L2BlockRef{ + Hash: common.HexToHash("0x1113a"), + Number: 3, + ParentHash: block2.Hash, + Time: 1006, + L1Origin: l1Block3A.ID(), + SequenceNumber: 0, + } + block3B := eth.L2BlockRef{ + Hash: common.HexToHash("0x1113b"), + Number: 3, + ParentHash: block2.Hash, + Time: 1006, + L1Origin: eth.BlockID{Hash: common.HexToHash("0xbbb3"), Number: 3}, // Different L1 origin + SequenceNumber: 0, + } + + // Setup sync node with all blocks + chain.setupSyncNodeBlocks(genesis, block1, block2, block3A, block3B) + + // Seal blocks up to block3A + s.sealBlocks(chainID, genesis, block1, block2, block3A) + + // Make blocks safe up to block3A + s.makeBlockSafe(chainID, genesis, l1Block0, true) + s.makeBlockSafe(chainID, genesis, l1Block1, true) // Bump scope + s.makeBlockSafe(chainID, block1, l1Block1, true) + s.makeBlockSafe(chainID, block1, l1Block2, true) // Bump scope + s.makeBlockSafe(chainID, block2, l1Block2, true) + s.makeBlockSafe(chainID, block2, l1Block3A, true) // Bump scope + s.makeBlockSafe(chainID, block3A, l1Block3A, true) + + // Set L1 blocks as finalized up to l1Block3A + s.chainsDB.OnEvent(superevents.FinalizedL1RequestEvent{ + FinalizedL1: l1Block0, + }) + s.chainsDB.OnEvent(superevents.FinalizedL1RequestEvent{ + FinalizedL1: l1Block1, + }) + s.chainsDB.OnEvent(superevents.FinalizedL1RequestEvent{ + FinalizedL1: l1Block2, + }) + + // Create rewinder with all dependencies + i := New(s.logger, s.chainsDB, chain.l1Node) + i.AttachEmitter(&mockEmitter{}) + + // Verify block3A is the latest sealed and safe block + s.verifyHeads(chainID, block3A.ID(), "should have block3A as the latest sealed and safe block") + + // Replace l1Block3A with l1Block3B + l1Block3B := eth.BlockRef{ + Hash: common.HexToHash("0xbbb3"), + Number: 3, + Time: 936, + ParentHash: l1Block2.Hash, + } + chain.l1Node.blocks[l1Block3B.Number] = l1Block3B + + // Trigger L1 reorg + chain.l1Node.reorg(t, l1Block3B) + i.OnEvent(superevents.RewindL1Event{ + IncomingBlock: l1Block3B.ID(), + }) + + // Verify we rewound to block2 since it's derived from l1Block2 which is still canonical + s.verifyHeads(chainID, block2.ID(), "should have rewound to block2") + + // Simulate receiving a LocalDerivedEvent for block3B + i.OnEvent(superevents.LocalSafeUpdateEvent{ + ChainID: chainID, + NewLocalSafe: types.DerivedBlockSealPair{ + Source: types.BlockSeal{ + Hash: l1Block3B.Hash, + Number: l1Block3B.Number, + }, + Derived: types.BlockSeal{ + Hash: block3B.Hash, + Number: block3B.Number, + }, + }, + }) + + // Add block3B to chain + s.sealBlocks(chainID, block3B) + s.makeBlockSafe(chainID, block2, l1Block3B, true) // Bump scope + s.makeBlockSafe(chainID, block3B, l1Block3B, true) + + // Verify now on new chain with block3B as latest + s.verifyHeads(chainID, block3B.ID(), "should have block3B as the latest sealed and safe block") +} + type testSetup struct { t *testing.T logger log.Logger @@ -876,13 +1542,13 @@ func (s *testSetup) verifyHeads(chainID eth.ChainID, expectedHead eth.BlockID, m func (s *testSetup) verifyLocalSafe(chainID eth.ChainID, expectedHead eth.BlockID, msg string) { localSafe, err := s.chainsDB.LocalSafe(chainID) require.NoError(s.t, err) - require.Equal(s.t, expectedHead.Hash, localSafe.Derived.Hash, msg) + require.Equal(s.t, expectedHead.Hash, localSafe.Derived.Hash, fmt.Sprintf("%s: local-safe head mismatch", msg)) } func (s *testSetup) verifyCrossSafe(chainID eth.ChainID, expectedHead eth.BlockID, msg string) { crossSafe, err := s.chainsDB.CrossSafe(chainID) require.NoError(s.t, err) - require.Equal(s.t, expectedHead.Hash, crossSafe.Derived.Hash, msg) + require.Equal(s.t, expectedHead.Hash, crossSafe.Derived.Hash, fmt.Sprintf("%s: cross-safe head mismatch", msg)) } func (s *testSetup) verifyLogsHead(chainID eth.ChainID, expectedHead eth.BlockID, msg string) { @@ -1014,7 +1680,26 @@ func newMockL1Node() *mockL1Node { func (m *mockL1Node) L1BlockRefByNumber(ctx context.Context, number uint64) (eth.L1BlockRef, error) { block, ok := m.blocks[number] if !ok { - return eth.L1BlockRef{}, fmt.Errorf("block not found: %d", number) + return eth.L1BlockRef{}, fmt.Errorf("block %d not found: %w", number, ethereum.NotFound) } return eth.L1BlockRef(block), nil } + +func (m *mockL1Node) reorg(t *testing.T, newBlock eth.BlockRef) { + if newBlock.Number > 0 { + parent := m.blocks[newBlock.Number-1] + if parent.Hash != newBlock.ParentHash { + t.Fatalf("block %d parent hash %s does not match canonical parent %s", newBlock.Number, newBlock.ParentHash, parent.Hash) + } + } + + // Remove all blocks after reorg point from blocks map + newBlocks := make(map[uint64]eth.BlockRef) + for i := uint64(0); i < newBlock.Number; i++ { + newBlocks[i] = m.blocks[i] + } + m.blocks = newBlocks + + // Add the new block + m.blocks[newBlock.Number] = newBlock +} From c46bb168e221c2b961a63b0107cbf6a95ffc5348 Mon Sep 17 00:00:00 2001 From: George Knee Date: Mon, 3 Mar 2025 13:45:54 +0000 Subject: [PATCH 044/130] op-batcher: introduce `PREFER_LOCAL_SAFE_L2` config var (#14587) * op-batcher: introduce PREFER_LOCAL_SAFE_L2 config var * lint * Apply suggestions from code review Co-authored-by: Sebastian Stammler * lint --------- Co-authored-by: Sebastian Stammler --- op-batcher/batcher/config.go | 4 + op-batcher/batcher/driver.go | 2 +- op-batcher/batcher/service.go | 4 + op-batcher/batcher/sync_actions.go | 30 +++-- op-batcher/batcher/sync_actions_test.go | 142 ++++++++++++++++-------- op-batcher/flags/flags.go | 7 ++ 6 files changed, 134 insertions(+), 55 deletions(-) diff --git a/op-batcher/batcher/config.go b/op-batcher/batcher/config.go index c712b74bc8e..85919a56114 100644 --- a/op-batcher/batcher/config.go +++ b/op-batcher/batcher/config.go @@ -110,6 +110,9 @@ type CLIConfig struct { // ThrottleAlwaysBlockSize is the total per-block DA limit to always imposing on block building. ThrottleAlwaysBlockSize uint64 + // PreferLocalSafeL2 triggers the batcher to load blocks from the sequencer based on the LocalSafeL2 SyncStatus field (instead of the SafeL2 field). + PreferLocalSafeL2 bool + // TestUseMaxTxSizeForBlobs allows to set the blob size with MaxL1TxSize. // Should only be used for testing purposes. TestUseMaxTxSizeForBlobs bool @@ -215,5 +218,6 @@ func NewConfig(ctx *cli.Context) *CLIConfig { ThrottleTxSize: ctx.Uint64(flags.ThrottleTxSizeFlag.Name), ThrottleBlockSize: ctx.Uint64(flags.ThrottleBlockSizeFlag.Name), ThrottleAlwaysBlockSize: ctx.Uint64(flags.ThrottleAlwaysBlockSizeFlag.Name), + PreferLocalSafeL2: ctx.Bool(flags.PreferLocalSafeL2Flag.Name), } } diff --git a/op-batcher/batcher/driver.go b/op-batcher/batcher/driver.go index 3bbeb5f1706..4ff50753a48 100644 --- a/op-batcher/batcher/driver.go +++ b/op-batcher/batcher/driver.go @@ -422,7 +422,7 @@ func (l *BatchSubmitter) syncAndPrune(syncStatus *eth.SyncStatus) *inclusiveBloc defer l.channelMgrMutex.Unlock() // Decide appropriate actions - syncActions, outOfSync := computeSyncActions(*syncStatus, l.prevCurrentL1, l.channelMgr.blocks, l.channelMgr.channelQueue, l.Log) + syncActions, outOfSync := computeSyncActions(*syncStatus, l.prevCurrentL1, l.channelMgr.blocks, l.channelMgr.channelQueue, l.Log, l.Config.PreferLocalSafeL2) if outOfSync { // If the sequencer is out of sync diff --git a/op-batcher/batcher/service.go b/op-batcher/batcher/service.go index dbf826b034c..4c2d9c8ed48 100644 --- a/op-batcher/batcher/service.go +++ b/op-batcher/batcher/service.go @@ -48,6 +48,8 @@ type BatcherConfig struct { // For throttling DA. See CLIConfig in config.go for details on these parameters. ThrottleThreshold, ThrottleTxSize uint64 ThrottleBlockSize, ThrottleAlwaysBlockSize uint64 + + PreferLocalSafeL2 bool } // BatcherService represents a full batch-submitter instance and its resources, @@ -111,6 +113,8 @@ func (bs *BatcherService) initFromCLIConfig(ctx context.Context, version string, bs.ThrottleBlockSize = cfg.ThrottleBlockSize bs.ThrottleAlwaysBlockSize = cfg.ThrottleAlwaysBlockSize + bs.PreferLocalSafeL2 = cfg.PreferLocalSafeL2 + optsFromRPC, err := bs.initRPCClients(ctx, cfg) if err != nil { return err diff --git a/op-batcher/batcher/sync_actions.go b/op-batcher/batcher/sync_actions.go index 232ab5a86ef..a6645e6ae74 100644 --- a/op-batcher/batcher/sync_actions.go +++ b/op-batcher/batcher/sync_actions.go @@ -47,15 +47,29 @@ func (s syncActions) TerminalString() string { // state of the batcher (blocks and channels), the new sync status, and the previous current L1 block. The actions are returned // in a struct specifying the number of blocks to prune, the number of channels to prune, whether to wait for node sync, the block // range to load into the local state, and whether to clear the state entirely. Returns an boolean indicating if the sequencer is out of sync. -func computeSyncActions[T channelStatuser](newSyncStatus eth.SyncStatus, prevCurrentL1 eth.L1BlockRef, blocks queue.Queue[*types.Block], channels []T, l log.Logger) (syncActions, bool) { +func computeSyncActions[T channelStatuser]( + newSyncStatus eth.SyncStatus, + prevCurrentL1 eth.L1BlockRef, + blocks queue.Queue[*types.Block], + channels []T, + l log.Logger, + preferLocalSafeL2 bool, +) (syncActions, bool) { m := l.With( "syncStatus.headL1", newSyncStatus.HeadL1, "syncStatus.currentL1", newSyncStatus.CurrentL1, "syncStatus.localSafeL2", newSyncStatus.LocalSafeL2, + "syncStatus.safeL2", newSyncStatus.SafeL2, "syncStatus.unsafeL2", newSyncStatus.UnsafeL2, ) + safeL2 := newSyncStatus.SafeL2 + if preferLocalSafeL2 { + // This is preffered when running interop, but not yet enabled by default. + safeL2 = newSyncStatus.LocalSafeL2 + } + // PART 1: Initial checks on the sync status if newSyncStatus.HeadL1 == (eth.L1BlockRef{}) { m.Warn("empty sync status") @@ -69,8 +83,8 @@ func computeSyncActions[T channelStatuser](newSyncStatus eth.SyncStatus, prevCur } var allUnsafeBlocks *inclusiveBlockRange - if newSyncStatus.UnsafeL2.Number > newSyncStatus.LocalSafeL2.Number { - allUnsafeBlocks = &inclusiveBlockRange{newSyncStatus.LocalSafeL2.Number + 1, newSyncStatus.UnsafeL2.Number} + if newSyncStatus.UnsafeL2.Number > safeL2.Number { + allUnsafeBlocks = &inclusiveBlockRange{safeL2.Number + 1, newSyncStatus.UnsafeL2.Number} } // PART 2: checks involving only the oldest block in the state @@ -89,12 +103,12 @@ func computeSyncActions[T channelStatuser](newSyncStatus eth.SyncStatus, prevCur // and we need to start over, loading all unsafe blocks // from the sequencer. startAfresh := syncActions{ - clearState: &newSyncStatus.LocalSafeL2.L1Origin, + clearState: &safeL2.L1Origin, blocksToLoad: allUnsafeBlocks, } oldestBlockInStateNum := oldestBlockInState.NumberU64() - nextSafeBlockNum := newSyncStatus.LocalSafeL2.Number + 1 + nextSafeBlockNum := safeL2.Number + 1 if nextSafeBlockNum < oldestBlockInStateNum { m.Warn("next safe block is below oldest block in state", @@ -120,7 +134,7 @@ func computeSyncActions[T channelStatuser](newSyncStatus eth.SyncStatus, prevCur return startAfresh, false } - if numBlocksToDequeue > 0 && blocks[numBlocksToDequeue-1].Hash() != newSyncStatus.LocalSafeL2.Hash { + if numBlocksToDequeue > 0 && blocks[numBlocksToDequeue-1].Hash() != safeL2.Hash { m.Warn("safe chain reorg, clearing channel manager state", "syncActions", startAfresh, "existingBlock", eth.ToBlockID(blocks[numBlocksToDequeue-1])) @@ -132,7 +146,7 @@ func computeSyncActions[T channelStatuser](newSyncStatus eth.SyncStatus, prevCur if ch.isFullySubmitted() && !ch.isTimedOut() && newSyncStatus.CurrentL1.Number > ch.MaxInclusionBlock() && - newSyncStatus.LocalSafeL2.Number < ch.LatestL2().Number { + safeL2.Number < ch.LatestL2().Number { // Safe head did not make the expected progress // for a fully submitted channel. This indicates // that the derivation pipeline may have stalled @@ -147,7 +161,7 @@ func computeSyncActions[T channelStatuser](newSyncStatus eth.SyncStatus, prevCur // PART 5: happy path numChannelsToPrune := 0 for _, ch := range channels { - if ch.LatestL2().Number > newSyncStatus.LocalSafeL2.Number { + if ch.LatestL2().Number > safeL2.Number { // If the channel has blocks which are not yet safe // we do not want to prune it. break diff --git a/op-batcher/batcher/sync_actions_test.go b/op-batcher/batcher/sync_actions_test.go index 365b4022a66..ff5ca7f2d2d 100644 --- a/op-batcher/batcher/sync_actions_test.go +++ b/op-batcher/batcher/sync_actions_test.go @@ -56,6 +56,7 @@ func TestBatchSubmitter_computeSyncActions(t *testing.T) { } happyCaseLogs := []string{"computed sync actions"} + noBlocksLogs := []string{"no blocks in state"} type TestCase struct { name string @@ -68,6 +69,7 @@ func TestBatchSubmitter_computeSyncActions(t *testing.T) { expected syncActions expectedSeqOutOfSync bool expectedLogs []string + preferLocalSafeL2 bool } testCases := []TestCase{ @@ -95,10 +97,10 @@ func TestBatchSubmitter_computeSyncActions(t *testing.T) { // although the sequencer has derived up the same // L1 block height, it derived fewer safe L2 blocks. newSyncStatus: eth.SyncStatus{ - HeadL1: eth.BlockRef{Number: 6}, - CurrentL1: eth.BlockRef{Number: 1}, - LocalSafeL2: eth.L2BlockRef{Number: 100, L1Origin: eth.BlockID{Number: 1}}, - UnsafeL2: eth.L2BlockRef{Number: 109}, + HeadL1: eth.BlockRef{Number: 6}, + CurrentL1: eth.BlockRef{Number: 1}, + SafeL2: eth.L2BlockRef{Number: 100, L1Origin: eth.BlockID{Number: 1}}, + UnsafeL2: eth.L2BlockRef{Number: 109}, }, prevCurrentL1: eth.BlockRef{Number: 1}, blocks: queue.Queue[*types.Block]{block102, block103}, // note absence of block101 @@ -113,10 +115,10 @@ func TestBatchSubmitter_computeSyncActions(t *testing.T) { // This can happen if another batcher instance got some blocks // included in the safe chain: newSyncStatus: eth.SyncStatus{ - HeadL1: eth.BlockRef{Number: 6}, - CurrentL1: eth.BlockRef{Number: 2}, - LocalSafeL2: eth.L2BlockRef{Number: 104, L1Origin: eth.BlockID{Number: 1}}, - UnsafeL2: eth.L2BlockRef{Number: 109}, + HeadL1: eth.BlockRef{Number: 6}, + CurrentL1: eth.BlockRef{Number: 2}, + SafeL2: eth.L2BlockRef{Number: 104, L1Origin: eth.BlockID{Number: 1}}, + UnsafeL2: eth.L2BlockRef{Number: 109}, }, prevCurrentL1: eth.BlockRef{Number: 1}, blocks: queue.Queue[*types.Block]{block101, block102, block103}, @@ -131,10 +133,10 @@ func TestBatchSubmitter_computeSyncActions(t *testing.T) { // This can happen if there is an L1 reorg, the safe chain is at an acceptable // height but it does not descend from the blocks in state: newSyncStatus: eth.SyncStatus{ - HeadL1: eth.BlockRef{Number: 5}, - CurrentL1: eth.BlockRef{Number: 2}, - LocalSafeL2: eth.L2BlockRef{Number: 103, Hash: block101.Hash(), L1Origin: eth.BlockID{Number: 1}}, // note hash mismatch - UnsafeL2: eth.L2BlockRef{Number: 109}, + HeadL1: eth.BlockRef{Number: 5}, + CurrentL1: eth.BlockRef{Number: 2}, + SafeL2: eth.L2BlockRef{Number: 103, Hash: block101.Hash(), L1Origin: eth.BlockID{Number: 1}}, // note hash mismatch + UnsafeL2: eth.L2BlockRef{Number: 109}, }, prevCurrentL1: eth.BlockRef{Number: 1}, blocks: queue.Queue[*types.Block]{block101, block102, block103}, @@ -149,10 +151,10 @@ func TestBatchSubmitter_computeSyncActions(t *testing.T) { // This could happen if the batcher unexpectedly violates the // Holocene derivation rules: newSyncStatus: eth.SyncStatus{ - HeadL1: eth.BlockRef{Number: 3}, - CurrentL1: eth.BlockRef{Number: 2}, - LocalSafeL2: eth.L2BlockRef{Number: 101, Hash: block101.Hash(), L1Origin: eth.BlockID{Number: 1}}, - UnsafeL2: eth.L2BlockRef{Number: 109}, + HeadL1: eth.BlockRef{Number: 3}, + CurrentL1: eth.BlockRef{Number: 2}, + SafeL2: eth.L2BlockRef{Number: 101, Hash: block101.Hash(), L1Origin: eth.BlockID{Number: 1}}, + UnsafeL2: eth.L2BlockRef{Number: 109}, }, prevCurrentL1: eth.BlockRef{Number: 1}, blocks: queue.Queue[*types.Block]{block101, block102, block103}, @@ -166,10 +168,10 @@ func TestBatchSubmitter_computeSyncActions(t *testing.T) { {name: "failed to make expected progress (unsafe=safe)", // Edge case where unsafe = safe newSyncStatus: eth.SyncStatus{ - HeadL1: eth.BlockRef{Number: 3}, - CurrentL1: eth.BlockRef{Number: 2}, - LocalSafeL2: eth.L2BlockRef{Number: 101, Hash: block101.Hash(), L1Origin: eth.BlockID{Number: 1}}, - UnsafeL2: eth.L2BlockRef{Number: 101}, + HeadL1: eth.BlockRef{Number: 3}, + CurrentL1: eth.BlockRef{Number: 2}, + SafeL2: eth.L2BlockRef{Number: 101, Hash: block101.Hash(), L1Origin: eth.BlockID{Number: 1}}, + UnsafeL2: eth.L2BlockRef{Number: 101}, }, prevCurrentL1: eth.BlockRef{Number: 1}, blocks: queue.Queue[*types.Block]{block102, block103}, @@ -185,10 +187,10 @@ func TestBatchSubmitter_computeSyncActions(t *testing.T) { // and we didn't submit or have any txs confirmed since // the last sync. newSyncStatus: eth.SyncStatus{ - HeadL1: eth.BlockRef{Number: 4}, - CurrentL1: eth.BlockRef{Number: 1}, - LocalSafeL2: eth.L2BlockRef{Number: 100}, - UnsafeL2: eth.L2BlockRef{Number: 109}, + HeadL1: eth.BlockRef{Number: 4}, + CurrentL1: eth.BlockRef{Number: 1}, + SafeL2: eth.L2BlockRef{Number: 100}, + UnsafeL2: eth.L2BlockRef{Number: 109}, }, prevCurrentL1: eth.BlockRef{Number: 1}, blocks: queue.Queue[*types.Block]{block101, block102, block103}, @@ -201,10 +203,10 @@ func TestBatchSubmitter_computeSyncActions(t *testing.T) { {name: "no blocks", // This happens when the batcher is starting up for the first time newSyncStatus: eth.SyncStatus{ - HeadL1: eth.BlockRef{Number: 5}, - CurrentL1: eth.BlockRef{Number: 2}, - LocalSafeL2: eth.L2BlockRef{Number: 103, Hash: block103.Hash()}, - UnsafeL2: eth.L2BlockRef{Number: 109}, + HeadL1: eth.BlockRef{Number: 5}, + CurrentL1: eth.BlockRef{Number: 2}, + SafeL2: eth.L2BlockRef{Number: 103, Hash: block103.Hash()}, + UnsafeL2: eth.L2BlockRef{Number: 109}, }, prevCurrentL1: eth.BlockRef{Number: 1}, blocks: queue.Queue[*types.Block]{}, @@ -217,10 +219,10 @@ func TestBatchSubmitter_computeSyncActions(t *testing.T) { {name: "happy path", // This happens when the safe chain is being progressed as expected: newSyncStatus: eth.SyncStatus{ - HeadL1: eth.BlockRef{Number: 5}, - CurrentL1: eth.BlockRef{Number: 2}, - LocalSafeL2: eth.L2BlockRef{Number: 103, Hash: block103.Hash()}, - UnsafeL2: eth.L2BlockRef{Number: 109}, + HeadL1: eth.BlockRef{Number: 5}, + CurrentL1: eth.BlockRef{Number: 2}, + SafeL2: eth.L2BlockRef{Number: 103, Hash: block103.Hash()}, + UnsafeL2: eth.L2BlockRef{Number: 109}, }, prevCurrentL1: eth.BlockRef{Number: 1}, blocks: queue.Queue[*types.Block]{block101, block102, block103}, @@ -234,10 +236,10 @@ func TestBatchSubmitter_computeSyncActions(t *testing.T) { }, {name: "happy path + multiple channels", newSyncStatus: eth.SyncStatus{ - HeadL1: eth.BlockRef{Number: 5}, - CurrentL1: eth.BlockRef{Number: 2}, - LocalSafeL2: eth.L2BlockRef{Number: 103, Hash: block103.Hash()}, - UnsafeL2: eth.L2BlockRef{Number: 109}, + HeadL1: eth.BlockRef{Number: 5}, + CurrentL1: eth.BlockRef{Number: 2}, + SafeL2: eth.L2BlockRef{Number: 103, Hash: block103.Hash()}, + UnsafeL2: eth.L2BlockRef{Number: 109}, }, prevCurrentL1: eth.BlockRef{Number: 1}, blocks: queue.Queue[*types.Block]{block101, block102, block103, block104}, @@ -251,23 +253,23 @@ func TestBatchSubmitter_computeSyncActions(t *testing.T) { }, {name: "no progress + unsafe=safe", newSyncStatus: eth.SyncStatus{ - HeadL1: eth.BlockRef{Number: 5}, - CurrentL1: eth.BlockRef{Number: 2}, - LocalSafeL2: eth.L2BlockRef{Number: 100}, - UnsafeL2: eth.L2BlockRef{Number: 100}, + HeadL1: eth.BlockRef{Number: 5}, + CurrentL1: eth.BlockRef{Number: 2}, + SafeL2: eth.L2BlockRef{Number: 100}, + UnsafeL2: eth.L2BlockRef{Number: 100}, }, prevCurrentL1: eth.BlockRef{Number: 1}, blocks: queue.Queue[*types.Block]{}, channels: []channelStatuser{}, expected: syncActions{}, - expectedLogs: []string{"no blocks in state"}, + expectedLogs: noBlocksLogs, }, {name: "no progress + unsafe=safe + blocks in state", newSyncStatus: eth.SyncStatus{ - HeadL1: eth.BlockRef{Number: 5}, - CurrentL1: eth.BlockRef{Number: 2}, - LocalSafeL2: eth.L2BlockRef{Number: 101, Hash: block101.Hash()}, - UnsafeL2: eth.L2BlockRef{Number: 101}, + HeadL1: eth.BlockRef{Number: 5}, + CurrentL1: eth.BlockRef{Number: 2}, + SafeL2: eth.L2BlockRef{Number: 101, Hash: block101.Hash()}, + UnsafeL2: eth.L2BlockRef{Number: 101}, }, prevCurrentL1: eth.BlockRef{Number: 1}, blocks: queue.Queue[*types.Block]{block101}, @@ -277,6 +279,54 @@ func TestBatchSubmitter_computeSyncActions(t *testing.T) { }, expectedLogs: happyCaseLogs, }, + {name: "localSafeL2 > safeL2, preferLocalSafeL2=false", + newSyncStatus: eth.SyncStatus{ + HeadL1: eth.BlockRef{Number: 5}, + CurrentL1: eth.BlockRef{Number: 2}, + SafeL2: eth.L2BlockRef{Number: 103, Hash: block103.Hash()}, + LocalSafeL2: eth.L2BlockRef{Number: 104, Hash: block104.Hash()}, + UnsafeL2: eth.L2BlockRef{Number: 109}, + }, + prevCurrentL1: eth.BlockRef{Number: 1}, + blocks: queue.Queue[*types.Block]{}, + channels: []channelStatuser{}, + expected: syncActions{ + blocksToLoad: &inclusiveBlockRange{104, 109}, + }, + expectedLogs: noBlocksLogs, + }, + {name: "localSafeL2 > safeL2, preferLocalSafeL2=true", + preferLocalSafeL2: true, + newSyncStatus: eth.SyncStatus{ + HeadL1: eth.BlockRef{Number: 5}, + CurrentL1: eth.BlockRef{Number: 2}, + SafeL2: eth.L2BlockRef{Number: 103, Hash: block103.Hash()}, + LocalSafeL2: eth.L2BlockRef{Number: 104, Hash: block104.Hash()}, + UnsafeL2: eth.L2BlockRef{Number: 109}, + }, + prevCurrentL1: eth.BlockRef{Number: 1}, + blocks: queue.Queue[*types.Block]{}, + channels: []channelStatuser{}, + expected: syncActions{ + blocksToLoad: &inclusiveBlockRange{105, 109}, + }, + expectedLogs: noBlocksLogs, + }, + {name: "LocalSafeL2=0,SafeL2>0", // This shouldn't ever happen, but has occurred due to bugs + newSyncStatus: eth.SyncStatus{ + HeadL1: eth.BlockRef{Number: 5}, + CurrentL1: eth.BlockRef{Number: 2}, + SafeL2: eth.L2BlockRef{Number: 104, Hash: block104.Hash()}, + UnsafeL2: eth.L2BlockRef{Number: 109}, + }, + prevCurrentL1: eth.BlockRef{Number: 1}, + blocks: queue.Queue[*types.Block]{}, + channels: []channelStatuser{}, + expected: syncActions{ + blocksToLoad: &inclusiveBlockRange{105, 109}, + }, + expectedLogs: noBlocksLogs, + }, } for _, tc := range testCases { @@ -285,7 +335,7 @@ func TestBatchSubmitter_computeSyncActions(t *testing.T) { l, h := testlog.CaptureLogger(t, log.LevelDebug) result, outOfSync := computeSyncActions( - tc.newSyncStatus, tc.prevCurrentL1, tc.blocks, tc.channels, l, + tc.newSyncStatus, tc.prevCurrentL1, tc.blocks, tc.channels, l, tc.preferLocalSafeL2, ) require.Equal(t, tc.expected, result, "unexpected actions") diff --git a/op-batcher/flags/flags.go b/op-batcher/flags/flags.go index 843122e13c1..d62e12ca680 100644 --- a/op-batcher/flags/flags.go +++ b/op-batcher/flags/flags.go @@ -180,6 +180,12 @@ var ( Value: 130_000, // should be larger than the builder's max-l2-tx-size to prevent endlessly throttling some txs EnvVars: prefixEnvVars("THROTTLE_ALWAYS_BLOCK_SIZE"), } + PreferLocalSafeL2Flag = &cli.BoolFlag{ + Name: "prefer-local-safe-l2", + Usage: "Load unsafe blocks higher than the sequencer's LocalSafeL2 instead of SafeL2", + Value: false, + EnvVars: prefixEnvVars("PREFER_LOCAL_SAFE_L2"), + } // Legacy Flags SequencerHDPathFlag = txmgr.SequencerHDPathFlag ) @@ -212,6 +218,7 @@ var optionalFlags = []cli.Flag{ ThrottleTxSizeFlag, ThrottleBlockSizeFlag, ThrottleAlwaysBlockSizeFlag, + PreferLocalSafeL2Flag, } func init() { From e1516f01b379868f50fd3610a89daf0a560277f4 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Mon, 3 Mar 2025 08:59:55 -0700 Subject: [PATCH 045/130] op-deployer: Start finalizing support for v2.0.0 (#14557) * op-deployer: Start finalizing support for v2.0.0 * remove stdvertoml * remove another usage of stdvertoml * goimports * update to latest SR * bump SR again * Add updated sepolia SV200 addresses * update v200 validator * fix test * Update op-deployer/book/src/reference-guide/releases.md Co-authored-by: Matt Solomon --------- Co-authored-by: Maurelian Co-authored-by: Matt Solomon --- go.mod | 2 +- go.sum | 6 + op-chain-ops/interopgen/configs.go | 2 - op-chain-ops/interopgen/recipe.go | 5 +- op-deployer/book/src/SUMMARY.md | 2 + .../src/reference-guide/artifacts-locators.md | 17 +- .../book/src/reference-guide/releases.md | 90 +++++++ op-deployer/book/src/user-guide/bootstrap.md | 55 +++++ .../book/src/user-guide/known-limitations.md | 2 + op-deployer/justfile | 5 - op-deployer/pkg/deployer/bootstrap/flags.go | 1 - .../pkg/deployer/bootstrap/implementations.go | 4 + .../deployer/integration_test/apply_test.go | 8 +- op-deployer/pkg/deployer/opcm/dispute_game.go | 5 +- .../pkg/deployer/opcm/dispute_game_test.go | 3 - op-deployer/pkg/deployer/pipeline/init.go | 44 +--- .../standard/standard-versions-mainnet.toml | 62 ----- .../standard/standard-versions-sepolia.toml | 40 --- op-deployer/pkg/deployer/standard/standard.go | 232 ++++-------------- op-deployer/pkg/deployer/state/intent.go | 4 +- op-deployer/pkg/deployer/state/intent_test.go | 4 +- .../pkg/deployer/upgrade/v2_0_0/upgrade.go | 4 +- op-validator/pkg/validations/addresses.go | 5 +- .../pkg/validations/addresses_test.go | 2 +- .../testdata/validations-v200.json | 2 +- .../pkg/validations/validations_test.go | 2 +- .../src/L1/StandardValidator.sol | 26 +- .../test/L1/StandardValidator.t.sol | 14 +- 28 files changed, 277 insertions(+), 371 deletions(-) create mode 100644 op-deployer/book/src/reference-guide/releases.md create mode 100644 op-deployer/book/src/user-guide/bootstrap.md delete mode 100644 op-deployer/pkg/deployer/standard/standard-versions-mainnet.toml delete mode 100644 op-deployer/pkg/deployer/standard/standard-versions-sepolia.toml diff --git a/go.mod b/go.mod index 4d839b84604..e1304a0328d 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/crate-crypto/go-kzg-4844 v1.1.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 - github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250205201532-8ff62ada16e1 + github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250228185245-d4bb112dc979 github.com/ethereum/go-ethereum v1.15.1 github.com/fatih/color v1.18.0 github.com/fsnotify/fsnotify v1.8.0 diff --git a/go.sum b/go.sum index a6dd712478e..e1c0086e5ce 100644 --- a/go.sum +++ b/go.sum @@ -196,6 +196,12 @@ github.com/ethereum-optimism/op-geth v1.101500.2-rc.1 h1:CCQtyKKobjMbK9jYd676YbH github.com/ethereum-optimism/op-geth v1.101500.2-rc.1/go.mod h1:OMpyVMMy5zpAAHlR5s/aGbXRk+7cIKczUEIJj54APbY= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250205201532-8ff62ada16e1 h1:OqRYDcjiOx5QCLn5krpd3BK1CW+VfSZx7YIa6zY9ePE= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250205201532-8ff62ada16e1/go.mod h1:NZ816PzLU1TLv1RdAvYAb6KWOj4Zm5aInT0YpDVml2Y= +github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250226225001-eda352a0be39 h1:PKn3dL9W0FyGC/oynhP6T43P0Xm80W+woW53lRUp2nE= +github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250226225001-eda352a0be39/go.mod h1:NZ816PzLU1TLv1RdAvYAb6KWOj4Zm5aInT0YpDVml2Y= +github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250227173852-b4f7e4022c2e h1:CWdI4k9JxMvWLv6HjWuAFJuuEwMEEwKdW/mOVnrNBD8= +github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250227173852-b4f7e4022c2e/go.mod h1:NZ816PzLU1TLv1RdAvYAb6KWOj4Zm5aInT0YpDVml2Y= +github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250228185245-d4bb112dc979 h1:P37l7EFCz5KxE20+yPa3LWiH2Cg+xx6lQ4mOKYCZFPs= +github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250228185245-d4bb112dc979/go.mod h1:NZ816PzLU1TLv1RdAvYAb6KWOj4Zm5aInT0YpDVml2Y= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= diff --git a/op-chain-ops/interopgen/configs.go b/op-chain-ops/interopgen/configs.go index 948d9daa305..c3113d240e1 100644 --- a/op-chain-ops/interopgen/configs.go +++ b/op-chain-ops/interopgen/configs.go @@ -40,8 +40,6 @@ type OPCMImplementationsConfig struct { FaultProof SuperFaultProofConfig UseInterop bool // to deploy Interop implementation contracts, instead of the regular ones. - - StandardVersionsToml string // serialized string of superchain-registry 'standard-versions-mainnet.toml' file } type SuperchainConfig struct { diff --git a/op-chain-ops/interopgen/recipe.go b/op-chain-ops/interopgen/recipe.go index 66444982c8c..56196584f73 100644 --- a/op-chain-ops/interopgen/recipe.go +++ b/op-chain-ops/interopgen/recipe.go @@ -4,8 +4,6 @@ import ( "fmt" "math/big" - "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/params" @@ -79,8 +77,7 @@ func (r *InteropDevRecipe) Build(addrs devkeys.Addresses) (*WorldConfig, error) DisputeGameFinalityDelaySeconds: big.NewInt(6), MipsVersion: big.NewInt(1), }, - UseInterop: true, - StandardVersionsToml: standard.VersionsMainnetData, + UseInterop: true, }, SuperchainL1DeployConfig: genesis.SuperchainL1DeployConfig{ RequiredProtocolVersion: params.OPStackSupport, diff --git a/op-deployer/book/src/SUMMARY.md b/op-deployer/book/src/SUMMARY.md index 0aa68074818..862b2bba9cd 100644 --- a/op-deployer/book/src/SUMMARY.md +++ b/op-deployer/book/src/SUMMARY.md @@ -8,6 +8,7 @@ - [Usage](user-guide/usage.md) - [init](user-guide/init.md) - [apply](user-guide/apply.md) + - [bootstrap](user-guide/bootstrap.md) - [Known Limitations](user-guide/known-limitations.md) # Reference Guide @@ -16,3 +17,4 @@ - [Deployment Pipeline](reference-guide/pipeline.md) - [Scripting Engine](reference-guide/engine.md) - [Artifacts Locators](reference-guide/artifacts-locators.md) +- [Releases](reference-guide/releases.md) diff --git a/op-deployer/book/src/reference-guide/artifacts-locators.md b/op-deployer/book/src/reference-guide/artifacts-locators.md index 729cdc8bba1..0f4af8805a2 100644 --- a/op-deployer/book/src/reference-guide/artifacts-locators.md +++ b/op-deployer/book/src/reference-guide/artifacts-locators.md @@ -14,19 +14,4 @@ Locators can be one of three types: cached on disk to avoid repeated downloads. - `https://` locators, which point to a tarball of contract artifacts somewhere on the web. HTTP locators are cached just like tagged locators are, but they are not validated against a checksum. -- `file://` locators, which point to a directory on disk containing the artifacts. - -## Version Hints - -OP Deployer supports multiple different contract versions at the same time. Sometimes, contracts at version X are -backwards-incompatible with version Y. OP Deployer will support both versions at the same time when this happens. -However, OP Deployer needs to know which versioning behavior to use with each locator. For `tag` locators this is -easy since the behavior is encoded in the tag itself. However, it's more complicated for `https` and `file` locators. - -To support multiple versions of each contract, OP Deployer supports specifying _version hints_ in the locator. These -hints are URL fragments (e.g., the part of the URL that comes after the `#` symbol) denoting how OP Deployer should -treat the artifacts at that URL. For example, the URL `https://example.com/artifacts.tar.gz#v1` would treat the -artifacts at the URL with the versioning behavior of version `v1`. - -This only applies to `https` and `file` locators. `tag` locators are versioned by the tag itself, and any hints will -be ignored. \ No newline at end of file +- `file://` locators, which point to a directory on disk containing the artifacts. \ No newline at end of file diff --git a/op-deployer/book/src/reference-guide/releases.md b/op-deployer/book/src/reference-guide/releases.md new file mode 100644 index 00000000000..aefad5d89db --- /dev/null +++ b/op-deployer/book/src/reference-guide/releases.md @@ -0,0 +1,90 @@ +# Releases + +## Versioning + +For all releases after `v0.0.11`, each minor version of OP Deployer will support the current governance-approved +release of the smart contracts as well as the tip of the `develop` branch at the time the tag was created. If you +want to deploy an earlier version of the contracts (which may be dangerous!), you should use an earlier version of +OP Deployer. This setup allows our smart contract developers to make breaking changes on `develop`, while still +allowing new chains to be deployed and upgraded using production-ready smart contracts. + +For example (note that these are just examples, check out the [releases][releases] page for the exact versions to use): + +- `v0.1.x` : Supports deploying `develop` and `op-contracts/v2.0.0`. +- `v0.2.x`: Supports deploying `develop` and `op-contracts/v3.0.0`. + +If you deploy from an HTTPS or file [locator](./artifacts-locators.md), the deployment behavior will match that of +the supported tag. For example, if you use `v0.1.x` then the deployment will work as if you were deploying +`op-contracts/v2.0.0`. Typically, errors like `unknown selector: ` imply that you're using the wrong +version of OP Deployer for your contract artifacts. If this happens, we recommend trying different versions until +you get one that works. Note that this workflow is **not recommended** for production chains. + +[releases]: https://github.com/ethereum-optimism/optimism/releases + +## Adding Support for New Contract Versions + +Adding support for a new contract version is a multi-step process. Here's a high-level overview. For the sake of +simplicity we will assume you are adding support for a new `rc` release. + +### Step 1: Add Support on `develop` + +**This section is designed for people developing OP Deployer itself.** + +First, you need to add support for the new contract version on the `develop` branch. This means ensuring that the +deployment pipeline supports whatever changes are required for the new version. Typically, this means passing in new +deployment variables, and responding to ABI changes in the Solidity scripts/OPCM. + +### Step 2: Add the Published Artifacts + +Run the following from the root of the monorepo: + +```bash +cd packages/contracts-bedrock +just clean +just build +bash scripts/ops/calculate-checksum.sh +# copy the outputted checksum +cd ../../op-deployer +just calculate-artifacts-hash +``` + +This will calculate the checksum of your artifacts as well as the hash of the artifacts tarball. OP Deployer uses +these values to download and verify tagged contract locators. + +Now, update `standard/standard.go` with these values so that the new artifacts tarball can be downloaded: + +```go +// Add a new const for your release + +const ContractsVXTag = "op-contracts/vX.Y.Z" + +var taggedReleases = map[string]TaggedRelease{ + // Other releases... + ContractsVXTag: { + ArtifactsHash: common.HexToHash(""), + ContentHash: common.HexToHash(""), + }, +} + +// Update the L1/L2 versions accordingly +func IsSupportedL1Version(tag string) bool { + return tag == ContractsVXTag +} +``` + +### Step 3: Update the SR With the New Release + +Add the new RC to the [standard versions][std-vers] in the Superchain Registry. + +[std-vers]: https://github.com/ethereum-optimism/superchain-registry/tree/main/validation/standard + +### Step 4: Update the `validation` Package + +The SR is pulled into OP Deployer via the `validation` package. Update it by running the following command from the +root of the monorepo: + +```shell +go get -u github.com/ethereum-optimism/superchain-registry/validation@ +``` + +That should be it! \ No newline at end of file diff --git a/op-deployer/book/src/user-guide/bootstrap.md b/op-deployer/book/src/user-guide/bootstrap.md new file mode 100644 index 00000000000..cf2450db28c --- /dev/null +++ b/op-deployer/book/src/user-guide/bootstrap.md @@ -0,0 +1,55 @@ +# The Bootstrap Commands + +Bootstrap commands are used to deploy global singletons and implementation contracts for use with future invocations +of `apply`. Most users won't need to use these commands, since `op-deployer apply` will automatically use +predeployed contracts if they are available. However, you may need to use bootstrap commands if you're deploying +chains to an L1 that isn't natively supported by `op-deployer`. + +There are several bootstrap commands available, which you can view by running `op-deployer bootstrap --help`. We'll +focus on the most important ones below. + +## Implementations + +You can bootstrap implementations by running a command like this: + +```shell +op-deployer bootstrap implementations \ + --artifacts-locator \ + --l1-contracts-release op-contracts/ \ + --l1-rpc-url \ + --mips-version <1 or 2, for MIPS32 or MIPS64> \ + --private-key \ + --protocol-versions-proxy \ + --superchain-config-proxy \ + --upgrade-controller +``` + +This command will deploy implementations, blueprints, and the OPCM. Deployments are (for the most part) +deterministic, so contracts will only be deployed once per chain as long as the implementation and constructor args +remain the same. This applies to the `op-deployer apply` pipeline - that is, if someone else ran `op-deployer +boostrap implementations` at some point on a given L1 chain, then the `apply` pipeline will re-use those +implementations. + +The command will output a JSON like the one below: + +```json +{ + "Opcm": "0x4eeb114aaf812e21285e5b076030110e7e18fed9", + "DelayedWETHImpl": "0x5e40b9231b86984b5150507046e354dbfbed3d9e", + "OptimismPortalImpl": "0x2d7e764a0d9919e16983a46595cfa81fc34fa7cd", + "PreimageOracleSingleton": "0x1fb8cdfc6831fc866ed9c51af8817da5c287add3", + "MipsSingleton": "0xf027f4a985560fb13324e943edf55ad6f1d15dc1", + "SystemConfigImpl": "0x760c48c62a85045a6b69f07f4a9f22868659cbcc", + "L1CrossDomainMessengerImpl": "0x3ea6084748ed1b2a9b5d4426181f1ad8c93f6231", + "L1ERC721BridgeImpl": "0x276d3730f219f7ec22274f7263180b8452b46d47", + "L1StandardBridgeImpl": "0x78972e88ab8bbb517a36caea23b931bab58ad3c6", + "OptimismMintableERC20FactoryImpl": "0x5493f4677a186f64805fe7317d6993ba4863988f", + "DisputeGameFactoryImpl": "0x4bba758f006ef09402ef31724203f316ab74e4a0", + "AnchorStateRegistryImpl": "0x7b465370bb7a333f99edd19599eb7fb1c2d3f8d2", + "SuperchainConfigImpl": "0x4da82a327773965b8d4d85fa3db8249b387458e7", + "ProtocolVersionsImpl": "0x37e15e4d6dffa9e5e320ee1ec036922e563cb76c" +} +``` + +**It is safe to call this command from a hot wallet.** None of the contracts deployed by this command are "ownable," +so the deployment address has no further control over the system. \ No newline at end of file diff --git a/op-deployer/book/src/user-guide/known-limitations.md b/op-deployer/book/src/user-guide/known-limitations.md index aaf5aa33974..c5574f39d70 100644 --- a/op-deployer/book/src/user-guide/known-limitations.md +++ b/op-deployer/book/src/user-guide/known-limitations.md @@ -4,6 +4,8 @@ OP Deployer is subject to some known limitations which we're working on addressi ## Tagged Releases on New Chains +**Fixed in all versions after v0.0.11.** + It is not currently possible to deploy chains using tagged contract locators (i.e., those starting with `tag://`) anywhere except Sepolia and Ethereum mainnet. If you try to, you'll see an error like this: diff --git a/op-deployer/justfile b/op-deployer/justfile index fdf3b8e2c96..8686b8ce194 100644 --- a/op-deployer/justfile +++ b/op-deployer/justfile @@ -8,8 +8,3 @@ calculate-artifacts-hash checksum: just download-artifacts {{checksum}} /tmp/artifact.tgz sha256sum /tmp/artifact.tgz rm /tmp/artifact.tgz - -# Sync standard versions -sync-standard-version: - curl -Lo ./pkg/deployer/standard/standard-versions-mainnet.toml https://raw.githubusercontent.com/ethereum-optimism/superchain-registry/refs/heads/main/validation/standard/standard-versions-mainnet.toml - curl -Lo ./pkg/deployer/standard/standard-versions-sepolia.toml https://raw.githubusercontent.com/ethereum-optimism/superchain-registry/refs/heads/main/validation/standard/standard-versions-sepolia.toml \ No newline at end of file diff --git a/op-deployer/pkg/deployer/bootstrap/flags.go b/op-deployer/pkg/deployer/bootstrap/flags.go index f2476741052..ba29f614116 100644 --- a/op-deployer/pkg/deployer/bootstrap/flags.go +++ b/op-deployer/pkg/deployer/bootstrap/flags.go @@ -198,7 +198,6 @@ var Commands = []*cli.Command{ Usage: "Bootstraps implementations.", Flags: cliapp.ProtectFlags(ImplementationsFlags), Action: ImplementationsCLI, - Hidden: true, }, { Name: "proxy", diff --git a/op-deployer/pkg/deployer/bootstrap/implementations.go b/op-deployer/pkg/deployer/bootstrap/implementations.go index 30ee8498d0c..9875ee6c0f3 100644 --- a/op-deployer/pkg/deployer/bootstrap/implementations.go +++ b/op-deployer/pkg/deployer/bootstrap/implementations.go @@ -134,6 +134,10 @@ func Implementations(ctx context.Context, cfg ImplementationsConfig) (opcm.Deplo lgr := cfg.Logger + if cfg.ArtifactsLocator.IsTag() && !standard.IsSupportedL1Version(cfg.ArtifactsLocator.Tag) { + return dio, fmt.Errorf("unsupported L1 version: %s", cfg.ArtifactsLocator.Tag) + } + artifactsFS, err := artifacts.Download(ctx, cfg.ArtifactsLocator, artifacts.BarProgressor(), cfg.CacheDir) if err != nil { return dio, fmt.Errorf("failed to download artifacts: %w", err) diff --git a/op-deployer/pkg/deployer/integration_test/apply_test.go b/op-deployer/pkg/deployer/integration_test/apply_test.go index 025b57f288e..83884e07bac 100644 --- a/op-deployer/pkg/deployer/integration_test/apply_test.go +++ b/op-deployer/pkg/deployer/integration_test/apply_test.go @@ -128,8 +128,9 @@ func TestEndToEndApply(t *testing.T) { intent, st := newIntent(t, l1ChainID, dk, l2ChainID1, loc, loc) intent.L1ContractsLocator = artifacts.DefaultL1ContractsLocator intent.L2ContractsLocator = artifacts.DefaultL2ContractsLocator + cg := ethClientCodeGetter(ctx, l1Client) - require.ErrorIs(t, deployer.ApplyPipeline( + require.NoError(t, deployer.ApplyPipeline( ctx, deployer.ApplyPipelineOpts{ DeploymentTarget: deployer.DeploymentTargetLive, @@ -141,7 +142,10 @@ func TestEndToEndApply(t *testing.T) { StateWriter: pipeline.NoopStateWriter(), CacheDir: testCacheDir, }, - ), pipeline.ErrRefusingToDeployTaggedReleaseWithoutOPCM) + )) + + validateSuperchainDeployment(t, st, cg) + validateOPChainDeployment(t, cg, st, intent, false) }) t.Run("with calldata broadcasts", func(t *testing.T) { diff --git a/op-deployer/pkg/deployer/opcm/dispute_game.go b/op-deployer/pkg/deployer/opcm/dispute_game.go index 8ab20a8dea6..862819b9294 100644 --- a/op-deployer/pkg/deployer/opcm/dispute_game.go +++ b/op-deployer/pkg/deployer/opcm/dispute_game.go @@ -8,7 +8,6 @@ import ( type DeployDisputeGameInput struct { Release string - StandardVersionsToml string VmAddress common.Address GameKind string GameType uint32 @@ -24,6 +23,10 @@ type DeployDisputeGameInput struct { Challenger common.Address } +func (input *DeployDisputeGameInput) StandardVersionsToml() string { + return "" +} + func (input *DeployDisputeGameInput) InputSet() bool { return true } diff --git a/op-deployer/pkg/deployer/opcm/dispute_game_test.go b/op-deployer/pkg/deployer/opcm/dispute_game_test.go index 604329d7c09..61c6deb388a 100644 --- a/op-deployer/pkg/deployer/opcm/dispute_game_test.go +++ b/op-deployer/pkg/deployer/opcm/dispute_game_test.go @@ -28,8 +28,6 @@ func TestDeployDisputeGame(t *testing.T) { ) require.NoError(t, err) - standardVersionsTOML, err := standard.L1VersionsDataFor(11155111) - require.NoError(t, err) vmAddr := common.Address{'V'} host.ImportAccount(vmAddr, types.Account{Code: vmCode}) // Address has to match the one returned by vmCode for oracle()(address) @@ -37,7 +35,6 @@ func TestDeployDisputeGame(t *testing.T) { input := DeployDisputeGameInput{ Release: "dev", - StandardVersionsToml: standardVersionsTOML, VmAddress: vmAddr, GameKind: "PermissionedDisputeGame", GameType: 1, diff --git a/op-deployer/pkg/deployer/pipeline/init.go b/op-deployer/pkg/deployer/pipeline/init.go index ef4ddfed2f5..2b5d2396576 100644 --- a/op-deployer/pkg/deployer/pipeline/init.go +++ b/op-deployer/pkg/deployer/pipeline/init.go @@ -3,10 +3,7 @@ package pipeline import ( "context" "crypto/rand" - "errors" "fmt" - "os" - "strings" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/state" @@ -16,8 +13,6 @@ import ( "github.com/ethereum/go-ethereum/common" ) -var ErrRefusingToDeployTaggedReleaseWithoutOPCM = errors.New("refusing to deploy tagged release without OPCM") - func IsSupportedStateVersion(version int) bool { return version == 1 } @@ -32,9 +27,18 @@ func InitLiveStrategy(ctx context.Context, env *Env, intent *state.Intent, st *s opcmAddress, opcmAddrErr := standard.ManagerImplementationAddrFor(intent.L1ChainID, intent.L1ContractsLocator.Tag) hasPredeployedOPCM := opcmAddrErr == nil - isTag := intent.L1ContractsLocator.IsTag() + isL1Tag := intent.L1ContractsLocator.IsTag() + isL2Tag := intent.L2ContractsLocator.IsTag() + + if isL1Tag && !standard.IsSupportedL1Version(intent.L1ContractsLocator.Tag) { + return fmt.Errorf("unsupported L1 version: %s", intent.L1ContractsLocator.Tag) + } - if isTag && hasPredeployedOPCM { + if isL2Tag && !standard.IsSupportedL2Version(intent.L2ContractsLocator.Tag) { + return fmt.Errorf("unsupported L2 version: %s", intent.L2ContractsLocator.Tag) + } + + if isL1Tag && hasPredeployedOPCM { superCfg, err := standard.SuperchainFor(intent.L1ChainID) if err != nil { return fmt.Errorf("error getting superchain config: %w", err) @@ -45,8 +49,6 @@ func InitLiveStrategy(ctx context.Context, env *Env, intent *state.Intent, st *s return fmt.Errorf("error getting superchain proxy admin address: %w", err) } - // Have to do this weird pointer thing below because the Superchain Registry defines its - // own Address type. st.SuperchainDeployment = &state.SuperchainDeployment{ ProxyAdminAddress: proxyAdmin, ProtocolVersionsProxyAddress: superCfg.ProtocolVersionsAddr, @@ -56,10 +58,6 @@ func InitLiveStrategy(ctx context.Context, env *Env, intent *state.Intent, st *s st.ImplementationsDeployment = &state.ImplementationsDeployment{ OpcmAddress: opcmAddress, } - } else if isTag && !hasPredeployedOPCM { - if err := displayWarning(); err != nil { - return err - } } l1ChainID, err := env.L1Client.ChainID(ctx) @@ -136,23 +134,3 @@ func InitGenesisStrategy(env *Env, intent *state.Intent, st *state.State) error func immutableErr(field string, was, is any) error { return fmt.Errorf("%s is immutable: was %v, is %v", field, was, is) } - -func displayWarning() error { - warning := strings.TrimPrefix(` -####################### WARNING! WARNING WARNING! ####################### - -You are deploying a tagged release to a chain with no pre-deployed OPCM. -Due to a quirk of our contract version system, this can lead to deploying -contracts containing unaudited or untested code. As a result, this -functionality is currently disabled. - -We will fix this in an upcoming release. - -This process will now exit. - -####################### WARNING! WARNING WARNING! ####################### -`, "\n") - - _, _ = fmt.Fprint(os.Stderr, warning) - return ErrRefusingToDeployTaggedReleaseWithoutOPCM -} diff --git a/op-deployer/pkg/deployer/standard/standard-versions-mainnet.toml b/op-deployer/pkg/deployer/standard/standard-versions-mainnet.toml deleted file mode 100644 index 550fd32e1f0..00000000000 --- a/op-deployer/pkg/deployer/standard/standard-versions-mainnet.toml +++ /dev/null @@ -1,62 +0,0 @@ -# Contracts which are -# * unproxied singletons: specify a standard "address" -# * proxied : specify a standard "implementation_address" -# * neither : specify neither a standard "address" nor "implementation_address" - -# Holocene https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.8.0-rc.4 -["op-contracts/v1.8.0-rc.4"] -# Updated in this release -system_config = { version = "2.3.0", implementation_address = "0xAB9d6cB7A427c0765163A7f45BB91cAfe5f2D375" } # UPDATED IN THIS RELEASE -fault_dispute_game = { version = "1.3.1" } # UPDATED IN THIS RELEASE -permissioned_dispute_game = { version = "1.3.1" } # UPDATED IN THIS RELEASE -mips = { version = "1.2.1", address = "0x5fE03a12C1236F9C22Cb6479778DDAa4bce6299C" } # UPDATED IN THIS RELEASE -# Unchanged in this release -optimism_portal = { version = "3.10.0", implementation_address = "0xe2F826324b2faf99E513D16D266c3F80aE87832B" } -anchor_state_registry = { version = "2.0.0" } -delayed_weth = { version = "1.1.0", implementation_address = "0x71e966Ae981d1ce531a7b6d23DC0f27B38409087" } -dispute_game_factory = { version = "1.0.0", implementation_address = "0xc641A33cab81C559F2bd4b21EA34C290E2440C2B" } -preimage_oracle = { version = "1.1.2", address = "0x9c065e11870B891D214Bc2Da7EF1f9DDFA1BE277" } -l1_cross_domain_messenger = { version = "2.3.0", implementation_address = "0xD3494713A5cfaD3F5359379DfA074E2Ac8C6Fd65" } -l1_erc721_bridge = { version = "2.1.0", implementation_address = "0xAE2AF01232a6c4a4d3012C5eC5b1b35059caF10d" } -l1_standard_bridge = { version = "2.1.0", implementation_address = "0x64B5a5Ed26DCb17370Ff4d33a8D503f0fbD06CfF" } -# l2_output_oracle -- This contract not used in fault proofs -optimism_mintable_erc20_factory = { version = "1.9.0", implementation_address = "0xE01efbeb1089D1d1dB9c6c8b135C934C0734c846" } - -# Fault Proofs https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.6.0 -["op-contracts/v1.6.0"] -optimism_portal = { version = "3.10.0", implementation_address = "0xe2F826324b2faf99E513D16D266c3F80aE87832B" } -system_config = { version = "2.2.0", implementation_address = "0xF56D96B2535B932656d3c04Ebf51baBff241D886" } -anchor_state_registry = { version = "2.0.0" } -delayed_weth = { version = "1.1.0", implementation_address = "0x71e966Ae981d1ce531a7b6d23DC0f27B38409087" } -dispute_game_factory = { version = "1.0.0", implementation_address = "0xc641A33cab81C559F2bd4b21EA34C290E2440C2B" } -fault_dispute_game = { version = "1.3.0" } -permissioned_dispute_game = { version = "1.3.0" } -mips = { version = "1.1.0", address = "0x16e83cE5Ce29BF90AD9Da06D2fE6a15d5f344ce4" } -preimage_oracle = { version = "1.1.2", address = "0x9c065e11870B891D214Bc2Da7EF1f9DDFA1BE277" } -l1_cross_domain_messenger = { version = "2.3.0", implementation_address = "0xD3494713A5cfaD3F5359379DfA074E2Ac8C6Fd65" } -l1_erc721_bridge = { version = "2.1.0", implementation_address = "0xAE2AF01232a6c4a4d3012C5eC5b1b35059caF10d" } -l1_standard_bridge = { version = "2.1.0", implementation_address = "0x64B5a5Ed26DCb17370Ff4d33a8D503f0fbD06CfF" } -# l2_output_oracle -- This contract not used in fault proofs -optimism_mintable_erc20_factory = { version = "1.9.0", implementation_address = "0xE01efbeb1089D1d1dB9c6c8b135C934C0734c846" } - -# Fault Proofs https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.4.0 -["op-contracts/v1.4.0"] -optimism_portal = { version = "3.10.0", implementation_address = "0xe2F826324b2faf99E513D16D266c3F80aE87832B" } -system_config = { version = "2.2.0", implementation_address = "0xF56D96B2535B932656d3c04Ebf51baBff241D886" } -anchor_state_registry = { version = "1.0.0" } -delayed_weth = { version = "1.0.0", implementation_address = "0x97988d5624F1ba266E1da305117BCf20713bee08" } -dispute_game_factory = { version = "1.0.0", implementation_address = "0xc641A33cab81C559F2bd4b21EA34C290E2440C2B" } -fault_dispute_game = { version = "1.2.0" } -permissioned_dispute_game = { version = "1.2.0" } -mips = { version = "1.0.1", address = "0x0f8EdFbDdD3c0256A80AD8C0F2560B1807873C9c" } -preimage_oracle = { version = "1.0.0", address = "0xD326E10B8186e90F4E2adc5c13a2d0C137ee8b34" } - -# MCP https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.3.0 -["op-contracts/v1.3.0"] -l1_cross_domain_messenger = { version = "2.3.0", implementation_address = "0xD3494713A5cfaD3F5359379DfA074E2Ac8C6Fd65" } -l1_erc721_bridge = { version = "2.1.0", implementation_address = "0xAE2AF01232a6c4a4d3012C5eC5b1b35059caF10d" } -l1_standard_bridge = { version = "2.1.0", implementation_address = "0x64B5a5Ed26DCb17370Ff4d33a8D503f0fbD06CfF" } -l2_output_oracle = { version = "1.8.0", implementation_address = "0xF243BEd163251380e78068d317ae10f26042B292" } -optimism_mintable_erc20_factory = { version = "1.9.0", implementation_address = "0xE01efbeb1089D1d1dB9c6c8b135C934C0734c846" } -optimism_portal = { version = "2.5.0", implementation_address = "0x2D778797049FE9259d947D1ED8e5442226dFB589" } -system_config = { version = "1.12.0", implementation_address = "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1" } diff --git a/op-deployer/pkg/deployer/standard/standard-versions-sepolia.toml b/op-deployer/pkg/deployer/standard/standard-versions-sepolia.toml deleted file mode 100644 index cbb664fbc63..00000000000 --- a/op-deployer/pkg/deployer/standard/standard-versions-sepolia.toml +++ /dev/null @@ -1,40 +0,0 @@ -# Contracts which are -# * unproxied singletons: specify a standard "address" -# * proxied : specify a standard "implementation_address" -# * neither : specify neither a standard "address" nor "implementation_address" - -# Holocene https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.8.0-rc.4 -["op-contracts/v1.8.0-rc.4"] -# Updated in this release -system_config = { version = "2.3.0", implementation_address = "0x33b83E4C305c908B2Fc181dDa36e230213058d7d" } # UPDATED IN THIS RELEASE -fault_dispute_game = { version = "1.3.1" } # UPDATED IN THIS RELEASE -permissioned_dispute_game = { version = "1.3.1" } # UPDATED IN THIS RELEASE -mips = { version = "1.2.1", address = "0x69470D6970Cd2A006b84B1d4d70179c892cFCE01" } # UPDATED IN THIS RELEASE -# Unchanged in this release -optimism_portal = { version = "3.10.0", implementation_address = "0x35028bae87d71cbc192d545d38f960ba30b4b233" } -anchor_state_registry = { version = "2.0.0" } -delayed_weth = { version = "1.1.0", implementation_address = "0x07f69b19532476c6cd03056d6bc3f1b110ab7538" } -dispute_game_factory = { version = "1.0.0", implementation_address = "0xa51bea7e4d34206c0bcb04a776292f2f19f0beec" } -preimage_oracle = { version = "1.1.2", address = "0x92240135b46fc1142dA181f550aE8f595B858854" } -l1_cross_domain_messenger = { version = "2.3.0", implementation_address = "0xD3494713A5cfaD3F5359379DfA074E2Ac8C6Fd65" } -l1_erc721_bridge = { version = "2.1.0", implementation_address = "0xae2af01232a6c4a4d3012c5ec5b1b35059caf10d" } -l1_standard_bridge = { version = "2.1.0", implementation_address = "0x64b5a5ed26dcb17370ff4d33a8d503f0fbd06cff" } -# l2_output_oracle -- This contract not used in fault proofs -optimism_mintable_erc20_factory = { version = "1.9.0", implementation_address = "0xe01efbeb1089d1d1db9c6c8b135c934c0734c846" } - -# Fault Proofs https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts%2Fv1.6.0 -["op-contracts/v1.6.0"] -optimism_portal = { version = "3.10.0", implementation_address = "0x35028bae87d71cbc192d545d38f960ba30b4b233" } -system_config = { version = "2.2.0", implementation_address = "0xCcdd86d581e40fb5a1C77582247BC493b6c8B169" } -anchor_state_registry = { version = "2.0.0" } -delayed_weth = { version = "1.1.0", implementation_address = "0x07f69b19532476c6cd03056d6bc3f1b110ab7538" } -dispute_game_factory = { version = "1.0.0", implementation_address = "0xa51bea7e4d34206c0bcb04a776292f2f19f0beec" } -fault_dispute_game = { version = "1.3.0" } -permissioned_dispute_game = { version = "1.3.0" } -mips = { version = "1.1.0", address = "0x47B0E34C1054009e696BaBAAd56165e1e994144d" } -preimage_oracle = { version = "1.1.2", address = "0x92240135b46fc1142dA181f550aE8f595B858854" } -l1_cross_domain_messenger = { version = "2.3.0", implementation_address = "0xD3494713A5cfaD3F5359379DfA074E2Ac8C6Fd65" } -l1_erc721_bridge = { version = "2.1.0", implementation_address = "0xae2af01232a6c4a4d3012c5ec5b1b35059caf10d" } -l1_standard_bridge = { version = "2.1.0", implementation_address = "0x64b5a5ed26dcb17370ff4d33a8d503f0fbd06cff" } -# l2_output_oracle -- This contract not used in fault proofs -optimism_mintable_erc20_factory = { version = "1.9.0", implementation_address = "0xe01efbeb1089d1d1db9c6c8b135c934c0734c846" } diff --git a/op-deployer/pkg/deployer/standard/standard.go b/op-deployer/pkg/deployer/standard/standard.go index ee881706620..1f43b33fe11 100644 --- a/op-deployer/pkg/deployer/standard/standard.go +++ b/op-deployer/pkg/deployer/standard/standard.go @@ -13,8 +13,6 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup" op_service "github.com/ethereum-optimism/optimism/op-service" - "github.com/BurntSushi/toml" - "github.com/ethereum/go-ethereum/common" ) @@ -40,151 +38,61 @@ const ( ContractsV160Tag = "op-contracts/v1.6.0" ContractsV180Tag = "op-contracts/v1.8.0-rc.4" ContractsV170Beta1L2Tag = "op-contracts/v1.7.0-beta.1+l2-contracts" + ContractsV200Tag = "op-contracts/v2.0.0-rc.1" ) var DisputeAbsolutePrestate = common.HexToHash("0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c") -//go:embed standard-versions-mainnet.toml -var VersionsMainnetData string - -//go:embed standard-versions-sepolia.toml -var VersionsSepoliaData string - -var L1VersionsSepolia L1Versions - -var L1VersionsMainnet L1Versions - -var DefaultL1ContractsTag = ContractsV180Tag +var DefaultL1ContractsTag = ContractsV200Tag var DefaultL2ContractsTag = ContractsV170Beta1L2Tag -type L1Versions map[string]L1VersionsReleases - -type L1VersionsReleases struct { - OptimismPortal VersionRelease `toml:"optimism_portal"` - SystemConfig VersionRelease `toml:"system_config"` - AnchorStateRegistry VersionRelease `toml:"anchor_state_registry"` - DelayedWETH VersionRelease `toml:"delayed_weth"` - DisputeGameFactory VersionRelease `toml:"dispute_game_factory"` - FaultDisputeGame VersionRelease `toml:"fault_dispute_game"` - PermissionedDisputeGame VersionRelease `toml:"permissioned_dispute_game"` - MIPS VersionRelease `toml:"mips"` - PreimageOracle VersionRelease `toml:"preimage_oracle"` - L1CrossDomainMessenger VersionRelease `toml:"l1_cross_domain_messenger"` - L1ERC721Bridge VersionRelease `toml:"l1_erc721_bridge"` - L1StandardBridge VersionRelease `toml:"l1_standard_bridge"` - OptimismMintableERC20Factory VersionRelease `toml:"optimism_mintable_erc20_factory"` -} - -type VersionRelease struct { - Version string `toml:"version"` - ImplementationAddress common.Address `toml:"implementation_address"` - Address common.Address `toml:"address"` -} - -var _ embed.FS - -type OPCMBlueprints struct { - AddressManager common.Address - Proxy common.Address - ProxyAdmin common.Address - L1ChugSplashProxy common.Address - ResolvedDelegateProxy common.Address - AnchorStateRegistry common.Address - PermissionedDisputeGame1 common.Address - PermissionedDisputeGame2 common.Address +type TaggedRelease struct { + ArtifactsHash common.Hash + ContentHash common.Hash } -type OPCMBlueprintsByChain struct { - Mainnet *OPCMBlueprints - Sepolia *OPCMBlueprints +func (t TaggedRelease) URL() string { + return fmt.Sprintf("https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-%x.tar.gz", t.ContentHash) } -var opcmBlueprintsByVersion = map[string]OPCMBlueprintsByChain{ - "op-contracts/v1.6.0": { - Mainnet: &OPCMBlueprints{ - AddressManager: common.HexToAddress("0x29aA24714c06914d9689e933cae2293C569AfeEa"), - Proxy: common.HexToAddress("0x3626ebD458c7f34FD98789A373593fF2fc227bA0"), - ProxyAdmin: common.HexToAddress("0x7170678A5CFFb6872606d251B3CcdB27De962631"), - L1ChugSplashProxy: common.HexToAddress("0x538906C8B000D621fd11B7e8642f504dD8730837"), - ResolvedDelegateProxy: common.HexToAddress("0xF12bD34d6a1d26d230240ECEA761f77e2013926E"), - AnchorStateRegistry: common.HexToAddress("0xbA7Be2bEE016568274a4D1E6c852Bb9a99FaAB8B"), - PermissionedDisputeGame1: common.HexToAddress("0xb94bF6130Df8BD9a9eA45D8dD8C18957002d1986"), - PermissionedDisputeGame2: common.HexToAddress("0xe0a642B249CF6cbF0fF7b4dDf41443Ea7a5C8Cc8"), - }, - Sepolia: &OPCMBlueprints{ - AddressManager: common.HexToAddress("0x3125a4cB2179E04203D3Eb2b5784aaef9FD64216"), - Proxy: common.HexToAddress("0xe650ADb86a0de96e2c434D0a52E7D5B70980D6f1"), - ProxyAdmin: common.HexToAddress("0x3AC6b88F6bC4A5038DB7718dE47a5ab1a9609319"), - L1ChugSplashProxy: common.HexToAddress("0x58770FC7ed304c43D2B70248914eb34A741cF411"), - ResolvedDelegateProxy: common.HexToAddress("0x0449adB72D489a137d476aB49c6b812161754fD3"), - AnchorStateRegistry: common.HexToAddress("0xB98095199437883b7661E0D58256060f3bc730a4"), - PermissionedDisputeGame1: common.HexToAddress("0xf72Ac5f164cC024DE09a2c249441715b69a16eAb"), - PermissionedDisputeGame2: common.HexToAddress("0x713dAC5A23728477547b484f9e0D751077E300a2"), - }, +var taggedReleases = map[string]TaggedRelease{ + ContractsV160Tag: { + ArtifactsHash: common.HexToHash("d20a930cc0ff204c2d93b7aa60755ec7859ba4f328b881f5090c6a6a2a86dcba"), + ContentHash: common.HexToHash("e1f0c4020618c4a98972e7124c39686cab2e31d5d7846f9ce5e0d5eed0f5ff32"), + }, + ContractsV170Beta1L2Tag: { + ArtifactsHash: common.HexToHash("9e3ad322ec9b2775d59143ce6874892f9b04781742c603ad59165159e90b00b9"), + ContentHash: common.HexToHash("b0fb1f6f674519d637cff39a22187a5993d7f81a6d7b7be6507a0b50a5e38597"), + }, + ContractsV180Tag: { + ArtifactsHash: common.HexToHash("78f186df4e9a02a6421bd9c3641b281e297535140967faa428c938286923976a"), + ContentHash: common.HexToHash("361ebf1f520c20d932695b00babfff6923ce2530cd05b2776eb74e07038898a6"), }, - "op-contracts/v1.8.0-rc.4": { - Mainnet: &OPCMBlueprints{ - AddressManager: common.HexToAddress("0x29aA24714c06914d9689e933cae2293C569AfeEa"), - Proxy: common.HexToAddress("0x3626ebD458c7f34FD98789A373593fF2fc227bA0"), - ProxyAdmin: common.HexToAddress("0x7170678A5CFFb6872606d251B3CcdB27De962631"), - L1ChugSplashProxy: common.HexToAddress("0x538906C8B000D621fd11B7e8642f504dD8730837"), - ResolvedDelegateProxy: common.HexToAddress("0xF12bD34d6a1d26d230240ECEA761f77e2013926E"), - AnchorStateRegistry: common.HexToAddress("0xbA7Be2bEE016568274a4D1E6c852Bb9a99FaAB8B"), - PermissionedDisputeGame1: common.HexToAddress("0x596A4334a28056c7943c8bcEf220F38cA5B42dC5"), // updated - PermissionedDisputeGame2: common.HexToAddress("0x4E3E5C09B07AAA3fe482F5A1f82a19e91944Fffc"), // updated - }, - Sepolia: &OPCMBlueprints{ - AddressManager: common.HexToAddress("0x3125a4cB2179E04203D3Eb2b5784aaef9FD64216"), - Proxy: common.HexToAddress("0xe650ADb86a0de96e2c434D0a52E7D5B70980D6f1"), - ProxyAdmin: common.HexToAddress("0x3AC6b88F6bC4A5038DB7718dE47a5ab1a9609319"), - L1ChugSplashProxy: common.HexToAddress("0x58770FC7ed304c43D2B70248914eb34A741cF411"), - ResolvedDelegateProxy: common.HexToAddress("0x0449adB72D489a137d476aB49c6b812161754fD3"), - AnchorStateRegistry: common.HexToAddress("0xB98095199437883b7661E0D58256060f3bc730a4"), - PermissionedDisputeGame1: common.HexToAddress("0x596A4334a28056c7943c8bcEf220F38cA5B42dC5"), // updated - PermissionedDisputeGame2: common.HexToAddress("0x4E3E5C09B07AAA3fe482F5A1f82a19e91944Fffc"), // updated - }, + ContractsV200Tag: { + ArtifactsHash: common.HexToHash("32e11c96e07b83619f419595facb273368dccfe2439287549e7b436c9b522204"), + ContentHash: common.HexToHash("1cec51ed629c0394b8fb17ff2c6fa45c406c30f94ebbd37d4c90ede6c29ad608"), }, } -func OPCMBlueprintsFor(chainID uint64, version string) (OPCMBlueprints, error) { - switch chainID { - case 1: - bps := opcmBlueprintsByVersion[version].Mainnet - if bps == nil { - return OPCMBlueprints{}, fmt.Errorf("unsupported version: %s", version) - } - return *bps, nil - case 11155111: - bps := opcmBlueprintsByVersion[version].Sepolia - if bps == nil { - return OPCMBlueprints{}, fmt.Errorf("unsupported version: %s", version) - } - return *bps, nil - default: - return OPCMBlueprints{}, fmt.Errorf("unsupported chain ID: %d", chainID) - } +var _ embed.FS + +func IsSupportedL1Version(tag string) bool { + return tag == ContractsV200Tag } -func L1VersionsDataFor(chainID uint64) (string, error) { - switch chainID { - case 1: - return VersionsMainnetData, nil - case 11155111: - return VersionsSepoliaData, nil - default: - return "", fmt.Errorf("unsupported chain ID: %d", chainID) - } +func IsSupportedL2Version(tag string) bool { + return tag == ContractsV170Beta1L2Tag } -func L1VersionsFor(chainID uint64) (L1Versions, error) { +func L1VersionsFor(chainID uint64) (validation.Versions, error) { switch chainID { case 1: - return L1VersionsMainnet, nil + return validation.StandardVersionsMainnet, nil case 11155111: - return L1VersionsSepolia, nil + return validation.StandardVersionsSepolia, nil default: - return L1Versions{}, fmt.Errorf("unsupported chain ID: %d", chainID) + return nil, fmt.Errorf("unsupported chain ID: %d", chainID) } } @@ -222,38 +130,15 @@ func SuperchainFor(chainID uint64) (superchain.Superchain, error) { } func ManagerImplementationAddrFor(chainID uint64, tag string) (common.Address, error) { - switch chainID { - case 1: - switch tag { - case "op-contracts/v1.6.0": - // Generated using the bootstrap command on 11/18/2024. - // Verified against compiled bytecode at: - // https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts-v160-artifacts-opcm-redesign-backport - return common.HexToAddress("0x9BC0A1eD534BFb31a6Be69e5b767Cba332f14347"), nil - case "op-contracts/v1.8.0-rc.4": - // Generated using the bootstrap command on 01/23/2025. - // Verified against compiled bytecode at: - // https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts-v180-blueprints-script - return common.HexToAddress("0x5269eed89b0d04d909a0973439e2587e815ba932"), nil - default: - return common.Address{}, fmt.Errorf("unsupported mainnet tag: %s", tag) - } - case 11155111: - switch tag { - case "op-contracts/v1.6.0": - // Generated using the bootstrap command on 11/18/2024. - // Verified against compiled bytecode at: - // https://github.com/ethereum-optimism/optimism/releases/tag/op-contracts-v160-artifacts-opcm-redesign-backport - return common.HexToAddress("0x760B1d2Dc68DC51fb6E8B2b8722B8ed08903540c"), nil - case "op-contracts/v1.8.0-rc.4": - // Generated using the bootstrap command on 12/19/2024. - return common.HexToAddress("0xefb0779120d9cc3582747e5eb787d859e3a53a5c"), nil - default: - return common.Address{}, fmt.Errorf("unsupported sepolia tag: %s", tag) - } - default: + versionsData, err := L1VersionsFor(chainID) + if err != nil { return common.Address{}, fmt.Errorf("unsupported chain ID: %d", chainID) } + versionData, ok := versionsData[validation.Semver(tag)] + if !ok { + return common.Address{}, fmt.Errorf("unsupported tag for chain ID %d: %s", chainID, tag) + } + return common.Address(*versionData.OPContractsManager.Address), nil } // SuperchainProxyAdminAddrFor returns the address of the Superchain ProxyAdmin for the given chain ID. @@ -293,29 +178,20 @@ func ProtocolVersionsOwner(chainID uint64) (common.Address, error) { } func ArtifactsURLForTag(tag string) (*url.URL, error) { - switch tag { - case "op-contracts/v1.6.0": - return url.Parse(standardArtifactsURL("e1f0c4020618c4a98972e7124c39686cab2e31d5d7846f9ce5e0d5eed0f5ff32")) - case "op-contracts/v1.7.0-beta.1+l2-contracts": - return url.Parse(standardArtifactsURL("b0fb1f6f674519d637cff39a22187a5993d7f81a6d7b7be6507a0b50a5e38597")) - case "op-contracts/v1.8.0-rc.4": - return url.Parse(standardArtifactsURL("361ebf1f520c20d932695b00babfff6923ce2530cd05b2776eb74e07038898a6")) - default: + release, ok := taggedReleases[tag] + if !ok { return nil, fmt.Errorf("unsupported tag: %s", tag) } + + return url.Parse(release.URL()) } func ArtifactsHashForTag(tag string) (common.Hash, error) { - switch tag { - case "op-contracts/v1.6.0": - return common.HexToHash("d20a930cc0ff204c2d93b7aa60755ec7859ba4f328b881f5090c6a6a2a86dcba"), nil - case "op-contracts/v1.7.0-beta.1+l2-contracts": - return common.HexToHash("9e3ad322ec9b2775d59143ce6874892f9b04781742c603ad59165159e90b00b9"), nil - case "op-contracts/v1.8.0-rc.4": - return common.HexToHash("78f186df4e9a02a6421bd9c3641b281e297535140967faa428c938286923976a"), nil - default: + release, ok := taggedReleases[tag] + if !ok { return common.Hash{}, fmt.Errorf("unsupported tag: %s", tag) } + return release.ArtifactsHash, nil } // DefaultHardforkScheduleForTag is used to determine which hardforks should be activated by default given a @@ -341,19 +217,3 @@ func DefaultHardforkScheduleForTag(tag string) *genesis.UpgradeScheduleDeployCon return sched } - -func standardArtifactsURL(checksum string) string { - return fmt.Sprintf("https://storage.googleapis.com/oplabs-contract-artifacts/artifacts-v1-%s.tar.gz", checksum) -} - -func init() { - L1VersionsMainnet = L1Versions{} - if err := toml.Unmarshal([]byte(VersionsMainnetData), &L1VersionsMainnet); err != nil { - panic(err) - } - - L1VersionsSepolia = L1Versions{} - if err := toml.Unmarshal([]byte(VersionsSepoliaData), &L1VersionsSepolia); err != nil { - panic(err) - } -} diff --git a/op-deployer/pkg/deployer/state/intent.go b/op-deployer/pkg/deployer/state/intent.go index 7210b931224..e142b59ec39 100644 --- a/op-deployer/pkg/deployer/state/intent.go +++ b/op-deployer/pkg/deployer/state/intent.go @@ -7,6 +7,8 @@ import ( "net/url" "reflect" + "github.com/ethereum-optimism/superchain-registry/validation" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" @@ -229,7 +231,7 @@ func (c *Intent) checkL1Prod() error { return err } - if _, ok := versions[c.L1ContractsLocator.Tag]; !ok { + if _, ok := versions[validation.Semver(c.L1ContractsLocator.Tag)]; !ok { return fmt.Errorf("tag '%s' not found in standard versions", c.L1ContractsLocator.Tag) } diff --git a/op-deployer/pkg/deployer/state/intent_test.go b/op-deployer/pkg/deployer/state/intent_test.go index 718a11357b1..7451397bb50 100644 --- a/op-deployer/pkg/deployer/state/intent_test.go +++ b/op-deployer/pkg/deployer/state/intent_test.go @@ -8,7 +8,7 @@ import ( ) func TestValidateStandardValues(t *testing.T) { - intent, err := NewIntentStandard(1, []common.Hash{common.HexToHash("0x336")}) + intent, err := NewIntentStandard(11155111, []common.Hash{common.HexToHash("0x336")}) require.NoError(t, err) err = intent.Check() @@ -64,7 +64,7 @@ func TestValidateStandardValues(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - intent, err := NewIntentStandard(1, []common.Hash{common.HexToHash("0x336")}) + intent, err := NewIntentStandard(11155111, []common.Hash{common.HexToHash("0x336")}) require.NoError(t, err) setChainRoles(&intent) setFeeAddresses(&intent) diff --git a/op-deployer/pkg/deployer/upgrade/v2_0_0/upgrade.go b/op-deployer/pkg/deployer/upgrade/v2_0_0/upgrade.go index 579fbe3b7c9..a5926899d01 100644 --- a/op-deployer/pkg/deployer/upgrade/v2_0_0/upgrade.go +++ b/op-deployer/pkg/deployer/upgrade/v2_0_0/upgrade.go @@ -4,6 +4,8 @@ import ( "encoding/json" "fmt" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/standard" + "github.com/ethereum-optimism/optimism/op-chain-ops/script" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/opcm" "github.com/ethereum/go-ethereum/common" @@ -55,7 +57,7 @@ func (u *Upgrader) SupportsVersion(version string) bool { } func (u *Upgrader) ArtifactsURL() string { - return "" + return "tag://" + standard.ContractsV200Tag } var DefaultUpgrader = new(Upgrader) diff --git a/op-validator/pkg/validations/addresses.go b/op-validator/pkg/validations/addresses.go index 1b5cf5b9f93..57f8fef9d9f 100644 --- a/op-validator/pkg/validations/addresses.go +++ b/op-validator/pkg/validations/addresses.go @@ -13,9 +13,10 @@ const ( var addresses = map[uint64]map[string]common.Address{ 11155111: { - // Both versions below bootstrapped on 02/23/2025 using OP Deployer. + // Bootstrapped on 03/02/2025 using OP Deployer. VersionV180: common.HexToAddress("0x0a5bf8ebb4b177b2dcc6eba933db726a2e2e2b4d"), - VersionV200: common.HexToAddress("0xaf72eedb110f114a3b4e921c12755b4e47dbd63d"), + // Bootstrapped on 03/02/2025 using OP Deployer. + VersionV200: common.HexToAddress("0x37739a6b0a3f1e7429499a4ec4a0685439daff5c"), }, } diff --git a/op-validator/pkg/validations/addresses_test.go b/op-validator/pkg/validations/addresses_test.go index c6bf0b3e533..d3967e348f6 100644 --- a/op-validator/pkg/validations/addresses_test.go +++ b/op-validator/pkg/validations/addresses_test.go @@ -26,7 +26,7 @@ func TestValidatorAddress(t *testing.T) { name: "Valid Sepolia v2.0.0", chainID: 11155111, version: VersionV200, - want: common.HexToAddress("0xaf72eedb110f114a3b4e921c12755b4e47dbd63d"), + want: common.HexToAddress("0x37739a6b0a3F1E7429499a4eC4A0685439Daff5C"), expectError: false, }, { diff --git a/op-validator/pkg/validations/testdata/validations-v200.json b/op-validator/pkg/validations/testdata/validations-v200.json index 56c24a4345b..b9e26d9a809 100644 --- a/op-validator/pkg/validations/testdata/validations-v200.json +++ b/op-validator/pkg/validations/testdata/validations-v200.json @@ -7,7 +7,7 @@ "method": "eth_call", "params": [ { - "to": "0xaf72eedb110f114a3b4e921c12755b4e47dbd63d", + "to": "0x37739a6b0a3f1e7429499a4ec4a0685439daff5c", "data": "0x30d14888000000000000000000000000189abaaaa82dfc015a588a7dbad6f13b1d3485bc000000000000000000000000034edd2a225f7f429a63e0f1d2084b9e0a93b538038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c0000000000000000000000000000000000000000000000000000000000aa37dc0000000000000000000000000000000000000000000000000000000000000001" }, "latest" diff --git a/op-validator/pkg/validations/validations_test.go b/op-validator/pkg/validations/validations_test.go index 451d31516db..5c5fc43c1e6 100644 --- a/op-validator/pkg/validations/validations_test.go +++ b/op-validator/pkg/validations/validations_test.go @@ -16,7 +16,7 @@ import ( "github.com/ethereum-optimism/optimism/op-service/testutils/mockrpc" ) -func TestValidate(t *testing.T) { +func TestValidate_Mocked(t *testing.T) { tests := []struct { version string validator func(rpcClient *rpc.Client) Validator diff --git a/packages/contracts-bedrock/src/L1/StandardValidator.sol b/packages/contracts-bedrock/src/L1/StandardValidator.sol index cb54e935c1a..4ff5dc75e9f 100644 --- a/packages/contracts-bedrock/src/L1/StandardValidator.sol +++ b/packages/contracts-bedrock/src/L1/StandardValidator.sol @@ -621,11 +621,11 @@ contract StandardValidatorV200 is StandardValidatorBase { } function l1ERC721BridgeVersion() public pure override returns (string memory) { - return "2.3.0"; + return "2.3.1"; } function optimismPortalVersion() public pure override returns (string memory) { - return "3.12.0"; + return "3.13.0"; } function systemConfigVersion() public pure override returns (string memory) { @@ -641,10 +641,30 @@ contract StandardValidatorV200 is StandardValidatorBase { } function l1StandardBridgeVersion() public pure override returns (string memory) { - return "2.2.1"; + return "2.2.2"; } function disputeGameFactoryVersion() public pure override returns (string memory) { return "1.0.1"; } + + function anchorStateRegistryVersion() public pure override returns (string memory) { + return "2.2.2"; + } + + function delayedWETHVersion() public pure override returns (string memory) { + return "1.3.0"; + } + + function mipsVersion() public pure override returns (string memory) { + return "1.3.0"; + } + + function permissionedDisputeGameVersion() public pure override returns (string memory) { + return "1.4.1"; + } + + function preimageOracleVersion() public pure override returns (string memory) { + return "1.1.4"; + } } diff --git a/packages/contracts-bedrock/test/L1/StandardValidator.t.sol b/packages/contracts-bedrock/test/L1/StandardValidator.t.sol index 9e6062bc9f2..39852cf273d 100644 --- a/packages/contracts-bedrock/test/L1/StandardValidator.t.sol +++ b/packages/contracts-bedrock/test/L1/StandardValidator.t.sol @@ -1135,12 +1135,20 @@ contract StandardValidatorV200_Test is StandardValidatorTest { super._mockValidationCalls(); // Override version numbers for V200 - vm.mockCall(address(l1ERC721Bridge), abi.encodeCall(ISemver.version, ()), abi.encode("2.3.0")); - vm.mockCall(address(optimismPortal), abi.encodeCall(ISemver.version, ()), abi.encode("3.12.0")); + vm.mockCall(address(l1ERC721Bridge), abi.encodeCall(ISemver.version, ()), abi.encode("2.3.1")); + vm.mockCall(address(optimismPortal), abi.encodeCall(ISemver.version, ()), abi.encode("3.13.0")); vm.mockCall(address(systemConfig), abi.encodeCall(ISemver.version, ()), abi.encode("2.4.0")); vm.mockCall(address(optimismMintableERC20Factory), abi.encodeCall(ISemver.version, ()), abi.encode("1.10.1")); vm.mockCall(address(l1CrossDomainMessenger), abi.encodeCall(ISemver.version, ()), abi.encode("2.5.0")); - vm.mockCall(address(l1StandardBridge), abi.encodeCall(ISemver.version, ()), abi.encode("2.2.1")); + vm.mockCall(address(l1StandardBridge), abi.encodeCall(ISemver.version, ()), abi.encode("2.2.2")); vm.mockCall(address(disputeGameFactory), abi.encodeCall(ISemver.version, ()), abi.encode("1.0.1")); + vm.mockCall(address(permissionedASR), abi.encodeCall(ISemver.version, ()), abi.encode("2.2.2")); + vm.mockCall(address(permissionedDelayedWETH), abi.encodeCall(ISemver.version, ()), abi.encode("1.3.0")); + vm.mockCall(address(permissionlessASR), abi.encodeCall(ISemver.version, ()), abi.encode("2.2.2")); + vm.mockCall(address(permissionlessDelayedWETH), abi.encodeCall(ISemver.version, ()), abi.encode("1.3.0")); + vm.mockCall(address(mips), abi.encodeCall(ISemver.version, ()), abi.encode("1.3.0")); + vm.mockCall(address(permissionedDisputeGame), abi.encodeCall(ISemver.version, ()), abi.encode("1.4.1")); + vm.mockCall(address(permissionlessDisputeGame), abi.encodeCall(ISemver.version, ()), abi.encode("1.4.1")); + vm.mockCall(address(preimageOracle), abi.encodeCall(ISemver.version, ()), abi.encode("1.1.4")); } } From 14fdfbe393d2b4912407551b92f0a2174ccf6e26 Mon Sep 17 00:00:00 2001 From: Eric Tu <6364934+ec2@users.noreply.github.com> Date: Mon, 3 Mar 2025 16:35:38 -0500 Subject: [PATCH 046/130] mipsevm Memory Merklization Abstraction (#14292) * Abstract the Merkle representation * Implement asterisc's MPT * migrate tests for mpt * migrate tests for mpt * copied benchmarks from asterisc * fix failed merge * Avoid pagelookup twice during setword invalidation * fix state json codec test * fix for singlethread too * fix op-challenger test * Remove MPT implementation * address comments * fix benchmark --- cannon/mipsevm/memory/binary_tree.go | 118 ++++++ cannon/mipsevm/memory/memory.go | 355 +++++++----------- ...y_test.go => memory32_binary_tree_test.go} | 2 +- .../mipsevm/memory/memory64_benchmark_test.go | 137 +++++++ ...4_test.go => memory64_binary_tree_test.go} | 48 +-- cannon/mipsevm/multithreaded/state_test.go | 2 +- cannon/mipsevm/singlethreaded/state.go | 1 + cannon/mipsevm/singlethreaded/state_test.go | 2 +- .../game/fault/trace/cannon/provider_test.go | 10 +- 9 files changed, 434 insertions(+), 241 deletions(-) create mode 100644 cannon/mipsevm/memory/binary_tree.go rename cannon/mipsevm/memory/{memory_test.go => memory32_binary_tree_test.go} (99%) create mode 100644 cannon/mipsevm/memory/memory64_benchmark_test.go rename cannon/mipsevm/memory/{memory64_test.go => memory64_binary_tree_test.go} (89%) diff --git a/cannon/mipsevm/memory/binary_tree.go b/cannon/mipsevm/memory/binary_tree.go new file mode 100644 index 00000000000..5888ccc4146 --- /dev/null +++ b/cannon/mipsevm/memory/binary_tree.go @@ -0,0 +1,118 @@ +package memory + +import ( + "math/bits" +) + +// BinaryTreeIndex is a representation of the state of the memory in a binary merkle tree. +type BinaryTreeIndex struct { + // generalized index -> merkle root or nil if invalidated + nodes map[uint64]*[32]byte + // Reference to the page table from Memory. + pageTable map[Word]*CachedPage +} + +func NewBinaryTreeMemory() *Memory { + pages := make(map[Word]*CachedPage) + index := NewBinaryTreeIndex(pages) + return &Memory{ + merkleIndex: index, + pageTable: pages, + lastPageKeys: [2]Word{^Word(0), ^Word(0)}, // default to invalid keys, to not match any pages + } +} + +func NewBinaryTreeIndex(pages map[Word]*CachedPage) *BinaryTreeIndex { + return &BinaryTreeIndex{ + nodes: make(map[uint64]*[32]byte), + pageTable: pages, + } +} + +func (m *BinaryTreeIndex) New(pages map[Word]*CachedPage) PageIndex { + x := NewBinaryTreeIndex(pages) + return x +} + +func (m *BinaryTreeIndex) Invalidate(addr Word) { + // find the gindex of the first page covering the address: i.e. ((1 << WordSize) | addr) >> PageAddrSize + // Avoid 64-bit overflow by distributing the right shift across the OR. + gindex := (uint64(1) << (WordSize - PageAddrSize)) | uint64(addr>>PageAddrSize) + + for gindex > 0 { + m.nodes[gindex] = nil + gindex >>= 1 + } +} + +func (m *BinaryTreeIndex) MerkleizeSubtree(gindex uint64) [32]byte { + l := uint64(bits.Len64(gindex)) + if l > MemProofLeafCount { + panic("gindex too deep") + } + if l > PageKeySize { + depthIntoPage := l - 1 - PageKeySize + pageIndex := (gindex >> depthIntoPage) & PageKeyMask + if p, ok := m.pageTable[Word(pageIndex)]; ok { + pageGindex := (1 << depthIntoPage) | (gindex & ((1 << depthIntoPage) - 1)) + return p.MerkleizeSubtree(pageGindex) + } else { + return zeroHashes[MemProofLeafCount-l] // page does not exist + } + } + n, ok := m.nodes[gindex] + if !ok { + // if the node doesn't exist, the whole sub-tree is zeroed + return zeroHashes[MemProofLeafCount-l] + } + if n != nil { + return *n + } + left := m.MerkleizeSubtree(gindex << 1) + right := m.MerkleizeSubtree((gindex << 1) | 1) + r := HashPair(left, right) + m.nodes[gindex] = &r + return r +} + +func (m *BinaryTreeIndex) MerkleProof(addr Word) (out [MemProofSize]byte) { + proof := m.traverseBranch(1, addr, 0) + // encode the proof + for i := 0; i < MemProofLeafCount; i++ { + copy(out[i*32:(i+1)*32], proof[i][:]) + } + return out +} + +func (m *BinaryTreeIndex) traverseBranch(parent uint64, addr Word, depth uint8) (proof [][32]byte) { + if depth == WordSize-5 { + proof = make([][32]byte, 0, WordSize-5+1) + proof = append(proof, m.MerkleizeSubtree(parent)) + return + } + if depth > WordSize-5 { + panic("traversed too deep") + } + self := parent << 1 + sibling := self | 1 + if addr&(1<<((WordSize-1)-depth)) != 0 { + self, sibling = sibling, self + } + proof = m.traverseBranch(self, addr, depth+1) + siblingNode := m.MerkleizeSubtree(sibling) + proof = append(proof, siblingNode) + return +} + +func (m *BinaryTreeIndex) MerkleRoot() [32]byte { + return m.MerkleizeSubtree(1) +} + +func (m *BinaryTreeIndex) AddPage(pageIndex Word) { + // make nodes to root + k := (1 << PageKeySize) | uint64(pageIndex) + for k > 0 { + m.nodes[k] = nil + k >>= 1 + } +} diff --git a/cannon/mipsevm/memory/memory.go b/cannon/mipsevm/memory/memory.go index 375aac49920..aef95b901f8 100644 --- a/cannon/mipsevm/memory/memory.go +++ b/cannon/mipsevm/memory/memory.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "io" - "math/bits" "slices" "sort" @@ -45,14 +44,11 @@ var zeroHashes = func() [256][32]byte { }() type Memory struct { - // generalized index -> merkle root or nil if invalidated - nodes map[uint64]*[32]byte - - // pageIndex -> cached page - pages map[Word]*CachedPage - - // Note: since we don't de-alloc pages, we don't do ref-counting. - // Once a page exists, it doesn't leave memory + merkleIndex PageIndex + // Note: since we don't de-alloc Pages, we don't do ref-counting. + // Once a page exists, it doesn't leave memory. + // This map will usually be shared with the PageIndex as well. + pageTable map[Word]*CachedPage // two caches: we often read instructions from one page, and do memory things with another page. // this prevents map lookups each instruction @@ -60,20 +56,34 @@ type Memory struct { lastPage [2]*CachedPage } +type PageIndex interface { + MerkleRoot() [32]byte + AddPage(pageIndex Word) + MerkleProof(addr Word) [MemProofSize]byte + MerkleizeSubtree(gindex uint64) [32]byte + Invalidate(addr Word) + + New(pages map[Word]*CachedPage) PageIndex +} + func NewMemory() *Memory { - return &Memory{ - nodes: make(map[uint64]*[32]byte), - pages: make(map[Word]*CachedPage), - lastPageKeys: [2]Word{^Word(0), ^Word(0)}, // default to invalid keys, to not match any pages - } + return NewBinaryTreeMemory() +} + +func (m *Memory) MerkleRoot() [32]byte { + return m.MerkleizeSubtree(1) +} + +func (m *Memory) MerkleProof(addr Word) [MemProofSize]byte { + return m.merkleIndex.MerkleProof(addr) } func (m *Memory) PageCount() int { - return len(m.pages) + return len(m.pageTable) } func (m *Memory) ForEachPage(fn func(pageIndex Word, page *Page) error) error { - for pageIndex, cachedPage := range m.pages { + for pageIndex, cachedPage := range m.pageTable { if err := fn(pageIndex, cachedPage.Data); err != nil { return err } @@ -82,69 +92,10 @@ func (m *Memory) ForEachPage(fn func(pageIndex Word, page *Page) error) error { } func (m *Memory) MerkleizeSubtree(gindex uint64) [32]byte { - l := uint64(bits.Len64(gindex)) - if l > MemProofLeafCount { - panic("gindex too deep") - } - if l > PageKeySize { - depthIntoPage := l - 1 - PageKeySize - pageIndex := (gindex >> depthIntoPage) & PageKeyMask - if p, ok := m.pages[Word(pageIndex)]; ok { - pageGindex := (1 << depthIntoPage) | (gindex & ((1 << depthIntoPage) - 1)) - return p.MerkleizeSubtree(pageGindex) - } else { - return zeroHashes[MemProofLeafCount-l] // page does not exist - } - } - n, ok := m.nodes[gindex] - if !ok { - // if the node doesn't exist, the whole sub-tree is zeroed - return zeroHashes[MemProofLeafCount-l] - } - if n != nil { - return *n - } - left := m.MerkleizeSubtree(gindex << 1) - right := m.MerkleizeSubtree((gindex << 1) | 1) - r := HashPair(left, right) - m.nodes[gindex] = &r - return r -} - -func (m *Memory) MerkleProof(addr Word) (out [MemProofSize]byte) { - proof := m.traverseBranch(1, addr, 0) - // encode the proof - for i := 0; i < MemProofLeafCount; i++ { - copy(out[i*32:(i+1)*32], proof[i][:]) - } - return out + return m.merkleIndex.MerkleizeSubtree(gindex) } -func (m *Memory) traverseBranch(parent uint64, addr Word, depth uint8) (proof [][32]byte) { - if depth == WordSize-5 { - proof = make([][32]byte, 0, WordSize-5+1) - proof = append(proof, m.MerkleizeSubtree(parent)) - return - } - if depth > WordSize-5 { - panic("traversed too deep") - } - self := parent << 1 - sibling := self | 1 - if addr&(1<<((WordSize-1)-depth)) != 0 { - self, sibling = sibling, self - } - proof = m.traverseBranch(self, addr, depth+1) - siblingNode := m.MerkleizeSubtree(sibling) - proof = append(proof, siblingNode) - return -} - -func (m *Memory) MerkleRoot() [32]byte { - return m.MerkleizeSubtree(1) -} - -func (m *Memory) pageLookup(pageIndex Word) (*CachedPage, bool) { +func (m *Memory) PageLookup(pageIndex Word) (*CachedPage, bool) { // hit caches if pageIndex == m.lastPageKeys[0] { return m.lastPage[0], true @@ -152,7 +103,7 @@ func (m *Memory) pageLookup(pageIndex Word) (*CachedPage, bool) { if pageIndex == m.lastPageKeys[1] { return m.lastPage[1], true } - p, ok := m.pages[pageIndex] + p, ok := m.pageTable[pageIndex] // only cache existing pages. if ok { @@ -165,6 +116,30 @@ func (m *Memory) pageLookup(pageIndex Word) (*CachedPage, bool) { return p, ok } +func (m *Memory) SetMemoryRange(addr Word, r io.Reader) error { + for { + pageIndex := addr >> PageAddrSize + pageAddr := addr & PageAddrMask + readLen := PageSize - pageAddr + chunk := make([]byte, readLen) + n, err := r.Read(chunk) + if err != nil { + if err == io.EOF { + return nil + } + return err + } + + p, ok := m.PageLookup(pageIndex) + if !ok { + p = m.AllocPage(pageIndex) + } + p.InvalidateFull() + copy(p.Data[pageAddr:], chunk[:n]) + addr += Word(n) + } +} + // SetWord stores [arch.Word] sized values at the specified address func (m *Memory) SetWord(addr Word, v Word) { // addr must be aligned to WordSizeBytes bytes @@ -174,7 +149,7 @@ func (m *Memory) SetWord(addr Word, v Word) { pageIndex := addr >> PageAddrSize pageAddr := addr & PageAddrMask - p, ok := m.pageLookup(pageIndex) + p, ok := m.PageLookup(pageIndex) if !ok { // allocate the page if we have not already. // Go may mmap relatively large ranges, but we only allocate the pages just in time. @@ -182,17 +157,8 @@ func (m *Memory) SetWord(addr Word, v Word) { } else { prevValid := p.Ok[1] p.invalidate(pageAddr) - if prevValid { // if the page was already invalid before, then nodes to mem-root will also still be. - - // find the gindex of the first page covering the address: i.e. ((1 << WordSize) | addr) >> PageAddrSize - // Avoid 64-bit overflow by distributing the right shift across the OR. - gindex := (uint64(1) << (WordSize - PageAddrSize)) | uint64(addr>>PageAddrSize) - - for gindex > 0 { - m.nodes[gindex] = nil - gindex >>= 1 - } - + if prevValid { + m.merkleIndex.Invalidate(addr) // invalidate this branch of memory, now that the value changed } } arch.ByteOrderWord.PutWord(p.Data[pageAddr:pageAddr+arch.WordSizeBytes], v) @@ -205,7 +171,8 @@ func (m *Memory) GetWord(addr Word) Word { if addr&arch.ExtMask != 0 { panic(fmt.Errorf("unaligned memory access: %x", addr)) } - p, ok := m.pageLookup(addr >> PageAddrSize) + pageIndex := addr >> PageAddrSize + p, ok := m.PageLookup(pageIndex) if !ok { return 0 } @@ -215,75 +182,82 @@ func (m *Memory) GetWord(addr Word) Word { func (m *Memory) AllocPage(pageIndex Word) *CachedPage { p := &CachedPage{Data: new(Page)} - m.pages[pageIndex] = p - // make nodes to root - k := (1 << PageKeySize) | uint64(pageIndex) - for k > 0 { - m.nodes[k] = nil - k >>= 1 - } + m.pageTable[pageIndex] = p + m.merkleIndex.AddPage(pageIndex) return p } -type pageEntry struct { - Index Word `json:"index"` - Data *Page `json:"data"` +type memReader struct { + m *Memory + addr Word + count Word } -func (m *Memory) MarshalJSON() ([]byte, error) { // nosemgrep - pages := make([]pageEntry, 0, len(m.pages)) - for k, p := range m.pages { - pages = append(pages, pageEntry{ - Index: k, - Data: p.Data, - }) +func (m *Memory) ReadMemoryRange(addr Word, count Word) io.Reader { + return &memReader{m: m, addr: addr, count: count} +} +func (r *memReader) Read(dest []byte) (n int, err error) { + if r.count == 0 { + return 0, io.EOF } - sort.Slice(pages, func(i, j int) bool { - return pages[i].Index < pages[j].Index - }) - return json.Marshal(pages) + + // Keep iterating over memory until we have all our data. + // It may wrap around the address range, and may not be aligned + endAddr := r.addr + r.count + + pageIndex := r.addr >> PageAddrSize + start := r.addr & PageAddrMask + end := Word(PageSize) + + if pageIndex == (endAddr >> PageAddrSize) { + end = endAddr & PageAddrMask + } + p, ok := r.m.PageLookup(pageIndex) + if ok { + n = copy(dest, p.Data[start:end]) + } else { + n = copy(dest, make([]byte, end-start)) // default to zeroes + } + r.addr += Word(n) + r.count -= Word(n) + return n, nil } -func (m *Memory) UnmarshalJSON(data []byte) error { - var pages []pageEntry - if err := json.Unmarshal(data, &pages); err != nil { - return err +func (m *Memory) UsageRaw() uint64 { + return uint64(len(m.pageTable)) * PageSize +} + +func (m *Memory) Usage() string { + total := m.UsageRaw() + const unit = 1024 + if total < unit { + return fmt.Sprintf("%d B", total) } - m.nodes = make(map[uint64]*[32]byte) - m.pages = make(map[Word]*CachedPage) - m.lastPageKeys = [2]Word{^Word(0), ^Word(0)} - m.lastPage = [2]*CachedPage{nil, nil} - for i, p := range pages { - if _, ok := m.pages[p.Index]; ok { - return fmt.Errorf("cannot load duplicate page, entry %d, page index %d", i, p.Index) - } - m.AllocPage(p.Index).Data = p.Data + div, exp := uint64(unit), 0 + for n := total / unit; n >= unit; n /= unit { + div *= unit + exp++ } - return nil + // KiB, MiB, GiB, TiB, ... + return fmt.Sprintf("%.1f %ciB", float64(total)/float64(div), "KMGTPE"[exp]) } -func (m *Memory) SetMemoryRange(addr Word, r io.Reader) error { - for { - pageIndex := addr >> PageAddrSize - pageAddr := addr & PageAddrMask - readLen := PageSize - pageAddr - chunk := make([]byte, readLen) - n, err := r.Read(chunk) - if err != nil { - if err == io.EOF { - return nil - } - return err - } +func (m *Memory) Copy() *Memory { + pages := make(map[Word]*CachedPage) + table := m.merkleIndex.New(pages) + out := &Memory{ + merkleIndex: table, + pageTable: pages, + lastPageKeys: [2]Word{^Word(0), ^Word(0)}, // default to invalid keys, to not match any pages + lastPage: [2]*CachedPage{nil, nil}, + } - p, ok := m.pageLookup(pageIndex) - if !ok { - p = m.AllocPage(pageIndex) - } - p.InvalidateFull() - copy(p.Data[pageAddr:], chunk[:n]) - addr += Word(n) + for k, page := range m.pageTable { + data := new(Page) + *data = *page.Data + out.AllocPage(k).Data = data } + return out } // Serialize writes the memory in a simple binary format which can be read again using Deserialize @@ -299,11 +273,11 @@ func (m *Memory) Serialize(out io.Writer) error { if err := binary.Write(out, binary.BigEndian, Word(m.PageCount())); err != nil { return err } - indexes := maps.Keys(m.pages) + indexes := maps.Keys(m.pageTable) // iterate sorted map keys for consistent serialization slices.Sort(indexes) for _, pageIndex := range indexes { - page := m.pages[pageIndex] + page := m.pageTable[pageIndex] if err := binary.Write(out, binary.BigEndian, pageIndex); err != nil { return err } @@ -332,72 +306,35 @@ func (m *Memory) Deserialize(in io.Reader) error { return nil } -func (m *Memory) Copy() *Memory { - out := NewMemory() - out.nodes = make(map[uint64]*[32]byte) - out.pages = make(map[Word]*CachedPage) - out.lastPageKeys = [2]Word{^Word(0), ^Word(0)} - out.lastPage = [2]*CachedPage{nil, nil} - for k, page := range m.pages { - data := new(Page) - *data = *page.Data - out.AllocPage(k).Data = data - } - return out -} - -type memReader struct { - m *Memory - addr Word - count Word +type pageEntry struct { + Index Word `json:"index"` + Data *Page `json:"data"` } -func (r *memReader) Read(dest []byte) (n int, err error) { - if r.count == 0 { - return 0, io.EOF - } - - // Keep iterating over memory until we have all our data. - // It may wrap around the address range, and may not be aligned - endAddr := r.addr + r.count - - pageIndex := r.addr >> PageAddrSize - start := r.addr & PageAddrMask - end := Word(PageSize) - - if pageIndex == (endAddr >> PageAddrSize) { - end = endAddr & PageAddrMask - } - p, ok := r.m.pageLookup(pageIndex) - if ok { - n = copy(dest, p.Data[start:end]) - } else { - n = copy(dest, make([]byte, end-start)) // default to zeroes +func (m *Memory) MarshalJSON() ([]byte, error) { // nosemgrep + pages := make([]pageEntry, 0, len(m.pageTable)) + for k, p := range m.pageTable { + pages = append(pages, pageEntry{ + Index: k, + Data: p.Data, + }) } - r.addr += Word(n) - r.count -= Word(n) - return n, nil -} - -func (m *Memory) ReadMemoryRange(addr Word, count Word) io.Reader { - return &memReader{m: m, addr: addr, count: count} -} - -func (m *Memory) UsageRaw() uint64 { - return uint64(len(m.pages)) * PageSize + sort.Slice(pages, func(i, j int) bool { + return pages[i].Index < pages[j].Index + }) + return json.Marshal(pages) } -func (m *Memory) Usage() string { - total := m.UsageRaw() - const unit = 1024 - if total < unit { - return fmt.Sprintf("%d B", total) +func (m *Memory) UnmarshalJSON(data []byte) error { + var pages []pageEntry + if err := json.Unmarshal(data, &pages); err != nil { + return err } - div, exp := uint64(unit), 0 - for n := total / unit; n >= unit; n /= unit { - div *= unit - exp++ + for i, p := range pages { + if _, ok := m.pageTable[p.Index]; ok { + return fmt.Errorf("cannot load duplicate page, entry %d, page index %d", i, p.Index) + } + m.AllocPage(p.Index).Data = p.Data } - // KiB, MiB, GiB, TiB, ... - return fmt.Sprintf("%.1f %ciB", float64(total)/float64(div), "KMGTPE"[exp]) + return nil } diff --git a/cannon/mipsevm/memory/memory_test.go b/cannon/mipsevm/memory/memory32_binary_tree_test.go similarity index 99% rename from cannon/mipsevm/memory/memory_test.go rename to cannon/mipsevm/memory/memory32_binary_tree_test.go index 5100de5b415..d7597eaace0 100644 --- a/cannon/mipsevm/memory/memory_test.go +++ b/cannon/mipsevm/memory/memory32_binary_tree_test.go @@ -231,7 +231,7 @@ func TestMemoryJSON(t *testing.T) { m.SetWord(8, 0xAABBCCDD) dat, err := json.Marshal(m) require.NoError(t, err) - var res Memory + res := NewMemory() require.NoError(t, json.Unmarshal(dat, &res)) require.Equal(t, uint32(0xAABBCCDD), res.GetWord(8)) } diff --git a/cannon/mipsevm/memory/memory64_benchmark_test.go b/cannon/mipsevm/memory/memory64_benchmark_test.go new file mode 100644 index 00000000000..8ae56c5b590 --- /dev/null +++ b/cannon/mipsevm/memory/memory64_benchmark_test.go @@ -0,0 +1,137 @@ +//go:build cannon64 +// +build cannon64 + +package memory + +import ( + "testing" + + "math/rand" + + "github.com/ethereum-optimism/optimism/cannon/mipsevm/arch" +) + +const ( + smallDataset = 12_500_000 + mediumDataset = 100_000_000 + largeDataset = 400_000_000 +) + +func BenchmarkMemoryOperations(b *testing.B) { + benchmarks := []struct { + name string + fn func(b *testing.B, m *Memory) + }{ + {"RandomReadWrite_Small", benchRandomReadWrite(smallDataset)}, + {"RandomReadWrite_Medium", benchRandomReadWrite(mediumDataset)}, + {"RandomReadWrite_Large", benchRandomReadWrite(largeDataset)}, + {"SequentialReadWrite_Small", benchSequentialReadWrite(smallDataset)}, + {"SequentialReadWrite_Large", benchSequentialReadWrite(largeDataset)}, + {"SparseMemoryUsage", benchSparseMemoryUsage}, + {"DenseMemoryUsage", benchDenseMemoryUsage}, + {"SmallFrequentUpdates", benchSmallFrequentUpdates}, + {"MerkleProofGeneration_Small", benchMerkleProofGeneration(smallDataset)}, + {"MerkleProofGeneration_Large", benchMerkleProofGeneration(largeDataset)}, + {"MerkleRootCalculation_Small", benchMerkleRootCalculation(smallDataset)}, + {"MerkleRootCalculation_Large", benchMerkleRootCalculation(largeDataset)}, + } + + for _, bm := range benchmarks { + b.Run("BinaryTree", func(b *testing.B) { + b.Run(bm.name, func(b *testing.B) { + m := NewBinaryTreeMemory() + b.ResetTimer() + bm.fn(b, m) + }) + }) + } +} + +func benchRandomReadWrite(size int) func(b *testing.B, m *Memory) { + return func(b *testing.B, m *Memory) { + addresses := make([]uint64, size) + for i := range addresses { + addresses[i] = rand.Uint64() & arch.AddressMask + } + data := Word(0x1234567890ABCDEF) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + addr := addresses[i%len(addresses)] + if i%2 == 0 { + m.SetWord(addr, data) + } else { + data = m.GetWord(addr) + } + } + } +} + +func benchSequentialReadWrite(size int) func(b *testing.B, m *Memory) { + return func(b *testing.B, m *Memory) { + data := Word(0x1234567890ABCDEF) + b.ResetTimer() + for i := 0; i < b.N; i++ { + addr := Word((i % size) * 8) + if i%2 == 0 { + m.SetWord(addr, data) + } else { + data = m.GetWord(addr) + } + } + } +} + +func benchSparseMemoryUsage(b *testing.B, m *Memory) { + data := Word(0x1234567890ABCDEF) + b.ResetTimer() + for i := 0; i < b.N; i++ { + addr := (uint64(i) * 10_000_000) & arch.AddressMask // Large gaps between addresses + m.SetWord(addr, data) + } +} + +func benchDenseMemoryUsage(b *testing.B, m *Memory) { + data := Word(0x1234567890ABCDEF) + b.ResetTimer() + for i := 0; i < b.N; i++ { + addr := uint64(i) * 8 // Contiguous 8-byte allocations + m.SetWord(addr, data) + } +} + +func benchSmallFrequentUpdates(b *testing.B, m *Memory) { + data := Word(0x1234567890ABCDEF) + b.ResetTimer() + for i := 0; i < b.N; i++ { + addr := Word(rand.Intn(1000000)) & arch.AddressMask // Confined to a smaller range + m.SetWord(addr, data) + } +} + +func benchMerkleProofGeneration(size int) func(b *testing.B, m *Memory) { + return func(b *testing.B, m *Memory) { + // Setup: allocate some memory + for i := 0; i < size; i++ { + m.SetWord(uint64(i)*8, Word(i)) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + addr := uint64(rand.Intn(size) * 8) + _ = m.MerkleProof(addr) + } + } +} + +func benchMerkleRootCalculation(size int) func(b *testing.B, m *Memory) { + return func(b *testing.B, m *Memory) { + // Setup: allocate some memory + for i := 0; i < size; i++ { + m.SetWord(uint64(i)*8, Word(i)) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = m.MerkleRoot() + } + } +} diff --git a/cannon/mipsevm/memory/memory64_test.go b/cannon/mipsevm/memory/memory64_binary_tree_test.go similarity index 89% rename from cannon/mipsevm/memory/memory64_test.go rename to cannon/mipsevm/memory/memory64_binary_tree_test.go index 203ff5f80d2..1fce4123765 100644 --- a/cannon/mipsevm/memory/memory64_test.go +++ b/cannon/mipsevm/memory/memory64_binary_tree_test.go @@ -18,9 +18,9 @@ import ( // These tests are mostly copied from memory_test.go. With a few tweaks for 64-bit. -func TestMemory64MerkleProof(t *testing.T) { +func TestMemory64BinaryTreeMerkleProof(t *testing.T) { t.Run("nearly empty tree", func(t *testing.T) { - m := NewMemory() + m := NewBinaryTreeMemory() m.SetWord(0x10000, 0xAABBCCDD_EEFF1122) proof := m.MerkleProof(0x10000) require.Equal(t, uint64(0xAABBCCDD_EEFF1122), binary.BigEndian.Uint64(proof[:8])) @@ -29,7 +29,7 @@ func TestMemory64MerkleProof(t *testing.T) { } }) t.Run("fuller tree", func(t *testing.T) { - m := NewMemory() + m := NewBinaryTreeMemory() m.SetWord(0x10000, 0xaabbccdd) m.SetWord(0x80008, 42) m.SetWord(0x13370000, 123) @@ -51,40 +51,40 @@ func TestMemory64MerkleProof(t *testing.T) { }) } -func TestMemory64MerkleRoot(t *testing.T) { +func TestMemory64BinaryTreeMerkleRoot(t *testing.T) { t.Run("empty", func(t *testing.T) { - m := NewMemory() + m := NewBinaryTreeMemory() root := m.MerkleRoot() require.Equal(t, zeroHashes[64-5], root, "fully zeroed memory should have expected zero hash") }) t.Run("empty page", func(t *testing.T) { - m := NewMemory() + m := NewBinaryTreeMemory() m.SetWord(0xF000, 0) root := m.MerkleRoot() require.Equal(t, zeroHashes[64-5], root, "fully zeroed memory should have expected zero hash") }) t.Run("single page", func(t *testing.T) { - m := NewMemory() + m := NewBinaryTreeMemory() m.SetWord(0xF000, 1) root := m.MerkleRoot() require.NotEqual(t, zeroHashes[64-5], root, "non-zero memory") }) t.Run("repeat zero", func(t *testing.T) { - m := NewMemory() + m := NewBinaryTreeMemory() m.SetWord(0xF000, 0) m.SetWord(0xF008, 0) root := m.MerkleRoot() require.Equal(t, zeroHashes[64-5], root, "zero still") }) t.Run("two empty pages", func(t *testing.T) { - m := NewMemory() + m := NewBinaryTreeMemory() m.SetWord(PageSize*3, 0) m.SetWord(PageSize*10, 0) root := m.MerkleRoot() require.Equal(t, zeroHashes[64-5], root, "zero still") }) t.Run("random few pages", func(t *testing.T) { - m := NewMemory() + m := NewBinaryTreeMemory() m.SetWord(PageSize*3, 1) m.SetWord(PageSize*5, 42) m.SetWord(PageSize*6, 123) @@ -106,7 +106,7 @@ func TestMemory64MerkleRoot(t *testing.T) { require.Equal(t, r1, r2, "expecting manual page combination to match subtree merkle func") }) t.Run("invalidate page", func(t *testing.T) { - m := NewMemory() + m := NewBinaryTreeMemory() m.SetWord(0xF000, 0) require.Equal(t, zeroHashes[64-5], m.MerkleRoot(), "zero at first") m.SetWord(0xF008, 1) @@ -116,9 +116,9 @@ func TestMemory64MerkleRoot(t *testing.T) { }) } -func TestMemory64ReadWrite(t *testing.T) { +func TestMemory64BinaryTreeReadWrite(t *testing.T) { t.Run("large random", func(t *testing.T) { - m := NewMemory() + m := NewBinaryTreeMemory() data := make([]byte, 20_000) _, err := rand.Read(data[:]) require.NoError(t, err) @@ -131,7 +131,7 @@ func TestMemory64ReadWrite(t *testing.T) { }) t.Run("repeat range", func(t *testing.T) { - m := NewMemory() + m := NewBinaryTreeMemory() data := []byte(strings.Repeat("under the big bright yellow sun ", 40)) require.NoError(t, m.SetMemoryRange(0x1337, bytes.NewReader(data))) res, err := io.ReadAll(m.ReadMemoryRange(0x1337-10, Word(len(data)+20))) @@ -142,7 +142,7 @@ func TestMemory64ReadWrite(t *testing.T) { }) t.Run("empty range", func(t *testing.T) { - m := NewMemory() + m := NewBinaryTreeMemory() addr := Word(0xAABBCC00) r := bytes.NewReader(nil) pre := m.MerkleRoot() @@ -168,7 +168,7 @@ func TestMemory64ReadWrite(t *testing.T) { }) t.Run("range page overlap", func(t *testing.T) { - m := NewMemory() + m := NewBinaryTreeMemory() data := bytes.Repeat([]byte{0xAA}, PageAddrSize) require.NoError(t, m.SetMemoryRange(0, bytes.NewReader(data))) for i := 0; i < PageAddrSize/arch.WordSizeBytes; i++ { @@ -186,7 +186,7 @@ func TestMemory64ReadWrite(t *testing.T) { }) t.Run("read-write", func(t *testing.T) { - m := NewMemory() + m := NewBinaryTreeMemory() m.SetWord(16, 0xAABBCCDD_EEFF1122) require.Equal(t, Word(0xAABBCCDD_EEFF1122), m.GetWord(16)) m.SetWord(16, 0xAABB1CDD_EEFF1122) @@ -196,7 +196,7 @@ func TestMemory64ReadWrite(t *testing.T) { }) t.Run("unaligned read", func(t *testing.T) { - m := NewMemory() + m := NewBinaryTreeMemory() m.SetWord(16, Word(0xAABBCCDD_EEFF1122)) m.SetWord(24, 0x11223344_55667788) for i := Word(17); i < 24; i++ { @@ -210,7 +210,7 @@ func TestMemory64ReadWrite(t *testing.T) { }) t.Run("unaligned write", func(t *testing.T) { - m := NewMemory() + m := NewBinaryTreeMemory() m.SetWord(16, 0xAABBCCDD_EEFF1122) require.Panics(t, func() { m.SetWord(17, 0x11223344) @@ -237,18 +237,18 @@ func TestMemory64ReadWrite(t *testing.T) { }) } -func TestMemory64JSON(t *testing.T) { - m := NewMemory() +func TestMemory64BinaryTreeJSON(t *testing.T) { + m := NewBinaryTreeMemory() m.SetWord(8, 0xAABBCCDD_EEFF1122) dat, err := json.Marshal(m) require.NoError(t, err) - var res Memory + res := NewBinaryTreeMemory() require.NoError(t, json.Unmarshal(dat, &res)) require.Equal(t, Word(0xAABBCCDD_EEFF1122), res.GetWord(8)) } -func TestMemory64Copy(t *testing.T) { - m := NewMemory() +func TestMemory64BinaryTreeCopy(t *testing.T) { + m := NewBinaryTreeMemory() m.SetWord(0xAABBCCDD_8000, 0x000000_AABB) mcpy := m.Copy() require.Equal(t, Word(0xAABB), mcpy.GetWord(0xAABBCCDD_8000)) diff --git a/cannon/mipsevm/multithreaded/state_test.go b/cannon/mipsevm/multithreaded/state_test.go index 1a457b83556..2badb9ee1f6 100644 --- a/cannon/mipsevm/multithreaded/state_test.go +++ b/cannon/mipsevm/multithreaded/state_test.go @@ -118,7 +118,7 @@ func TestState_JSONCodec(t *testing.T) { stateJSON, err := json.Marshal(state) require.NoError(t, err) - var newState *State + newState := CreateEmptyState() err = json.Unmarshal(stateJSON, &newState) require.NoError(t, err) diff --git a/cannon/mipsevm/singlethreaded/state.go b/cannon/mipsevm/singlethreaded/state.go index c02528ec352..71e664cbbc1 100644 --- a/cannon/mipsevm/singlethreaded/state.go +++ b/cannon/mipsevm/singlethreaded/state.go @@ -111,6 +111,7 @@ func (s *State) MarshalJSON() ([]byte, error) { // nosemgrep func (s *State) UnmarshalJSON(data []byte) error { sm := new(stateMarshaling) + sm.Memory = memory.NewMemory() if err := json.Unmarshal(data, sm); err != nil { return err } diff --git a/cannon/mipsevm/singlethreaded/state_test.go b/cannon/mipsevm/singlethreaded/state_test.go index bfab4dd2798..be2ce50357d 100644 --- a/cannon/mipsevm/singlethreaded/state_test.go +++ b/cannon/mipsevm/singlethreaded/state_test.go @@ -73,7 +73,7 @@ func TestStateJSONCodec(t *testing.T) { stateJSON, err := state.MarshalJSON() require.NoError(t, err) - newState := new(State) + newState := CreateEmptyState() require.NoError(t, newState.UnmarshalJSON(stateJSON)) require.Equal(t, state.PreimageKey, newState.PreimageKey) diff --git a/op-challenger/game/fault/trace/cannon/provider_test.go b/op-challenger/game/fault/trace/cannon/provider_test.go index 01cd513cb85..d7ec9fd8da7 100644 --- a/op-challenger/game/fault/trace/cannon/provider_test.go +++ b/op-challenger/game/fault/trace/cannon/provider_test.go @@ -52,7 +52,7 @@ func TestGet(t *testing.T) { t.Run("ProofAfterEndOfTrace", func(t *testing.T) { provider, generator := setupWithTestData(t, dataDir, prestate) generator.finalState = &singlethreaded.State{ - Memory: &memory.Memory{}, + Memory: memory.NewMemory(), Step: 10, Exited: true, } @@ -108,7 +108,7 @@ func TestGetStepData(t *testing.T) { dataDir, prestate := setupTestData(t) provider, generator := setupWithTestData(t, dataDir, prestate) generator.finalState = &singlethreaded.State{ - Memory: &memory.Memory{}, + Memory: memory.NewMemory(), Step: 10, Exited: true, } @@ -134,7 +134,7 @@ func TestGetStepData(t *testing.T) { dataDir, prestate := setupTestData(t) provider, generator := setupWithTestData(t, dataDir, prestate) generator.finalState = &singlethreaded.State{ - Memory: &memory.Memory{}, + Memory: memory.NewMemory(), Step: 10, Exited: true, } @@ -160,7 +160,7 @@ func TestGetStepData(t *testing.T) { dataDir, prestate := setupTestData(t) provider, initGenerator := setupWithTestData(t, dataDir, prestate) initGenerator.finalState = &singlethreaded.State{ - Memory: &memory.Memory{}, + Memory: memory.NewMemory(), Step: 10, Exited: true, } @@ -178,7 +178,7 @@ func TestGetStepData(t *testing.T) { provider, generator := setupWithTestData(t, dataDir, prestate) generator.finalState = &singlethreaded.State{ - Memory: &memory.Memory{}, + Memory: memory.NewMemory(), Step: 10, Exited: true, } From f9ee04cebf7663d2b3033eeb4d48fd0cbc2b674f Mon Sep 17 00:00:00 2001 From: leopardracer <136604165+leopardracer@users.noreply.github.com> Date: Mon, 3 Mar 2025 23:44:09 +0100 Subject: [PATCH 047/130] Update README.md (#14590) --- op-node/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/op-node/README.md b/op-node/README.md index b655eb5811e..77ab67ff029 100644 --- a/op-node/README.md +++ b/op-node/README.md @@ -23,7 +23,7 @@ The blocks are processed by an execution layer client, like [op-geth]. ## Quickstart ```bash -make op-node +just op-node # Network selection: # - Join any of the pre-configured networks with the `--network` flag. From aeafa4c8935b89bfa61ffc073913be5d2154ef15 Mon Sep 17 00:00:00 2001 From: zhiqiangxu <652732310@qq.com> Date: Tue, 4 Mar 2025 23:09:28 +0800 Subject: [PATCH 048/130] remove devnetL1 files (#14619) --- .circleci/config.yml | 2 -- .gitignore | 2 +- packages/contracts-bedrock/.gitignore | 3 +-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ebc84ee53f9..1a61bc574bf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -342,8 +342,6 @@ jobs: - "packages/contracts-bedrock/cache" - "packages/contracts-bedrock/artifacts" - "packages/contracts-bedrock/forge-artifacts" - - "packages/contracts-bedrock/deploy-config/devnetL1.json" - - "packages/contracts-bedrock/deployments/devnetL1" - notify-failures-on-develop check-kontrol-build: diff --git a/.gitignore b/.gitignore index f58f7f9c1f6..ea86d7c6834 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,7 @@ cache !op-deployer/pkg/deployer/artifacts -packages/contracts-bedrock/deployments/devnetL1 + packages/contracts-bedrock/deployments/anvil # vim diff --git a/packages/contracts-bedrock/.gitignore b/packages/contracts-bedrock/.gitignore index 06f6d9f22d8..1e2b6f844e2 100644 --- a/packages/contracts-bedrock/.gitignore +++ b/packages/contracts-bedrock/.gitignore @@ -36,8 +36,7 @@ deployments/kontrol-fp.json deployments/kontrol-fp.jsonReversed deployments/1-deploy.json -# Devnet config which changes with each 'make devnet-up' -deploy-config/devnetL1.json + # Getting Started guide deploy config deploy-config/getting-started.json From ffd17869d7adc1329fb3ef4cfbbe74487e4f5df7 Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Tue, 4 Mar 2025 07:17:29 -0800 Subject: [PATCH 049/130] feat: implement EIP-7251, EIP-7002 end-to-end test (#14253) * feat: implement EIP-7251 end-to-end test * Add missing import * Fix rebase issue * Fix lint * Ensure requests hash is empty * Move to action test * Remove untested EIP from comment --- op-e2e/actions/proofs/helpers/env.go | 8 +- op-e2e/actions/proofs/helpers/matrix.go | 3 + .../actions/proofs/isthmus_requests_test.go | 127 ++++++++++++++++++ .../client/l2/engineapi/block_processor.go | 8 ++ 4 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 op-e2e/actions/proofs/isthmus_requests_test.go diff --git a/op-e2e/actions/proofs/helpers/env.go b/op-e2e/actions/proofs/helpers/env.go index c937f7822f2..e347d6b1245 100644 --- a/op-e2e/actions/proofs/helpers/env.go +++ b/op-e2e/actions/proofs/helpers/env.go @@ -73,7 +73,13 @@ func NewL2FaultProofEnv[c any](t helpers.Testing, testCfg *TestCfg[c], tp *e2eut override(dp.DeployConfig) } }) - sd := e2eutils.Setup(t, dp, helpers.DefaultAlloc) + + genesisAlloc := testCfg.Allocs + if genesisAlloc == nil { + genesisAlloc = helpers.DefaultAlloc + } + + sd := e2eutils.Setup(t, dp, genesisAlloc) jwtPath := e2eutils.WriteDefaultJWT(t) diff --git a/op-e2e/actions/proofs/helpers/matrix.go b/op-e2e/actions/proofs/helpers/matrix.go index a20a51933e6..4f161531ce8 100644 --- a/op-e2e/actions/proofs/helpers/matrix.go +++ b/op-e2e/actions/proofs/helpers/matrix.go @@ -3,6 +3,8 @@ package helpers import ( "fmt" "testing" + + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" ) type RunTest[cfg any] func(t *testing.T, testCfg *TestCfg[cfg]) @@ -12,6 +14,7 @@ type TestCfg[cfg any] struct { CheckResult CheckResult InputParams []FixtureInputParam Custom cfg + Allocs *e2eutils.AllocParams } type TestCase[cfg any] struct { diff --git a/op-e2e/actions/proofs/isthmus_requests_test.go b/op-e2e/actions/proofs/isthmus_requests_test.go new file mode 100644 index 00000000000..847685519e4 --- /dev/null +++ b/op-e2e/actions/proofs/isthmus_requests_test.go @@ -0,0 +1,127 @@ +package proofs_test + +import ( + "context" + "math/big" + "testing" + + actionsHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" + "github.com/ethereum-optimism/optimism/op-e2e/actions/proofs/helpers" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" +) + +func TestIsthmusExcludedPredeploys(gt *testing.T) { + // Ensures that if EIP-7251, or EIP-7002 predeploys are deployed manually after the fork, + // Isthmus block processing still works correctly. Also ensures that if requests are sent to these + // contracts, they are not processed and do not show up in the block body or requests hash. + + allocs := *actionsHelpers.DefaultAlloc + allocs.L2Alloc = make(map[common.Address]types.Account) + + // Deploy EIP-7251 and EIP-7002 contracts + allocs.L2Alloc[params.WithdrawalQueueAddress] = types.Account{ // EIP-7002 + Code: params.WithdrawalQueueCode, + Nonce: 1, + Balance: new(big.Int), + } + allocs.L2Alloc[params.ConsolidationQueueAddress] = types.Account{ // EIP-7251 + Code: params.ConsolidationQueueCode, + Nonce: 1, + Balance: new(big.Int), + } + + t := actionsHelpers.NewDefaultTesting(gt) + testCfg := &helpers.TestCfg[interface{}]{ + Hardfork: helpers.Isthmus, + Allocs: &allocs, + } + + tp := helpers.NewTestParams() + env := helpers.NewL2FaultProofEnv(t, testCfg, tp, helpers.NewBatcherCfg()) + + dp := e2eutils.MakeDeployParams(t, actionsHelpers.DefaultRollupTestParams()) + + engine := env.Engine + sequencer := env.Sequencer + + ethCl := engine.EthClient() + signer := types.NewPragueSigner(new(big.Int).SetUint64(dp.DeployConfig.L2ChainID)) + + sequencer.ActL2StartBlock(t) + + ret, err := ethCl.CallContract(context.Background(), ethereum.CallMsg{ + To: ¶ms.WithdrawalQueueAddress, + Data: []byte{}, + }, nil) + + require.NoError(t, err) + fee := new(uint256.Int).SetBytes(ret) + + // Send a transaction to the EIP-7251 contract + txdata := &types.DynamicFeeTx{ + ChainID: new(big.Int).SetUint64(dp.DeployConfig.L2ChainID), + Nonce: 0, + To: ¶ms.WithdrawalQueueAddress, + Gas: 500000, + Data: make([]byte, 56), + Value: fee.ToBig(), + GasFeeCap: new(big.Int).SetUint64(5000000000), + GasTipCap: new(big.Int).SetUint64(2), + } + tx := types.MustSignNewTx(dp.Secrets.Alice, signer, txdata) + + err = ethCl.SendTransaction(t.Ctx(), tx) + require.NoError(gt, err, "failed to send withdrawal request tx") + + _, err = engine.EngineApi.IncludeTx(tx, dp.Addresses.Alice) + require.NoError(gt, err, "failed to include tx") + + ret, err = ethCl.CallContract(context.Background(), ethereum.CallMsg{ + To: ¶ms.ConsolidationQueueAddress, + Data: []byte{}, + }, nil) + + require.NoError(t, err) + fee = new(uint256.Int).SetBytes(ret) + + // Send a transaction to the EIP-7251 contract + txdata = &types.DynamicFeeTx{ + ChainID: new(big.Int).SetUint64(dp.DeployConfig.L2ChainID), + Nonce: 1, + To: ¶ms.ConsolidationQueueAddress, + Gas: 500000, + Data: make([]byte, 96), + Value: fee.ToBig(), + GasFeeCap: new(big.Int).SetUint64(5000000000), + GasTipCap: new(big.Int).SetUint64(2), + } + tx = types.MustSignNewTx(dp.Secrets.Alice, signer, txdata) + + err = ethCl.SendTransaction(t.Ctx(), tx) + require.NoError(gt, err, "failed to send consolidation queue request tx") + + _, err = engine.EngineApi.IncludeTx(tx, dp.Addresses.Alice) + require.NoError(gt, err, "failed to include tx") + + sequencer.ActL2EndBlock(t) + + // ensure requests hash is still empty + latestBlock, err := ethCl.BlockByNumber(t.Ctx(), nil) + require.NoError(t, err, "error fetching latest block") + require.Equal(t, types.EmptyRequestsHash, *latestBlock.RequestsHash()) + + // get receipt + receipt, err := ethCl.TransactionReceipt(context.Background(), tx.Hash()) + require.NoError(t, err) + require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status, "transaction must pass") + + env.RunFaultProofProgram(t, latestBlock.NumberU64()-1, func(t actionsHelpers.Testing, err error) { + require.NoError(t, err, "no error expected running FP program") + }) +} diff --git a/op-program/client/l2/engineapi/block_processor.go b/op-program/client/l2/engineapi/block_processor.go index ae968500650..b88642abce5 100644 --- a/op-program/client/l2/engineapi/block_processor.go +++ b/op-program/client/l2/engineapi/block_processor.go @@ -152,7 +152,15 @@ func (b *BlockProcessor) Assemble() (*types.Block, types.Receipts, error) { body := types.Body{ Transactions: b.transactions, } + if b.dataProvider.Config().IsPrague(b.header.Number, b.header.Time) { + _requests := [][]byte{} + // EIP-6110 - no-op because we just ignore all deposit requests, so no need to parse logs + // EIP-7002 + core.ProcessWithdrawalQueue(&_requests, b.evm) + // EIP-7251 + core.ProcessConsolidationQueue(&_requests, b.evm) + } block, err := b.dataProvider.Engine().FinalizeAndAssemble(b.dataProvider, b.header, b.state, &body, b.receipts) if err != nil { return nil, nil, err From e76409e18ee82b4765ae9d51991ffeae1e032671 Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Tue, 4 Mar 2025 07:20:21 -0800 Subject: [PATCH 050/130] feat: add acceptance test for BLS precompiles (#13934) * feat: add acceptance test for BLS precompiles * Fix get proof block hash * Revert get proof block hash change * Test all BLS precompiles * Add reference to test vectors * Fix test name * Add test before activation and remove extra geth test logic --- op-e2e/opgeth/op_geth.go | 17 ----- op-e2e/opgeth/op_geth_test.go | 130 ++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 17 deletions(-) diff --git a/op-e2e/opgeth/op_geth.go b/op-e2e/opgeth/op_geth.go index 28e162b3bac..e3920372199 100644 --- a/op-e2e/opgeth/op_geth.go +++ b/op-e2e/opgeth/op_geth.go @@ -18,10 +18,8 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-service/predeploys" "github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum-optimism/optimism/op-service/testlog" - "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" @@ -156,21 +154,6 @@ func (d *OpGeth) AddL2Block(ctx context.Context, txs ...*types.Transaction) (*et return nil, errors.New("required transactions were not included") } - // if we are at Isthmus, set the withdrawalsRoot in the execution payload to the storage root of the message passer contract - if d.L2ChainConfig.IsIsthmus(uint64(payload.Timestamp)) { - var getProofResponse *eth.AccountResult - rpcClient := d.l2Engine.RPC - err := rpcClient.CallContext(ctx, &getProofResponse, "eth_getProof", predeploys.L2ToL1MessagePasserAddr, []common.Hash{}, payload.BlockHash.String()) - if err != nil { - return nil, err - } - if getProofResponse == nil { - return nil, ethereum.NotFound - } - storageHash := getProofResponse.StorageHash - payload.WithdrawalsRoot = &storageHash - } - status, err := d.l2Engine.NewPayload(ctx, payload, envelope.ParentBeaconBlockRoot) if err != nil { return nil, fmt.Errorf("new payload: %w", err) diff --git a/op-e2e/opgeth/op_geth_test.go b/op-e2e/opgeth/op_geth_test.go index dcc18440f91..36ea6f457fa 100644 --- a/op-e2e/opgeth/op_geth_test.go +++ b/op-e2e/opgeth/op_geth_test.go @@ -1099,3 +1099,133 @@ func TestFjord(t *testing.T) { }) } } + +func TestIsthmus(t *testing.T) { + tests := []struct { + name string + isthmusTime hexutil.Uint64 + activateIsthmus func(ctx context.Context, t *testing.T, opGeth *OpGeth) + // expectEmpty is true if calling the precompiles should result in an 0x1 success (default for empty contract) + expectEmpty bool + }{ + {name: "BeforeActivation", isthmusTime: 2, activateIsthmus: func(ctx context.Context, t *testing.T, opGeth *OpGeth) {}, expectEmpty: true}, + {name: "ActivateAtGenesis", isthmusTime: 0, activateIsthmus: func(ctx context.Context, t *testing.T, opGeth *OpGeth) {}, expectEmpty: false}, + {name: "ActivateAfterGenesis", isthmusTime: 2, activateIsthmus: func(ctx context.Context, t *testing.T, opGeth *OpGeth) { + // Adding this block advances us to the fork time. + _, err := opGeth.AddL2Block(ctx) + require.NoError(t, err) + }, expectEmpty: false}, + } + + // Taken from https://eips.ethereum.org/assets/eip-2537/test-vectors + precompilesToTest := []struct { + precompileName string + precompileAddr common.Address + failInput []byte + expectedErrorContains string + successInput []byte + expectedResult []byte + }{ + { + precompileName: "G1Add", + precompileAddr: common.BytesToAddress([]byte{0x0b}), + failInput: common.FromHex("0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a2100000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a21"), + successInput: common.FromHex("0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a21"), + expectedResult: common.FromHex("0x000000000000000000000000000000000a40300ce2dec9888b60690e9a41d3004fda4886854573974fab73b046d3147ba5b7a5bde85279ffede1b45b3918d82d0000000000000000000000000000000006d3d887e9f53b9ec4eb6cedf5607226754b07c01ace7834f57f3e7315faefb739e59018e22c492006190fba4a870025"), + expectedErrorContains: "invalid point: not on curve", + }, + { + precompileName: "G2Add", + precompileAddr: common.BytesToAddress([]byte{0x0d}), + failInput: common.FromHex("0x000000000000000000000000000000001c4bb49d2a0ef12b7123acdd7110bd292b5bc659edc54dc21b81de057194c79b2a5803255959bbef8e7f56c8c12168630000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d878451"), + successInput: common.FromHex("0x00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be00000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d878451"), + expectedResult: common.FromHex("0x000000000000000000000000000000000b54a8a7b08bd6827ed9a797de216b8c9057b3a9ca93e2f88e7f04f19accc42da90d883632b9ca4dc38d013f71ede4db00000000000000000000000000000000077eba4eecf0bd764dce8ed5f45040dd8f3b3427cb35230509482c14651713282946306247866dfe39a8e33016fcbe520000000000000000000000000000000014e60a76a29ef85cbd69f251b9f29147b67cfe3ed2823d3f9776b3a0efd2731941d47436dc6d2b58d9e65f8438bad073000000000000000000000000000000001586c3c910d95754fef7a732df78e279c3d37431c6a2b77e67a00c7c130a8fcd4d19f159cbeb997a178108fffffcbd20"), + expectedErrorContains: "invalid fp.Element encoding", + }, + { + precompileName: "G1MSM", + precompileAddr: common.BytesToAddress([]byte{0x0c}), + failInput: common.FromHex("0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb00000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a21000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a210000000000000000000000000000000000000000000000000000000000000002"), + successInput: common.FromHex("0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a210000000000000000000000000000000000000000000000000000000000000002"), + expectedResult: common.FromHex("0x00000000000000000000000000000000148f92dced907361b4782ab542a75281d4b6f71f65c8abf94a5a9082388c64662d30fd6a01ced724feef3e284752038c0000000000000000000000000000000015c3634c3b67bc18e19150e12bfd8a1769306ed010f59be645a0823acb5b38f39e8e0d86e59b6353fdafc59ca971b769"), + expectedErrorContains: "invalid point: not on curve", + }, + { + precompileName: "G2MSM", + precompileAddr: common.BytesToAddress([]byte{0x0e}), + failInput: common.FromHex("0x000000000000000000000000000000001c4bb49d2a0ef12b7123acdd7110bd292b5bc659edc54dc21b81de057194c79b2a5803255959bbef8e7f56c8c12168630000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000002"), + successInput: common.FromHex("0x00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000002"), + expectedResult: common.FromHex("0x00000000000000000000000000000000009cc9ed6635623ba19b340cbc1b0eb05c3a58770623986bb7e041645175b0a38d663d929afb9a949f7524656043bccc000000000000000000000000000000000c0fb19d3f083fd5641d22a861a11979da258003f888c59c33005cb4a2df4df9e5a2868832063ac289dfa3e997f21f8a00000000000000000000000000000000168bf7d87cef37cf1707849e0a6708cb856846f5392d205ae7418dd94d94ef6c8aa5b424af2e99d957567654b9dae1d90000000000000000000000000000000017e0fa3c3b2665d52c26c7d4cea9f35443f4f9007840384163d3aa3c7d4d18b21b65ff4380cf3f3b48e94b5eecb221dd"), + expectedErrorContains: "invalid fp.Element encoding", + }, + { + precompileName: "MapFpToG1", + precompileAddr: common.BytesToAddress([]byte{0x10}), + failInput: common.FromHex("0x000000000000000000000000000000002f6d9c5465982c0421b61e74579709b3b5b91e57bdd4f6015742b4ff301abb7ef895b9cce00c33c7d48f8e5fa4ac09ae"), + successInput: common.FromHex("0x00000000000000000000000000000000147e1ed29f06e4c5079b9d14fc89d2820d32419b990c1c7bb7dbea2a36a045124b31ffbde7c99329c05c559af1c6cc82"), + expectedResult: common.FromHex("0x00000000000000000000000000000000009769f3ab59bfd551d53a5f846b9984c59b97d6842b20a2c565baa167945e3d026a3755b6345df8ec7e6acb6868ae6d000000000000000000000000000000001532c00cf61aa3d0ce3e5aa20c3b531a2abd2c770a790a2613818303c6b830ffc0ecf6c357af3317b9575c567f11cd2c"), + expectedErrorContains: "invalid fp.Element encoding", + }, + { + precompileName: "MapFp2ToG2", + precompileAddr: common.BytesToAddress([]byte{0x11}), + failInput: common.FromHex("0x0000000000000000000000000000000021366f100476ce8d3be6cfc90d59fe13349e388ed12b6dd6dc31ccd267ff000e2c993a063ca66beced06f804d4b8e5af0000000000000000000000000000000002829ce3c021339ccb5caf3e187f6370e1e2a311dec9b75363117063ab2015603ff52c3d3b98f19c2f65575e99e8b78c"), + successInput: common.FromHex("0x0000000000000000000000000000000007355d25caf6e7f2f0cb2812ca0e513bd026ed09dda65b177500fa31714e09ea0ded3a078b526bed3307f804d4b93b040000000000000000000000000000000002829ce3c021339ccb5caf3e187f6370e1e2a311dec9b75363117063ab2015603ff52c3d3b98f19c2f65575e99e8b78c"), + expectedResult: common.FromHex("0x0000000000000000000000000000000000e7f4568a82b4b7dc1f14c6aaa055edf51502319c723c4dc2688c7fe5944c213f510328082396515734b6612c4e7bb700000000000000000000000000000000126b855e9e69b1f691f816e48ac6977664d24d99f8724868a184186469ddfd4617367e94527d4b74fc86413483afb35b000000000000000000000000000000000caead0fd7b6176c01436833c79d305c78be307da5f6af6c133c47311def6ff1e0babf57a0fb5539fce7ee12407b0a42000000000000000000000000000000001498aadcf7ae2b345243e281ae076df6de84455d766ab6fcdaad71fab60abb2e8b980a440043cd305db09d283c895e3d"), + expectedErrorContains: "invalid fp.Element encoding", + }, + { + precompileName: "PairingCheck", + precompileAddr: common.BytesToAddress([]byte{0x0f}), + failInput: common.FromHex("0x000000000000000000000000000000000123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000000000000000000000000000000193fb7cedb32b2c3adc06ec11a96bc0d661869316f5e4a577a9f7c179593987beb4fb2ee424dbb2f5dd891e228b46c4a00000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000d1b3cc2c7027888be51d9ef691d77bcb679afda66c73f17f9ee3837a55024f78c71363275a75d75d86bab79f74782aa0000000000000000000000000000000013fa4d4a0ad8b1ce186ed5061789213d993923066dddaf1040bc3ff59f825c78df74f2d75467e25e0f55f8a00fa030ed"), + successInput: common.FromHex("0x0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb0000000000000000000000000000000008b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80000000000000000000000000000000013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e000000000000000000000000000000000ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801000000000000000000000000000000000606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be"), + expectedResult: common.FromHex("0x0000000000000000000000000000000000000000000000000000000000000001"), + expectedErrorContains: "g1 point is not on correct subgroup", + }, + } + + for _, test := range tests { + test := test + for _, precompileToTest := range precompilesToTest { + precompileToTest := precompileToTest + t.Run(fmt.Sprintf("EIP2537_%s_%s", test.name, precompileToTest.precompileName), func(t *testing.T) { + op_e2e.InitParallel(t) + cfg := e2esys.IsthmusSystemConfig(t, &test.isthmusTime) + + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + opGeth, err := NewOpGeth(t, ctx, &cfg) + require.NoError(t, err) + defer opGeth.Close() + + test.activateIsthmus(ctx, t, opGeth) + + // check response against test cases + response, err := opGeth.L2Client.CallContract(ctx, ethereum.CallMsg{ + To: &precompileToTest.precompileAddr, + Data: precompileToTest.successInput, + }, nil) + + if test.expectEmpty { + require.NoError(t, err) + require.Equal(t, []byte{}, response, "should return proper result") + return + } + + require.NoError(t, err) + + require.Equal(t, precompileToTest.expectedResult, response, "should return proper result") + + // invalid request reverts with an error + _, err = opGeth.L2Client.CallContract(ctx, ethereum.CallMsg{ + To: &precompileToTest.precompileAddr, + Data: precompileToTest.failInput, + }, nil) + + require.Error(t, err) + require.ErrorContains(t, err, precompileToTest.expectedErrorContains, "should return proper error") + }) + } + } +} From 44ec5a294d6c161f1da538b6d3563a3e9b805323 Mon Sep 17 00:00:00 2001 From: protolambda Date: Tue, 4 Mar 2025 17:07:02 +0100 Subject: [PATCH 051/130] op-node: drop stale todo in finality code (#14620) --- op-node/rollup/finality/finalizer.go | 1 - 1 file changed, 1 deletion(-) diff --git a/op-node/rollup/finality/finalizer.go b/op-node/rollup/finality/finalizer.go index 3dcbd1563bb..e50ae1a8752 100644 --- a/op-node/rollup/finality/finalizer.go +++ b/op-node/rollup/finality/finalizer.go @@ -224,7 +224,6 @@ func (fi *Finalizer) tryFinalize() { // Sanity check the finality signal of L1. // Even though the signal is trusted and we do the below check also, // the signal itself has to be canonical to proceed. - // TODO(#10724): This check could be removed if the finality signal is fully trusted, and if tests were more flexible for this case. signalRef, err := fi.l1Fetcher.L1BlockRefByNumber(ctx, fi.finalizedL1.Number) if err != nil { fi.emitter.Emit(rollup.L1TemporaryErrorEvent{Err: fmt.Errorf("failed to check if on finalizing L1 chain, could not fetch block %d: %w", fi.finalizedL1.Number, err)}) From aa132b3ba30b0433cdeff43c1334cbbeb3ecc185 Mon Sep 17 00:00:00 2001 From: protolambda Date: Tue, 4 Mar 2025 17:54:42 +0100 Subject: [PATCH 052/130] op-test-sequencer: structure sub-responsibilities (#14501) * op-test-sequencer: structure sub-responsibilities * op-test-sequencer: update todo comment with issue number --- op-service/endpoint/rpc.go | 59 +++ op-test-sequencer/README.md | 37 ++ op-test-sequencer/config/config.go | 10 + op-test-sequencer/flags/flags.go | 15 +- .../sequencer/backend/backend.go | 113 ++++- .../sequencer/backend/backend_test.go | 89 +++- op-test-sequencer/sequencer/backend/mock.go | 23 +- .../work/builders/noopbuilder/builder.go | 48 ++ .../work/builders/noopbuilder/builder_test.go | 32 ++ .../work/builders/noopbuilder/config.go | 18 + .../work/builders/noopbuilder/config_test.go | 32 ++ .../backend/work/builders/noopbuilder/job.go | 35 ++ .../committers/noopcommitter/committer.go | 38 ++ .../noopcommitter/committer_test.go | 26 ++ .../work/committers/noopcommitter/config.go | 18 + .../committers/noopcommitter/config_test.go | 32 ++ .../sequencer/backend/work/config/builder.go | 22 + .../backend/work/config/committer.go | 22 + .../backend/work/config/publisher.go | 22 + .../backend/work/config/sequencer.go | 26 ++ .../sequencer/backend/work/config/signer.go | 22 + .../sequencer/backend/work/config/static.go | 93 ++++ .../backend/work/config/static_test.go | 74 ++++ .../backend/work/config/testdata/config.yaml | 42 ++ .../sequencer/backend/work/config/yaml.go | 33 ++ .../backend/work/config/yaml_test.go | 39 ++ .../sequencer/backend/work/ensemble.go | 182 ++++++++ .../sequencer/backend/work/ensemble_test.go | 77 ++++ .../sequencer/backend/work/iface.go | 177 ++++++++ .../work/publishers/nooppublisher/config.go | 18 + .../publishers/nooppublisher/config_test.go | 32 ++ .../publishers/nooppublisher/publisher.go | 38 ++ .../nooppublisher/publisher_test.go | 26 ++ .../sequencer/backend/work/registry.go | 41 ++ .../sequencer/backend/work/registry_test.go | 46 ++ .../backend/work/sequencers/fullseq/config.go | 67 +++ .../work/sequencers/fullseq/config_test.go | 57 +++ .../work/sequencers/fullseq/sequencer.go | 259 +++++++++++ .../work/sequencers/fullseq/sequencer_test.go | 419 ++++++++++++++++++ .../backend/work/sequencers/noopseq/config.go | 18 + .../work/sequencers/noopseq/config_test.go | 32 ++ .../work/sequencers/noopseq/sequencer.go | 78 ++++ .../work/sequencers/noopseq/sequencer_test.go | 40 ++ .../backend/work/signers/noopsigner/config.go | 18 + .../work/signers/noopsigner/config_test.go | 32 ++ .../work/signers/noopsigner/publisher.go | 38 ++ .../work/signers/noopsigner/publisher_test.go | 26 ++ .../frontend/{frontend.go => admin.go} | 4 +- .../sequencer/frontend/admin_test.go | 24 + .../sequencer/frontend/builder.go | 55 +++ .../sequencer/frontend/builder_test.go | 182 ++++++++ .../sequencer/frontend/sequencer.go | 82 ++++ .../sequencer/frontend/sequencer_test.go | 165 +++++++ op-test-sequencer/sequencer/frontend/util.go | 25 ++ op-test-sequencer/sequencer/seqtypes/types.go | 154 +++++++ .../sequencer/seqtypes/types_test.go | 66 +++ op-test-sequencer/sequencer/service.go | 94 ++-- op-test-sequencer/sequencer/service_test.go | 3 +- 58 files changed, 3535 insertions(+), 60 deletions(-) create mode 100644 op-test-sequencer/sequencer/backend/work/builders/noopbuilder/builder.go create mode 100644 op-test-sequencer/sequencer/backend/work/builders/noopbuilder/builder_test.go create mode 100644 op-test-sequencer/sequencer/backend/work/builders/noopbuilder/config.go create mode 100644 op-test-sequencer/sequencer/backend/work/builders/noopbuilder/config_test.go create mode 100644 op-test-sequencer/sequencer/backend/work/builders/noopbuilder/job.go create mode 100644 op-test-sequencer/sequencer/backend/work/committers/noopcommitter/committer.go create mode 100644 op-test-sequencer/sequencer/backend/work/committers/noopcommitter/committer_test.go create mode 100644 op-test-sequencer/sequencer/backend/work/committers/noopcommitter/config.go create mode 100644 op-test-sequencer/sequencer/backend/work/committers/noopcommitter/config_test.go create mode 100644 op-test-sequencer/sequencer/backend/work/config/builder.go create mode 100644 op-test-sequencer/sequencer/backend/work/config/committer.go create mode 100644 op-test-sequencer/sequencer/backend/work/config/publisher.go create mode 100644 op-test-sequencer/sequencer/backend/work/config/sequencer.go create mode 100644 op-test-sequencer/sequencer/backend/work/config/signer.go create mode 100644 op-test-sequencer/sequencer/backend/work/config/static.go create mode 100644 op-test-sequencer/sequencer/backend/work/config/static_test.go create mode 100644 op-test-sequencer/sequencer/backend/work/config/testdata/config.yaml create mode 100644 op-test-sequencer/sequencer/backend/work/config/yaml.go create mode 100644 op-test-sequencer/sequencer/backend/work/config/yaml_test.go create mode 100644 op-test-sequencer/sequencer/backend/work/ensemble.go create mode 100644 op-test-sequencer/sequencer/backend/work/ensemble_test.go create mode 100644 op-test-sequencer/sequencer/backend/work/iface.go create mode 100644 op-test-sequencer/sequencer/backend/work/publishers/nooppublisher/config.go create mode 100644 op-test-sequencer/sequencer/backend/work/publishers/nooppublisher/config_test.go create mode 100644 op-test-sequencer/sequencer/backend/work/publishers/nooppublisher/publisher.go create mode 100644 op-test-sequencer/sequencer/backend/work/publishers/nooppublisher/publisher_test.go create mode 100644 op-test-sequencer/sequencer/backend/work/registry.go create mode 100644 op-test-sequencer/sequencer/backend/work/registry_test.go create mode 100644 op-test-sequencer/sequencer/backend/work/sequencers/fullseq/config.go create mode 100644 op-test-sequencer/sequencer/backend/work/sequencers/fullseq/config_test.go create mode 100644 op-test-sequencer/sequencer/backend/work/sequencers/fullseq/sequencer.go create mode 100644 op-test-sequencer/sequencer/backend/work/sequencers/fullseq/sequencer_test.go create mode 100644 op-test-sequencer/sequencer/backend/work/sequencers/noopseq/config.go create mode 100644 op-test-sequencer/sequencer/backend/work/sequencers/noopseq/config_test.go create mode 100644 op-test-sequencer/sequencer/backend/work/sequencers/noopseq/sequencer.go create mode 100644 op-test-sequencer/sequencer/backend/work/sequencers/noopseq/sequencer_test.go create mode 100644 op-test-sequencer/sequencer/backend/work/signers/noopsigner/config.go create mode 100644 op-test-sequencer/sequencer/backend/work/signers/noopsigner/config_test.go create mode 100644 op-test-sequencer/sequencer/backend/work/signers/noopsigner/publisher.go create mode 100644 op-test-sequencer/sequencer/backend/work/signers/noopsigner/publisher_test.go rename op-test-sequencer/sequencer/frontend/{frontend.go => admin.go} (82%) create mode 100644 op-test-sequencer/sequencer/frontend/admin_test.go create mode 100644 op-test-sequencer/sequencer/frontend/builder.go create mode 100644 op-test-sequencer/sequencer/frontend/builder_test.go create mode 100644 op-test-sequencer/sequencer/frontend/sequencer.go create mode 100644 op-test-sequencer/sequencer/frontend/sequencer_test.go create mode 100644 op-test-sequencer/sequencer/frontend/util.go create mode 100644 op-test-sequencer/sequencer/seqtypes/types.go create mode 100644 op-test-sequencer/sequencer/seqtypes/types_test.go diff --git a/op-service/endpoint/rpc.go b/op-service/endpoint/rpc.go index 79b2a477227..69cbd887f3f 100644 --- a/op-service/endpoint/rpc.go +++ b/op-service/endpoint/rpc.go @@ -1,9 +1,59 @@ package endpoint import ( + "errors" + "fmt" + "github.com/ethereum/go-ethereum/rpc" ) +type MustRPC struct { + Value RPC +} + +func (u *MustRPC) UnmarshalText(data []byte) error { + if u == nil { + return fmt.Errorf("cannot unmarshal %q into nil MustRPC", string(data)) + } + v := URL(data) + if v == "" { + return errors.New("empty RPC URL") + } + u.Value = v + return nil +} + +func (u MustRPC) MarshalText() ([]byte, error) { + if u.Value == nil { + return nil, errors.New("missing RPC") + } + out := u.Value.RPC() + if out == "" { + return nil, errors.New("missing RPC") + } + return []byte(out), nil +} + +type OptionalRPC struct { + Value RPC +} + +func (u *OptionalRPC) UnmarshalText(data []byte) error { + if u == nil { + return fmt.Errorf("cannot unmarshal %q into nil OptionalRPC", string(data)) + } + u.Value = URL(data) + return nil +} + +func (u OptionalRPC) MarshalText() ([]byte, error) { + if u.Value == nil { + return []byte{}, nil + } + out := u.Value.RPC() + return []byte(out), nil +} + // RPC is an interface for an endpoint to provide flexibility. // By default the RPC just returns an RPC endpoint string. // But the RPC can implement one or more extension interfaces, @@ -13,6 +63,15 @@ type RPC interface { RPC() string } +// URL is a generic RPC endpoint URL +type URL string + +var _ RPC = URL("") + +func (u URL) RPC() string { + return string(u) +} + // WsRPC is an RPC extension interface, // to explicitly provide the Websocket RPC option. type WsRPC interface { diff --git a/op-test-sequencer/README.md b/op-test-sequencer/README.md index bc59cc161da..0d961cf7364 100644 --- a/op-test-sequencer/README.md +++ b/op-test-sequencer/README.md @@ -30,3 +30,40 @@ This service is in active development. See [design doc](https://github.com/ethereum-optimism/design-docs/blob/main/protocol/test-sequencing.md) for design considerations. + +### RPC + +On the configured RPC address/port multiple HTTP routes are served, each serving RPCs. +Every RPC is authenticated with a JWT-secret. +For every route, both HTTP and Websocket RPC connections are supported. + +#### Main RPC + +The main RPC is served on the root path `/` of the configured RPC host/port. + +##### `admin` + +Work in progress. + +##### `build` + +Types: +- `BuilderID`: string, identifies a builder by its configured name +- `BuildJobID`: string, identifies a build job +- `BuildOpts`: `{parent: hash, l1Origin: hash,optional}` (work in progress, will be extended) +- `Block`: block, a JSON object, as defined by the builder + +Methods: +- `build_open(id: BuilderID, opts: BuildOpts) -> BuildJobID` +- `build_cancel(jobID: BuildJobID)` +- `build_seal(jobID: BuildJobID) -> Block` + +#### Sequencer RPC routes + +`/sequencers/{sequencerID}` serves an RPC (Both HTTP and Websocket) + +##### `sequencer` + +Actively changing. Methods to run through each part of the sequencing flow. +See [`sequencer/frontend/sequencer.go`](./sequencer/frontend/sequencer.go). + diff --git a/op-test-sequencer/config/config.go b/op-test-sequencer/config/config.go index 469c182bd7c..207259f269c 100644 --- a/op-test-sequencer/config/config.go +++ b/op-test-sequencer/config/config.go @@ -7,6 +7,12 @@ import ( opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" "github.com/ethereum-optimism/optimism/op-service/oppprof" oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/config" +) + +const ( + DefaultConfigYaml = "config.yaml" ) type Config struct { @@ -19,6 +25,8 @@ type Config struct { JWTSecretPath string + Ensemble work.Loader + MockRun bool } @@ -37,5 +45,7 @@ func DefaultCLIConfig() *Config { MetricsConfig: opmetrics.DefaultCLIConfig(), PprofConfig: oppprof.DefaultCLIConfig(), RPC: oprpc.DefaultCLIConfig(), + Ensemble: &config.YamlLoader{Path: DefaultConfigYaml}, + MockRun: false, } } diff --git a/op-test-sequencer/flags/flags.go b/op-test-sequencer/flags/flags.go index 55abb82e6a9..c652e31af45 100644 --- a/op-test-sequencer/flags/flags.go +++ b/op-test-sequencer/flags/flags.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum-optimism/optimism/op-service/oppprof" oprpc "github.com/ethereum-optimism/optimism/op-service/rpc" "github.com/ethereum-optimism/optimism/op-test-sequencer/config" + wconfig "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/config" ) const EnvVarPrefix = "OP_TEST_SEQUENCER" @@ -20,7 +21,13 @@ func prefixEnvVars(name string) []string { } var ( - RPCJWTSecret = &cli.StringFlag{ + BuildersConfigFlag = &cli.StringFlag{ + Name: "builders.config", + Usage: "Config file to builder(s) to load", + EnvVars: prefixEnvVars("BUILDERS_CONFIG"), + Value: config.DefaultConfigYaml, + } + RPCJWTSecretFlag = &cli.StringFlag{ Name: "rpc.jwt-secret", Usage: "Path to JWT secret key for sequencer admin RPC.", EnvVars: prefixEnvVars("RPC_JWT_SECRET"), @@ -37,7 +44,8 @@ var ( var requiredFlags = []cli.Flag{} var optionalFlags = []cli.Flag{ - RPCJWTSecret, + BuildersConfigFlag, + RPCJWTSecretFlag, MockRunFlag, } @@ -70,7 +78,8 @@ func ConfigFromCLI(ctx *cli.Context, version string) *config.Config { MetricsConfig: opmetrics.ReadCLIConfig(ctx), PprofConfig: oppprof.ReadCLIConfig(ctx), RPC: oprpc.ReadCLIConfig(ctx), + JWTSecretPath: ctx.Path(RPCJWTSecretFlag.Name), + Ensemble: &wconfig.YamlLoader{Path: ctx.String(BuildersConfigFlag.Name)}, MockRun: ctx.Bool(MockRunFlag.Name), - JWTSecretPath: ctx.Path(RPCJWTSecret.Name), } } diff --git a/op-test-sequencer/sequencer/backend/backend.go b/op-test-sequencer/sequencer/backend/backend.go index 05828bf279a..4093aa34c48 100644 --- a/op-test-sequencer/sequencer/backend/backend.go +++ b/op-test-sequencer/sequencer/backend/backend.go @@ -2,46 +2,121 @@ package backend import ( "context" - "errors" - "sync/atomic" + "fmt" + "sync" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum-optimism/optimism/op-test-sequencer/metrics" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/frontend" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" ) -var ( - errAlreadyStarted = errors.New("already started") - errAlreadyStopped = errors.New("already stopped") -) +type APIRouter interface { + AddRPC(route string) error + AddAPIToRPC(route string, api rpc.API) error +} type Backend struct { - started atomic.Bool - logger log.Logger - m metrics.Metricer + started bool + active bool + activeMu sync.RWMutex + + logger log.Logger + m metrics.Metricer + + ensemble *work.Ensemble + jobs work.Jobs + + router APIRouter +} + +var _ frontend.BuildBackend = (*Backend)(nil) +var _ frontend.AdminBackend = (*Backend)(nil) + +func NewBackend(log log.Logger, m metrics.Metricer, ensemble *work.Ensemble, jobs work.Jobs, router APIRouter) *Backend { + b := &Backend{ + logger: log, + m: m, + ensemble: ensemble, + jobs: jobs, + router: router, + } + return b +} + +// setupSequencerFrontend attaches the sequencer with the given ID as a new route, serving a sequencer RPC. +// errors if the route was invalid (when it already exists, or the ID is not a valid HTTP route). +func (ba *Backend) setupSequencerFrontend(id seqtypes.SequencerID) error { + route := "/sequencers/" + id.String() + if err := ba.router.AddRPC(route); err != nil { + return fmt.Errorf("invalid sequencer RPC route: %w", err) + } + f := &frontend.SequencerFrontend{Sequencer: ba.ensemble.Sequencer(id)} + if err := ba.router.AddAPIToRPC(route, rpc.API{ + Namespace: "sequencer", + Service: f, + }); err != nil { + return fmt.Errorf("invalid sequencer RPC frontend: %w", err) + } + ba.logger.Info("Added sequencer RPC route", "route", route) + return nil } -func NewBackend(log log.Logger, m metrics.Metricer) *Backend { - return &Backend{ - logger: log, - m: m, +func (ba *Backend) CreateJob(ctx context.Context, id seqtypes.BuilderID, opts *seqtypes.BuildOpts) (work.BuildJob, error) { + ba.activeMu.RLock() + defer ba.activeMu.RUnlock() + if !ba.active { + return nil, seqtypes.ErrBackendInactive + } + bu := ba.ensemble.Builder(id) + if bu == nil { + return nil, seqtypes.ErrUnknownBuilder } + job, err := bu.NewJob(ctx, opts) + if err != nil { + return nil, err + } + return job, nil +} + +// GetJob returns nil if the job isn't known. +func (ba *Backend) GetJob(id seqtypes.BuildJobID) work.BuildJob { + return ba.jobs.GetJob(id) } func (ba *Backend) Start(ctx context.Context) error { - if !ba.started.CompareAndSwap(false, true) { - return errAlreadyStarted + ba.activeMu.Lock() + defer ba.activeMu.Unlock() + if ba.started { // can only start once, no restarts after stopping + return seqtypes.ErrBackendAlreadyStarted } + ba.active = true + ba.started = true ba.logger.Info("Starting sequencer backend") + // Each sequencer gets its own API route, for a distinct RPC to interact with just that sequencer. + for _, id := range ba.ensemble.Sequencers() { + if err := ba.setupSequencerFrontend(id); err != nil { + return fmt.Errorf("failed to setup sequencer %q RPC: %w", id, err) + } + } return nil } func (ba *Backend) Stop(ctx context.Context) error { - if !ba.started.CompareAndSwap(true, false) { - return errAlreadyStopped + ba.activeMu.Lock() + defer ba.activeMu.Unlock() + if !ba.active { + return seqtypes.ErrBackendInactive } - ba.logger.Info("Stopping sequencer backend") - return nil + ba.active = false + ba.logger.Info("Stopping backend") + result := ba.ensemble.Close() + // builders should have closed the build jobs gracefully where needed. We can clear the jobs now. + ba.jobs.Clear() + return result } func (ba *Backend) Hello(ctx context.Context, name string) (string, error) { diff --git a/op-test-sequencer/sequencer/backend/backend_test.go b/op-test-sequencer/sequencer/backend/backend_test.go index 2b4c79fc2dd..07d894d66c5 100644 --- a/op-test-sequencer/sequencer/backend/backend_test.go +++ b/op-test-sequencer/sequencer/backend/backend_test.go @@ -2,26 +2,107 @@ package backend import ( "context" + "fmt" "testing" "github.com/stretchr/testify/require" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-test-sequencer/metrics" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/builders/noopbuilder" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/committers/noopcommitter" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/publishers/nooppublisher" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/sequencers/fullseq" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/signers/noopsigner" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" ) +type noopRouter struct { + log log.Logger +} + +func (n *noopRouter) AddRPC(route string) error { + n.log.Debug("Adding RPC route", "route", route) + return nil +} + +func (n noopRouter) AddAPIToRPC(route string, api rpc.API) error { + n.log.Debug("Adding API on route", + "route", route, + "namespace", api.Namespace, + "handler", fmt.Sprintf("%T", api.Service)) + return nil +} + +var _ APIRouter = (*noopRouter)(nil) + func TestBackend(t *testing.T) { - logger := testlog.Logger(t, log.LevelWarn) - b := NewBackend(logger, metrics.NoopMetrics{}) + logger := testlog.Logger(t, log.LevelDebug) + m := &metrics.NoopMetrics{} + builderID := seqtypes.BuilderID("test-builder") + signerID := seqtypes.SignerID("test-signer") + committerID := seqtypes.CommitterID("test-committer") + publisherID := seqtypes.PublisherID("test-publisher") + sequencerID := seqtypes.SequencerID("test-sequencer") + ensemble := &work.Ensemble{} + jobs := work.NewJobRegistry() + require.NoError(t, ensemble.AddBuilder(noopbuilder.NewBuilder(builderID, jobs))) + require.NoError(t, ensemble.AddSigner(noopsigner.NewSigner(signerID, logger))) + require.NoError(t, ensemble.AddCommitter(noopcommitter.NewCommitter(committerID, logger))) + require.NoError(t, ensemble.AddPublisher(nooppublisher.NewPublisher(publisherID, logger))) + seqCfg := &fullseq.Config{ + ChainID: eth.ChainIDFromUInt64(123), + Builder: builderID, + Signer: signerID, + Committer: committerID, + Publisher: publisherID, + SequencerConfDepth: 0, + SequencerEnabled: false, + SequencerStopped: false, + SequencerMaxSafeLag: 0, + } + sequencer, err := seqCfg.Start(context.Background(), sequencerID, &work.ServiceOpts{ + StartOpts: &work.StartOpts{ + Log: logger, + Metrics: m, + Jobs: jobs, + }, + Services: ensemble, + }) + require.NoError(t, err) + require.NoError(t, ensemble.AddSequencer(sequencer)) + + router := &noopRouter{log: logger} + b := NewBackend(logger, m, ensemble, jobs, router) require.NoError(t, b.Start(context.Background())) - require.ErrorIs(t, b.Start(context.Background()), errAlreadyStarted) + require.ErrorIs(t, b.Start(context.Background()), seqtypes.ErrBackendAlreadyStarted) result, err := b.Hello(context.Background(), "alice") require.NoError(t, err) require.Contains(t, result, "alice") + _, err = b.CreateJob(context.Background(), "not there", nil) + require.ErrorIs(t, err, seqtypes.ErrUnknownBuilder) + + job, err := b.CreateJob(context.Background(), builderID, nil) + require.NoError(t, err) + + _, err = job.Seal(context.Background()) + require.ErrorIs(t, err, noopbuilder.ErrNoBuild) + + require.Equal(t, job, b.GetJob(job.ID())) + require.NoError(t, b.Stop(context.Background())) - require.ErrorIs(t, b.Stop(context.Background()), errAlreadyStopped) + require.ErrorIs(t, b.Stop(context.Background()), seqtypes.ErrBackendInactive) + require.ErrorIs(t, b.Start(context.Background()), seqtypes.ErrBackendAlreadyStarted, "no restarts") + + _, err = b.CreateJob(context.Background(), builderID, nil) + require.ErrorIs(t, err, seqtypes.ErrBackendInactive) + + require.Zero(t, b.jobs.Len()) } diff --git a/op-test-sequencer/sequencer/backend/mock.go b/op-test-sequencer/sequencer/backend/mock.go index 9b0818f66a6..a3fd97034e6 100644 --- a/op-test-sequencer/sequencer/backend/mock.go +++ b/op-test-sequencer/sequencer/backend/mock.go @@ -1,13 +1,34 @@ package backend -import "context" +import ( + "context" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/builders/noopbuilder" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/frontend" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) type MockBackend struct{} +var _ frontend.BuildBackend = (*MockBackend)(nil) +var _ frontend.AdminBackend = (*MockBackend)(nil) + func NewMockBackend() *MockBackend { return &MockBackend{} } +func (ba *MockBackend) CreateJob(ctx context.Context, id seqtypes.BuilderID, opts *seqtypes.BuildOpts) (work.BuildJob, error) { + return nil, noopbuilder.ErrNoBuild +} + +func (ba *MockBackend) GetJob(id seqtypes.BuildJobID) work.BuildJob { + return nil +} + +func (ba *MockBackend) UnregisterJob(id seqtypes.BuildJobID) { +} + func (ba *MockBackend) Start(ctx context.Context) error { return nil } diff --git a/op-test-sequencer/sequencer/backend/work/builders/noopbuilder/builder.go b/op-test-sequencer/sequencer/backend/work/builders/noopbuilder/builder.go new file mode 100644 index 00000000000..e9d56e6cd4e --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/builders/noopbuilder/builder.go @@ -0,0 +1,48 @@ +package noopbuilder + +import ( + "context" + "errors" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +var ErrNoBuild = errors.New("no building supported") + +type Builder struct { + id seqtypes.BuilderID + registry work.Jobs +} + +var _ work.Builder = (*Builder)(nil) + +func NewBuilder(id seqtypes.BuilderID, registry work.Jobs) *Builder { + return &Builder{id: id, registry: registry} +} + +func (n *Builder) NewJob(ctx context.Context, opts *seqtypes.BuildOpts) (work.BuildJob, error) { + id := seqtypes.RandomJobID() + job := &Job{ + id: id, + unregister: func() { + n.registry.UnregisterJob(id) + }, + } + if err := n.registry.RegisterJob(job); err != nil { + return nil, err + } + return job, nil +} + +func (n *Builder) Close() error { + return nil +} + +func (n *Builder) String() string { + return "noop-builder-" + n.id.String() +} + +func (n *Builder) ID() seqtypes.BuilderID { + return n.id +} diff --git a/op-test-sequencer/sequencer/backend/work/builders/noopbuilder/builder_test.go b/op-test-sequencer/sequencer/backend/work/builders/noopbuilder/builder_test.go new file mode 100644 index 00000000000..ae66e360b1e --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/builders/noopbuilder/builder_test.go @@ -0,0 +1,32 @@ +package noopbuilder + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +func TestNoopBuilder(t *testing.T) { + id := seqtypes.BuilderID("foobar") + x := NewBuilder(id, work.NewJobRegistry()) + + job, err := x.NewJob(context.Background(), nil) + require.NoError(t, err) + require.Contains(t, job.String(), "noop-job-") + require.NotEmpty(t, job.ID()) + require.NotNil(t, x.registry.GetJob(job.ID())) + + _, err = job.Seal(context.Background()) + require.ErrorIs(t, err, ErrNoBuild) + + require.NoError(t, job.Cancel(context.Background())) + + require.NoError(t, x.Close()) + require.Equal(t, "noop-builder-foobar", x.String()) + job.Close() + require.Nil(t, x.registry.GetJob(job.ID())) +} diff --git a/op-test-sequencer/sequencer/backend/work/builders/noopbuilder/config.go b/op-test-sequencer/sequencer/backend/work/builders/noopbuilder/config.go new file mode 100644 index 00000000000..bbd918caf2e --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/builders/noopbuilder/config.go @@ -0,0 +1,18 @@ +package noopbuilder + +import ( + "context" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type Config struct { +} + +func (c *Config) Start(ctx context.Context, id seqtypes.BuilderID, opts *work.ServiceOpts) (work.Builder, error) { + return &Builder{ + id: id, + registry: opts.Jobs, + }, nil +} diff --git a/op-test-sequencer/sequencer/backend/work/builders/noopbuilder/config_test.go b/op-test-sequencer/sequencer/backend/work/builders/noopbuilder/config_test.go new file mode 100644 index 00000000000..a6dbd6485ab --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/builders/noopbuilder/config_test.go @@ -0,0 +1,32 @@ +package noopbuilder + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-test-sequencer/metrics" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +func TestConfig(t *testing.T) { + logger := testlog.Logger(t, log.LevelInfo) + cfg := &Config{} + id := seqtypes.BuilderID("test") + opts := &work.ServiceOpts{ + StartOpts: &work.StartOpts{ + Log: logger, + Metrics: &metrics.NoopMetrics{}, + Jobs: work.NewJobRegistry(), + }, + Services: &work.Ensemble{}, + } + builder, err := cfg.Start(context.Background(), id, opts) + require.NoError(t, err) + require.Equal(t, id, builder.ID()) +} diff --git a/op-test-sequencer/sequencer/backend/work/builders/noopbuilder/job.go b/op-test-sequencer/sequencer/backend/work/builders/noopbuilder/job.go new file mode 100644 index 00000000000..bde7d4ea49c --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/builders/noopbuilder/job.go @@ -0,0 +1,35 @@ +package noopbuilder + +import ( + "context" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type Job struct { + id seqtypes.BuildJobID + unregister func() +} + +var _ work.BuildJob = (*Job)(nil) + +func (job *Job) ID() seqtypes.BuildJobID { + return job.id +} + +func (job *Job) Cancel(ctx context.Context) error { + return nil +} + +func (job *Job) Seal(ctx context.Context) (work.Block, error) { + return nil, ErrNoBuild +} + +func (job *Job) String() string { + return "noop-job-" + job.id.String() +} + +func (job *Job) Close() { + job.unregister() +} diff --git a/op-test-sequencer/sequencer/backend/work/committers/noopcommitter/committer.go b/op-test-sequencer/sequencer/backend/work/committers/noopcommitter/committer.go new file mode 100644 index 00000000000..df9056f5ec1 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/committers/noopcommitter/committer.go @@ -0,0 +1,38 @@ +package noopcommitter + +import ( + "context" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type Committer struct { + id seqtypes.CommitterID + log log.Logger +} + +var _ work.Committer = (*Committer)(nil) + +func NewCommitter(id seqtypes.CommitterID, log log.Logger) *Committer { + return &Committer{id: id, log: log} +} + +func (n *Committer) Close() error { + return nil +} + +func (n *Committer) String() string { + return "noop-committer-" + n.id.String() +} + +func (n *Committer) ID() seqtypes.CommitterID { + return n.id +} + +func (n *Committer) Commit(ctx context.Context, block work.SignedBlock) error { + n.log.Info("No-op commit", "block", block) + return nil +} diff --git a/op-test-sequencer/sequencer/backend/work/committers/noopcommitter/committer_test.go b/op-test-sequencer/sequencer/backend/work/committers/noopcommitter/committer_test.go new file mode 100644 index 00000000000..9bea576ae2f --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/committers/noopcommitter/committer_test.go @@ -0,0 +1,26 @@ +package noopcommitter + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +func TestNoopCommitter(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + id := seqtypes.CommitterID("foo") + x := NewCommitter(id, logger) + + err := x.Commit(context.Background(), nil) + require.NoError(t, err) + + require.NoError(t, x.Close()) + require.Equal(t, "noop-committer-foo", x.String()) + require.Equal(t, id, x.ID()) +} diff --git a/op-test-sequencer/sequencer/backend/work/committers/noopcommitter/config.go b/op-test-sequencer/sequencer/backend/work/committers/noopcommitter/config.go new file mode 100644 index 00000000000..be0ba7c99cb --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/committers/noopcommitter/config.go @@ -0,0 +1,18 @@ +package noopcommitter + +import ( + "context" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type Config struct { +} + +func (c *Config) Start(ctx context.Context, id seqtypes.CommitterID, opts *work.ServiceOpts) (work.Committer, error) { + return &Committer{ + id: id, + log: opts.Log, + }, nil +} diff --git a/op-test-sequencer/sequencer/backend/work/committers/noopcommitter/config_test.go b/op-test-sequencer/sequencer/backend/work/committers/noopcommitter/config_test.go new file mode 100644 index 00000000000..2d0b1f9d394 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/committers/noopcommitter/config_test.go @@ -0,0 +1,32 @@ +package noopcommitter + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-test-sequencer/metrics" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +func TestConfig(t *testing.T) { + logger := testlog.Logger(t, log.LevelInfo) + cfg := &Config{} + id := seqtypes.CommitterID("test") + ensemble := &work.Ensemble{} + opts := &work.ServiceOpts{ + StartOpts: &work.StartOpts{ + Log: logger, + Metrics: &metrics.NoopMetrics{}, + }, + Services: ensemble, + } + committer, err := cfg.Start(context.Background(), id, opts) + require.NoError(t, err) + require.Equal(t, id, committer.ID()) +} diff --git a/op-test-sequencer/sequencer/backend/work/config/builder.go b/op-test-sequencer/sequencer/backend/work/config/builder.go new file mode 100644 index 00000000000..25970887c81 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/config/builder.go @@ -0,0 +1,22 @@ +package config + +import ( + "context" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/builders/noopbuilder" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type BuilderEntry struct { + Noop *noopbuilder.Config `yaml:"noop,omitempty"` +} + +func (b *BuilderEntry) Start(ctx context.Context, id seqtypes.BuilderID, opts *work.ServiceOpts) (work.Builder, error) { + switch { + case b.Noop != nil: + return b.Noop.Start(ctx, id, opts) + default: + return nil, seqtypes.ErrUnknownKind + } +} diff --git a/op-test-sequencer/sequencer/backend/work/config/committer.go b/op-test-sequencer/sequencer/backend/work/config/committer.go new file mode 100644 index 00000000000..5307be2e43e --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/config/committer.go @@ -0,0 +1,22 @@ +package config + +import ( + "context" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/committers/noopcommitter" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type CommitterEntry struct { + Noop *noopcommitter.Config `yaml:"noop,omitempty"` +} + +func (b *CommitterEntry) Start(ctx context.Context, id seqtypes.CommitterID, opts *work.ServiceOpts) (work.Committer, error) { + switch { + case b.Noop != nil: + return b.Noop.Start(ctx, id, opts) + default: + return nil, seqtypes.ErrUnknownKind + } +} diff --git a/op-test-sequencer/sequencer/backend/work/config/publisher.go b/op-test-sequencer/sequencer/backend/work/config/publisher.go new file mode 100644 index 00000000000..d6fe59055b7 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/config/publisher.go @@ -0,0 +1,22 @@ +package config + +import ( + "context" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/publishers/nooppublisher" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type PublisherEntry struct { + Noop *nooppublisher.Config `yaml:"noop,omitempty"` +} + +func (b *PublisherEntry) Start(ctx context.Context, id seqtypes.PublisherID, opts *work.ServiceOpts) (work.Publisher, error) { + switch { + case b.Noop != nil: + return b.Noop.Start(ctx, id, opts) + default: + return nil, seqtypes.ErrUnknownKind + } +} diff --git a/op-test-sequencer/sequencer/backend/work/config/sequencer.go b/op-test-sequencer/sequencer/backend/work/config/sequencer.go new file mode 100644 index 00000000000..4e81d05c05b --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/config/sequencer.go @@ -0,0 +1,26 @@ +package config + +import ( + "context" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/sequencers/fullseq" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/sequencers/noopseq" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type SequencerEntry struct { + Full *fullseq.Config `yaml:"full,omitempty"` + Noop *noopseq.Config `yaml:"noop,omitempty"` +} + +func (b *SequencerEntry) Start(ctx context.Context, id seqtypes.SequencerID, opts *work.ServiceOpts) (work.Sequencer, error) { + switch { + case b.Full != nil: + return b.Full.Start(ctx, id, opts) + case b.Noop != nil: + return b.Noop.Start(ctx, id, opts) + default: + return nil, seqtypes.ErrUnknownKind + } +} diff --git a/op-test-sequencer/sequencer/backend/work/config/signer.go b/op-test-sequencer/sequencer/backend/work/config/signer.go new file mode 100644 index 00000000000..5f85f635f71 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/config/signer.go @@ -0,0 +1,22 @@ +package config + +import ( + "context" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/signers/noopsigner" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type SignerEntry struct { + Noop *noopsigner.Config `yaml:"noop,omitempty"` +} + +func (b *SignerEntry) Start(ctx context.Context, id seqtypes.SignerID, opts *work.ServiceOpts) (work.Signer, error) { + switch { + case b.Noop != nil: + return b.Noop.Start(ctx, id, opts) + default: + return nil, seqtypes.ErrUnknownKind + } +} diff --git a/op-test-sequencer/sequencer/backend/work/config/static.go b/op-test-sequencer/sequencer/backend/work/config/static.go new file mode 100644 index 00000000000..00760599ad3 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/config/static.go @@ -0,0 +1,93 @@ +package config + +import ( + "context" + "errors" + "fmt" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type Ensemble struct { + // Endpoints is a no-op list of endpoints + // to prevent repetitive endpoints in a yaml. + // This list can all be declared at the top, and items can be referenced with yaml refs. + Endpoints []string `yaml:"endpoints"` + + Builders map[seqtypes.BuilderID]*BuilderEntry `yaml:"builders"` + Signers map[seqtypes.SignerID]*SignerEntry `yaml:"signers"` + Committers map[seqtypes.CommitterID]*CommitterEntry `yaml:"committers"` + Publishers map[seqtypes.PublisherID]*PublisherEntry `yaml:"publishers"` + Sequencers map[seqtypes.SequencerID]*SequencerEntry `yaml:"sequencers"` +} + +var _ work.Loader = (*Ensemble)(nil) + +// Load is a short-cut to skip the config-loading phase, and use an existing config instead. +// This can be used by tests to plug in a config directly, +// without having to store it on disk somewhere. +func (c *Ensemble) Load(ctx context.Context) (work.Starter, error) { + return c, nil +} + +var _ work.Starter = (*Ensemble)(nil) + +// Start sets up the configured group of builders. +func (c *Ensemble) Start(ctx context.Context, opts *work.StartOpts) (ensemble *work.Ensemble, errResult error) { + ensemble = new(work.Ensemble) + defer func() { + if errResult == nil { + return + } + // If there is any error, close the builders we may have opened already + errResult = errors.Join(errResult, ensemble.Close()) + }() + serviceOpts := &work.ServiceOpts{ + StartOpts: opts, + Services: ensemble, + } + if err := startAndAdd(ctx, c.Builders, serviceOpts, ensemble.AddBuilder); err != nil { + return nil, fmt.Errorf("failed to start builders: %w", err) + } + if err := startAndAdd(ctx, c.Signers, serviceOpts, ensemble.AddSigner); err != nil { + return nil, fmt.Errorf("failed to start signers: %w", err) + } + if err := startAndAdd(ctx, c.Committers, serviceOpts, ensemble.AddCommitter); err != nil { + return nil, fmt.Errorf("failed to start committers: %w", err) + } + if err := startAndAdd(ctx, c.Publishers, serviceOpts, ensemble.AddPublisher); err != nil { + return nil, fmt.Errorf("failed to start publishers: %w", err) + } + if err := startAndAdd(ctx, c.Sequencers, serviceOpts, ensemble.AddSequencer); err != nil { + return nil, fmt.Errorf("failed to start sequencers: %w", err) + } + return ensemble, nil +} + +type valueIface[K comparable, R any] interface { + Start(ctx context.Context, id K, opts *work.ServiceOpts) (result R, err error) +} + +type keyIface interface { + comparable + String() string +} + +// startAndAdd is a util function to start entities from a configuration map, and add them to the ensemble. +func startAndAdd[K keyIface, R any, E valueIface[K, R]]( + ctx context.Context, + entries map[K]E, + opts *work.ServiceOpts, + addFn func(v R) error) error { + for id, conf := range entries { + v, err := conf.Start(ctx, id, opts) + if err != nil { + return fmt.Errorf("failed to start %q: %w", id, err) + } + if err := addFn(v); err != nil { + return fmt.Errorf("failed to add %q: %w", id, err) + } + } + return nil +} diff --git a/op-test-sequencer/sequencer/backend/work/config/static_test.go b/op-test-sequencer/sequencer/backend/work/config/static_test.go new file mode 100644 index 00000000000..1158d1c71ca --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/config/static_test.go @@ -0,0 +1,74 @@ +package config + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-test-sequencer/metrics" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/builders/noopbuilder" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/committers/noopcommitter" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/publishers/nooppublisher" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/sequencers/noopseq" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/signers/noopsigner" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +func TestEnsemble_Start(t *testing.T) { + t.Run("empty", func(t *testing.T) { + v := new(Ensemble) + out, err := v.Start(context.Background(), nil) + require.NoError(t, err) + require.Empty(t, out.Builders()) + require.Empty(t, out.Signers()) + require.Empty(t, out.Committers()) + require.Empty(t, out.Publishers()) + require.Empty(t, out.Sequencers()) + }) + t.Run("noops", func(t *testing.T) { + v := &Ensemble{ + Endpoints: nil, + Builders: map[seqtypes.BuilderID]*BuilderEntry{ + "noop-builder": { + Noop: &noopbuilder.Config{}, + }, + }, + Signers: map[seqtypes.SignerID]*SignerEntry{ + "noop-signer": { + Noop: &noopsigner.Config{}, + }, + }, + Committers: map[seqtypes.CommitterID]*CommitterEntry{ + "noop-committer": { + Noop: &noopcommitter.Config{}, + }, + }, + Publishers: map[seqtypes.PublisherID]*PublisherEntry{ + "noop-publisher": { + Noop: &nooppublisher.Config{}, + }, + }, + Sequencers: map[seqtypes.SequencerID]*SequencerEntry{ + "noop-sequencer": { + Noop: &noopseq.Config{}, + }, + }, + } + logger := testlog.Logger(t, log.LevelError) + out, err := v.Start(context.Background(), &work.StartOpts{ + Log: logger, + Metrics: &metrics.NoopMetrics{}, + }) + require.NoError(t, err) + require.Len(t, out.Builders(), 1) + require.Len(t, out.Signers(), 1) + require.Len(t, out.Committers(), 1) + require.Len(t, out.Publishers(), 1) + require.Len(t, out.Sequencers(), 1) + }) +} diff --git a/op-test-sequencer/sequencer/backend/work/config/testdata/config.yaml b/op-test-sequencer/sequencer/backend/work/config/testdata/config.yaml new file mode 100644 index 00000000000..7b4542fe51f --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/config/testdata/config.yaml @@ -0,0 +1,42 @@ +endpoints: [] + +builders: + builder-a: + noop: + builder-b: + noop: + +signers: + local-key-a: + local-key: + path: "./my-key.txt" + signer-b: + noop: + +committers: + commit-a: + noop: + commit-b: + noop: + +publishers: + pub-a: + noop: + pub-b: + noop: + +sequencers: + sequencer-a: + full: + builder: builder-a + signer: local-key-a + committer: commit-a + publisher: pub-a + sequencer-b: + full: + builder: builder-b + signer: signer-b + committer: commit-b + publisher: pub-b + sequencer-c: + noop: diff --git a/op-test-sequencer/sequencer/backend/work/config/yaml.go b/op-test-sequencer/sequencer/backend/work/config/yaml.go new file mode 100644 index 00000000000..e2af01f0dc7 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/config/yaml.go @@ -0,0 +1,33 @@ +package config + +import ( + "bytes" + "context" + "fmt" + "os" + + "gopkg.in/yaml.v3" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" +) + +// YamlLoader is a Loader that loads a builders configuration from a YAML file path. +type YamlLoader struct { + Path string +} + +var _ work.Loader = (*YamlLoader)(nil) + +func (l *YamlLoader) Load(ctx context.Context) (work.Starter, error) { + data, err := os.ReadFile(l.Path) + if err != nil { + return nil, fmt.Errorf("failed to read config file: %w", err) + } + var out Ensemble + dec := yaml.NewDecoder(bytes.NewReader(data)) + dec.KnownFields(true) // ensure all the fields are known. Config correctness is critical. + if err := dec.Decode(&out); err != nil { + return nil, fmt.Errorf("failed to parse config YAML: %w", err) + } + return &out, nil +} diff --git a/op-test-sequencer/sequencer/backend/work/config/yaml_test.go b/op-test-sequencer/sequencer/backend/work/config/yaml_test.go new file mode 100644 index 00000000000..2541125fdf3 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/config/yaml_test.go @@ -0,0 +1,39 @@ +package config + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestYamlLoader_Load(t *testing.T) { + x := &YamlLoader{Path: filepath.Join(".", "testdata", "config.yaml")} + result, err := x.Load(context.Background()) + require.NoError(t, err) + static := result.(*Ensemble) + require.NotEmpty(t, static.Builders) + require.NotEmpty(t, static.Signers) + require.NotEmpty(t, static.Committers) + require.NotEmpty(t, static.Publishers) + require.NotEmpty(t, static.Sequencers) +} + +func TestYamlLoader_NotFound(t *testing.T) { + x := &YamlLoader{Path: filepath.Join(t.TempDir(), "missing.yaml")} + _, err := x.Load(context.Background()) + require.ErrorContains(t, err, "failed to read config") +} + +func TestYamlLoader_Invalid(t *testing.T) { + p := filepath.Join(t.TempDir(), "invalid.yaml") + // Strictly speaking a valid yaml map, but missing all the data. + // The config decoder is strict + require.NoError(t, os.WriteFile(p, []byte("foobar: invalid"), 0755)) + + x := &YamlLoader{Path: p} + _, err := x.Load(context.Background()) + require.ErrorContains(t, err, "field foobar not found") +} diff --git a/op-test-sequencer/sequencer/backend/work/ensemble.go b/op-test-sequencer/sequencer/backend/work/ensemble.go new file mode 100644 index 00000000000..75bca81fec6 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/ensemble.go @@ -0,0 +1,182 @@ +package work + +import ( + "context" + "errors" + "fmt" + + "github.com/ethereum-optimism/optimism/op-service/locks" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +var ErrAlreadyExists = errors.New("entry with same ID already exists") + +// Ensemble is a group of active services to sequence blocks with. +// Services can only be added to an ensemble, +// the ensemble is meant to run for the full lifetime of the Go service. +type Ensemble struct { + // builders build unsigned block alternatives + builders locks.RWMap[seqtypes.BuilderID, Builder] + + // signers sign blocks + signers locks.RWMap[seqtypes.SignerID, Signer] + + // committers commit to blocks for persistence + committers locks.RWMap[seqtypes.CommitterID, Committer] + + // publishers publish blocks + publishers locks.RWMap[seqtypes.PublisherID, Publisher] + + // sequencers perform all block responsibilities + sequencers locks.RWMap[seqtypes.SequencerID, Sequencer] +} + +var _ Loader = (*Ensemble)(nil) + +// Load is a short-cut to skip the config phase, and use an existing group of builders. +func (bs *Ensemble) Load(ctx context.Context) (Starter, error) { + return bs, nil +} + +var _ Starter = (*Ensemble)(nil) + +// Start is a short-cut to skip the start phase, and use an existing group of builders. +func (bs *Ensemble) Start(ctx context.Context, opts *StartOpts) (*Ensemble, error) { + return bs, nil +} + +func (bs *Ensemble) Close() error { + // We close all services in reverse order: user-facing first most, then underlying services. + var result error + bs.sequencers.Range(func(id seqtypes.SequencerID, v Sequencer) bool { + if err := v.Close(); err != nil { + result = errors.Join(result, fmt.Errorf("failed to close sequencer %q: %w", id, err)) + } + return true + }) + bs.publishers.Range(func(id seqtypes.PublisherID, v Publisher) bool { + if err := v.Close(); err != nil { + result = errors.Join(result, fmt.Errorf("failed to close publisher %q: %w", id, err)) + } + return true + }) + bs.committers.Range(func(id seqtypes.CommitterID, v Committer) bool { + if err := v.Close(); err != nil { + result = errors.Join(result, fmt.Errorf("failed to close committer %q: %w", id, err)) + } + return true + }) + bs.signers.Range(func(id seqtypes.SignerID, v Signer) bool { + if err := v.Close(); err != nil { + result = errors.Join(result, fmt.Errorf("failed to close signer %q: %w", id, err)) + } + return true + }) + bs.builders.Range(func(id seqtypes.BuilderID, v Builder) bool { + if err := v.Close(); err != nil { + result = errors.Join(result, fmt.Errorf("failed to close builder %q: %w", id, err)) + } + return true + }) + return result +} + +type Collection interface { + Builder(id seqtypes.BuilderID) Builder + Signer(id seqtypes.SignerID) Signer + Committer(id seqtypes.CommitterID) Committer + Publisher(id seqtypes.PublisherID) Publisher + Sequencer(id seqtypes.SequencerID) Sequencer +} + +var _ Collection = (*Ensemble)(nil) + +// Builder gets a builder. Nil is returned if the ID is unknown. +func (bs *Ensemble) Builder(id seqtypes.BuilderID) Builder { + s, _ := bs.builders.Get(id) + return s +} + +// Signer gets a signer. Nil is returned if the ID is unknown. +func (bs *Ensemble) Signer(id seqtypes.SignerID) Signer { + s, _ := bs.signers.Get(id) + return s +} + +// Committer gets a committer. Nil is returned if the ID is unknown. +func (bs *Ensemble) Committer(id seqtypes.CommitterID) Committer { + s, _ := bs.committers.Get(id) + return s +} + +// Publisher gets a publisher. Nil is returned if the ID is unknown. +func (bs *Ensemble) Publisher(id seqtypes.PublisherID) Publisher { + s, _ := bs.publishers.Get(id) + return s +} + +// Sequencer gets a sequencer. Nil is returned if the ID is unknown. +func (bs *Ensemble) Sequencer(id seqtypes.SequencerID) Sequencer { + s, _ := bs.sequencers.Get(id) + return s +} + +// AddBuilder adds a builder. Errors if the builder already exists. +func (bs *Ensemble) AddBuilder(v Builder) error { + if !bs.builders.SetIfMissing(v.ID(), v) { + return ErrAlreadyExists + } + return nil +} + +// AddSigner adds a signer. Errors if the signer already exists. +func (bs *Ensemble) AddSigner(v Signer) error { + if !bs.signers.SetIfMissing(v.ID(), v) { + return ErrAlreadyExists + } + return nil +} + +// AddCommitter adds a committer. Errors if the committer already exists. +func (bs *Ensemble) AddCommitter(v Committer) error { + if !bs.committers.SetIfMissing(v.ID(), v) { + return ErrAlreadyExists + } + return nil +} + +// AddPublisher adds a publisher. Errors if the publisher already exists. +func (bs *Ensemble) AddPublisher(v Publisher) error { + if !bs.publishers.SetIfMissing(v.ID(), v) { + return ErrAlreadyExists + } + return nil +} + +// AddSequencer adds a sequencer. Errors if the sequencer already exists. +func (bs *Ensemble) AddSequencer(v Sequencer) error { + if !bs.sequencers.SetIfMissing(v.ID(), v) { + return ErrAlreadyExists + } + return nil +} + +func (bs *Ensemble) Builders() []seqtypes.BuilderID { + return bs.builders.Keys() +} + +func (bs *Ensemble) Signers() []seqtypes.SignerID { + return bs.signers.Keys() +} + +func (bs *Ensemble) Committers() []seqtypes.CommitterID { + return bs.committers.Keys() +} + +func (bs *Ensemble) Publishers() []seqtypes.PublisherID { + return bs.publishers.Keys() +} + +func (bs *Ensemble) Sequencers() []seqtypes.SequencerID { + return bs.sequencers.Keys() +} diff --git a/op-test-sequencer/sequencer/backend/work/ensemble_test.go b/op-test-sequencer/sequencer/backend/work/ensemble_test.go new file mode 100644 index 00000000000..d6e56d10ff4 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/ensemble_test.go @@ -0,0 +1,77 @@ +package work_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-test-sequencer/metrics" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/builders/noopbuilder" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/committers/noopcommitter" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/publishers/nooppublisher" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/sequencers/noopseq" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/signers/noopsigner" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +func TestEnsemble(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + builderID := seqtypes.BuilderID("test-builder") + signerID := seqtypes.SignerID("test-signer") + committerID := seqtypes.CommitterID("test-committer") + publisherID := seqtypes.PublisherID("test-publisher") + sequencerID := seqtypes.SequencerID("test-sequencer") + ensemble := &work.Ensemble{} + jobs := work.NewJobRegistry() + require.NoError(t, ensemble.AddBuilder(noopbuilder.NewBuilder(builderID, jobs))) + require.ErrorIs(t, ensemble.AddBuilder(noopbuilder.NewBuilder(builderID, jobs)), work.ErrAlreadyExists) + require.NoError(t, ensemble.AddSigner(noopsigner.NewSigner(signerID, logger))) + require.ErrorIs(t, ensemble.AddSigner(noopsigner.NewSigner(signerID, logger)), work.ErrAlreadyExists) + require.NoError(t, ensemble.AddCommitter(noopcommitter.NewCommitter(committerID, logger))) + require.ErrorIs(t, ensemble.AddCommitter(noopcommitter.NewCommitter(committerID, logger)), work.ErrAlreadyExists) + require.NoError(t, ensemble.AddPublisher(nooppublisher.NewPublisher(publisherID, logger))) + require.ErrorIs(t, ensemble.AddPublisher(nooppublisher.NewPublisher(publisherID, logger)), work.ErrAlreadyExists) + require.NoError(t, ensemble.AddSequencer(noopseq.NewSequencer(sequencerID, logger))) + require.ErrorIs(t, ensemble.AddSequencer(noopseq.NewSequencer(sequencerID, logger)), work.ErrAlreadyExists) + + require.NotNil(t, ensemble.Builder(builderID)) + require.NotNil(t, ensemble.Signer(signerID)) + require.NotNil(t, ensemble.Committer(committerID)) + require.NotNil(t, ensemble.Publisher(publisherID)) + require.NotNil(t, ensemble.Sequencer(sequencerID)) + + builderID2 := seqtypes.BuilderID("test-builder2") + signerID2 := seqtypes.SignerID("test-signer2") + committerID2 := seqtypes.CommitterID("test-committer2") + publisherID2 := seqtypes.PublisherID("test-publisher2") + sequencerID2 := seqtypes.SequencerID("test-sequencer2") + require.NoError(t, ensemble.AddBuilder(noopbuilder.NewBuilder(builderID2, jobs))) + require.NoError(t, ensemble.AddSigner(noopsigner.NewSigner(signerID2, logger))) + require.NoError(t, ensemble.AddCommitter(noopcommitter.NewCommitter(committerID2, logger))) + require.NoError(t, ensemble.AddPublisher(nooppublisher.NewPublisher(publisherID2, logger))) + require.NoError(t, ensemble.AddSequencer(noopseq.NewSequencer(sequencerID2, logger))) + + require.Len(t, ensemble.Builders(), 2) + require.Len(t, ensemble.Signers(), 2) + require.Len(t, ensemble.Committers(), 2) + require.Len(t, ensemble.Publishers(), 2) + require.Len(t, ensemble.Sequencers(), 2) + + starter, err := ensemble.Load(context.Background()) + require.NoError(t, err) + require.Equal(t, starter, ensemble, "can load in-place, to skip pre-config phase") + + self, err := ensemble.Start(context.Background(), &work.StartOpts{ + Log: logger, + Metrics: metrics.NoopMetrics{}, + }) + require.NoError(t, err) + require.Equal(t, self, ensemble, "can start in-place, to skip config phase") + + require.NoError(t, ensemble.Close()) +} diff --git a/op-test-sequencer/sequencer/backend/work/iface.go b/op-test-sequencer/sequencer/backend/work/iface.go new file mode 100644 index 00000000000..b74e86c343c --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/iface.go @@ -0,0 +1,177 @@ +package work + +import ( + "context" + "io" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-test-sequencer/metrics" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +// Builder provides access to block-building work. +// Different implementations are available, e.g. for local or remote block-building. +type Builder interface { + NewJob(ctx context.Context, opts *seqtypes.BuildOpts) (BuildJob, error) + String() string + ID() seqtypes.BuilderID + io.Closer +} + +type Block interface { + ID() eth.BlockID + String() string +} + +type SignedBlock interface { + Block + VerifySignature() error +} + +// BuildJob provides access to the building work of a single protocol block. +// This may include extra access, such as inclusion of individual txs or block-building steps. +type BuildJob interface { + ID() seqtypes.BuildJobID + Cancel(ctx context.Context) error + Seal(ctx context.Context) (Block, error) + String() string + Close() // cleans up and unregisters the job +} + +// Jobs tracks block-building jobs by ID, so the jobs can be inspected and updated. +type Jobs interface { + // RegisterJob registers the given block-building job. + // It may return an error if there already exists a job with the same ID. + RegisterJob(job BuildJob) error + // GetJob returns nil if the job isn't known. + GetJob(id seqtypes.BuildJobID) BuildJob + // UnregisterJob removes the block-building job from the tracker. + UnregisterJob(id seqtypes.BuildJobID) + // Clear unregisters all jobs + Clear() + // Len returns the number of registered jobs + Len() int +} + +// Signer signs a block to be published +type Signer interface { + String() string + ID() seqtypes.SignerID + io.Closer + Sign(ctx context.Context, block Block) (SignedBlock, error) +} + +// Committer commits to a (signed) block to become canonical. +// This work is critical: if a block cannot be committed, +// the block is not safe to continue to work with, as it can be replaced by another block. +// E.g.: +// - commit a block to be persisted in the local node. +// - commit a block to an op-conductor service. +type Committer interface { + String() string + ID() seqtypes.CommitterID + io.Closer + Commit(ctx context.Context, block SignedBlock) error +} + +// Publisher publishes a (signed) block to external actors. +// Publishing may fail. +// E.g. publish the block to node(s) for propagation via P2P. +type Publisher interface { + String() string + ID() seqtypes.PublisherID + io.Closer + Publish(ctx context.Context, block SignedBlock) error +} + +// Sequencer utilizes Builder, Committer, Signer, Publisher to +// perform all the responsibilities to extend the chain. +// A Sequencer may internally pipeline work, +// but does not expose parallel work like a builder does. +type Sequencer interface { + String() string + ID() seqtypes.SequencerID + + // Close the sequencer. After closing successfully the sequencer is no longer usable. + Close() error + + // Open starts a next sequencing slot + Open(ctx context.Context) error + + // BuildJob identifies the current block-building work. + // This work may be interacted with through the block-building API. + // This may returns nil if there is no active job. + BuildJob() BuildJob + + // Seal seals the current ongoing block-building job. + // Returns an error if there was no block-building job open. + Seal(ctx context.Context) error + + // Prebuilt inserts a pre-built block, skipping block-building. + Prebuilt(ctx context.Context, block Block) error + + // Sign the previously built block. + // Returns seqtypes.ErrAlreadySigned if already signed. + // Returns an error if the block could not be signed. + Sign(ctx context.Context) error + + // Commit the previously signed block, this ensures we can persist it before relying on it as canonical block. + // Returns seqtypes.ErrAlreadyCommitted if already committed. + // Returns an error if the block failed to commit. + Commit(ctx context.Context) error + + // Publish the previously committed block. + // Publishing is not respected by next steps. + // For harder guarantees, use Commit to ensure the payload is successfully shared before the next phase. + // Re-publishing is allowed. + // Returns an error if none was previously successfully committed. + // Returns an error if publishing fails. + Publish(ctx context.Context) error + + // Next continues to the next sequencing slot. + // If the current slot is unfinished, then it will finish the current slot. + // If the current slot is fresh, it will be built. + // An error is returned if any step fails. It is safe to re-attempt with Next if so. + Next(ctx context.Context) error + + // Start starts automatic sequencing, on top of the given chain head. + // An error is returned if the head of the chain does not match the provided head, + // as safety measure to prevent reorgs during sequencer rotations. + // An seqtypes.ErrSequencerAlreadyActive error is returned if the sequencer has already been started. + Start(ctx context.Context, head common.Hash) error + + // Stop stops automatic sequencing, and returns the block-hash of the last sequenced block. + // An seqtypes.ErrSequencerInactive error is returned if the sequencer has already been stopped. + Stop(ctx context.Context) (last common.Hash, err error) + + // Active returns true if the automatic sequencing (see Start and Stop) is actively running. + Active() bool + + // TODO: later, to fit old sequencer functionality fully + //OverrideLeader(ctx context.Context) error + //ConductorEnabled(ctx context.Context) bool +} + +// Loader loads a configuration, ready to start builders with. +type Loader interface { + Load(ctx context.Context) (Starter, error) +} + +type ServiceOpts struct { + *StartOpts + Services Collection +} + +type StartOpts struct { + Log log.Logger + Metrics metrics.Metricer + Jobs Jobs +} + +// Starter starts an ensemble from some form of setup. +type Starter interface { + Start(ctx context.Context, opts *StartOpts) (*Ensemble, error) +} diff --git a/op-test-sequencer/sequencer/backend/work/publishers/nooppublisher/config.go b/op-test-sequencer/sequencer/backend/work/publishers/nooppublisher/config.go new file mode 100644 index 00000000000..82a4493800e --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/publishers/nooppublisher/config.go @@ -0,0 +1,18 @@ +package nooppublisher + +import ( + "context" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type Config struct { +} + +func (c *Config) Start(ctx context.Context, id seqtypes.PublisherID, opts *work.ServiceOpts) (work.Publisher, error) { + return &Publisher{ + id: id, + log: opts.Log, + }, nil +} diff --git a/op-test-sequencer/sequencer/backend/work/publishers/nooppublisher/config_test.go b/op-test-sequencer/sequencer/backend/work/publishers/nooppublisher/config_test.go new file mode 100644 index 00000000000..6ee31eed746 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/publishers/nooppublisher/config_test.go @@ -0,0 +1,32 @@ +package nooppublisher + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-test-sequencer/metrics" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +func TestConfig(t *testing.T) { + logger := testlog.Logger(t, log.LevelInfo) + cfg := &Config{} + id := seqtypes.PublisherID("test") + ensemble := &work.Ensemble{} + opts := &work.ServiceOpts{ + StartOpts: &work.StartOpts{ + Log: logger, + Metrics: &metrics.NoopMetrics{}, + }, + Services: ensemble, + } + publisher, err := cfg.Start(context.Background(), id, opts) + require.NoError(t, err) + require.Equal(t, id, publisher.ID()) +} diff --git a/op-test-sequencer/sequencer/backend/work/publishers/nooppublisher/publisher.go b/op-test-sequencer/sequencer/backend/work/publishers/nooppublisher/publisher.go new file mode 100644 index 00000000000..d29abed2c1f --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/publishers/nooppublisher/publisher.go @@ -0,0 +1,38 @@ +package nooppublisher + +import ( + "context" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type Publisher struct { + id seqtypes.PublisherID + log log.Logger +} + +var _ work.Publisher = (*Publisher)(nil) + +func NewPublisher(id seqtypes.PublisherID, log log.Logger) *Publisher { + return &Publisher{id: id, log: log} +} + +func (n *Publisher) Close() error { + return nil +} + +func (n *Publisher) String() string { + return "noop-publisher-" + n.id.String() +} + +func (n *Publisher) ID() seqtypes.PublisherID { + return n.id +} + +func (n *Publisher) Publish(ctx context.Context, block work.SignedBlock) error { + n.log.Info("No-op publish", "block", block) + return nil +} diff --git a/op-test-sequencer/sequencer/backend/work/publishers/nooppublisher/publisher_test.go b/op-test-sequencer/sequencer/backend/work/publishers/nooppublisher/publisher_test.go new file mode 100644 index 00000000000..65f367d13ee --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/publishers/nooppublisher/publisher_test.go @@ -0,0 +1,26 @@ +package nooppublisher + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +func TestNoopPublisher(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + id := seqtypes.PublisherID("foo") + x := NewPublisher(id, logger) + + err := x.Publish(context.Background(), nil) + require.NoError(t, err) + + require.NoError(t, x.Close()) + require.Equal(t, "noop-publisher-foo", x.String()) + require.Equal(t, id, x.ID()) +} diff --git a/op-test-sequencer/sequencer/backend/work/registry.go b/op-test-sequencer/sequencer/backend/work/registry.go new file mode 100644 index 00000000000..5cda197cf31 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/registry.go @@ -0,0 +1,41 @@ +package work + +import ( + "github.com/ethereum-optimism/optimism/op-service/locks" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type JobRegistry struct { + jobs locks.RWMap[seqtypes.BuildJobID, BuildJob] +} + +var _ Jobs = (*JobRegistry)(nil) + +func NewJobRegistry() *JobRegistry { + return &JobRegistry{} +} + +func (ba *JobRegistry) RegisterJob(job BuildJob) error { + if !ba.jobs.SetIfMissing(job.ID(), job) { + return seqtypes.ErrConflictingJob + } + return nil +} + +// GetJob returns nil if the job isn't known. +func (ba *JobRegistry) GetJob(id seqtypes.BuildJobID) BuildJob { + job, _ := ba.jobs.Get(id) + return job +} + +func (ba *JobRegistry) UnregisterJob(id seqtypes.BuildJobID) { + ba.jobs.Delete(id) +} + +func (ba *JobRegistry) Clear() { + ba.jobs.Clear() +} + +func (ba *JobRegistry) Len() int { + return ba.jobs.Len() +} diff --git a/op-test-sequencer/sequencer/backend/work/registry_test.go b/op-test-sequencer/sequencer/backend/work/registry_test.go new file mode 100644 index 00000000000..dfe1a261604 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/registry_test.go @@ -0,0 +1,46 @@ +package work + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type mockJob struct { + id seqtypes.BuildJobID + BuildJob +} + +func (m *mockJob) ID() seqtypes.BuildJobID { + return m.id +} + +var _ BuildJob = (*mockJob)(nil) + +func TestRegistry(t *testing.T) { + reg := NewJobRegistry() + require.Equal(t, 0, reg.Len()) + reg.Clear() + + jobA := &mockJob{id: "A"} + require.NoError(t, reg.RegisterJob(jobA)) + jobA2 := &mockJob{id: "A"} + require.ErrorIs(t, reg.RegisterJob(jobA2), seqtypes.ErrConflictingJob) + require.Equal(t, 1, reg.Len()) + + reg.UnregisterJob(jobA.ID()) + require.Equal(t, 0, reg.Len()) + + job1 := &mockJob{id: "1"} + job2 := &mockJob{id: "2"} + job3 := &mockJob{id: "3"} + require.NoError(t, reg.RegisterJob(job1)) + require.NoError(t, reg.RegisterJob(job2)) + require.NoError(t, reg.RegisterJob(job3)) + require.Equal(t, job2, reg.GetJob("2")) + + reg.Clear() + require.Equal(t, 0, reg.Len()) +} diff --git a/op-test-sequencer/sequencer/backend/work/sequencers/fullseq/config.go b/op-test-sequencer/sequencer/backend/work/sequencers/fullseq/config.go new file mode 100644 index 00000000000..e05528f02bf --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/sequencers/fullseq/config.go @@ -0,0 +1,67 @@ +package fullseq + +import ( + "context" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type Config struct { + ChainID eth.ChainID `yaml:"chainID"` + + Builder seqtypes.BuilderID `yaml:"builder"` + Signer seqtypes.SignerID `yaml:"signer,omitempty"` + Committer seqtypes.CommitterID `yaml:"committer,omitempty"` + Publisher seqtypes.PublisherID `yaml:"publisher,omitempty"` + + // SequencerConfDepth is the distance to keep from the L1 head as origin when sequencing new L2 blocks. + // If this distance is too large, the sequencer may: + // - not adopt a L1 origin within the allowed time (rollup.Config.MaxSequencerDrift) + // - not adopt a L1 origin that can be included on L1 within the allowed range (rollup.Config.SeqWindowSize) + // and thus fail to produce a block with anything more than deposits. + SequencerConfDepth uint64 `json:"sequencer_conf_depth"` + + // SequencerEnabled is true when the sequencer is operational. + SequencerEnabled bool `json:"sequencer_enabled"` + + // SequencerStopped is false when the sequencer should not be auto-sequencing at startup. + SequencerStopped bool `json:"sequencer_stopped"` + + // SequencerMaxSafeLag is the maximum number of L2 blocks for restricting the distance between L2 safe and unsafe. + // Disabled if 0. + SequencerMaxSafeLag uint64 `json:"sequencer_max_safe_lag"` +} + +func (c *Config) Start(ctx context.Context, id seqtypes.SequencerID, opts *work.ServiceOpts) (work.Sequencer, error) { + + builder := opts.Services.Builder(c.Builder) + signer := opts.Services.Signer(c.Signer) + committer := opts.Services.Committer(c.Committer) + publisher := opts.Services.Publisher(c.Publisher) + + // TODO(#14129) load persisted sequencer state (add config var for peristence path + use op-node persistence code) + + seq := &Sequencer{ + id: id, + + chainID: c.ChainID, + + log: opts.Log.New("chain", c.ChainID), + m: opts.Metrics, + + builder: builder, + signer: signer, + committer: committer, + publisher: publisher, + } + + // TODO(#14129) check persisted state, to determine if we should really start or stop + //if c.SequencerEnabled && !c.SequencerStopped { + // if err := seq.forceStart(); err != nil { + // return nil, fmt.Errorf("failed to start sequencer at startup phase: %w", err) + // } + //} + return seq, nil +} diff --git a/op-test-sequencer/sequencer/backend/work/sequencers/fullseq/config_test.go b/op-test-sequencer/sequencer/backend/work/sequencers/fullseq/config_test.go new file mode 100644 index 00000000000..2bcb6a07d94 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/sequencers/fullseq/config_test.go @@ -0,0 +1,57 @@ +package fullseq + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-test-sequencer/metrics" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/builders/noopbuilder" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/committers/noopcommitter" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/publishers/nooppublisher" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work/signers/noopsigner" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +func TestConfig(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + builderID := seqtypes.BuilderID("test-builder") + signerID := seqtypes.SignerID("test-signer") + committerID := seqtypes.CommitterID("test-committer") + publisherID := seqtypes.PublisherID("test-publisher") + sequencerID := seqtypes.SequencerID("test-sequencer") + ensemble := &work.Ensemble{} + jobs := work.NewJobRegistry() + require.NoError(t, ensemble.AddBuilder(noopbuilder.NewBuilder(builderID, jobs))) + require.NoError(t, ensemble.AddSigner(noopsigner.NewSigner(signerID, logger))) + require.NoError(t, ensemble.AddCommitter(noopcommitter.NewCommitter(committerID, logger))) + require.NoError(t, ensemble.AddPublisher(nooppublisher.NewPublisher(publisherID, logger))) + cfg := &Config{ + ChainID: eth.ChainIDFromUInt64(1), + Builder: builderID, + Signer: signerID, + Committer: committerID, + Publisher: publisherID, + SequencerConfDepth: 2, + SequencerEnabled: true, + SequencerStopped: true, + SequencerMaxSafeLag: 10, + } + opts := &work.ServiceOpts{ + StartOpts: &work.StartOpts{ + Log: logger, + Metrics: &metrics.NoopMetrics{}, + Jobs: jobs, + }, + Services: ensemble, + } + seq, err := cfg.Start(context.Background(), sequencerID, opts) + require.NoError(t, err) + require.NoError(t, ensemble.AddSequencer(seq)) +} diff --git a/op-test-sequencer/sequencer/backend/work/sequencers/fullseq/sequencer.go b/op-test-sequencer/sequencer/backend/work/sequencers/fullseq/sequencer.go new file mode 100644 index 00000000000..fc8688791bd --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/sequencers/fullseq/sequencer.go @@ -0,0 +1,259 @@ +package fullseq + +import ( + "context" + "errors" + "fmt" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-test-sequencer/metrics" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type Sequencer struct { + id seqtypes.SequencerID + + chainID eth.ChainID + + log log.Logger + m metrics.Metricer + + stateMu sync.RWMutex + + currentJob work.BuildJob + unsigned work.Block + signed work.SignedBlock + committed bool + published bool + + // active is true when it's currently running through block-building automatically + active bool + + builder work.Builder + signer work.Signer + committer work.Committer + publisher work.Publisher +} + +var _ work.Sequencer = (*Sequencer)(nil) + +func (s *Sequencer) String() string { + return "sequencer-" + s.id.String() +} + +func (s *Sequencer) ID() seqtypes.SequencerID { + return s.id +} + +func (s *Sequencer) Close() error { + return nil +} + +func (s *Sequencer) Open(ctx context.Context) error { + s.stateMu.Lock() + defer s.stateMu.Unlock() + if s.unsigned != nil { + return seqtypes.ErrAlreadySealed + } + if s.currentJob != nil { + return seqtypes.ErrConflictingJob + } + + opts := &seqtypes.BuildOpts{ + Parent: common.Hash{}, // TODO(#14124): update sequencer to chain blocks together + L1Origin: nil, + } + + job, err := s.builder.NewJob(ctx, opts) + if err != nil { + return fmt.Errorf("failed to start new build job: %w", err) + } + s.currentJob = job + return nil +} + +func (s *Sequencer) BuildJob() work.BuildJob { + s.stateMu.RLock() + defer s.stateMu.RUnlock() + return s.currentJob +} + +func (s *Sequencer) Seal(ctx context.Context) error { + s.stateMu.Lock() + defer s.stateMu.Unlock() + if s.unsigned != nil { + return seqtypes.ErrAlreadySealed + } + if s.currentJob == nil { + return seqtypes.ErrUnknownJob + } + block, err := s.currentJob.Seal(ctx) + if err != nil { + return fmt.Errorf("failed to seal block: %w", err) + } + s.currentJob.Close() + s.unsigned = block + return nil +} + +func (s *Sequencer) Prebuilt(ctx context.Context, block work.Block) error { + s.stateMu.Lock() + defer s.stateMu.Unlock() + if s.currentJob != nil { + return seqtypes.ErrConflictingJob + } + if s.unsigned != nil { + return seqtypes.ErrAlreadySealed + } + s.unsigned = block + return nil +} + +func (s *Sequencer) Sign(ctx context.Context) error { + s.stateMu.Lock() + defer s.stateMu.Unlock() + if s.signed != nil { + return seqtypes.ErrAlreadySigned + } + if s.unsigned == nil { + return seqtypes.ErrNotSealed + } + result, err := s.signer.Sign(ctx, s.unsigned) + if err != nil { + return err + } + s.signed = result + return nil +} + +func (s *Sequencer) Commit(ctx context.Context) error { + s.stateMu.Lock() + defer s.stateMu.Unlock() + if s.committed { + return seqtypes.ErrAlreadyCommitted + } + if s.signed == nil { + return seqtypes.ErrUnsigned + } + if err := s.committer.Commit(ctx, s.signed); err != nil { + return err + } + s.committed = true + return nil +} + +func (s *Sequencer) Publish(ctx context.Context) error { + s.stateMu.Lock() + defer s.stateMu.Unlock() + // re-publishing is allowed + return s.publish(ctx) +} + +var errAlreadyPublished = errors.New("block alreadyb published") + +func (s *Sequencer) publishMaybe(ctx context.Context) error { + s.stateMu.Lock() + defer s.stateMu.Unlock() + if s.published { + // Re-publishing is allowed, but not in the Next() routine + // which skips already-completed actions. + return errAlreadyPublished + } + return s.publish(ctx) +} + +func (s *Sequencer) publish(ctx context.Context) error { + if !s.committed { + return seqtypes.ErrUncommitted + } + if err := s.publisher.Publish(ctx, s.signed); err != nil { + return err + } + s.published = true + return nil +} + +func (s *Sequencer) Next(ctx context.Context) error { + if err := s.Open(ctx); err != nil && !(errors.Is(err, seqtypes.ErrAlreadySealed) || + errors.Is(err, seqtypes.ErrConflictingJob)) { // forced-in blocks don't count as job + return fmt.Errorf("block-open failed: %w", err) + } + if err := s.Seal(ctx); err != nil && !errors.Is(err, seqtypes.ErrAlreadySealed) { + return fmt.Errorf("block-seal failed: %w", err) + } + if err := s.Sign(ctx); err != nil && !errors.Is(err, seqtypes.ErrAlreadySigned) { + return fmt.Errorf("block-sign failed: %w", err) + } + if err := s.Commit(ctx); err != nil && !errors.Is(err, seqtypes.ErrAlreadyCommitted) { + return fmt.Errorf("block-commit failed: %w", err) + } + if err := s.publishMaybe(ctx); err != nil && !errors.Is(err, errAlreadyPublished) { + return fmt.Errorf("block-publish failed: %w", err) + } + s.lockingReset() + return nil +} + +func (s *Sequencer) lockingReset() { + s.stateMu.Lock() + defer s.stateMu.Unlock() + s.reset() +} + +func (s *Sequencer) reset() { + if s.currentJob != nil { + s.currentJob.Close() + } + s.currentJob = nil + s.unsigned = nil + s.signed = nil + s.committed = false + s.published = false +} + +func (s *Sequencer) Start(ctx context.Context, head common.Hash) error { + s.stateMu.Lock() + defer s.stateMu.Unlock() + + if s.active { + return seqtypes.ErrSequencerAlreadyActive + } + return s.forceStart() +} + +func (s *Sequencer) forceStart() error { + // TODO(#14129) start schedule + s.reset() + + return seqtypes.ErrNotImplemented +} + +func (s *Sequencer) Stop(ctx context.Context) (hash common.Hash, err error) { + s.stateMu.Lock() + defer s.stateMu.Unlock() + + if !s.active { + return common.Hash{}, seqtypes.ErrSequencerInactive + } + + s.active = false + return common.Hash{}, seqtypes.ErrNotImplemented + /* + // TODO(#14129) stop schedule + var last common.Hash + s.reset() + return last, nil + */ +} + +func (s *Sequencer) Active() bool { + s.stateMu.RLock() + active := s.active + s.stateMu.RUnlock() + return active +} diff --git a/op-test-sequencer/sequencer/backend/work/sequencers/fullseq/sequencer_test.go b/op-test-sequencer/sequencer/backend/work/sequencers/fullseq/sequencer_test.go new file mode 100644 index 00000000000..d791e16c1a5 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/sequencers/fullseq/sequencer_test.go @@ -0,0 +1,419 @@ +package fullseq + +import ( + "context" + "errors" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-test-sequencer/metrics" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type mockBlock struct{} + +func (m *mockBlock) ID() eth.BlockID { + return eth.BlockID{Number: 123, Hash: common.Hash{0xff}} +} + +func (m *mockBlock) String() string { + return "mock block" +} + +var _ work.Block = (*mockBlock)(nil) + +type mockSignedBlock struct { + bl work.Block +} + +func (m *mockSignedBlock) ID() eth.BlockID { + return m.bl.ID() +} + +func (m *mockSignedBlock) String() string { + return "mock signed block" +} + +func (m *mockSignedBlock) VerifySignature() error { + return nil +} + +var _ work.SignedBlock = (*mockSignedBlock)(nil) + +type mockBuildJob struct { + id seqtypes.BuildJobID + bl work.Block + err error +} + +func (m *mockBuildJob) ID() seqtypes.BuildJobID { + return m.id +} + +func (m *mockBuildJob) Cancel(ctx context.Context) error { + return nil +} + +func (m *mockBuildJob) Seal(ctx context.Context) (work.Block, error) { + return m.bl, m.err +} + +func (m *mockBuildJob) String() string { + return "mock build job" +} + +func (m *mockBuildJob) Close() {} + +var _ work.BuildJob = (*mockBuildJob)(nil) + +type mockBuilder struct { + id seqtypes.BuilderID + closed bool + job func() (work.BuildJob, error) +} + +func (m *mockBuilder) NewJob(ctx context.Context, opts *seqtypes.BuildOpts) (work.BuildJob, error) { + return m.job() +} + +func (m *mockBuilder) String() string { + return "mock-builder-" + m.id.String() +} + +func (m *mockBuilder) ID() seqtypes.BuilderID { + return m.id +} + +func (m *mockBuilder) Close() error { + m.closed = true + return nil +} + +var _ work.Builder = (*mockBuilder)(nil) + +type mockSigner struct { + id seqtypes.SignerID + closed bool + err error +} + +func (m *mockSigner) String() string { + return "mock-signer-" + m.id.String() +} + +func (m *mockSigner) ID() seqtypes.SignerID { + return m.id +} + +func (m *mockSigner) Close() error { + m.closed = true + return nil +} + +func (m *mockSigner) Sign(ctx context.Context, block work.Block) (work.SignedBlock, error) { + return &mockSignedBlock{bl: block}, m.err +} + +var _ work.Signer = (*mockSigner)(nil) + +type mockCommitter struct { + id seqtypes.CommitterID + closed bool + err error +} + +func (m *mockCommitter) String() string { + return "mock-committer-" + m.id.String() +} + +func (m *mockCommitter) ID() seqtypes.CommitterID { + return m.id +} + +func (m *mockCommitter) Close() error { + m.closed = true + return nil +} + +func (m *mockCommitter) Commit(ctx context.Context, block work.SignedBlock) error { + return m.err +} + +var _ work.Committer = (*mockCommitter)(nil) + +type mockPublisher struct { + id seqtypes.PublisherID + closed bool + err error +} + +func (m *mockPublisher) String() string { + return "mock-publisher-" + m.id.String() +} + +func (m *mockPublisher) ID() seqtypes.PublisherID { + return m.id +} + +func (m *mockPublisher) Close() error { + m.closed = true + return nil +} + +func (m *mockPublisher) Publish(ctx context.Context, block work.SignedBlock) error { + return m.err +} + +var _ work.Publisher = (*mockPublisher)(nil) + +func TestSequencer(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + seqID := seqtypes.SequencerID("seq") + builder := &mockBuilder{id: seqtypes.BuilderID("builder")} + signer := &mockSigner{id: seqtypes.SignerID("signer")} + committer := &mockCommitter{id: seqtypes.CommitterID("committer")} + publisher := &mockPublisher{id: seqtypes.PublisherID("publisher")} + seq := &Sequencer{ + id: seqID, + chainID: eth.ChainIDFromUInt64(1), + log: logger, + m: &metrics.NoopMetrics{}, + builder: builder, + signer: signer, + committer: committer, + publisher: publisher, + } + require.Contains(t, seq.String(), "sequencer") + ctx := context.Background() + + resetBuildJob := func() { + job := &mockBuildJob{ + id: seqtypes.RandomJobID(), + bl: &mockBlock{}, + err: nil, + } + builder.job = func() (work.BuildJob, error) { + return job, nil + } + } + t.Run("no action without the pre-requisite action", func(t *testing.T) { + seq.reset() + require.ErrorIs(t, seq.Seal(ctx), seqtypes.ErrUnknownJob) + require.ErrorIs(t, seq.Sign(ctx), seqtypes.ErrNotSealed) + require.ErrorIs(t, seq.Commit(ctx), seqtypes.ErrUnsigned) + require.ErrorIs(t, seq.Publish(ctx), seqtypes.ErrUncommitted) + }) + + t.Run("do a full routine", func(t *testing.T) { + seq.reset() + resetBuildJob() + require.Nil(t, seq.BuildJob(), "no job yet") + require.NoError(t, seq.Open(ctx)) + require.ErrorIs(t, seq.Open(ctx), seqtypes.ErrConflictingJob) + require.NotNil(t, seq.BuildJob(), "job is opened") + require.NoError(t, seq.Seal(ctx)) + require.ErrorIs(t, seq.Seal(ctx), seqtypes.ErrAlreadySealed) + require.NoError(t, seq.Sign(ctx)) + require.ErrorIs(t, seq.Sign(ctx), seqtypes.ErrAlreadySigned) + require.NoError(t, seq.Commit(ctx)) + require.ErrorIs(t, seq.Commit(ctx), seqtypes.ErrAlreadyCommitted) + require.NoError(t, seq.Publish(ctx)) + require.NoError(t, seq.Publish(ctx), "re-publishing blocks is allowed") + }) + + t.Run("continue from scratch", func(t *testing.T) { + seq.reset() + resetBuildJob() + require.NoError(t, seq.Next(ctx)) + }) + + t.Run("continue from open block", func(t *testing.T) { + seq.reset() + resetBuildJob() + require.NoError(t, seq.Open(ctx)) + require.NoError(t, seq.Next(ctx)) + }) + + t.Run("continue from sealed block", func(t *testing.T) { + seq.reset() + resetBuildJob() + require.NoError(t, seq.Open(ctx)) + require.NoError(t, seq.Seal(ctx)) + require.NoError(t, seq.Next(ctx)) + }) + + t.Run("continue from signed block", func(t *testing.T) { + seq.reset() + resetBuildJob() + require.NoError(t, seq.Open(ctx)) + require.NoError(t, seq.Seal(ctx)) + require.NoError(t, seq.Sign(ctx)) + require.NoError(t, seq.Next(ctx)) + }) + + t.Run("continue from committed block", func(t *testing.T) { + seq.reset() + resetBuildJob() + require.NoError(t, seq.Open(ctx)) + require.NoError(t, seq.Seal(ctx)) + require.NoError(t, seq.Sign(ctx)) + require.NoError(t, seq.Commit(ctx)) + require.NoError(t, seq.Next(ctx)) + }) + + t.Run("continue from published block", func(t *testing.T) { + seq.reset() + resetBuildJob() + require.NoError(t, seq.Open(ctx)) + require.NoError(t, seq.Seal(ctx)) + require.NoError(t, seq.Sign(ctx)) + require.NoError(t, seq.Commit(ctx)) + require.NoError(t, seq.Publish(ctx)) + require.NoError(t, seq.Next(ctx)) + }) + + t.Run("continue from prebuilt block", func(t *testing.T) { + seq.reset() + resetBuildJob() + bl := &mockBlock{} + require.NoError(t, seq.Prebuilt(ctx, bl)) + require.NoError(t, seq.Sign(ctx)) + require.Equal(t, bl, seq.signed.(*mockSignedBlock).bl) + require.NoError(t, seq.Commit(ctx)) + require.NoError(t, seq.Next(ctx)) + }) + + t.Run("no prebuilt after job open", func(t *testing.T) { + seq.reset() + resetBuildJob() + bl := &mockBlock{} + require.NoError(t, seq.Open(ctx)) + require.ErrorIs(t, seq.Prebuilt(ctx, bl), seqtypes.ErrConflictingJob) + }) + + t.Run("no prebuilt after job", func(t *testing.T) { + seq.reset() + resetBuildJob() + bl := &mockBlock{} + require.NoError(t, seq.Open(ctx)) + require.NoError(t, seq.Seal(ctx)) + require.ErrorIs(t, seq.Prebuilt(ctx, bl), seqtypes.ErrConflictingJob) + }) + + t.Run("no duplicate prebuilt", func(t *testing.T) { + seq.reset() + resetBuildJob() + bl := &mockBlock{} + require.NoError(t, seq.Prebuilt(ctx, bl)) + require.ErrorIs(t, seq.Prebuilt(ctx, bl), seqtypes.ErrAlreadySealed) + }) + + t.Run("fail to open", func(t *testing.T) { + seq.reset() + testErr := errors.New("test open err") + builder.job = func() (work.BuildJob, error) { + return nil, testErr + } + require.ErrorIs(t, seq.Open(ctx), testErr) + require.ErrorIs(t, seq.Next(ctx), testErr) + }) + + t.Run("fail to seal", func(t *testing.T) { + seq.reset() + testErr := errors.New("test seal err") + job := &mockBuildJob{ + id: seqtypes.RandomJobID(), + bl: nil, + err: testErr, + } + builder.job = func() (work.BuildJob, error) { + return job, nil + } + require.NoError(t, seq.Open(ctx)) + require.ErrorIs(t, seq.Seal(ctx), testErr) + require.ErrorIs(t, seq.Next(ctx), testErr) + }) + + t.Run("fail to sign", func(t *testing.T) { + seq.reset() + resetBuildJob() + testErr := errors.New("test sign err") + signer.err = testErr + require.NoError(t, seq.Open(ctx)) + require.NoError(t, seq.Seal(ctx)) + require.ErrorIs(t, seq.Sign(ctx), testErr) + require.ErrorIs(t, seq.Next(ctx), testErr) + signer.err = nil + }) + + t.Run("fail to commit", func(t *testing.T) { + seq.reset() + resetBuildJob() + testErr := errors.New("test commit err") + committer.err = testErr + require.NoError(t, seq.Open(ctx)) + require.NoError(t, seq.Seal(ctx)) + require.NoError(t, seq.Sign(ctx)) + require.ErrorIs(t, seq.Commit(ctx), testErr) + require.ErrorIs(t, seq.Next(ctx), testErr) + committer.err = nil + }) + + t.Run("fail to publish", func(t *testing.T) { + seq.reset() + resetBuildJob() + testErr := errors.New("test publish err") + publisher.err = testErr + require.NoError(t, seq.Open(ctx)) + require.NoError(t, seq.Seal(ctx)) + require.NoError(t, seq.Sign(ctx)) + require.NoError(t, seq.Commit(ctx)) + require.ErrorIs(t, seq.Publish(ctx), testErr) + require.ErrorIs(t, seq.Next(ctx), testErr) + publisher.err = nil + }) + + t.Run("sequencer start/stop", func(t *testing.T) { + seq.reset() + resetBuildJob() + require.False(t, seq.Active()) + // Start/stop not supported yet + require.ErrorIs(t, seq.Start(ctx, common.Hash{}), seqtypes.ErrNotImplemented) + seq.active = true + _, err := seq.Stop(ctx) + require.ErrorIs(t, err, seqtypes.ErrNotImplemented) + // inactive again + require.False(t, seq.Active()) + }) + + t.Run("no duplicate start", func(t *testing.T) { + seq.reset() + resetBuildJob() + seq.active = true + require.ErrorIs(t, seq.Start(ctx, common.Hash{}), seqtypes.ErrSequencerAlreadyActive) + seq.active = false + }) + + t.Run("no duplicate stop", func(t *testing.T) { + seq.reset() + resetBuildJob() + seq.active = false + _, err := seq.Stop(ctx) + require.ErrorIs(t, err, seqtypes.ErrSequencerInactive) + }) + + require.NoError(t, seq.Close()) + // other services are independent, not closed as part of the sequencer, + // but closed as part of the total ensemble. + require.False(t, builder.closed) + require.False(t, signer.closed) + require.False(t, committer.closed) + require.False(t, publisher.closed) + +} diff --git a/op-test-sequencer/sequencer/backend/work/sequencers/noopseq/config.go b/op-test-sequencer/sequencer/backend/work/sequencers/noopseq/config.go new file mode 100644 index 00000000000..97f24502ddc --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/sequencers/noopseq/config.go @@ -0,0 +1,18 @@ +package noopseq + +import ( + "context" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type Config struct { +} + +func (c *Config) Start(ctx context.Context, id seqtypes.SequencerID, opts *work.ServiceOpts) (work.Sequencer, error) { + return &Sequencer{ + id: id, + log: opts.Log, + }, nil +} diff --git a/op-test-sequencer/sequencer/backend/work/sequencers/noopseq/config_test.go b/op-test-sequencer/sequencer/backend/work/sequencers/noopseq/config_test.go new file mode 100644 index 00000000000..528250e15b4 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/sequencers/noopseq/config_test.go @@ -0,0 +1,32 @@ +package noopseq + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-test-sequencer/metrics" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +func TestConfig(t *testing.T) { + logger := testlog.Logger(t, log.LevelInfo) + cfg := &Config{} + id := seqtypes.SequencerID("test") + ensemble := &work.Ensemble{} + opts := &work.ServiceOpts{ + StartOpts: &work.StartOpts{ + Log: logger, + Metrics: &metrics.NoopMetrics{}, + }, + Services: ensemble, + } + sequencer, err := cfg.Start(context.Background(), id, opts) + require.NoError(t, err) + require.Equal(t, id, sequencer.ID()) +} diff --git a/op-test-sequencer/sequencer/backend/work/sequencers/noopseq/sequencer.go b/op-test-sequencer/sequencer/backend/work/sequencers/noopseq/sequencer.go new file mode 100644 index 00000000000..05f790c0d01 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/sequencers/noopseq/sequencer.go @@ -0,0 +1,78 @@ +package noopseq + +import ( + "context" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type Sequencer struct { + id seqtypes.SequencerID + log log.Logger +} + +var _ work.Sequencer = (*Sequencer)(nil) + +func NewSequencer(id seqtypes.SequencerID, log log.Logger) *Sequencer { + return &Sequencer{id: id, log: log} +} + +func (n *Sequencer) Close() error { + return nil +} + +func (n *Sequencer) String() string { + return "noop-sequencer-" + n.id.String() +} + +func (n *Sequencer) ID() seqtypes.SequencerID { + return n.id +} + +func (n *Sequencer) Open(ctx context.Context) error { + return nil +} + +func (n *Sequencer) BuildJob() work.BuildJob { + return nil +} + +func (n *Sequencer) Seal(ctx context.Context) error { + return nil +} + +func (n *Sequencer) Prebuilt(ctx context.Context, block work.Block) error { + return nil +} + +func (n *Sequencer) Sign(ctx context.Context) error { + return nil +} + +func (n *Sequencer) Commit(ctx context.Context) error { + return nil +} + +func (n *Sequencer) Publish(ctx context.Context) error { + return nil +} + +func (n *Sequencer) Next(ctx context.Context) error { + return nil +} + +func (n *Sequencer) Start(ctx context.Context, head common.Hash) error { + return seqtypes.ErrSequencerInactive +} + +func (n *Sequencer) Stop(ctx context.Context) (last common.Hash, err error) { + return common.Hash{}, seqtypes.ErrSequencerInactive +} + +func (n *Sequencer) Active() bool { + return false +} diff --git a/op-test-sequencer/sequencer/backend/work/sequencers/noopseq/sequencer_test.go b/op-test-sequencer/sequencer/backend/work/sequencers/noopseq/sequencer_test.go new file mode 100644 index 00000000000..4eeaffe64c1 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/sequencers/noopseq/sequencer_test.go @@ -0,0 +1,40 @@ +package noopseq + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +func TestNoopSequencer(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + id := seqtypes.SequencerID("foo") + x := NewSequencer(id, logger) + + ctx := context.Background() + require.NoError(t, x.Open(ctx)) + job := x.BuildJob() + require.Nil(t, job) + require.NoError(t, x.Seal(ctx)) + require.NoError(t, x.Prebuilt(ctx, nil)) + require.NoError(t, x.Sign(ctx)) + require.NoError(t, x.Commit(ctx)) + require.NoError(t, x.Publish(ctx)) + require.NoError(t, x.Next(ctx)) + err := x.Start(ctx, common.Hash{}) + require.ErrorIs(t, err, seqtypes.ErrSequencerInactive) + _, err = x.Stop(ctx) + require.ErrorIs(t, err, seqtypes.ErrSequencerInactive) + + require.NoError(t, x.Close()) + require.Equal(t, "noop-sequencer-foo", x.String()) + require.Equal(t, id, x.ID()) + require.False(t, x.Active()) +} diff --git a/op-test-sequencer/sequencer/backend/work/signers/noopsigner/config.go b/op-test-sequencer/sequencer/backend/work/signers/noopsigner/config.go new file mode 100644 index 00000000000..4058794ee2f --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/signers/noopsigner/config.go @@ -0,0 +1,18 @@ +package noopsigner + +import ( + "context" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type Config struct { +} + +func (c *Config) Start(ctx context.Context, id seqtypes.SignerID, opts *work.ServiceOpts) (work.Signer, error) { + return &Signer{ + id: id, + log: opts.Log, + }, nil +} diff --git a/op-test-sequencer/sequencer/backend/work/signers/noopsigner/config_test.go b/op-test-sequencer/sequencer/backend/work/signers/noopsigner/config_test.go new file mode 100644 index 00000000000..06f74d5c960 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/signers/noopsigner/config_test.go @@ -0,0 +1,32 @@ +package noopsigner + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-test-sequencer/metrics" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +func TestConfig(t *testing.T) { + logger := testlog.Logger(t, log.LevelInfo) + cfg := &Config{} + id := seqtypes.SignerID("test") + ensemble := &work.Ensemble{} + opts := &work.ServiceOpts{ + StartOpts: &work.StartOpts{ + Log: logger, + Metrics: &metrics.NoopMetrics{}, + }, + Services: ensemble, + } + signer, err := cfg.Start(context.Background(), id, opts) + require.NoError(t, err) + require.Equal(t, id, signer.ID()) +} diff --git a/op-test-sequencer/sequencer/backend/work/signers/noopsigner/publisher.go b/op-test-sequencer/sequencer/backend/work/signers/noopsigner/publisher.go new file mode 100644 index 00000000000..845b7d18269 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/signers/noopsigner/publisher.go @@ -0,0 +1,38 @@ +package noopsigner + +import ( + "context" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type Signer struct { + id seqtypes.SignerID + log log.Logger +} + +var _ work.Signer = (*Signer)(nil) + +func NewSigner(id seqtypes.SignerID, log log.Logger) *Signer { + return &Signer{id: id, log: log} +} + +func (n *Signer) Close() error { + return nil +} + +func (n *Signer) String() string { + return "noop-signer-" + n.id.String() +} + +func (n *Signer) ID() seqtypes.SignerID { + return n.id +} + +func (n *Signer) Sign(ctx context.Context, block work.Block) (work.SignedBlock, error) { + n.log.Info("No-op sign", "block", block) + return nil, nil +} diff --git a/op-test-sequencer/sequencer/backend/work/signers/noopsigner/publisher_test.go b/op-test-sequencer/sequencer/backend/work/signers/noopsigner/publisher_test.go new file mode 100644 index 00000000000..1abdbc380b1 --- /dev/null +++ b/op-test-sequencer/sequencer/backend/work/signers/noopsigner/publisher_test.go @@ -0,0 +1,26 @@ +package noopsigner + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +func TestNoopSigner(t *testing.T) { + logger := testlog.Logger(t, log.LevelDebug) + id := seqtypes.SignerID("foo") + x := NewSigner(id, logger) + + _, err := x.Sign(context.Background(), nil) + require.NoError(t, err) + + require.NoError(t, x.Close()) + require.Equal(t, "noop-signer-foo", x.String()) + require.Equal(t, id, x.ID()) +} diff --git a/op-test-sequencer/sequencer/frontend/frontend.go b/op-test-sequencer/sequencer/frontend/admin.go similarity index 82% rename from op-test-sequencer/sequencer/frontend/frontend.go rename to op-test-sequencer/sequencer/frontend/admin.go index b9dc094a379..f5bc62e2f10 100644 --- a/op-test-sequencer/sequencer/frontend/frontend.go +++ b/op-test-sequencer/sequencer/frontend/admin.go @@ -2,12 +2,12 @@ package frontend import "context" -type Backend interface { +type AdminBackend interface { Hello(ctx context.Context, name string) (string, error) } type AdminFrontend struct { - Backend Backend + Backend AdminBackend } func (af *AdminFrontend) Hello(ctx context.Context, name string) (string, error) { diff --git a/op-test-sequencer/sequencer/frontend/admin_test.go b/op-test-sequencer/sequencer/frontend/admin_test.go new file mode 100644 index 00000000000..4e503d37b56 --- /dev/null +++ b/op-test-sequencer/sequencer/frontend/admin_test.go @@ -0,0 +1,24 @@ +package frontend + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" +) + +type mockAdminBackend struct{} + +func (m *mockAdminBackend) Hello(ctx context.Context, name string) (string, error) { + return "hello " + name, nil +} + +var _ AdminBackend = (*mockAdminBackend)(nil) + +func TestAdmin(t *testing.T) { + b := &mockAdminBackend{} + front := &AdminFrontend{Backend: b} + out, err := front.Hello(context.Background(), "world") + require.NoError(t, err) + require.Equal(t, "hello world", out) +} diff --git a/op-test-sequencer/sequencer/frontend/builder.go b/op-test-sequencer/sequencer/frontend/builder.go new file mode 100644 index 00000000000..019dcbb7ceb --- /dev/null +++ b/op-test-sequencer/sequencer/frontend/builder.go @@ -0,0 +1,55 @@ +package frontend + +import ( + "context" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type BuildBackend interface { + CreateJob(ctx context.Context, id seqtypes.BuilderID, opts *seqtypes.BuildOpts) (work.BuildJob, error) + GetJob(id seqtypes.BuildJobID) work.BuildJob +} + +type BuildFrontend struct { + Backend BuildBackend +} + +func (bf *BuildFrontend) Open(ctx context.Context, builderID seqtypes.BuilderID, opts *seqtypes.BuildOpts) (seqtypes.BuildJobID, error) { + job, err := bf.Backend.CreateJob(ctx, builderID, opts) + if err != nil { + return "", err + } + return job.ID(), nil +} + +func (bf *BuildFrontend) Cancel(ctx context.Context, jobID seqtypes.BuildJobID) error { + job := bf.Backend.GetJob(jobID) + if job == nil { + return seqtypes.ErrUnknownJob + } + return toJsonError(job.Cancel(ctx)) +} + +func (bf *BuildFrontend) Seal(ctx context.Context, jobID seqtypes.BuildJobID) (work.Block, error) { + job := bf.Backend.GetJob(jobID) + if job == nil { + return eth.BlockRef{}, seqtypes.ErrUnknownJob + } + result, err := job.Seal(ctx) + if err != nil { + return nil, toJsonError(err) + } + return result, nil +} + +func (bf *BuildFrontend) CloseJob(id seqtypes.BuildJobID) error { + job := bf.Backend.GetJob(id) + if job == nil { + return seqtypes.ErrUnknownJob + } + job.Close() + return nil +} diff --git a/op-test-sequencer/sequencer/frontend/builder_test.go b/op-test-sequencer/sequencer/frontend/builder_test.go new file mode 100644 index 00000000000..b6cf397f35b --- /dev/null +++ b/op-test-sequencer/sequencer/frontend/builder_test.go @@ -0,0 +1,182 @@ +package frontend + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/crypto" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type mockBlock struct { + From seqtypes.BuildJobID `json:"from"` + BlockID eth.BlockID +} + +func (m *mockBlock) ID() eth.BlockID { + return m.BlockID +} + +func (m *mockBlock) String() string { + return m.From.String() + "-" + m.BlockID.String() +} + +var _ work.Block = (*mockBlock)(nil) + +type mockBuildJob struct { + id seqtypes.BuildJobID + canceled bool + sealed bool + result work.Block + err error + unregister func() +} + +func (m *mockBuildJob) ID() seqtypes.BuildJobID { + return m.id +} + +func (m *mockBuildJob) Cancel(ctx context.Context) error { + m.canceled = true + return m.err +} + +func (m *mockBuildJob) Seal(ctx context.Context) (work.Block, error) { + m.sealed = true + return m.result, m.err +} + +func (m *mockBuildJob) String() string { + return "mock-build-job-" + m.id.String() +} + +func (m *mockBuildJob) Close() { + m.unregister() +} + +var _ work.BuildJob = (*mockBuildJob)(nil) + +type mockBuildBackend struct { + jobs map[seqtypes.BuildJobID]*mockBuildJob +} + +const ( + testBuilderA = seqtypes.BuilderID("builder-a") + testBuilderB = seqtypes.BuilderID("builder-b") +) + +func (m *mockBuildBackend) CreateJob(ctx context.Context, id seqtypes.BuilderID, opts *seqtypes.BuildOpts) (work.BuildJob, error) { + if id == testBuilderB { + return nil, seqtypes.ErrUnknownBuilder + } + jobID := seqtypes.RandomJobID() + job := &mockBuildJob{ + id: jobID, + canceled: false, + sealed: false, + result: &mockBlock{ + From: jobID, + BlockID: eth.BlockID{Number: 123, Hash: crypto.Keccak256Hash([]byte(jobID))}, + }, + unregister: func() { + m.UnregisterJob(jobID) + }, + } + m.jobs[jobID] = job + return job, nil +} + +func (m *mockBuildBackend) GetJob(id seqtypes.BuildJobID) work.BuildJob { + job, ok := m.jobs[id] + if !ok { + return nil + } + return job +} + +func (m *mockBuildBackend) UnregisterJob(id seqtypes.BuildJobID) { + delete(m.jobs, id) +} + +var _ BuildBackend = (*mockBuildBackend)(nil) + +func TestBuildFrontend(t *testing.T) { + backend := &mockBuildBackend{ + jobs: make(map[seqtypes.BuildJobID]*mockBuildJob), + } + front := &BuildFrontend{Backend: backend} + ctx := context.Background() + + t.Run("unknown builder", func(t *testing.T) { + _, err := front.Open(ctx, testBuilderB, nil) + require.ErrorIs(t, err, seqtypes.ErrUnknownBuilder) + }) + + t.Run("non-existent jobs", func(t *testing.T) { + err := front.Cancel(ctx, seqtypes.RandomJobID()) + require.ErrorIs(t, err, seqtypes.ErrUnknownJob) + + _, err = front.Seal(ctx, seqtypes.RandomJobID()) + require.ErrorIs(t, err, seqtypes.ErrUnknownJob) + err = front.CloseJob(seqtypes.RandomJobID()) + require.ErrorIs(t, err, seqtypes.ErrUnknownJob) + }) + + t.Run("seal", func(t *testing.T) { + jobID, err := front.Open(ctx, testBuilderA, nil) + require.NoError(t, err) + + require.False(t, backend.jobs[jobID].sealed) + + block, err := front.Seal(ctx, jobID) + require.NoError(t, err) + require.Equal(t, uint64(123), block.ID().Number) + require.Equal(t, crypto.Keccak256Hash([]byte(jobID)), block.ID().Hash) + + require.Contains(t, backend.jobs, jobID) + require.True(t, backend.jobs[jobID].sealed) + require.NoError(t, front.CloseJob(jobID)) + require.NotContains(t, backend.jobs, jobID) + }) + + t.Run("seal error", func(t *testing.T) { + jobID, err := front.Open(ctx, testBuilderA, nil) + require.NoError(t, err) + require.False(t, backend.jobs[jobID].sealed) + backend.jobs[jobID].err = seqtypes.ErrAlreadySealed + _, err = front.Seal(ctx, jobID) + require.ErrorIs(t, err, seqtypes.ErrAlreadySealed) + require.Contains(t, backend.jobs, jobID) + require.NoError(t, front.CloseJob(jobID)) + require.NotContains(t, backend.jobs, jobID) + }) + + t.Run("cancel", func(t *testing.T) { + jobID, err := front.Open(ctx, testBuilderA, nil) + require.NoError(t, err) + require.False(t, backend.jobs[jobID].canceled) + err = front.Cancel(ctx, jobID) + require.NoError(t, err) + require.Contains(t, backend.jobs, jobID) + require.True(t, backend.jobs[jobID].canceled) + require.NoError(t, front.CloseJob(jobID)) + require.NotContains(t, backend.jobs, jobID) + }) + + t.Run("cancel error", func(t *testing.T) { + jobID, err := front.Open(ctx, testBuilderA, nil) + require.NoError(t, err) + require.False(t, backend.jobs[jobID].canceled) + backend.jobs[jobID].err = seqtypes.ErrAlreadySealed + err = front.Cancel(ctx, jobID) + require.ErrorIs(t, err, seqtypes.ErrAlreadySealed) + require.Contains(t, backend.jobs, jobID) + require.NoError(t, front.CloseJob(jobID)) + require.NotContains(t, backend.jobs, jobID) + }) +} diff --git a/op-test-sequencer/sequencer/frontend/sequencer.go b/op-test-sequencer/sequencer/frontend/sequencer.go new file mode 100644 index 00000000000..a047cd1f03f --- /dev/null +++ b/op-test-sequencer/sequencer/frontend/sequencer.go @@ -0,0 +1,82 @@ +package frontend + +import ( + "context" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type SequencerFrontend struct { + Sequencer work.Sequencer +} + +func (bf *SequencerFrontend) Open(ctx context.Context) error { + return toJsonError(bf.Sequencer.Open(ctx)) +} + +func (bf *SequencerFrontend) BuildJob() (seqtypes.BuildJobID, error) { + job := bf.Sequencer.BuildJob() + if job == nil { + return "", toJsonError(seqtypes.ErrUnknownJob) + } + return job.ID(), nil +} + +func (bf *SequencerFrontend) Seal(ctx context.Context) error { + return toJsonError(bf.Sequencer.Seal(ctx)) +} + +func (bf *SequencerFrontend) PrebuiltEnvelope(ctx context.Context, block *eth.ExecutionPayloadEnvelope) error { + return toJsonError(bf.Sequencer.Prebuilt(ctx, block)) +} + +func (bf *SequencerFrontend) Sign(ctx context.Context) error { + return toJsonError(bf.Sequencer.Sign(ctx)) +} + +func (bf *SequencerFrontend) Commit(ctx context.Context) error { + return toJsonError(bf.Sequencer.Commit(ctx)) +} + +func (bf *SequencerFrontend) Publish(ctx context.Context) error { + return toJsonError(bf.Sequencer.Publish(ctx)) +} + +func (bf *SequencerFrontend) Next(ctx context.Context) error { + return toJsonError(bf.Sequencer.Next(ctx)) +} + +func (bf *SequencerFrontend) Start(ctx context.Context, head common.Hash) error { + return toJsonError(bf.Sequencer.Start(ctx, head)) +} + +func (bf *SequencerFrontend) Stop(ctx context.Context) (last common.Hash, err error) { + last, err = bf.Sequencer.Stop(ctx) + if err != nil { + return common.Hash{}, toJsonError(err) + } + return +} + +type IncludeTxSupport interface { + IncludeTx(ctx context.Context, tx hexutil.Bytes) error +} + +func (bf *SequencerFrontend) IncludeTx(ctx context.Context, tx hexutil.Bytes) error { + job := bf.Sequencer.BuildJob() + if job == nil { + return seqtypes.ErrUnknownJob + } + // Not all build-jobs may support manual forced tx inclusion + x, ok := job.(IncludeTxSupport) + if !ok { + return &rpc.JsonError{Code: -39000, Message: "not supported"} + } + return toJsonError(x.IncludeTx(ctx, tx)) +} diff --git a/op-test-sequencer/sequencer/frontend/sequencer_test.go b/op-test-sequencer/sequencer/frontend/sequencer_test.go new file mode 100644 index 00000000000..ea05bc67ca1 --- /dev/null +++ b/op-test-sequencer/sequencer/frontend/sequencer_test.go @@ -0,0 +1,165 @@ +package frontend + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +type advancedBuildJob struct { + mockBuildJob + txs []hexutil.Bytes +} + +var _ work.BuildJob = (*advancedBuildJob)(nil) +var _ IncludeTxSupport = (*advancedBuildJob)(nil) + +func (a *advancedBuildJob) IncludeTx(ctx context.Context, tx hexutil.Bytes) error { + a.txs = append(a.txs, tx) + return nil +} + +type mockSequencer struct { + id seqtypes.SequencerID + job work.BuildJob + err error + action string +} + +func (m *mockSequencer) String() string { + return "test" +} + +func (m *mockSequencer) ID() seqtypes.SequencerID { + return m.id +} + +func (m *mockSequencer) Close() error { + return m.err +} + +func (m *mockSequencer) Open(ctx context.Context) error { + m.action = "open" + return m.err +} + +func (m *mockSequencer) BuildJob() work.BuildJob { + return m.job +} + +func (m *mockSequencer) Seal(ctx context.Context) error { + m.action = "seal" + return m.err +} + +func (m *mockSequencer) Prebuilt(ctx context.Context, block work.Block) error { + m.action = "prebuilt" + return m.err +} + +func (m *mockSequencer) Sign(ctx context.Context) error { + m.action = "sign" + return m.err +} + +func (m *mockSequencer) Commit(ctx context.Context) error { + m.action = "commit" + return m.err +} + +func (m *mockSequencer) Publish(ctx context.Context) error { + m.action = "publish" + return m.err +} + +func (m *mockSequencer) Next(ctx context.Context) error { + m.action = "next" + return m.err +} + +func (m *mockSequencer) Start(ctx context.Context, head common.Hash) error { + m.action = "start" + return m.err +} + +func (m *mockSequencer) Stop(ctx context.Context) (last common.Hash, err error) { + m.action = "stop" + return common.Hash{0: 42}, m.err +} + +func (m *mockSequencer) Active() bool { + m.action = "active" + return false +} + +var _ work.Sequencer = (*mockSequencer)(nil) + +func TestSequencer(t *testing.T) { + seqId := seqtypes.SequencerID("foobar") + seq := &mockSequencer{id: seqId} + front := &SequencerFrontend{Sequencer: seq} + + ctx := context.Background() + + t.Run("unknown job", func(t *testing.T) { + _, err := front.BuildJob() + seq.err = seqtypes.ErrUnknownJob + require.ErrorIs(t, err, seqtypes.ErrUnknownJob) + seq.err = nil + }) + t.Run("include tx", func(t *testing.T) { + dest := &advancedBuildJob{ + mockBuildJob: mockBuildJob{id: seqtypes.RandomJobID()}, + txs: nil, + } + seq.job = dest + jobId, err := front.BuildJob() + require.NoError(t, err) + require.Equal(t, seq.job.ID(), jobId) + require.NoError(t, front.IncludeTx(ctx, []byte("test"))) + require.NotEmpty(t, dest.txs) + seq.job = nil + require.ErrorIs(t, front.IncludeTx(ctx, []byte("abc")), seqtypes.ErrUnknownJob) + seq.job = &mockBuildJob{} + require.ErrorContains(t, front.IncludeTx(ctx, []byte("123")), "not supported") + seq.job = nil + }) + t.Run("step by step", func(t *testing.T) { + seq.action = "" + require.NoError(t, front.Open(ctx)) + require.Equal(t, "open", seq.action) + require.NoError(t, front.Seal(ctx)) + require.Equal(t, "seal", seq.action) + require.NoError(t, front.Sign(ctx)) + require.Equal(t, "sign", seq.action) + require.NoError(t, front.Commit(ctx)) + require.Equal(t, "commit", seq.action) + require.NoError(t, front.Publish(ctx)) + require.Equal(t, "publish", seq.action) + require.NoError(t, front.Next(ctx)) + require.Equal(t, "next", seq.action) + require.NoError(t, front.PrebuiltEnvelope(ctx, nil)) + require.Equal(t, "prebuilt", seq.action) + seq.action = "" + }) + t.Run("start stop", func(t *testing.T) { + seq.err = seqtypes.ErrSequencerAlreadyActive + require.ErrorIs(t, front.Start(ctx, common.Hash{0: 123}), seqtypes.ErrSequencerAlreadyActive) + seq.err = nil + require.NoError(t, front.Start(ctx, common.Hash{0: 123})) + out, err := front.Stop(ctx) + require.NoError(t, err) + require.Equal(t, common.Hash{0: 42}, out) + seq.err = seqtypes.ErrSequencerInactive + _, err = front.Stop(ctx) + require.ErrorIs(t, err, seqtypes.ErrSequencerInactive) + seq.err = nil + }) +} diff --git a/op-test-sequencer/sequencer/frontend/util.go b/op-test-sequencer/sequencer/frontend/util.go new file mode 100644 index 00000000000..b340c10d3ec --- /dev/null +++ b/op-test-sequencer/sequencer/frontend/util.go @@ -0,0 +1,25 @@ +package frontend + +import ( + "errors" + + "github.com/ethereum/go-ethereum/rpc" + + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/seqtypes" +) + +// toJsonError turns the error into a JSON error with error-code, +// to preserve the code when the error is wrapped +func toJsonError(err error) error { + if err == nil { + return nil + } + var x *rpc.JsonError + if errors.As(err, &x) { + return x + } + return &rpc.JsonError{ + Code: seqtypes.ErrUnknownKind.Code, + Message: err.Error(), + } +} diff --git a/op-test-sequencer/sequencer/seqtypes/types.go b/op-test-sequencer/sequencer/seqtypes/types.go new file mode 100644 index 00000000000..62bd7f570ca --- /dev/null +++ b/op-test-sequencer/sequencer/seqtypes/types.go @@ -0,0 +1,154 @@ +package seqtypes + +import ( + "errors" + + "github.com/google/uuid" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rpc" +) + +const maxIDLength = 100 + +var ErrInvalidID = errors.New("invalid ID") + +type genericID string + +func (id genericID) String() string { + return string(id) +} + +func (id genericID) MarshalText() ([]byte, error) { + if len(id) > maxIDLength { + return nil, ErrInvalidID + } + return []byte(id), nil +} + +func (id *genericID) UnmarshalText(data []byte) error { + if len(data) > maxIDLength { + return ErrInvalidID + } + *id = genericID(data) + return nil +} + +// BuildJobID identifies a block-building job. +// Multiple alternative blocks may be built in parallel. +type BuildJobID genericID + +func (id BuildJobID) String() string { + return genericID(id).String() +} + +func (id BuildJobID) MarshalText() ([]byte, error) { + return genericID(id).MarshalText() +} + +func (id *BuildJobID) UnmarshalText(data []byte) error { + return (*genericID)(id).UnmarshalText(data) +} + +type BuilderID genericID + +func (id BuilderID) String() string { + return genericID(id).String() +} + +func (id BuilderID) MarshalText() ([]byte, error) { + return genericID(id).MarshalText() +} + +func (id *BuilderID) UnmarshalText(data []byte) error { + return (*genericID)(id).UnmarshalText(data) +} + +type SignerID genericID + +func (id SignerID) String() string { + return genericID(id).String() +} + +func (id SignerID) MarshalText() ([]byte, error) { + return genericID(id).MarshalText() +} + +func (id *SignerID) UnmarshalText(data []byte) error { + return (*genericID)(id).UnmarshalText(data) +} + +type CommitterID genericID + +func (id CommitterID) String() string { + return genericID(id).String() +} + +func (id CommitterID) MarshalText() ([]byte, error) { + return genericID(id).MarshalText() +} + +func (id *CommitterID) UnmarshalText(data []byte) error { + return (*genericID)(id).UnmarshalText(data) +} + +type PublisherID genericID + +func (id PublisherID) String() string { + return genericID(id).String() +} + +func (id PublisherID) MarshalText() ([]byte, error) { + return genericID(id).MarshalText() +} + +func (id *PublisherID) UnmarshalText(data []byte) error { + return (*genericID)(id).UnmarshalText(data) +} + +type SequencerID genericID + +func (id SequencerID) String() string { + return genericID(id).String() +} + +func (id SequencerID) MarshalText() ([]byte, error) { + return genericID(id).MarshalText() +} + +func (id *SequencerID) UnmarshalText(data []byte) error { + return (*genericID)(id).UnmarshalText(data) +} + +var ( + ErrGeneric = &rpc.JsonError{Code: -38500, Message: "sequencer error"} + ErrUnknownKind = &rpc.JsonError{Code: -38501, Message: "unknown kind"} + ErrUnknownBuilder = &rpc.JsonError{Code: -38502, Message: "unknown builder"} + ErrNotImplemented = &rpc.JsonError{Code: -38503, Message: "not implemented"} + ErrUnknownJob = &rpc.JsonError{Code: -38510, Message: "unknown job"} + ErrConflictingJob = &rpc.JsonError{Code: -38511, Message: "conflicting job"} + ErrNotSealed = &rpc.JsonError{Code: -38520, Message: "block not yet sealed"} + ErrAlreadySealed = &rpc.JsonError{Code: -38521, Message: "block already sealed"} + ErrUnsigned = &rpc.JsonError{Code: -38530, Message: "block not yet signed"} + ErrAlreadySigned = &rpc.JsonError{Code: -38531, Message: "block already signed"} + ErrUncommitted = &rpc.JsonError{Code: -38540, Message: "block not yet committed"} + ErrAlreadyCommitted = &rpc.JsonError{Code: -38541, Message: "block already committed"} + ErrSequencerInactive = &rpc.JsonError{Code: -38550, Message: "sequencer inactive"} + ErrSequencerAlreadyActive = &rpc.JsonError{Code: -38551, Message: "sequencer already active"} + ErrBackendInactive = &rpc.JsonError{Code: -38560, Message: "backend inactive"} + ErrBackendAlreadyStarted = &rpc.JsonError{Code: -38561, Message: "backend already started"} +) + +func RandomJobID() BuildJobID { + return BuildJobID("job-" + uuid.New().String()) +} + +type BuildOpts struct { + // Parent block to build on top of + Parent common.Hash `json:"parent"` + + // L1Origin overrides the L1 origin of the block. + // Optional, by default the L1 origin of the parent block + // is progressed when first allowed (respecting time invariants). + L1Origin *common.Hash `json:"l1Origin,omitempty"` +} diff --git a/op-test-sequencer/sequencer/seqtypes/types_test.go b/op-test-sequencer/sequencer/seqtypes/types_test.go new file mode 100644 index 00000000000..97a93b53ae6 --- /dev/null +++ b/op-test-sequencer/sequencer/seqtypes/types_test.go @@ -0,0 +1,66 @@ +package seqtypes + +import ( + "bytes" + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestIDs(t *testing.T) { + testID[*genericID](t, func() *genericID { + return new(genericID) + }) + testID[*BuilderID](t, func() *BuilderID { + return new(BuilderID) + }) + testID[*SignerID](t, func() *SignerID { + return new(SignerID) + }) + testID[*CommitterID](t, func() *CommitterID { + return new(CommitterID) + }) + testID[*PublisherID](t, func() *PublisherID { + return new(PublisherID) + }) + testID[*SequencerID](t, func() *SequencerID { + return new(SequencerID) + }) + testID[*BuildJobID](t, func() *BuildJobID { + return new(BuildJobID) + }) +} + +type id interface { + String() string + MarshalText() ([]byte, error) + UnmarshalText(data []byte) error +} + +func testID[K id](t *testing.T, alloc func() K) { + var x K + name := fmt.Sprintf("%T", x) + t.Run(name, func(t *testing.T) { + v := alloc() + require.Equal(t, "", v.String()) + out, err := v.MarshalText() + require.NoError(t, err) + require.Len(t, out, 0) + v = alloc() + require.NoError(t, v.UnmarshalText([]byte("123abc"))) + require.Equal(t, "123abc", v.String()) + out, err = v.MarshalText() + require.NoError(t, err) + require.Equal(t, "123abc", string(out)) + + require.NoError(t, v.UnmarshalText(bytes.Repeat([]byte{'1'}, maxIDLength))) + require.ErrorIs(t, v.UnmarshalText(bytes.Repeat([]byte{'1'}, maxIDLength+1)), ErrInvalidID) + }) +} + +func TestRandomJobID(t *testing.T) { + a := RandomJobID() + b := RandomJobID() + require.NotEqual(t, a, b) +} diff --git a/op-test-sequencer/sequencer/service.go b/op-test-sequencer/sequencer/service.go index be2515b3622..affc915b3a9 100644 --- a/op-test-sequencer/sequencer/service.go +++ b/op-test-sequencer/sequencer/service.go @@ -4,6 +4,8 @@ import ( "context" "errors" "fmt" + "net" + "strconv" "sync/atomic" "github.com/ethereum/go-ethereum/log" @@ -18,11 +20,13 @@ import ( "github.com/ethereum-optimism/optimism/op-test-sequencer/config" "github.com/ethereum-optimism/optimism/op-test-sequencer/metrics" "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend" + "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/backend/work" "github.com/ethereum-optimism/optimism/op-test-sequencer/sequencer/frontend" ) type serviceBackend interface { - frontend.Backend + frontend.AdminBackend + frontend.BuildBackend Start(ctx context.Context) error Stop(ctx context.Context) error } @@ -40,7 +44,8 @@ type Service struct { metrics metrics.Metricer pprofService *oppprof.Service metricsSrv *httputil.HTTPServer - rpcServer *oprpc.Server + rpcHandler *oprpc.Handler + httpServer *httputil.HTTPServer jwtSecret eth.Bytes32 } @@ -65,8 +70,11 @@ func (s *Service) initFromCLIConfig(ctx context.Context, cfg *config.Config) err if err := s.initBackend(ctx, cfg); err != nil { return fmt.Errorf("failed to start backend: %w", err) } - if err := s.initRPCServer(cfg); err != nil { - return fmt.Errorf("failed to start RPC server: %w", err) + if err := s.initRPCHandler(cfg); err != nil { + return fmt.Errorf("failed to start RPC handler: %w", err) + } + if err := s.initHTTPServer(cfg); err != nil { + return fmt.Errorf("failed to start HTTP server: %w", err) } return nil } @@ -117,43 +125,68 @@ func (s *Service) initMetricsServer(cfg *config.Config) error { return nil } -func (s *Service) initBackend(ctx context.Context, cfg *config.Config) error { - if cfg.MockRun { - s.backend = backend.NewMockBackend() - return nil - } - s.backend = backend.NewBackend(s.log, s.metrics) - return nil -} - -func (s *Service) initRPCServer(cfg *config.Config) error { +func (s *Service) initRPCHandler(cfg *config.Config) error { secret, err := oprpc.ObtainJWTSecret(s.log, cfg.JWTSecretPath, true) if err != nil { return err } - server := oprpc.NewServer( - cfg.RPC.ListenAddr, - cfg.RPC.ListenPort, - cfg.Version, + s.jwtSecret = secret + s.rpcHandler = oprpc.NewHandler(cfg.Version, oprpc.WithLogger(s.log), oprpc.WithJWTSecret(secret[:]), + oprpc.WithWebsocketEnabled(), ) if cfg.RPC.EnableAdmin { s.log.Info("Admin RPC enabled") - server.AddAPI(rpc.API{ + if err := s.rpcHandler.AddAPI(rpc.API{ Namespace: "admin", Service: &frontend.AdminFrontend{Backend: s.backend}, Authenticated: true, - }) + }); err != nil { + return fmt.Errorf("failed to add admin API: %w", err) + } } - s.jwtSecret = secret - s.rpcServer = server + if err := s.rpcHandler.AddAPI(rpc.API{ + Namespace: "build", + Service: &frontend.BuildFrontend{Backend: s.backend}, + }); err != nil { + return fmt.Errorf("failed to add build API: %w", err) + } + return nil +} + +func (s *Service) initBackend(ctx context.Context, cfg *config.Config) error { + if cfg.MockRun { + s.backend = backend.NewMockBackend() + return nil + } + setup, err := cfg.Ensemble.Load(ctx) + if err != nil { + return fmt.Errorf("failed to load builder setup: %w", err) + } + jobs := work.NewJobRegistry() + startOpts := &work.StartOpts{ + Log: s.log, + Metrics: s.metrics, + Jobs: jobs, + } + builders, err := setup.Start(ctx, startOpts) + if err != nil { + return fmt.Errorf("failed to setup builders: %w", err) + } + s.backend = backend.NewBackend(s.log, s.metrics, builders, jobs, s.rpcHandler) + return nil +} + +func (s *Service) initHTTPServer(cfg *config.Config) error { + endpoint := net.JoinHostPort(cfg.RPC.ListenAddr, strconv.Itoa(cfg.RPC.ListenPort)) + s.httpServer = httputil.NewHTTPServer(endpoint, s.rpcHandler) return nil } func (s *Service) Start(ctx context.Context) error { s.log.Info("Starting JSON-RPC server") - if err := s.rpcServer.Start(); err != nil { + if err := s.httpServer.Start(); err != nil { return fmt.Errorf("unable to start RPC server: %w", err) } @@ -162,7 +195,7 @@ func (s *Service) Start(ctx context.Context) error { } s.metrics.RecordUp() - s.log.Info("JSON-RPC Server started", "endpoint", s.rpcServer.Endpoint()) + s.log.Info("JSON-RPC Server started", "endpoint", s.httpServer.HTTPEndpoint()) return nil } @@ -173,11 +206,14 @@ func (s *Service) Stop(ctx context.Context) error { } s.log.Info("Stopping JSON-RPC server") var result error - if s.rpcServer != nil { - if err := s.rpcServer.Stop(); err != nil { - result = errors.Join(result, fmt.Errorf("failed to stop RPC server: %w", err)) + if s.httpServer != nil { + if err := s.httpServer.Stop(ctx); err != nil { + result = errors.Join(result, fmt.Errorf("failed to stop HTTP server: %w", err)) } } + if s.rpcHandler != nil { + s.rpcHandler.Stop() + } s.log.Info("Stopped RPC Server") if s.backend != nil { if err := s.backend.Stop(ctx); err != nil { @@ -205,7 +241,5 @@ func (s *Service) Stopped() bool { } func (s *Service) RPC() string { - // the RPC endpoint is assumed to be HTTP - // TODO(#11032): make this flexible for ws if the server supports it - return "http://" + s.rpcServer.Endpoint() + return s.httpServer.HTTPEndpoint() } diff --git a/op-test-sequencer/sequencer/service_test.go b/op-test-sequencer/sequencer/service_test.go index e61ce120052..0f1b60a0260 100644 --- a/op-test-sequencer/sequencer/service_test.go +++ b/op-test-sequencer/sequencer/service_test.go @@ -51,12 +51,13 @@ func TestService(t *testing.T) { JWTSecretPath: filepath.Join(t.TempDir(), "test_jwt_secret.txt"), } logger := testlog.Logger(t, log.LevelError) + s, err := FromConfig(context.Background(), cfg, logger) require.NoError(t, err) require.NoError(t, s.Start(context.Background()), "start service") // run some RPC tests against the service with the mock backend { - endpoint := "http://" + s.rpcServer.Endpoint() + endpoint := s.httpServer.HTTPEndpoint() t.Logf("dialing %s", endpoint) opts := []client.RPCOption{ client.WithFixedDialBackoff(time.Second * 5), From 8fe61f4e9fd1e9f3b99201c95adbe73ddefa783b Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Tue, 4 Mar 2025 14:54:52 -0700 Subject: [PATCH 053/130] ctb: Split up the OPCM (#14600) * ctb: Split up the OPCM * CR updates * Update packages/contracts-bedrock/src/L1/OPContractsManager.sol Co-authored-by: smartcontracts * snapshots --------- Co-authored-by: smartcontracts --- op-chain-ops/interopgen/deployments.go | 4 + .../pkg/deployer/opcm/implementations.go | 9 +- .../pkg/deployer/pipeline/implementations.go | 3 + op-deployer/pkg/deployer/state/state.go | 3 + .../interfaces/L1/IOPContractsManager.sol | 105 +- .../L1/IOPContractsManagerInterop.sol | 20 - .../deploy/DeployImplementations.s.sol | 176 +- .../deploy/DeployOPPrestateUpdater.s.sol | 113 - .../snapshots/abi/OPContractsManager.json | 335 +-- .../OPContractsManagerContractsContainer.json | 262 +++ ...r.json => OPContractsManagerDeployer.json} | 470 +--- ...=> OPContractsManagerDeployerInterop.json} | 510 +---- .../abi/OPContractsManagerGameTypeAdder.json | 435 ++++ .../abi/OPContractsManagerUpgrader.json | 349 +++ .../snapshots/semver-lock.json | 12 +- .../storageLayout/OPContractsManager.json | 16 +- ...OPContractsManagerContractsContainer.json} | 18 +- .../OPContractsManagerDeployer.json | 1 + .../OPContractsManagerDeployerInterop.json | 1 + .../OPContractsManagerGameTypeAdder.json | 1 + .../OPContractsManagerUpgrader.json | 1 + .../storageLayout/OPPrestateUpdater.json | 30 - .../src/L1/OPContractsManager.sol | 1915 ++++++++++------- .../src/L1/OPContractsManagerInterop.sol | 79 - .../src/L1/OPPrestateUpdater.sol | 172 -- .../test/L1/OPContractsManager.t.sol | 386 +++- .../test/L1/OPPrestateUpdater.t.sol | 385 ---- .../test/universal/Specs.t.sol | 38 +- 28 files changed, 2917 insertions(+), 2932 deletions(-) delete mode 100644 packages/contracts-bedrock/interfaces/L1/IOPContractsManagerInterop.sol delete mode 100644 packages/contracts-bedrock/scripts/deploy/DeployOPPrestateUpdater.s.sol create mode 100644 packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json rename packages/contracts-bedrock/snapshots/abi/{OPPrestateUpdater.json => OPContractsManagerDeployer.json} (55%) rename packages/contracts-bedrock/snapshots/abi/{OPContractsManagerInterop.json => OPContractsManagerDeployerInterop.json} (52%) create mode 100644 packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json create mode 100644 packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json rename packages/contracts-bedrock/snapshots/storageLayout/{OPContractsManagerInterop.json => OPContractsManagerContractsContainer.json} (53%) create mode 100644 packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerDeployer.json create mode 100644 packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerDeployerInterop.json create mode 100644 packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerGameTypeAdder.json create mode 100644 packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerUpgrader.json delete mode 100644 packages/contracts-bedrock/snapshots/storageLayout/OPPrestateUpdater.json delete mode 100644 packages/contracts-bedrock/src/L1/OPContractsManagerInterop.sol delete mode 100644 packages/contracts-bedrock/src/L1/OPPrestateUpdater.sol delete mode 100644 packages/contracts-bedrock/test/L1/OPPrestateUpdater.t.sol diff --git a/op-chain-ops/interopgen/deployments.go b/op-chain-ops/interopgen/deployments.go index a4ead545ac2..6b9e34b7a87 100644 --- a/op-chain-ops/interopgen/deployments.go +++ b/op-chain-ops/interopgen/deployments.go @@ -10,6 +10,10 @@ type L1Deployment struct { type Implementations struct { Opcm common.Address `json:"OPCM"` + OpcmContractsContainer common.Address `json:"OPCMContractsContainer"` + OpcmGameTypeAdder common.Address `json:"OPCMGameTypeAdder"` + OpcmDeployer common.Address `json:"OPCMDeployer"` + OpcmUpgrader common.Address `json:"OPCMUpgrader"` DelayedWETHImpl common.Address `json:"DelayedWETHImpl"` OptimismPortalImpl common.Address `json:"OptimismPortalImpl"` PreimageOracleSingleton common.Address `json:"PreimageOracleSingleton"` diff --git a/op-deployer/pkg/deployer/opcm/implementations.go b/op-deployer/pkg/deployer/opcm/implementations.go index 7e8d70bf535..163e899fc9d 100644 --- a/op-deployer/pkg/deployer/opcm/implementations.go +++ b/op-deployer/pkg/deployer/opcm/implementations.go @@ -31,6 +31,10 @@ func (input *DeployImplementationsInput) InputSet() bool { type DeployImplementationsOutput struct { Opcm common.Address + OpcmContractsContainer common.Address + OpcmGameTypeAdder common.Address + OpcmDeployer common.Address + OpcmUpgrader common.Address DelayedWETHImpl common.Address OptimismPortalImpl common.Address PreimageOracleSingleton common.Address @@ -86,10 +90,7 @@ func DeployImplementations( defer cleanupDeploy() opcmContract := "OPContractsManager" - if input.UseInterop { - opcmContract = "OPContractsManagerInterop" - } - if err := host.RememberOnLabel("OPContractsManager", opcmContract+".sol", opcmContract); err != nil { + if err := host.RememberOnLabel("OPContractsManager", "OPContractsManager.sol", opcmContract); err != nil { return output, fmt.Errorf("failed to link OPContractsManager label: %w", err) } diff --git a/op-deployer/pkg/deployer/pipeline/implementations.go b/op-deployer/pkg/deployer/pipeline/implementations.go index 015bb950737..ef591210b6d 100644 --- a/op-deployer/pkg/deployer/pipeline/implementations.go +++ b/op-deployer/pkg/deployer/pipeline/implementations.go @@ -67,6 +67,9 @@ func DeployImplementations(env *Env, intent *state.Intent, st *state.State) erro st.ImplementationsDeployment = &state.ImplementationsDeployment{ OpcmAddress: dio.Opcm, + OpcmGameTypeAdderAddress: dio.OpcmGameTypeAdder, + OpcmDeployerAddress: dio.OpcmDeployer, + OpcmUpgraderAddress: dio.OpcmUpgrader, DelayedWETHImplAddress: dio.DelayedWETHImpl, OptimismPortalImplAddress: dio.OptimismPortalImpl, PreimageOracleSingletonAddress: dio.PreimageOracleSingleton, diff --git a/op-deployer/pkg/deployer/state/state.go b/op-deployer/pkg/deployer/state/state.go index e2766eba65e..0ec6c923f30 100644 --- a/op-deployer/pkg/deployer/state/state.go +++ b/op-deployer/pkg/deployer/state/state.go @@ -71,6 +71,9 @@ type SuperchainDeployment struct { type ImplementationsDeployment struct { OpcmAddress common.Address `json:"opcmAddress"` + OpcmGameTypeAdderAddress common.Address `json:"opcmGameTypeAdderAddress"` + OpcmDeployerAddress common.Address `json:"opcmDeployerAddress"` + OpcmUpgraderAddress common.Address `json:"opcmUpgraderAddress"` DelayedWETHImplAddress common.Address `json:"delayedWETHImplAddress"` OptimismPortalImplAddress common.Address `json:"optimismPortalImplAddress"` PreimageOracleSingletonAddress common.Address `json:"preimageOracleSingletonAddress"` diff --git a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol index d3e92ae006a..37701010a88 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol @@ -10,7 +10,6 @@ import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; -import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; @@ -23,6 +22,65 @@ import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; +interface IOPContractsManagerContractsContainer { + function __constructor__( + IOPContractsManager.Blueprints memory _blueprints, + IOPContractsManager.Implementations memory _implementations + ) + external; + + function blueprints() external view returns (IOPContractsManager.Blueprints memory); + function implementations() external view returns (IOPContractsManager.Implementations memory); +} + +interface IOPContractsManagerGameTypeAdder { + event GameTypeAdded( + uint256 indexed l2ChainId, GameType indexed gameType, address newDisputeGame, address oldDisputeGame + ); + + function __constructor__( + IOPContractsManagerContractsContainer _contractsContainer + ) + external; + + function addGameType(IOPContractsManager.AddGameInput[] memory _gameConfigs, address _superchainConfig) + external + returns (IOPContractsManager.AddGameOutput[] memory); + + function updatePrestate(IOPContractsManager.OpChainConfig[] memory _prestateUpdateInputs, address _superchainConfig) external; + + function contractsContainer() external view returns (IOPContractsManagerContractsContainer); +} + +interface IOPContractsManagerDeployer { + event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); + + function __constructor__( + IOPContractsManagerContractsContainer _contractsContainer + ) + external; + + function deploy(IOPContractsManager.DeployInput memory _input, address _superchainConfig, address _deployer) + external + returns (IOPContractsManager.DeployOutput memory); + + function contractsContainer() external view returns (IOPContractsManagerContractsContainer); +} + +interface IOPContractsManagerUpgrader { + event Upgraded(uint256 indexed l2ChainId, address indexed systemConfig, address indexed upgrader); + + function __constructor__( + IOPContractsManagerContractsContainer _contractsContainer + ) + external; + + function upgrade(IOPContractsManager.OpChainConfig[] memory _opChainConfigs) external; + + function contractsContainer() external view returns (IOPContractsManagerContractsContainer); +} + + interface IOPContractsManager { // -------- Structs -------- @@ -153,36 +211,8 @@ interface IOPContractsManager { /// version of the L1 smart contracts is deployed. It takes the format of `op-contracts/vX.Y.Z`. function l1ContractsRelease() external view returns (string memory); - // -------- Events -------- - - /// @notice Emitted when a new OP Stack chain is deployed. - /// @param l2ChainId Chain ID of the new chain. - /// @param deployer Address that deployed the chain. - /// @param deployOutput ABI-encoded output of the deployment. - event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); - - /// @notice Emitted when a chain is upgraded - /// @param systemConfig Address of the chain's SystemConfig contract - /// @param upgrader Address that initiated the upgrade - event Upgraded(uint256 indexed l2ChainId, ISystemConfig indexed systemConfig, address indexed upgrader); - - /// @notice Emitted when a new game type is added to a chain - /// @param l2ChainId Chain ID of the chain - /// @param gameType Type of the game being added - /// @param newDisputeGame Address of the deployed dispute game - /// @param oldDisputeGame Address of the old dispute game - event GameTypeAdded(uint256 indexed l2ChainId, GameType indexed gameType, IDisputeGame newDisputeGame, IDisputeGame oldDisputeGame); - // -------- Errors -------- - error BytesArrayTooLong(); - error DeploymentFailed(); - error EmptyInitcode(); - error IdentityPrecompileCallFailed(); - error NotABlueprint(); - error ReservedBitsSet(); - error UnexpectedPreambleData(bytes data); - error UnsupportedERCVersion(uint8 version); error OnlyUpgradeController(); /// @notice Thrown when an address is the zero address. @@ -219,15 +249,18 @@ interface IOPContractsManager { error PrestateNotSet(); + error PrestateRequired(); + // -------- Methods -------- function __constructor__( + IOPContractsManagerGameTypeAdder _opcmGameTypeAdder, + IOPContractsManagerDeployer _opcmDeployer, + IOPContractsManagerUpgrader _opcmUpgrader, ISuperchainConfig _superchainConfig, IProtocolVersions _protocolVersions, IProxyAdmin _superchainProxyAdmin, string memory _l1ContractsRelease, - Blueprints memory _blueprints, - Implementations memory _implementations, address _upgradeController ) external; @@ -242,6 +275,10 @@ interface IOPContractsManager { /// must be added in ascending GameType order. function addGameType(AddGameInput[] memory _gameConfigs) external returns (AddGameOutput[] memory); + /// @notice Updates the prestate hash for a new game type while keeping all other parameters the same + /// @param _prestateUpdateInputs The new prestate hash to use + function updatePrestate(OpChainConfig[] memory _prestateUpdateInputs) external; + /// @notice Maps an L2 chain ID to an L1 batch inbox address as defined by the standard /// configuration's convention. This convention is `versionByte || keccak256(bytes32(chainId))[:19]`, /// where || denotes concatenation`, versionByte is 0x00, and chainId is a uint256. @@ -251,6 +288,12 @@ interface IOPContractsManager { /// @notice Returns the blueprint contract addresses. function blueprints() external view returns (Blueprints memory); + function opcmDeployer() external view returns (IOPContractsManagerDeployer); + + function opcmUpgrader() external view returns (IOPContractsManagerUpgrader); + + function opcmGameTypeAdder() external view returns (IOPContractsManagerGameTypeAdder); + /// @notice Returns the implementation contract addresses. function implementations() external view returns (Implementations memory); diff --git a/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerInterop.sol b/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerInterop.sol deleted file mode 100644 index 7e323f70de2..00000000000 --- a/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerInterop.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; -import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; -import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; - -interface IOPContractsManagerInterop is IOPContractsManager { - function __constructor__( - ISuperchainConfig _superchainConfig, - IProtocolVersions _protocolVersions, - IProxyAdmin _superchainProxyAdmin, - string memory _l1ContractsRelease, - Blueprints memory _blueprints, - Implementations memory _implementations, - address _upgradeController - ) - external; -} diff --git a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol index e94a4c18593..a8fd5a74300 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol @@ -17,8 +17,13 @@ import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; import { IMIPS } from "interfaces/cannon/IMIPS.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; -import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; -import { IOPContractsManagerInterop } from "interfaces/L1/IOPContractsManagerInterop.sol"; +import { + IOPContractsManager, + IOPContractsManagerGameTypeAdder, + IOPContractsManagerDeployer, + IOPContractsManagerUpgrader, + IOPContractsManagerContractsContainer +} from "interfaces/L1/IOPContractsManager.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; @@ -148,6 +153,10 @@ contract DeployImplementationsInput is BaseDeployIO { contract DeployImplementationsOutput is BaseDeployIO { IOPContractsManager internal _opcm; + IOPContractsManagerContractsContainer internal _opcmContractsContainer; + IOPContractsManagerGameTypeAdder internal _opcmGameTypeAdder; + IOPContractsManagerDeployer internal _opcmDeployer; + IOPContractsManagerUpgrader internal _opcmUpgrader; IDelayedWETH internal _delayedWETHImpl; IOptimismPortal2 internal _optimismPortalImpl; IPreimageOracle internal _preimageOracleSingleton; @@ -167,6 +176,10 @@ contract DeployImplementationsOutput is BaseDeployIO { // forgefmt: disable-start if (_sel == this.opcm.selector) _opcm = IOPContractsManager(_addr); + else if (_sel == this.opcmContractsContainer.selector) _opcmContractsContainer = IOPContractsManagerContractsContainer(_addr); + else if (_sel == this.opcmGameTypeAdder.selector) _opcmGameTypeAdder = IOPContractsManagerGameTypeAdder(_addr); + else if (_sel == this.opcmDeployer.selector) _opcmDeployer = IOPContractsManagerDeployer(_addr); + else if (_sel == this.opcmUpgrader.selector) _opcmUpgrader = IOPContractsManagerUpgrader(_addr); else if (_sel == this.superchainConfigImpl.selector) _superchainConfigImpl = ISuperchainConfig(_addr); else if (_sel == this.protocolVersionsImpl.selector) _protocolVersionsImpl = IProtocolVersions(_addr); else if (_sel == this.optimismPortalImpl.selector) _optimismPortalImpl = IOptimismPortal2(payable(_addr)); @@ -217,6 +230,26 @@ contract DeployImplementationsOutput is BaseDeployIO { return _opcm; } + function opcmContractsContainer() public view returns (IOPContractsManagerContractsContainer) { + DeployUtils.assertValidContractAddress(address(_opcmContractsContainer)); + return _opcmContractsContainer; + } + + function opcmGameTypeAdder() public view returns (IOPContractsManagerGameTypeAdder) { + DeployUtils.assertValidContractAddress(address(_opcmGameTypeAdder)); + return _opcmGameTypeAdder; + } + + function opcmDeployer() public view returns (IOPContractsManagerDeployer) { + DeployUtils.assertValidContractAddress(address(_opcmDeployer)); + return _opcmDeployer; + } + + function opcmUpgrader() public view returns (IOPContractsManagerUpgrader) { + DeployUtils.assertValidContractAddress(address(_opcmUpgrader)); + return _opcmUpgrader; + } + function superchainConfigImpl() public view returns (ISuperchainConfig) { DeployUtils.assertValidContractAddress(address(_superchainConfigImpl)); return _superchainConfigImpl; @@ -478,11 +511,6 @@ contract DeployImplementations is Script { virtual returns (IOPContractsManager opcm_) { - ISuperchainConfig superchainConfigProxy = _dii.superchainConfigProxy(); - IProtocolVersions protocolVersionsProxy = _dii.protocolVersionsProxy(); - IProxyAdmin superchainProxyAdmin = _dii.superchainProxyAdmin(); - address upgradeController = _dii.upgradeController(); - IOPContractsManager.Implementations memory implementations = IOPContractsManager.Implementations({ superchainConfigImpl: address(_dio.superchainConfigImpl()), protocolVersionsImpl: address(_dio.protocolVersionsImpl()), @@ -498,6 +526,11 @@ contract DeployImplementations is Script { mipsImpl: address(_dio.mipsSingleton()) }); + deployOPCMBPImplsContainer(_dio, _blueprints, implementations); + deployOPCMGameTypeAdder(_dio); + deployOPCMDeployer(_dio); + deployOPCMUpgrader(_dio); + opcm_ = IOPContractsManager( DeployUtils.createDeterministic({ _name: "OPContractsManager", @@ -505,13 +538,14 @@ contract DeployImplementations is Script { abi.encodeCall( IOPContractsManager.__constructor__, ( - superchainConfigProxy, - protocolVersionsProxy, - superchainProxyAdmin, + _dio.opcmGameTypeAdder(), + _dio.opcmDeployer(), + _dio.opcmUpgrader(), + _dii.superchainConfigProxy(), + _dii.protocolVersionsProxy(), + _dii.superchainProxyAdmin(), _l1ContractsRelease, - _blueprints, - implementations, - upgradeController + _dii.upgradeController() ) ) ), @@ -788,6 +822,68 @@ contract DeployImplementations is Script { _dio.set(_dio.anchorStateRegistryImpl.selector, address(impl)); } + function deployOPCMBPImplsContainer( + DeployImplementationsOutput _dio, + IOPContractsManager.Blueprints memory _blueprints, + IOPContractsManager.Implementations memory _implementations + ) + public + virtual + { + IOPContractsManagerContractsContainer impl = IOPContractsManagerContractsContainer( + DeployUtils.createDeterministic({ + _name: "OPContractsManager.sol:OPContractsManagerContractsContainer", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IOPContractsManagerContractsContainer.__constructor__, (_blueprints, _implementations)) + ), + _salt: _salt + }) + ); + vm.label(address(impl), "OPContractsManagerBPImplsContainerImpl"); + _dio.set(_dio.opcmContractsContainer.selector, address(impl)); + } + + function deployOPCMGameTypeAdder(DeployImplementationsOutput _dio) public virtual { + IOPContractsManagerGameTypeAdder impl = IOPContractsManagerGameTypeAdder( + DeployUtils.createDeterministic({ + _name: "OPContractsManager.sol:OPContractsManagerGameTypeAdder", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IOPContractsManagerGameTypeAdder.__constructor__, (_dio.opcmContractsContainer())) + ), + _salt: _salt + }) + ); + vm.label(address(impl), "OPContractsManagerGameTypeAdderImpl"); + _dio.set(_dio.opcmGameTypeAdder.selector, address(impl)); + } + + function deployOPCMDeployer(DeployImplementationsOutput _dio) public virtual { + IOPContractsManagerDeployer impl = IOPContractsManagerDeployer( + DeployUtils.createDeterministic({ + _name: "OPContractsManager.sol:OPContractsManagerDeployer", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IOPContractsManagerDeployer.__constructor__, (_dio.opcmContractsContainer())) + ), + _salt: _salt + }) + ); + vm.label(address(impl), "OPContractsManagerDeployerImpl"); + _dio.set(_dio.opcmDeployer.selector, address(impl)); + } + + function deployOPCMUpgrader(DeployImplementationsOutput _dio) public virtual { + IOPContractsManagerUpgrader impl = IOPContractsManagerUpgrader( + DeployUtils.createDeterministic({ + _name: "OPContractsManager.sol:OPContractsManagerUpgrader", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IOPContractsManagerUpgrader.__constructor__, (_dio.opcmContractsContainer())) + ), + _salt: _salt + }) + ); + vm.label(address(impl), "OPContractsManagerUpgraderImpl"); + _dio.set(_dio.opcmUpgrader.selector, address(impl)); + } // -------- Utilities -------- function etchIOContracts() public returns (DeployImplementationsInput dii_, DeployImplementationsOutput dio_) { @@ -845,60 +941,18 @@ contract DeployImplementations is Script { // resolve https://github.com/ethereum-optimism/optimism/issues/11783, we just assume this new role // is the same as the proxy admin owner. contract DeployImplementationsInterop is DeployImplementations { - function createOPCMContract( - DeployImplementationsInput _dii, - DeployImplementationsOutput _dio, - IOPContractsManager.Blueprints memory _blueprints, - string memory _l1ContractsRelease - ) - internal - virtual - override - returns (IOPContractsManager opcm_) - { - ISuperchainConfig superchainConfigProxy = _dii.superchainConfigProxy(); - IProtocolVersions protocolVersionsProxy = _dii.protocolVersionsProxy(); - IProxyAdmin superchainProxyAdmin = _dii.superchainProxyAdmin(); - address upgradeController = _dii.upgradeController(); - - IOPContractsManager.Implementations memory implementations = IOPContractsManager.Implementations({ - superchainConfigImpl: address(_dio.superchainConfigImpl()), - protocolVersionsImpl: address(_dio.protocolVersionsImpl()), - l1ERC721BridgeImpl: address(_dio.l1ERC721BridgeImpl()), - optimismPortalImpl: address(_dio.optimismPortalImpl()), - systemConfigImpl: address(_dio.systemConfigImpl()), - optimismMintableERC20FactoryImpl: address(_dio.optimismMintableERC20FactoryImpl()), - l1CrossDomainMessengerImpl: address(_dio.l1CrossDomainMessengerImpl()), - l1StandardBridgeImpl: address(_dio.l1StandardBridgeImpl()), - disputeGameFactoryImpl: address(_dio.disputeGameFactoryImpl()), - anchorStateRegistryImpl: address(_dio.anchorStateRegistryImpl()), - delayedWETHImpl: address(_dio.delayedWETHImpl()), - mipsImpl: address(_dio.mipsSingleton()) - }); - - opcm_ = IOPContractsManager( + function deployOPCMDeployer(DeployImplementationsOutput _dio) public override { + IOPContractsManagerDeployer impl = IOPContractsManagerDeployer( DeployUtils.createDeterministic({ - _name: "OPContractsManagerInterop", + _name: "OPContractsManager.sol:OPContractsManagerDeployerInterop", _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IOPContractsManagerInterop.__constructor__, - ( - superchainConfigProxy, - protocolVersionsProxy, - superchainProxyAdmin, - _l1ContractsRelease, - _blueprints, - implementations, - upgradeController - ) - ) + abi.encodeCall(IOPContractsManagerDeployer.__constructor__, (_dio.opcmContractsContainer())) ), _salt: _salt }) ); - - vm.label(address(opcm_), "OPContractsManager"); - _dio.set(_dio.opcm.selector, address(opcm_)); + vm.label(address(impl), "OPContractsManagerDeployerImpl"); + _dio.set(_dio.opcmDeployer.selector, address(impl)); } function deployOptimismPortalImpl( diff --git a/packages/contracts-bedrock/scripts/deploy/DeployOPPrestateUpdater.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployOPPrestateUpdater.s.sol deleted file mode 100644 index d5915ec761c..00000000000 --- a/packages/contracts-bedrock/scripts/deploy/DeployOPPrestateUpdater.s.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -import { console2 as console } from "forge-std/console2.sol"; - -// Scripting -import { Script } from "forge-std/Script.sol"; - -// Libraries -import { LibString } from "@solady/utils/LibString.sol"; - -// Scripts -import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; - -// Interfaces -import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; -import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; -import { IOPPrestateUpdater } from "interfaces/L1/IOPPrestateUpdater.sol"; -import { IOPContractsManager180 } from "interfaces/L1/IOPContractsManager180.sol"; - -// Contracts -import { OPPrestateUpdater } from "src/L1/OPPrestateUpdater.sol"; - -contract DeployOPPrestateUpdater is Script { - bytes32 internal _salt = DeployUtils.DEFAULT_SALT; - - function deployOPPrestateUpdater(string memory _baseChain) public returns (OPPrestateUpdater) { - string memory superchainBasePath = "./lib/superchain-registry/superchain/configs/"; - string memory superchainToml = vm.readFile(string.concat(superchainBasePath, _baseChain, "/superchain.toml")); - - // Superchain shared contracts - ISuperchainConfig superchainConfig = - ISuperchainConfig(vm.parseTomlAddress(superchainToml, ".superchain_config_addr")); - IProtocolVersions protocolVersions = - IProtocolVersions(vm.parseTomlAddress(superchainToml, ".protocol_versions_addr")); - - // The existing v1.8.0 OPCM - IOPContractsManager180 opContractsManager180 = - IOPContractsManager180(vm.parseTomlAddress(superchainToml, ".op_contracts_manager_proxy_addr")); - - // Declare a new set of blueprints to store in the OPPrestateUpdater - IOPContractsManager.Blueprints memory blueprints; - blueprints.addressManager = address(0); - blueprints.proxy = address(0); - blueprints.proxyAdmin = address(0); - blueprints.l1ChugSplashProxy = address(0); - blueprints.resolvedDelegateProxy = address(0); - - IOPContractsManager180.Blueprints memory blueprints180 = opContractsManager180.blueprints(); - blueprints.permissionedDisputeGame1 = blueprints180.permissionedDisputeGame1; - blueprints.permissionedDisputeGame2 = blueprints180.permissionedDisputeGame2; - - // forgefmt: disable-start - vm.startBroadcast(msg.sender); - bytes memory faultDisputeGameInitcode = hex"6101c06040523480156200001257600080fd5b506040516200648a3803806200648a833981016040819052620000359162000436565b620000436001607e6200050d565b60ff168811156200006757604051633beff19960e11b815260040160405180910390fd5b600019871480620000845750876200008188600162000533565b10155b15620000a35760405163e62ccf3960e01b815260040160405180910390fd5b6002871015620000c65760405163e62ccf3960e01b815260040160405180910390fd5b6001600160401b038016846001600160a01b0316637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200010f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200013591906200054e565b6001600160a01b031663f3f480d96040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000173573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200019991906200056e565b1115620001b95760405163b4e1243360e01b815260040160405180910390fd5b6000620001da876001600160401b0316620003e260201b62000c891760201c565b620001f0906001600160401b0316600262000588565b90506000856001600160a01b0316637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000233573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200025991906200054e565b6001600160a01b031663f3f480d96040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000297573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620002bd91906200056e565b620002dc896001600160401b0316620003e260201b62000c891760201c565b6001600160401b0316620002f1919062000533565b905060006200030c8383620003e560201b62003a261760201c565b90506001600160401b03811115620003375760405163235dfb2b60e21b815260040160405180910390fd5b62000356886001600160401b0316620003e260201b62000c891760201c565b6001600160401b0316816001600160401b03161115620003895760405163235dfb2b60e21b815260040160405180910390fd5b50505063ffffffff9099166101205260809790975260a09590955260c0939093526001600160401b039182166101a0521660e0526001600160a01b039081166101005290811661014052166101605261018052620005aa565b90565b600081831015620003f75781620003f9565b825b9392505050565b80516001600160401b03811681146200041857600080fd5b919050565b6001600160a01b03811681146200043357600080fd5b50565b6000806000806000806000806000806101408b8d0312156200045757600080fd5b8a5163ffffffff811681146200046c57600080fd5b809a505060208b0151985060408b0151975060608b015196506200049360808c0162000400565b9550620004a360a08c0162000400565b945060c08b0151620004b5816200041d565b60e08c0151909450620004c8816200041d565b6101008c0151909350620004dc816200041d565b809250506101208b015190509295989b9194979a5092959850565b634e487b7160e01b600052601160045260246000fd5b600060ff821660ff8416808210156200052a576200052a620004f7565b90039392505050565b60008219821115620005495762000549620004f7565b500190565b6000602082840312156200056157600080fd5b8151620003f9816200041d565b6000602082840312156200058157600080fd5b5051919050565b6000816000190483118215151615620005a557620005a5620004f7565b500290565b60805160a05160c05160e05161010051610120516101405161016051610180516101a051615d5a62000730600039600081816106bb01528181611fbb015281816120260152612059015260008181610a1e01526139390152600081816106130152818161178a01526125e80152600081816105270152818161194c01528181612480015281816129ea0152613e1401526000818161088d015281816125a701526139c80152600081816104b401528181611eba015281816132420152613597015260008181610a7101528181610f6001528181611e2a01528181612089015281816120e801528181612bf60152612c38015260008181610aa401528181611c7301528181611d9901528181611ff7015281816130950152818161379601528181613f0b015281816146280152818161475601528181614857015261492c015260008181610b4b01528181611d3c01528181611e8e01528181612d0601528181612d8c01528181612f8b01526130b601526000818161078601526131540152615d5a6000f3fe6080604052600436106102f25760003560e01c806370872aa51161018f578063c6f0308c116100e1578063ec5e63081161008a578063fa24f74311610064578063fa24f74314610b18578063fa315aa914610b3c578063fe2bbeb214610b6f57600080fd5b8063ec5e630814610a95578063eff0f59214610ac8578063f8f43ff614610af857600080fd5b8063d6ae3cd5116100bb578063d6ae3cd514610a0f578063d8cc1a3c14610a42578063dabd396d14610a6257600080fd5b8063c6f0308c14610937578063cf09e0d0146109c1578063d5d44d80146109e257600080fd5b80638d450a9511610143578063bcef3b551161011d578063bcef3b55146108b7578063bd8da956146108f7578063c395e1ca1461091757600080fd5b80638d450a9514610777578063a445ece6146107aa578063bbdc02db1461087657600080fd5b80638129fc1c116101745780638129fc1c1461071a5780638980e0cc146107225780638b85902b1461073757600080fd5b806370872aa5146106f25780637b0f0adc1461070757600080fd5b80633fc8cef3116102485780635c0cba33116101fc5780636361506d116101d65780636361506d1461066c5780636b6716c0146106ac5780636f034409146106df57600080fd5b80635c0cba3314610604578063609d33341461063757806360e274641461064c57600080fd5b806354fd4d501161022d57806354fd4d501461055e57806357da950e146105b45780635a5fa2d9146105e457600080fd5b80633fc8cef314610518578063472777c61461054b57600080fd5b80632810e1d6116102aa57806337b1b2291161028457806337b1b229146104655780633a768463146104a55780633e3ac912146104d857600080fd5b80632810e1d6146103de5780632ad69aeb146103f357806330dbe5701461041357600080fd5b806319effeb4116102db57806319effeb414610339578063200d2ed21461038457806325fc2ace146103bf57600080fd5b806301935130146102f757806303c2924d14610319575b600080fd5b34801561030357600080fd5b506103176103123660046154e2565b610b9f565b005b34801561032557600080fd5b5061031761033436600461553d565b610ec0565b34801561034557600080fd5b506000546103669068010000000000000000900467ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020015b60405180910390f35b34801561039057600080fd5b506000546103b290700100000000000000000000000000000000900460ff1681565b60405161037b919061558e565b3480156103cb57600080fd5b506008545b60405190815260200161037b565b3480156103ea57600080fd5b506103b2611566565b3480156103ff57600080fd5b506103d061040e36600461553d565b61180b565b34801561041f57600080fd5b506001546104409073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200161037b565b34801561047157600080fd5b50367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90033560601c610440565b3480156104b157600080fd5b507f0000000000000000000000000000000000000000000000000000000000000000610440565b3480156104e457600080fd5b50600054610508907201000000000000000000000000000000000000900460ff1681565b604051901515815260200161037b565b34801561052457600080fd5b507f0000000000000000000000000000000000000000000000000000000000000000610440565b6103176105593660046155cf565b611841565b34801561056a57600080fd5b506105a76040518060400160405280600581526020017f312e332e3100000000000000000000000000000000000000000000000000000081525081565b60405161037b9190615666565b3480156105c057600080fd5b506008546009546105cf919082565b6040805192835260208301919091520161037b565b3480156105f057600080fd5b506103d06105ff366004615679565b611853565b34801561061057600080fd5b507f0000000000000000000000000000000000000000000000000000000000000000610440565b34801561064357600080fd5b506105a761188d565b34801561065857600080fd5b506103176106673660046156b7565b61189b565b34801561067857600080fd5b50367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003603401356103d0565b3480156106b857600080fd5b507f0000000000000000000000000000000000000000000000000000000000000000610366565b6103176106ed3660046156e9565b611a42565b3480156106fe57600080fd5b506009546103d0565b6103176107153660046155cf565b61251b565b610317612528565b34801561072e57600080fd5b506002546103d0565b34801561074357600080fd5b50367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003605401356103d0565b34801561078357600080fd5b507f00000000000000000000000000000000000000000000000000000000000000006103d0565b3480156107b657600080fd5b506108226107c5366004615679565b6007602052600090815260409020805460019091015460ff821691610100810463ffffffff1691650100000000009091046fffffffffffffffffffffffffffffffff169073ffffffffffffffffffffffffffffffffffffffff1684565b60408051941515855263ffffffff90931660208501526fffffffffffffffffffffffffffffffff9091169183019190915273ffffffffffffffffffffffffffffffffffffffff16606082015260800161037b565b34801561088257600080fd5b5060405163ffffffff7f000000000000000000000000000000000000000000000000000000000000000016815260200161037b565b3480156108c357600080fd5b50367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003601401356103d0565b34801561090357600080fd5b50610366610912366004615679565b612a81565b34801561092357600080fd5b506103d0610932366004615728565b612c60565b34801561094357600080fd5b50610957610952366004615679565b612e43565b6040805163ffffffff909816885273ffffffffffffffffffffffffffffffffffffffff968716602089015295909416948601949094526fffffffffffffffffffffffffffffffff9182166060860152608085015291821660a08401521660c082015260e00161037b565b3480156109cd57600080fd5b506000546103669067ffffffffffffffff1681565b3480156109ee57600080fd5b506103d06109fd3660046156b7565b60036020526000908152604090205481565b348015610a1b57600080fd5b507f00000000000000000000000000000000000000000000000000000000000000006103d0565b348015610a4e57600080fd5b50610317610a5d36600461575a565b612eda565b348015610a6e57600080fd5b507f0000000000000000000000000000000000000000000000000000000000000000610366565b348015610aa157600080fd5b507f00000000000000000000000000000000000000000000000000000000000000006103d0565b348015610ad457600080fd5b50610508610ae3366004615679565b60046020526000908152604090205460ff1681565b348015610b0457600080fd5b50610317610b133660046155cf565b613509565b348015610b2457600080fd5b50610b2d6139c6565b60405161037b939291906157e4565b348015610b4857600080fd5b507f00000000000000000000000000000000000000000000000000000000000000006103d0565b348015610b7b57600080fd5b50610508610b8a366004615679565b60066020526000908152604090205460ff1681565b60008054700100000000000000000000000000000000900460ff166002811115610bcb57610bcb61555f565b14610c02576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000547201000000000000000000000000000000000000900460ff1615610c55576040517f0ea2e75200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610c8c367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90036014013590565b90565b610ca3610c9e36869003860186615838565b613a41565b14610cda576040517f9cc00b5b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82606001358282604051610cef9291906158c5565b604051809103902014610d2e576040517fd81d583b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610d77610d7284848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250613a9d92505050565b613b0a565b90506000610d9e82600881518110610d9157610d916158d5565b6020026020010151613cc0565b9050602081511115610ddc576040517fd81d583b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b602081810151825190910360031b1c367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003605401358103610e51576040517fb8ed883000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5050600180547fffffffffffffffffffffffff000000000000000000000000000000000000000016331790555050600080547fffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffff1672010000000000000000000000000000000000001790555050565b60008054700100000000000000000000000000000000900460ff166002811115610eec57610eec61555f565b14610f23576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060028381548110610f3857610f386158d5565b906000526020600020906005020190506000610f5384612a81565b905067ffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000081169082161015610fbc576040517ff2440b5300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008481526006602052604090205460ff1615611005576040517ff1a9458100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600084815260056020526040902080548015801561102257508515155b156110bd578354640100000000900473ffffffffffffffffffffffffffffffffffffffff16600081156110555781611071565b600186015473ffffffffffffffffffffffffffffffffffffffff165b905061107d8187613d74565b50505060009485525050600660205250506040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b6000868152600760209081526040918290208251608081018452815460ff81161515808352610100820463ffffffff16948301949094526501000000000090046fffffffffffffffffffffffffffffffff16938101939093526001015473ffffffffffffffffffffffffffffffffffffffff166060830152611160576fffffffffffffffffffffffffffffffff6040820152600181526000869003611160578195505b600086826020015163ffffffff166111789190615933565b90506000838211611189578161118b565b835b602084015190915063ffffffff165b818110156112d75760008682815481106111b6576111b66158d5565b6000918252602080832090910154808352600690915260409091205490915060ff1661120e576040517f9a07664600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060028281548110611223576112236158d5565b600091825260209091206005909102018054909150640100000000900473ffffffffffffffffffffffffffffffffffffffff161580156112805750600481015460408701516fffffffffffffffffffffffffffffffff9182169116115b156112c257600181015473ffffffffffffffffffffffffffffffffffffffff16606087015260048101546fffffffffffffffffffffffffffffffff1660408701525b505080806112cf9061594b565b91505061119a565b5063ffffffff818116602085810191825260008c81526007909152604090819020865181549351928801517fffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000009094169015157fffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000ff161761010092909416918202939093177fffffffffffffffffffffff00000000000000000000000000000000ffffffffff16650100000000006fffffffffffffffffffffffffffffffff909316929092029190911782556060850151600190920180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff9093169290921790915584900361155b57606083015160008a815260066020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790558915801561145757506000547201000000000000000000000000000000000000900460ff165b156114cc5760015473ffffffffffffffffffffffffffffffffffffffff1661147f818a613d74565b885473ffffffffffffffffffffffffffffffffffffffff909116640100000000027fffffffffffffffff0000000000000000000000000000000000000000ffffffff909116178855611559565b61151373ffffffffffffffffffffffffffffffffffffffff8216156114f1578161150d565b600189015473ffffffffffffffffffffffffffffffffffffffff165b89613d74565b87547fffffffffffffffff0000000000000000000000000000000000000000ffffffff1664010000000073ffffffffffffffffffffffffffffffffffffffff8316021788555b505b505050505050505050565b600080600054700100000000000000000000000000000000900460ff1660028111156115945761159461555f565b146115cb576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000805260066020527f54cdd369e4e8a8515e52ca72ec816c2101831ad1f18bf44102ed171459c9b4f85460ff1661162f576040517f9a07664600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008154811061165b5761165b6158d5565b6000918252602090912060059091020154640100000000900473ffffffffffffffffffffffffffffffffffffffff1614611696576001611699565b60025b6000805467ffffffffffffffff421668010000000000000000027fffffffffffffffffffffffffffffffff0000000000000000ffffffffffffffff82168117835592935083927fffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffff000000000000000000ffffffffffffffff9091161770010000000000000000000000000000000083600281111561174a5761174a61555f565b02179055600281111561175f5761175f61555f565b6040517f5e186f09b9c93491f14e277eea7faa5de6a2d4bda75a79af7a3684fbfb42da6090600090a27f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663838c2d1e6040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156117f057600080fd5b505af1158015611804573d6000803e3d6000fd5b5050505090565b6005602052816000526040600020818154811061182757600080fd5b90600052602060002001600091509150505481565b905090565b61184e8383836001611a42565b505050565b6000818152600760209081526040808320600590925282208054825461188490610100900463ffffffff1682615983565b95945050505050565b606061183c60546020613e75565b73ffffffffffffffffffffffffffffffffffffffff8116600090815260036020526040812080549082905590819003611900576040517f17bfe5f700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517ff3fef3a300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8381166004830152602482018390527f0000000000000000000000000000000000000000000000000000000000000000169063f3fef3a390604401600060405180830381600087803b15801561199057600080fd5b505af11580156119a4573d6000803e3d6000fd5b5050505060008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d8060008114611a02576040519150601f19603f3d011682016040523d82523d6000602084013e611a07565b606091505b505090508061184e576040517f83e6cc6b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008054700100000000000000000000000000000000900460ff166002811115611a6e57611a6e61555f565b14611aa5576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060028481548110611aba57611aba6158d5565b60009182526020918290206040805160e0810182526005909302909101805463ffffffff8116845273ffffffffffffffffffffffffffffffffffffffff64010000000090910481169484019490945260018101549093169082015260028201546fffffffffffffffffffffffffffffffff908116606083015260038301546080830181905260049093015480821660a084015270010000000000000000000000000000000090041660c082015291508514611ba1576040517f3014033200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60a0810151600083156fffffffffffffffffffffffffffffffff83161760011b90506000611c61826fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff169050861580611c9c5750611c997f00000000000000000000000000000000000000000000000000000000000000006002615933565b81145b8015611ca6575084155b15611cdd576040517fa42637bc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000547201000000000000000000000000000000000000900460ff168015611d03575086155b15611d3a576040517f0ea2e75200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f0000000000000000000000000000000000000000000000000000000000000000811115611d94576040517f56f57b2b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611dbf7f00000000000000000000000000000000000000000000000000000000000000006001615933565b8103611dd157611dd186888588613ec7565b34611ddb83612c60565b14611e12576040517f8620aa1900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000611e1d88612a81565b905067ffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000811690821603611e85576040517f3381d11400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000611eb260017f0000000000000000000000000000000000000000000000000000000000000000615983565b8303611ff0577f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa158015611f23573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611f47919061599a565b73ffffffffffffffffffffffffffffffffffffffff1663f3f480d96040518163ffffffff1660e01b8152600401602060405180830381865afa158015611f91573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611fb591906159b7565b611fe9907f000000000000000000000000000000000000000000000000000000000000000067ffffffffffffffff166159d0565b9050612083565b61201b60017f0000000000000000000000000000000000000000000000000000000000000000615983565b830361205657611fe97f000000000000000000000000000000000000000000000000000000000000000067ffffffffffffffff1660026159fc565b507f000000000000000000000000000000000000000000000000000000000000000067ffffffffffffffff165b6120b7817f000000000000000000000000000000000000000000000000000000000000000067ffffffffffffffff16615a2c565b67ffffffffffffffff166120d28367ffffffffffffffff1690565b67ffffffffffffffff16111561211957612116817f000000000000000000000000000000000000000000000000000000000000000067ffffffffffffffff16615a2c565b91505b6000604083901b421760008a8152608087901b6fffffffffffffffffffffffffffffffff8d1617602052604081209192509060008181526004602052604090205490915060ff1615612197576040517f80497e3b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60016004600083815260200190815260200160002060006101000a81548160ff02191690831515021790555060026040518060e001604052808d63ffffffff168152602001600073ffffffffffffffffffffffffffffffffffffffff1681526020013373ffffffffffffffffffffffffffffffffffffffff168152602001346fffffffffffffffffffffffffffffffff1681526020018c8152602001886fffffffffffffffffffffffffffffffff168152602001846fffffffffffffffffffffffffffffffff16815250908060018154018082558091505060019003906000526020600020906005020160009091909190915060008201518160000160006101000a81548163ffffffff021916908363ffffffff16021790555060208201518160000160046101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060408201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060608201518160020160006101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff1602179055506080820151816003015560a08201518160040160006101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff16021790555060c08201518160040160106101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff1602179055505050600560008c8152602001908152602001600020600160028054905061242d9190615983565b81546001810183556000928352602083200155604080517fd0e30db0000000000000000000000000000000000000000000000000000000008152905173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000169263d0e30db09234926004808301939282900301818588803b1580156124c557600080fd5b505af11580156124d9573d6000803e3d6000fd5b50506040513393508d92508e91507f9b3245740ec3b155098a55be84957a4da13eaf7f14a8bc6f53126c0b9350f2be90600090a4505050505050505050505050565b61184e8383836000611a42565b60005471010000000000000000000000000000000000900460ff161561257a576040517f0dc149f000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f7258a80700000000000000000000000000000000000000000000000000000000815263ffffffff7f0000000000000000000000000000000000000000000000000000000000000000166004820152600090819073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001690637258a807906024016040805180830381865afa15801561262e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126529190615a55565b90925090508161268e576040517f6a6bc3b200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080518082019091528281526020018190526008829055600981905536607a146126c157639824bdab6000526004601cfd5b80367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003605401351161275b576040517ff40239db000000000000000000000000000000000000000000000000000000008152367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90036014013560048201526024015b60405180910390fd5b6040805160e08101825263ffffffff8082526000602083018181527ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe369081013560f01c90038035606090811c868801908152346fffffffffffffffffffffffffffffffff81811693890193845260149094013560808901908152600160a08a0181815242871660c08c019081526002805493840181558a529a5160059092027f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace81018054995173ffffffffffffffffffffffffffffffffffffffff908116640100000000027fffffffffffffffff000000000000000000000000000000000000000000000000909b1694909c16939093179890981790915592517f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5acf87018054918a167fffffffffffffffffffffffff000000000000000000000000000000000000000090921691909117905592517f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ad0860180549186167fffffffffffffffffffffffffffffffff0000000000000000000000000000000090921691909117905591517f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ad185015551955182167001000000000000000000000000000000000295909116949094177f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ad29091015580547fffffffffffffffffffffffffffff00ffffffffffffffffffffffffffffffffff167101000000000000000000000000000000000017815583517fd0e30db000000000000000000000000000000000000000000000000000000000815293517f00000000000000000000000000000000000000000000000000000000000000009092169363d0e30db093926004828101939282900301818588803b158015612a3057600080fd5b505af1158015612a44573d6000803e3d6000fd5b5050600080547fffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000164267ffffffffffffffff161790555050505050565b600080600054700100000000000000000000000000000000900460ff166002811115612aaf57612aaf61555f565b14612ae6576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060028381548110612afb57612afb6158d5565b600091825260208220600590910201805490925063ffffffff90811614612b6a57815460028054909163ffffffff16908110612b3957612b396158d5565b906000526020600020906005020160040160109054906101000a90046fffffffffffffffffffffffffffffffff1690505b6004820154600090612ba290700100000000000000000000000000000000900467ffffffffffffffff165b67ffffffffffffffff1690565b612bb69067ffffffffffffffff1642615983565b612bd5612b95846fffffffffffffffffffffffffffffffff1660401c90565b67ffffffffffffffff16612be99190615933565b905067ffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001667ffffffffffffffff168167ffffffffffffffff1611612c365780611884565b7f000000000000000000000000000000000000000000000000000000000000000095945050505050565b600080612cff836fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1690507f0000000000000000000000000000000000000000000000000000000000000000811115612d5e576040517f56f57b2b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b642e90edd00062061a806311e1a3006000612d798383615aa8565b9050670de0b6b3a76400006000612db0827f0000000000000000000000000000000000000000000000000000000000000000615abc565b90506000612dce612dc9670de0b6b3a764000086615abc565b614078565b90506000612ddc84846142d3565b90506000612dea8383614322565b90506000612df782614350565b90506000612e1682612e11670de0b6b3a76400008f615abc565b614538565b90506000612e248b83614322565b9050612e30818d615abc565b9f9e505050505050505050505050505050565b60028181548110612e5357600080fd5b60009182526020909120600590910201805460018201546002830154600384015460049094015463ffffffff8416955064010000000090930473ffffffffffffffffffffffffffffffffffffffff908116949216926fffffffffffffffffffffffffffffffff91821692918082169170010000000000000000000000000000000090041687565b60008054700100000000000000000000000000000000900460ff166002811115612f0657612f0661555f565b14612f3d576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600060028781548110612f5257612f526158d5565b6000918252602082206005919091020160048101549092506fffffffffffffffffffffffffffffffff16908715821760011b9050612fb17f00000000000000000000000000000000000000000000000000000000000000006001615933565b61304d826fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1614613087576040517f5f53dd9800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600080891561317e576130da7f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000615983565b6001901b6130f9846fffffffffffffffffffffffffffffffff16614569565b6fffffffffffffffffffffffffffffffff166131159190615af9565b156131525761314961313a60016fffffffffffffffffffffffffffffffff8716615b0d565b865463ffffffff166000614608565b60030154613174565b7f00000000000000000000000000000000000000000000000000000000000000005b91508490506131a8565b600385015491506131a561313a6fffffffffffffffffffffffffffffffff86166001615b36565b90505b600882901b60088a8a6040516131bf9291906158c5565b6040518091039020901b14613200576040517f696550ff00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061320b8c6146ec565b9050600061321a836003015490565b6040517fe14ced320000000000000000000000000000000000000000000000000000000081527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff169063e14ced3290613294908f908f908f908f908a90600401615baa565b6020604051808303816000875af11580156132b3573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906132d791906159b7565b600485015491149150600090600290613382906fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b61341e896fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b6134289190615be4565b6134329190615c07565b60ff161590508115158103613473576040517ffb4e40dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8754640100000000900473ffffffffffffffffffffffffffffffffffffffff16156134ca576040517f9071e6af00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505085547fffffffffffffffff0000000000000000000000000000000000000000ffffffff163364010000000002179095555050505050505050505050565b60008054700100000000000000000000000000000000900460ff1660028111156135355761353561555f565b1461356c576040517f67fe195000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008060008061357b8661471b565b9350935093509350600061359185858585614b24565b905060007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16637dc0d1d06040518163ffffffff1660e01b8152600401602060405180830381865afa158015613600573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613624919061599a565b90506001890361371c5773ffffffffffffffffffffffffffffffffffffffff81166352f0f3ad8a84613680367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90036034013590565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e086901b16815260048101939093526024830191909152604482015260206064820152608481018a905260a4015b6020604051808303816000875af11580156136f2573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061371691906159b7565b5061155b565b600289036137485773ffffffffffffffffffffffffffffffffffffffff81166352f0f3ad8a8489613680565b600389036137745773ffffffffffffffffffffffffffffffffffffffff81166352f0f3ad8a8487613680565b600489036138fb5760006137ba6fffffffffffffffffffffffffffffffff85167f0000000000000000000000000000000000000000000000000000000000000000614bde565b6009546137c79190615933565b6137d2906001615933565b9050367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c900360540135811061383b57367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90036054013561383d565b805b905073ffffffffffffffffffffffffffffffffffffffff82166352f0f3ad8b8560405160e084901b7fffffffff000000000000000000000000000000000000000000000000000000001681526004810192909252602482015260c084901b604482015260086064820152608481018b905260a4016020604051808303816000875af11580156138d0573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906138f491906159b7565b505061155b565b60058903613994576040517f52f0f3ad000000000000000000000000000000000000000000000000000000008152600481018a9052602481018390527f000000000000000000000000000000000000000000000000000000000000000060c01b6044820152600860648201526084810188905273ffffffffffffffffffffffffffffffffffffffff8216906352f0f3ad9060a4016136d3565b6040517fff137e6500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f0000000000000000000000000000000000000000000000000000000000000000367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c9003601401356060613a1f61188d565b9050909192565b600081831015613a365781613a38565b825b90505b92915050565b60008160000151826020015183604001518460600151604051602001613a80949392919093845260208401929092526040830152606082015260800190565b604051602081830303815290604052805190602001209050919050565b60408051808201909152600080825260208201528151600003613aec576040517f5ab458fb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50604080518082019091528151815260209182019181019190915290565b60606000806000613b1a85614c8c565b919450925090506001816001811115613b3557613b3561555f565b14613b6c576040517f4b9c6abe00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8451613b788385615933565b14613baf576040517f5c5537b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604080516020808252610420820190925290816020015b6040805180820190915260008082526020820152815260200190600190039081613bc65790505093506000835b8651811015613cb457600080613c396040518060400160405280858c60000151613c1d9190615983565b8152602001858c60200151613c329190615933565b9052614c8c565b509150915060405180604001604052808383613c559190615933565b8152602001848b60200151613c6a9190615933565b815250888581518110613c7f57613c7f6158d5565b6020908102919091010152613c95600185615933565b9350613ca18183615933565b613cab9084615933565b92505050613bf3565b50845250919392505050565b60606000806000613cd085614c8c565b919450925090506000816001811115613ceb57613ceb61555f565b14613d22576040517f1ff9b2e400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b613d2c8284615933565b855114613d65576040517f5c5537b800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6118848560200151848461512a565b600281015473ffffffffffffffffffffffffffffffffffffffff8316600090815260036020526040812080546fffffffffffffffffffffffffffffffff90931692839290613dc3908490615933565b90915550506040517f7eee288d00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8481166004830152602482018390527f00000000000000000000000000000000000000000000000000000000000000001690637eee288d90604401600060405180830381600087803b158015613e5857600080fd5b505af1158015613e6c573d6000803e3d6000fd5b50505050505050565b604051818152367ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe81013560f01c90038284820160208401378260208301016000815260208101604052505092915050565b6000613ee66fffffffffffffffffffffffffffffffff84166001615b36565b90506000613ef682866001614608565b9050600086901a8380613fe25750613f2f60027f0000000000000000000000000000000000000000000000000000000000000000615af9565b6004830154600290613fd3906fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b613fdd9190615c07565b60ff16145b1561403a5760ff811660011480613ffc575060ff81166002145b614035576040517ff40239db00000000000000000000000000000000000000000000000000000000815260048101889052602401612752565b613e6c565b60ff811615613e6c576040517ff40239db00000000000000000000000000000000000000000000000000000000815260048101889052602401612752565b6fffffffffffffffffffffffffffffffff811160071b81811c67ffffffffffffffff1060061b1781811c63ffffffff1060051b1781811c61ffff1060041b1781811c60ff1060031b17600082136140d757631615e6386000526004601cfd5b7ff8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff6f8421084210842108cc6318c6db6d54be83831c1c601f161a1890811b609f90811c6c465772b2bbbb5f824b15207a3081018102606090811d6d0388eaa27412d5aca026815d636e018202811d6d0df99ac502031bf953eff472fdcc018202811d6d13cdffb29d51d99322bdff5f2211018202811d6d0a0f742023def783a307a986912e018202811d6d01920d8043ca89b5239253284e42018202811d6c0b7a86d7375468fac667a0a527016c29508e458543d8aa4df2abee7883018302821d6d0139601a2efabe717e604cbb4894018302821d6d02247f7a7b6594320649aa03aba1018302821d7fffffffffffffffffffffffffffffffffffffff73c0c716a594e00d54e3c4cbc9018302821d7ffffffffffffffffffffffffffffffffffffffdc7b88c420e53a9890533129f6f01830290911d7fffffffffffffffffffffffffffffffffffffff465fda27eb4d63ded474e5f832019091027ffffffffffffffff5f6af8f7b3396644f18e157960000000000000000000000000105711340daa0d5f769dba1915cef59f0815a5506029190037d0267a36c0c95b3975ab3ee5b203a7614a3f75373f047d803ae7b6687f2b302017d57115e47018c7177eebf7cd370a3356a1b7863008a5ae8028c72b88642840160ae1d90565b60007812725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f218311670de0b6b3a76400000215820261431057637c5f487d6000526004601cfd5b50670de0b6b3a7640000919091020490565b6000816000190483118202156143405763bac65e5b6000526004601cfd5b50670de0b6b3a764000091020490565b60007ffffffffffffffffffffffffffffffffffffffffffffffffdc0d0570925a462d7821361437e57919050565b680755bf798b4a1bf1e5821261439c5763a37bfec96000526004601cfd5b6503782dace9d9604e83901b059150600060606bb17217f7d1cf79abc9e3b39884821b056b80000000000000000000000001901d6bb17217f7d1cf79abc9e3b39881029093037fffffffffffffffffffffffffffffffffffffffdbf3ccf1604d263450f02a550481018102606090811d6d0277594991cfc85f6e2461837cd9018202811d7fffffffffffffffffffffffffffffffffffffe5adedaa1cb095af9e4da10e363c018202811d6db1bbb201f443cf962f1a1d3db4a5018202811d7ffffffffffffffffffffffffffffffffffffd38dc772608b0ae56cce01296c0eb018202811d6e05180bb14799ab47a8a8cb2a527d57016d02d16720577bd19bf614176fe9ea6c10fe68e7fd37d0007b713f765084018402831d9081019084017ffffffffffffffffffffffffffffffffffffffe2c69812cf03b0763fd454a8f7e010290911d6e0587f503bb6ea29d25fcb7401964500190910279d835ebba824c98fb31b83b2ca45c000000000000000000000000010574029d9dc38563c32e5c2f6dc192ee70ef65f9978af30260c3939093039290921c92915050565b6000613a38670de0b6b3a76400008361455086614078565b61455a9190615c29565b6145649190615ce5565b614350565b6000806145f6837e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b600160ff919091161b90920392915050565b600080826146515761464c6fffffffffffffffffffffffffffffffff86167f00000000000000000000000000000000000000000000000000000000000000006151bf565b61466c565b61466c856fffffffffffffffffffffffffffffffff1661534b565b905060028481548110614681576146816158d5565b906000526020600020906005020191505b60048201546fffffffffffffffffffffffffffffffff8281169116146146e457815460028054909163ffffffff169081106146cf576146cf6158d5565b90600052602060002090600502019150614692565b509392505050565b60008060008060006146fd8661471b565b935093509350935061471184848484614b24565b9695505050505050565b600080600080600085905060006002828154811061473b5761473b6158d5565b600091825260209091206004600590920201908101549091507f000000000000000000000000000000000000000000000000000000000000000090614812906fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff161161484c576040517fb34b5c2200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000815b60048301547f000000000000000000000000000000000000000000000000000000000000000090614913906fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff16925082111561498857825463ffffffff166149527f00000000000000000000000000000000000000000000000000000000000000006001615933565b830361495c578391505b6002818154811061496f5761496f6158d5565b9060005260206000209060050201935080945050614850565b600481810154908401546fffffffffffffffffffffffffffffffff91821691166000816fffffffffffffffffffffffffffffffff166149f16149dc856fffffffffffffffffffffffffffffffff1660011c90565b6fffffffffffffffffffffffffffffffff1690565b6fffffffffffffffffffffffffffffffff161490508015614ac0576000614a29836fffffffffffffffffffffffffffffffff16614569565b6fffffffffffffffffffffffffffffffff161115614a94576000614a6b614a6360016fffffffffffffffffffffffffffffffff8616615b0d565b896001614608565b6003810154600490910154909c506fffffffffffffffffffffffffffffffff169a50614a9a9050565b6008549a505b600386015460048701549099506fffffffffffffffffffffffffffffffff169750614b16565b6000614ae2614a636fffffffffffffffffffffffffffffffff85166001615b36565b6003808901546004808b015492840154930154909e506fffffffffffffffffffffffffffffffff9182169d50919b50169850505b505050505050509193509193565b60006fffffffffffffffffffffffffffffffff841615614b915760408051602081018790526fffffffffffffffffffffffffffffffff8087169282019290925260608101859052908316608082015260a00160405160208183030381529060405280519060200120611884565b8282604051602001614bbf9291909182526fffffffffffffffffffffffffffffffff16602082015260400190565b6040516020818303038152906040528051906020012095945050505050565b600080614c6b847e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1690508083036001841b600180831b0386831b17039250505092915050565b60008060008360000151600003614ccf576040517f5ab458fb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020840151805160001a607f8111614cf4576000600160009450945094505050615123565b60b78111614e0a576000614d09608083615983565b905080876000015111614d48576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001838101517fff00000000000000000000000000000000000000000000000000000000000000169082148015614dc057507f80000000000000000000000000000000000000000000000000000000000000007fff000000000000000000000000000000000000000000000000000000000000008216105b15614df7576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5060019550935060009250615123915050565b60bf8111614f68576000614e1f60b783615983565b905080876000015111614e5e576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018301517fff00000000000000000000000000000000000000000000000000000000000000166000819003614ec0576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600184015160088302610100031c60378111614f08576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b614f128184615933565b895111614f4b576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b614f56836001615933565b97509550600094506151239350505050565b60f78111614fcd576000614f7d60c083615983565b905080876000015111614fbc576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600195509350849250615123915050565b6000614fda60f783615983565b905080876000015111615019576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018301517fff0000000000000000000000000000000000000000000000000000000000000016600081900361507b576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600184015160088302610100031c603781116150c3576040517fbabb01dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6150cd8184615933565b895111615106576040517f66c9448500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b615111836001615933565b97509550600194506151239350505050565b9193909250565b60608167ffffffffffffffff81111561514557615145615809565b6040519080825280601f01601f19166020018201604052801561516f576020820181803683370190505b50905081156151b85760006151848486615933565b90506020820160005b848110156151a557828101518282015260200161518d565b848111156151b4576000858301525b5050505b9392505050565b60008161525e846fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff16116152745763b34b5c226000526004601cfd5b61527d8361534b565b90508161531c826fffffffffffffffffffffffffffffffff167e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff1611613a3b57613a38615332836001615933565b6fffffffffffffffffffffffffffffffff8316906153f0565b600081196001830116816153df827e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff169390931c8015179392505050565b60008061547d847e09010a0d15021d0b0e10121619031e080c141c0f111807131b17061a05041f7f07c4acdd0000000000000000000000000000000000000000000000000000000067ffffffffffffffff831160061b83811c63ffffffff1060051b1792831c600181901c17600281901c17600481901c17600881901c17601081901c170260fb1c1a1790565b60ff169050808303600180821b0385821b179250505092915050565b60008083601f8401126154ab57600080fd5b50813567ffffffffffffffff8111156154c357600080fd5b6020830191508360208285010111156154db57600080fd5b9250929050565b600080600083850360a08112156154f857600080fd5b608081121561550657600080fd5b50839250608084013567ffffffffffffffff81111561552457600080fd5b61553086828701615499565b9497909650939450505050565b6000806040838503121561555057600080fd5b50508035926020909101359150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60208101600383106155c9577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b91905290565b6000806000606084860312156155e457600080fd5b505081359360208301359350604090920135919050565b6000815180845260005b8181101561562157602081850181015186830182015201615605565b81811115615633576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000613a3860208301846155fb565b60006020828403121561568b57600080fd5b5035919050565b73ffffffffffffffffffffffffffffffffffffffff811681146156b457600080fd5b50565b6000602082840312156156c957600080fd5b81356151b881615692565b803580151581146156e457600080fd5b919050565b600080600080608085870312156156ff57600080fd5b84359350602085013592506040850135915061571d606086016156d4565b905092959194509250565b60006020828403121561573a57600080fd5b81356fffffffffffffffffffffffffffffffff811681146151b857600080fd5b6000806000806000806080878903121561577357600080fd5b86359550615783602088016156d4565b9450604087013567ffffffffffffffff808211156157a057600080fd5b6157ac8a838b01615499565b909650945060608901359150808211156157c557600080fd5b506157d289828a01615499565b979a9699509497509295939492505050565b63ffffffff8416815282602082015260606040820152600061188460608301846155fb565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006080828403121561584a57600080fd5b6040516080810181811067ffffffffffffffff82111715615894577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b8060405250823581526020830135602082015260408301356040820152606083013560608201528091505092915050565b8183823760009101908152919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000821982111561594657615946615904565b500190565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361597c5761597c615904565b5060010190565b60008282101561599557615995615904565b500390565b6000602082840312156159ac57600080fd5b81516151b881615692565b6000602082840312156159c957600080fd5b5051919050565b600067ffffffffffffffff8083168185168083038211156159f3576159f3615904565b01949350505050565b600067ffffffffffffffff80831681851681830481118215151615615a2357615a23615904565b02949350505050565b600067ffffffffffffffff83811690831681811015615a4d57615a4d615904565b039392505050565b60008060408385031215615a6857600080fd5b505080516020909101519092909150565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082615ab757615ab7615a79565b500490565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615615af457615af4615904565b500290565b600082615b0857615b08615a79565b500690565b60006fffffffffffffffffffffffffffffffff83811690831681811015615a4d57615a4d615904565b60006fffffffffffffffffffffffffffffffff8083168185168083038211156159f3576159f3615904565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b606081526000615bbe606083018789615b61565b8281036020840152615bd1818688615b61565b9150508260408301529695505050505050565b600060ff821660ff841680821015615bfe57615bfe615904565b90039392505050565b600060ff831680615c1a57615c1a615a79565b8060ff84160691505092915050565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600084136000841385830485118282161615615c6a57615c6a615904565b7f80000000000000000000000000000000000000000000000000000000000000006000871286820588128184161615615ca557615ca5615904565b60008712925087820587128484161615615cc157615cc1615904565b87850587128184161615615cd757615cd7615904565b505050929093029392505050565b600082615cf457615cf4615a79565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83147f800000000000000000000000000000000000000000000000000000000000000083141615615d4857615d48615904565b50059056fea164736f6c634300080f000a"; - console.log("FaultDisputeGame initCodeHash (compare with https://github.com/ethereum-optimism/optimism/blob/op-contracts/v1.8.0/packages/contracts-bedrock/semver-lock.json#L171):"); - console.logBytes32(keccak256(faultDisputeGameInitcode)); - - (blueprints.permissionlessDisputeGame1, blueprints.permissionlessDisputeGame2) = DeployUtils.createDeterministicBlueprint(faultDisputeGameInitcode, _salt); - - vm.stopBroadcast(); - require( - blueprints.permissionlessDisputeGame2 != address(0), - "DeployOPPrestateUpdater: Second permissionless dispute game not deployed as expected." - ); - // forgefmt: disable-end - - OPPrestateUpdater oppu = OPPrestateUpdater( - DeployUtils.createDeterministic({ - _name: "OPPrestateUpdater", - _args: DeployUtils.encodeConstructor( - abi.encodeCall(IOPPrestateUpdater.__constructor__, (superchainConfig, protocolVersions, blueprints)) - ), - _salt: bytes32(_salt) - }) - ); - - require(address(oppu.superchainConfig()) == address(superchainConfig), "OPPUI-10"); - require(address(oppu.protocolVersions()) == address(protocolVersions), "OPPUI-20"); - require(LibString.eq(oppu.l1ContractsRelease(), "none"), "OPPUI-30"); - - require(oppu.upgradeController() == address(0), "OPPUI-40"); - - // encode decode because oppu.implementations returns IOPPrestateUpdater.Implementations - IOPContractsManager.Implementations memory implementations = - abi.decode(abi.encode(oppu.implementations()), (IOPContractsManager.Implementations)); - require(implementations.l1CrossDomainMessengerImpl == address(0), "OPPUI-120"); - require(implementations.l1StandardBridgeImpl == address(0), "OPPUI-130"); - require(implementations.disputeGameFactoryImpl == address(0), "OPPUI-140"); - require(implementations.optimismMintableERC20FactoryImpl == address(0), "OPPUI-150"); - require(implementations.l1CrossDomainMessengerImpl == address(0), "OPPUI-160"); - require(implementations.l1StandardBridgeImpl == address(0), "OPPUI-170"); - require(implementations.disputeGameFactoryImpl == address(0), "OPPUI-180"); - require(implementations.anchorStateRegistryImpl == address(0), "OPPUI-190"); - require(implementations.delayedWETHImpl == address(0), "OPPUI-200"); - require(implementations.mipsImpl == address(0), "OPPUI-210"); - - IOPContractsManager.Blueprints memory actualBluePrints = - abi.decode(abi.encode(oppu.blueprints()), (IOPContractsManager.Blueprints)); - require(actualBluePrints.addressManager == address(0), "OPPUI-300"); - require(actualBluePrints.proxy == address(0), "OPPUI-310"); - require(actualBluePrints.proxyAdmin == address(0), "OPPUI-320"); - require(actualBluePrints.l1ChugSplashProxy == address(0), "OPPUI-330"); - require(actualBluePrints.resolvedDelegateProxy == address(0), "OPPUI-340"); - require(actualBluePrints.permissionedDisputeGame1 == blueprints180.permissionedDisputeGame1, "OPPUI-350"); - require(actualBluePrints.permissionedDisputeGame2 == blueprints180.permissionedDisputeGame2, "OPPUI-360"); - require(actualBluePrints.permissionlessDisputeGame1 == blueprints.permissionlessDisputeGame1, "OPPUI-370"); - require(actualBluePrints.permissionlessDisputeGame2 == blueprints.permissionlessDisputeGame2, "OPPUI-380"); - - return oppu; - } -} diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json index 22c1d40b77c..a12ea8e9411 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json @@ -1,6 +1,21 @@ [ { "inputs": [ + { + "internalType": "contract OPContractsManagerGameTypeAdder", + "name": "_opcmGameTypeAdder", + "type": "address" + }, + { + "internalType": "contract OPContractsManagerDeployer", + "name": "_opcmDeployer", + "type": "address" + }, + { + "internalType": "contract OPContractsManagerUpgrader", + "name": "_opcmUpgrader", + "type": "address" + }, { "internalType": "contract ISuperchainConfig", "name": "_superchainConfig", @@ -21,125 +36,6 @@ "name": "_l1ContractsRelease", "type": "string" }, - { - "components": [ - { - "internalType": "address", - "name": "addressManager", - "type": "address" - }, - { - "internalType": "address", - "name": "proxy", - "type": "address" - }, - { - "internalType": "address", - "name": "proxyAdmin", - "type": "address" - }, - { - "internalType": "address", - "name": "l1ChugSplashProxy", - "type": "address" - }, - { - "internalType": "address", - "name": "resolvedDelegateProxy", - "type": "address" - }, - { - "internalType": "address", - "name": "permissionedDisputeGame1", - "type": "address" - }, - { - "internalType": "address", - "name": "permissionedDisputeGame2", - "type": "address" - }, - { - "internalType": "address", - "name": "permissionlessDisputeGame1", - "type": "address" - }, - { - "internalType": "address", - "name": "permissionlessDisputeGame2", - "type": "address" - } - ], - "internalType": "struct OPContractsManager.Blueprints", - "name": "_blueprints", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "address", - "name": "superchainConfigImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "protocolVersionsImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "l1ERC721BridgeImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "optimismPortalImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "systemConfigImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "optimismMintableERC20FactoryImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "l1CrossDomainMessengerImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "l1StandardBridgeImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "disputeGameFactoryImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "anchorStateRegistryImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "delayedWETHImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "mipsImpl", - "type": "address" - } - ], - "internalType": "struct OPContractsManager.Implementations", - "name": "_implementations", - "type": "tuple" - }, { "internalType": "address", "name": "_upgradeController", @@ -323,7 +219,7 @@ "type": "address" } ], - "stateMutability": "pure", + "stateMutability": "view", "type": "function" }, { @@ -617,6 +513,45 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "opcmDeployer", + "outputs": [ + { + "internalType": "contract OPContractsManagerDeployer", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "opcmGameTypeAdder", + "outputs": [ + { + "internalType": "contract OPContractsManagerGameTypeAdder", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "opcmUpgrader", + "outputs": [ + { + "internalType": "contract OPContractsManagerUpgrader", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "protocolVersions", @@ -669,6 +604,36 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "components": [ + { + "internalType": "contract ISystemConfig", + "name": "systemConfigProxy", + "type": "address" + }, + { + "internalType": "contract IProxyAdmin", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "Claim", + "name": "absolutePrestate", + "type": "bytes32" + } + ], + "internalType": "struct OPContractsManager.OpChainConfig[]", + "name": "_prestateUpdateInputs", + "type": "tuple[]" + } + ], + "name": "updatePrestate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -725,87 +690,6 @@ "stateMutability": "pure", "type": "function" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "l2ChainId", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "deployer", - "type": "address" - }, - { - "indexed": false, - "internalType": "bytes", - "name": "deployOutput", - "type": "bytes" - } - ], - "name": "Deployed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "l2ChainId", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "GameType", - "name": "gameType", - "type": "uint32" - }, - { - "indexed": false, - "internalType": "contract IDisputeGame", - "name": "newDisputeGame", - "type": "address" - }, - { - "indexed": false, - "internalType": "contract IDisputeGame", - "name": "oldDisputeGame", - "type": "address" - } - ], - "name": "GameTypeAdded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "l2ChainId", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "contract ISystemConfig", - "name": "systemConfig", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "upgrader", - "type": "address" - } - ], - "name": "Upgraded", - "type": "event" - }, { "inputs": [ { @@ -833,26 +717,6 @@ "name": "AlreadyReleased", "type": "error" }, - { - "inputs": [], - "name": "BytesArrayTooLong", - "type": "error" - }, - { - "inputs": [], - "name": "DeploymentFailed", - "type": "error" - }, - { - "inputs": [], - "name": "EmptyInitcode", - "type": "error" - }, - { - "inputs": [], - "name": "IdentityPrecompileCallFailed", - "type": "error" - }, { "inputs": [], "name": "InvalidChainId", @@ -884,11 +748,6 @@ "name": "LatestReleaseNotSet", "type": "error" }, - { - "inputs": [], - "name": "NotABlueprint", - "type": "error" - }, { "inputs": [], "name": "OnlyDelegatecall", @@ -906,7 +765,7 @@ }, { "inputs": [], - "name": "ReservedBitsSet", + "name": "PrestateRequired", "type": "error" }, { @@ -924,27 +783,5 @@ "inputs": [], "name": "SuperchainProxyAdminMismatch", "type": "error" - }, - { - "inputs": [ - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "UnexpectedPreambleData", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint8", - "name": "version", - "type": "uint8" - } - ], - "name": "UnsupportedERCVersion", - "type": "error" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json new file mode 100644 index 00000000000..45af4462a56 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json @@ -0,0 +1,262 @@ +[ + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ChugSplashProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "resolvedDelegateProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame2", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame2", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Blueprints", + "name": "_blueprints", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "superchainConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "protocolVersionsImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ERC721BridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismPortalImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "systemConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismMintableERC20FactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1CrossDomainMessengerImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1StandardBridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "disputeGameFactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "anchorStateRegistryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "delayedWETHImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "mipsImpl", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Implementations", + "name": "_implementations", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "blueprints", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ChugSplashProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "resolvedDelegateProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame2", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame2", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Blueprints", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "implementations", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "superchainConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "protocolVersionsImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ERC721BridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismPortalImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "systemConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismMintableERC20FactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1CrossDomainMessengerImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1StandardBridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "disputeGameFactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "anchorStateRegistryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "delayedWETHImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "mipsImpl", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Implementations", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OPPrestateUpdater.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json similarity index 55% rename from packages/contracts-bedrock/snapshots/abi/OPPrestateUpdater.json rename to packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json index d44b099737a..d543175c828 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPPrestateUpdater.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json @@ -2,66 +2,9 @@ { "inputs": [ { - "internalType": "contract ISuperchainConfig", - "name": "_superchainConfig", + "internalType": "contract OPContractsManagerContractsContainer", + "name": "_contractsContainer", "type": "address" - }, - { - "internalType": "contract IProtocolVersions", - "name": "_protocolVersions", - "type": "address" - }, - { - "components": [ - { - "internalType": "address", - "name": "addressManager", - "type": "address" - }, - { - "internalType": "address", - "name": "proxy", - "type": "address" - }, - { - "internalType": "address", - "name": "proxyAdmin", - "type": "address" - }, - { - "internalType": "address", - "name": "l1ChugSplashProxy", - "type": "address" - }, - { - "internalType": "address", - "name": "resolvedDelegateProxy", - "type": "address" - }, - { - "internalType": "address", - "name": "permissionedDisputeGame1", - "type": "address" - }, - { - "internalType": "address", - "name": "permissionedDisputeGame2", - "type": "address" - }, - { - "internalType": "address", - "name": "permissionlessDisputeGame1", - "type": "address" - }, - { - "internalType": "address", - "name": "permissionlessDisputeGame2", - "type": "address" - } - ], - "internalType": "struct OPContractsManager.Blueprints", - "name": "_blueprints", - "type": "tuple" } ], "stateMutability": "nonpayable", @@ -70,99 +13,14 @@ { "inputs": [ { - "components": [ - { - "internalType": "string", - "name": "saltMixer", - "type": "string" - }, - { - "internalType": "contract ISystemConfig", - "name": "systemConfig", - "type": "address" - }, - { - "internalType": "contract IProxyAdmin", - "name": "proxyAdmin", - "type": "address" - }, - { - "internalType": "contract IDelayedWETH", - "name": "delayedWETH", - "type": "address" - }, - { - "internalType": "GameType", - "name": "disputeGameType", - "type": "uint32" - }, - { - "internalType": "Claim", - "name": "disputeAbsolutePrestate", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "disputeMaxGameDepth", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "disputeSplitDepth", - "type": "uint256" - }, - { - "internalType": "Duration", - "name": "disputeClockExtension", - "type": "uint64" - }, - { - "internalType": "Duration", - "name": "disputeMaxClockDuration", - "type": "uint64" - }, - { - "internalType": "uint256", - "name": "initialBond", - "type": "uint256" - }, - { - "internalType": "contract IBigStepper", - "name": "vm", - "type": "address" - }, - { - "internalType": "bool", - "name": "permissioned", - "type": "bool" - } - ], - "internalType": "struct OPContractsManager.AddGameInput[]", - "name": "_gameConfigs", - "type": "tuple[]" - } - ], - "name": "addGameType", - "outputs": [ - { - "components": [ - { - "internalType": "contract IDelayedWETH", - "name": "delayedWETH", - "type": "address" - }, - { - "internalType": "contract IFaultDisputeGame", - "name": "faultDisputeGame", - "type": "address" - } - ], - "internalType": "struct OPContractsManager.AddGameOutput[]", - "name": "", - "type": "tuple[]" + "internalType": "address", + "name": "_who", + "type": "address" } ], - "stateMutability": "pure", + "name": "assertValidContractAddress", + "outputs": [], + "stateMutability": "view", "type": "function" }, { @@ -244,6 +102,19 @@ "stateMutability": "pure", "type": "function" }, + { + "inputs": [], + "name": "contractsContainer", + "outputs": [ + { + "internalType": "contract OPContractsManagerContractsContainer", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -349,6 +220,16 @@ "internalType": "struct OPContractsManager.DeployInput", "name": "_input", "type": "tuple" + }, + { + "internalType": "contract ISuperchainConfig", + "name": "_superchainConfig", + "type": "address" + }, + { + "internalType": "address", + "name": "_deployer", + "type": "address" } ], "name": "deploy", @@ -431,7 +312,7 @@ "type": "tuple" } ], - "stateMutability": "pure", + "stateMutability": "nonpayable", "type": "function" }, { @@ -509,170 +390,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "isRC", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "l1ContractsRelease", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "protocolVersions", - "outputs": [ - { - "internalType": "contract IProtocolVersions", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bool", - "name": "_isRC", - "type": "bool" - } - ], - "name": "setRC", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "superchainConfig", - "outputs": [ - { - "internalType": "contract ISuperchainConfig", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "superchainProxyAdmin", - "outputs": [ - { - "internalType": "contract IProxyAdmin", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "contract ISystemConfig", - "name": "systemConfigProxy", - "type": "address" - }, - { - "internalType": "contract IProxyAdmin", - "name": "proxyAdmin", - "type": "address" - }, - { - "internalType": "Claim", - "name": "absolutePrestate", - "type": "bytes32" - } - ], - "internalType": "struct OPContractsManager.OpChainConfig[]", - "name": "_prestateUpdateInputs", - "type": "tuple[]" - } - ], - "name": "updatePrestate", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "contract ISystemConfig", - "name": "systemConfigProxy", - "type": "address" - }, - { - "internalType": "contract IProxyAdmin", - "name": "proxyAdmin", - "type": "address" - }, - { - "internalType": "Claim", - "name": "absolutePrestate", - "type": "bytes32" - } - ], - "internalType": "struct OPContractsManager.OpChainConfig[]", - "name": "_opChainConfigs", - "type": "tuple[]" - } - ], - "name": "upgrade", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [], - "name": "upgradeController", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "version", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "pure", - "type": "function" - }, { "anonymous": false, "inputs": [ @@ -698,62 +415,6 @@ "name": "Deployed", "type": "event" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "l2ChainId", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "GameType", - "name": "gameType", - "type": "uint32" - }, - { - "indexed": false, - "internalType": "contract IDisputeGame", - "name": "newDisputeGame", - "type": "address" - }, - { - "indexed": false, - "internalType": "contract IDisputeGame", - "name": "oldDisputeGame", - "type": "address" - } - ], - "name": "GameTypeAdded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "l2ChainId", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "contract ISystemConfig", - "name": "systemConfig", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "upgrader", - "type": "address" - } - ], - "name": "Upgraded", - "type": "event" - }, { "inputs": [ { @@ -765,22 +426,6 @@ "name": "AddressHasNoCode", "type": "error" }, - { - "inputs": [ - { - "internalType": "address", - "name": "who", - "type": "address" - } - ], - "name": "AddressNotFound", - "type": "error" - }, - { - "inputs": [], - "name": "AlreadyReleased", - "type": "error" - }, { "inputs": [], "name": "BytesArrayTooLong", @@ -806,11 +451,6 @@ "name": "InvalidChainId", "type": "error" }, - { - "inputs": [], - "name": "InvalidGameConfigs", - "type": "error" - }, { "inputs": [ { @@ -827,62 +467,16 @@ "name": "InvalidStartingAnchorRoot", "type": "error" }, - { - "inputs": [], - "name": "LatestReleaseNotSet", - "type": "error" - }, { "inputs": [], "name": "NotABlueprint", "type": "error" }, - { - "inputs": [], - "name": "NotImplemented", - "type": "error" - }, - { - "inputs": [], - "name": "OnlyDelegatecall", - "type": "error" - }, - { - "inputs": [], - "name": "OnlyUpgradeController", - "type": "error" - }, - { - "inputs": [], - "name": "PrestateNotSet", - "type": "error" - }, - { - "inputs": [], - "name": "PrestateRequired", - "type": "error" - }, { "inputs": [], "name": "ReservedBitsSet", "type": "error" }, - { - "inputs": [ - { - "internalType": "contract ISystemConfig", - "name": "systemConfig", - "type": "address" - } - ], - "name": "SuperchainConfigMismatch", - "type": "error" - }, - { - "inputs": [], - "name": "SuperchainProxyAdminMismatch", - "type": "error" - }, { "inputs": [ { diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInterop.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployerInterop.json similarity index 52% rename from packages/contracts-bedrock/snapshots/abi/OPContractsManagerInterop.json rename to packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployerInterop.json index 22c1d40b77c..d543175c828 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployerInterop.json @@ -2,147 +2,8 @@ { "inputs": [ { - "internalType": "contract ISuperchainConfig", - "name": "_superchainConfig", - "type": "address" - }, - { - "internalType": "contract IProtocolVersions", - "name": "_protocolVersions", - "type": "address" - }, - { - "internalType": "contract IProxyAdmin", - "name": "_superchainProxyAdmin", - "type": "address" - }, - { - "internalType": "string", - "name": "_l1ContractsRelease", - "type": "string" - }, - { - "components": [ - { - "internalType": "address", - "name": "addressManager", - "type": "address" - }, - { - "internalType": "address", - "name": "proxy", - "type": "address" - }, - { - "internalType": "address", - "name": "proxyAdmin", - "type": "address" - }, - { - "internalType": "address", - "name": "l1ChugSplashProxy", - "type": "address" - }, - { - "internalType": "address", - "name": "resolvedDelegateProxy", - "type": "address" - }, - { - "internalType": "address", - "name": "permissionedDisputeGame1", - "type": "address" - }, - { - "internalType": "address", - "name": "permissionedDisputeGame2", - "type": "address" - }, - { - "internalType": "address", - "name": "permissionlessDisputeGame1", - "type": "address" - }, - { - "internalType": "address", - "name": "permissionlessDisputeGame2", - "type": "address" - } - ], - "internalType": "struct OPContractsManager.Blueprints", - "name": "_blueprints", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "address", - "name": "superchainConfigImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "protocolVersionsImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "l1ERC721BridgeImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "optimismPortalImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "systemConfigImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "optimismMintableERC20FactoryImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "l1CrossDomainMessengerImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "l1StandardBridgeImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "disputeGameFactoryImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "anchorStateRegistryImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "delayedWETHImpl", - "type": "address" - }, - { - "internalType": "address", - "name": "mipsImpl", - "type": "address" - } - ], - "internalType": "struct OPContractsManager.Implementations", - "name": "_implementations", - "type": "tuple" - }, - { - "internalType": "address", - "name": "_upgradeController", + "internalType": "contract OPContractsManagerContractsContainer", + "name": "_contractsContainer", "type": "address" } ], @@ -152,99 +13,14 @@ { "inputs": [ { - "components": [ - { - "internalType": "string", - "name": "saltMixer", - "type": "string" - }, - { - "internalType": "contract ISystemConfig", - "name": "systemConfig", - "type": "address" - }, - { - "internalType": "contract IProxyAdmin", - "name": "proxyAdmin", - "type": "address" - }, - { - "internalType": "contract IDelayedWETH", - "name": "delayedWETH", - "type": "address" - }, - { - "internalType": "GameType", - "name": "disputeGameType", - "type": "uint32" - }, - { - "internalType": "Claim", - "name": "disputeAbsolutePrestate", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "disputeMaxGameDepth", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "disputeSplitDepth", - "type": "uint256" - }, - { - "internalType": "Duration", - "name": "disputeClockExtension", - "type": "uint64" - }, - { - "internalType": "Duration", - "name": "disputeMaxClockDuration", - "type": "uint64" - }, - { - "internalType": "uint256", - "name": "initialBond", - "type": "uint256" - }, - { - "internalType": "contract IBigStepper", - "name": "vm", - "type": "address" - }, - { - "internalType": "bool", - "name": "permissioned", - "type": "bool" - } - ], - "internalType": "struct OPContractsManager.AddGameInput[]", - "name": "_gameConfigs", - "type": "tuple[]" - } - ], - "name": "addGameType", - "outputs": [ - { - "components": [ - { - "internalType": "contract IDelayedWETH", - "name": "delayedWETH", - "type": "address" - }, - { - "internalType": "contract IFaultDisputeGame", - "name": "faultDisputeGame", - "type": "address" - } - ], - "internalType": "struct OPContractsManager.AddGameOutput[]", - "name": "", - "type": "tuple[]" + "internalType": "address", + "name": "_who", + "type": "address" } ], - "stateMutability": "nonpayable", + "name": "assertValidContractAddress", + "outputs": [], + "stateMutability": "view", "type": "function" }, { @@ -326,6 +102,19 @@ "stateMutability": "pure", "type": "function" }, + { + "inputs": [], + "name": "contractsContainer", + "outputs": [ + { + "internalType": "contract OPContractsManagerContractsContainer", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -431,6 +220,16 @@ "internalType": "struct OPContractsManager.DeployInput", "name": "_input", "type": "tuple" + }, + { + "internalType": "contract ISuperchainConfig", + "name": "_superchainConfig", + "type": "address" + }, + { + "internalType": "address", + "name": "_deployer", + "type": "address" } ], "name": "deploy", @@ -591,140 +390,6 @@ "stateMutability": "view", "type": "function" }, - { - "inputs": [], - "name": "isRC", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "l1ContractsRelease", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "protocolVersions", - "outputs": [ - { - "internalType": "contract IProtocolVersions", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bool", - "name": "_isRC", - "type": "bool" - } - ], - "name": "setRC", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "superchainConfig", - "outputs": [ - { - "internalType": "contract ISuperchainConfig", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "superchainProxyAdmin", - "outputs": [ - { - "internalType": "contract IProxyAdmin", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "contract ISystemConfig", - "name": "systemConfigProxy", - "type": "address" - }, - { - "internalType": "contract IProxyAdmin", - "name": "proxyAdmin", - "type": "address" - }, - { - "internalType": "Claim", - "name": "absolutePrestate", - "type": "bytes32" - } - ], - "internalType": "struct OPContractsManager.OpChainConfig[]", - "name": "_opChainConfigs", - "type": "tuple[]" - } - ], - "name": "upgrade", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "upgradeController", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "version", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "pure", - "type": "function" - }, { "anonymous": false, "inputs": [ @@ -750,62 +415,6 @@ "name": "Deployed", "type": "event" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "l2ChainId", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "GameType", - "name": "gameType", - "type": "uint32" - }, - { - "indexed": false, - "internalType": "contract IDisputeGame", - "name": "newDisputeGame", - "type": "address" - }, - { - "indexed": false, - "internalType": "contract IDisputeGame", - "name": "oldDisputeGame", - "type": "address" - } - ], - "name": "GameTypeAdded", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "l2ChainId", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "contract ISystemConfig", - "name": "systemConfig", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "upgrader", - "type": "address" - } - ], - "name": "Upgraded", - "type": "event" - }, { "inputs": [ { @@ -817,22 +426,6 @@ "name": "AddressHasNoCode", "type": "error" }, - { - "inputs": [ - { - "internalType": "address", - "name": "who", - "type": "address" - } - ], - "name": "AddressNotFound", - "type": "error" - }, - { - "inputs": [], - "name": "AlreadyReleased", - "type": "error" - }, { "inputs": [], "name": "BytesArrayTooLong", @@ -858,11 +451,6 @@ "name": "InvalidChainId", "type": "error" }, - { - "inputs": [], - "name": "InvalidGameConfigs", - "type": "error" - }, { "inputs": [ { @@ -879,52 +467,16 @@ "name": "InvalidStartingAnchorRoot", "type": "error" }, - { - "inputs": [], - "name": "LatestReleaseNotSet", - "type": "error" - }, { "inputs": [], "name": "NotABlueprint", "type": "error" }, - { - "inputs": [], - "name": "OnlyDelegatecall", - "type": "error" - }, - { - "inputs": [], - "name": "OnlyUpgradeController", - "type": "error" - }, - { - "inputs": [], - "name": "PrestateNotSet", - "type": "error" - }, { "inputs": [], "name": "ReservedBitsSet", "type": "error" }, - { - "inputs": [ - { - "internalType": "contract ISystemConfig", - "name": "systemConfig", - "type": "address" - } - ], - "name": "SuperchainConfigMismatch", - "type": "error" - }, - { - "inputs": [], - "name": "SuperchainProxyAdminMismatch", - "type": "error" - }, { "inputs": [ { diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json new file mode 100644 index 00000000000..a2088238caf --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json @@ -0,0 +1,435 @@ +[ + { + "inputs": [ + { + "internalType": "contract OPContractsManagerContractsContainer", + "name": "_contractsContainer", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "string", + "name": "saltMixer", + "type": "string" + }, + { + "internalType": "contract ISystemConfig", + "name": "systemConfig", + "type": "address" + }, + { + "internalType": "contract IProxyAdmin", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "contract IDelayedWETH", + "name": "delayedWETH", + "type": "address" + }, + { + "internalType": "GameType", + "name": "disputeGameType", + "type": "uint32" + }, + { + "internalType": "Claim", + "name": "disputeAbsolutePrestate", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "disputeMaxGameDepth", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "disputeSplitDepth", + "type": "uint256" + }, + { + "internalType": "Duration", + "name": "disputeClockExtension", + "type": "uint64" + }, + { + "internalType": "Duration", + "name": "disputeMaxClockDuration", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "initialBond", + "type": "uint256" + }, + { + "internalType": "contract IBigStepper", + "name": "vm", + "type": "address" + }, + { + "internalType": "bool", + "name": "permissioned", + "type": "bool" + } + ], + "internalType": "struct OPContractsManager.AddGameInput[]", + "name": "_gameConfigs", + "type": "tuple[]" + }, + { + "internalType": "contract ISuperchainConfig", + "name": "_superchainConfig", + "type": "address" + } + ], + "name": "addGameType", + "outputs": [ + { + "components": [ + { + "internalType": "contract IDelayedWETH", + "name": "delayedWETH", + "type": "address" + }, + { + "internalType": "contract IFaultDisputeGame", + "name": "faultDisputeGame", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.AddGameOutput[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_who", + "type": "address" + } + ], + "name": "assertValidContractAddress", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "blueprints", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ChugSplashProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "resolvedDelegateProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame2", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame2", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Blueprints", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_l2ChainId", + "type": "uint256" + } + ], + "name": "chainIdToBatchInboxAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "contractsContainer", + "outputs": [ + { + "internalType": "contract OPContractsManagerContractsContainer", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "implementations", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "superchainConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "protocolVersionsImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ERC721BridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismPortalImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "systemConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismMintableERC20FactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1CrossDomainMessengerImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1StandardBridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "disputeGameFactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "anchorStateRegistryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "delayedWETHImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "mipsImpl", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Implementations", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "contract ISystemConfig", + "name": "systemConfigProxy", + "type": "address" + }, + { + "internalType": "contract IProxyAdmin", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "Claim", + "name": "absolutePrestate", + "type": "bytes32" + } + ], + "internalType": "struct OPContractsManager.OpChainConfig[]", + "name": "_prestateUpdateInputs", + "type": "tuple[]" + }, + { + "internalType": "contract ISuperchainConfig", + "name": "_superchainConfig", + "type": "address" + } + ], + "name": "updatePrestate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "l2ChainId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "GameType", + "name": "gameType", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "contract IDisputeGame", + "name": "newDisputeGame", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract IDisputeGame", + "name": "oldDisputeGame", + "type": "address" + } + ], + "name": "GameTypeAdded", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "who", + "type": "address" + } + ], + "name": "AddressHasNoCode", + "type": "error" + }, + { + "inputs": [], + "name": "BytesArrayTooLong", + "type": "error" + }, + { + "inputs": [], + "name": "DeploymentFailed", + "type": "error" + }, + { + "inputs": [], + "name": "EmptyInitcode", + "type": "error" + }, + { + "inputs": [], + "name": "IdentityPrecompileCallFailed", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidGameConfigs", + "type": "error" + }, + { + "inputs": [], + "name": "NotABlueprint", + "type": "error" + }, + { + "inputs": [], + "name": "PrestateRequired", + "type": "error" + }, + { + "inputs": [], + "name": "ReservedBitsSet", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "UnexpectedPreambleData", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "UnsupportedERCVersion", + "type": "error" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json new file mode 100644 index 00000000000..0fd60a240f0 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json @@ -0,0 +1,349 @@ +[ + { + "inputs": [ + { + "internalType": "contract OPContractsManagerContractsContainer", + "name": "_contractsContainer", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_who", + "type": "address" + } + ], + "name": "assertValidContractAddress", + "outputs": [], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "blueprints", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "addressManager", + "type": "address" + }, + { + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "internalType": "address", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ChugSplashProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "resolvedDelegateProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionedDisputeGame2", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame1", + "type": "address" + }, + { + "internalType": "address", + "name": "permissionlessDisputeGame2", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Blueprints", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_l2ChainId", + "type": "uint256" + } + ], + "name": "chainIdToBatchInboxAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "contractsContainer", + "outputs": [ + { + "internalType": "contract OPContractsManagerContractsContainer", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "implementations", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "superchainConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "protocolVersionsImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1ERC721BridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismPortalImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "systemConfigImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "optimismMintableERC20FactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1CrossDomainMessengerImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "l1StandardBridgeImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "disputeGameFactoryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "anchorStateRegistryImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "delayedWETHImpl", + "type": "address" + }, + { + "internalType": "address", + "name": "mipsImpl", + "type": "address" + } + ], + "internalType": "struct OPContractsManager.Implementations", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "contract ISystemConfig", + "name": "systemConfigProxy", + "type": "address" + }, + { + "internalType": "contract IProxyAdmin", + "name": "proxyAdmin", + "type": "address" + }, + { + "internalType": "Claim", + "name": "absolutePrestate", + "type": "bytes32" + } + ], + "internalType": "struct OPContractsManager.OpChainConfig[]", + "name": "_opChainConfigs", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "contract ISuperchainConfig", + "name": "superchainConfig", + "type": "address" + }, + { + "internalType": "contract IProtocolVersions", + "name": "protocolVersions", + "type": "address" + }, + { + "internalType": "contract IProxyAdmin", + "name": "superchainProxyAdmin", + "type": "address" + } + ], + "internalType": "struct OPContractsManagerUpgrader.UpgradeInput", + "name": "_upgradeInput", + "type": "tuple" + } + ], + "name": "upgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "l2ChainId", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "contract ISystemConfig", + "name": "systemConfig", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "upgrader", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "who", + "type": "address" + } + ], + "name": "AddressHasNoCode", + "type": "error" + }, + { + "inputs": [], + "name": "BytesArrayTooLong", + "type": "error" + }, + { + "inputs": [], + "name": "DeploymentFailed", + "type": "error" + }, + { + "inputs": [], + "name": "EmptyInitcode", + "type": "error" + }, + { + "inputs": [], + "name": "IdentityPrecompileCallFailed", + "type": "error" + }, + { + "inputs": [], + "name": "NotABlueprint", + "type": "error" + }, + { + "inputs": [], + "name": "PrestateNotSet", + "type": "error" + }, + { + "inputs": [], + "name": "ReservedBitsSet", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract ISystemConfig", + "name": "systemConfig", + "type": "address" + } + ], + "name": "SuperchainConfigMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "UnexpectedPreambleData", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "UnsupportedERCVersion", + "type": "error" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 42eacc880d1..d5d937544c3 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -16,16 +16,8 @@ "sourceCodeHash": "0x7f614d0c5251365580c02dd898900a583ece2aa28579adea74a3808b575949b4" }, "src/L1/OPContractsManager.sol": { - "initCodeHash": "0xfc2b4c9a4b589d3bd5267a9335b0471d2adb13af3f9ae4c01a74fe7289dc5e48", - "sourceCodeHash": "0xa288e8bbe3cdc5361c904a57c55c0b2a658c1b7148df6592cd2e002ae2e957a0" - }, - "src/L1/OPContractsManagerInterop.sol": { - "initCodeHash": "0x8adc6744a20019ebc1ea77b4a3227d1644e334a6bf3ae5796dd84be10f81d8cf", - "sourceCodeHash": "0x504f8b1ef3fa5920ebab1ca438b1fe6e69587d3502da2b68502b4587f3abb1a4" - }, - "src/L1/OPPrestateUpdater.sol": { - "initCodeHash": "0x687553a705b47a57aa77de1af612913ed73f96b1929a84585b0022a0113203f1", - "sourceCodeHash": "0x7d2ec5f151a244e83f483a9a99598bee4915d0624a7af6c07cc82d82bf0dbb93" + "initCodeHash": "0xdc9f6153bfa79af8c0f0cffacb41d67cb037f334c221df1b306c235c3ceb299b", + "sourceCodeHash": "0x1b170e28f055c1f901c3709d193cbf2995527e66a2d9092ca51b155c8b293886" }, "src/L1/OptimismPortal2.sol": { "initCodeHash": "0x93c5e8ff52ff8b1cedd985b4a8890c12b56d5959832405c7622615c3541908f5", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManager.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManager.json index 3c1445a9829..a22b6b8e383 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManager.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManager.json @@ -6,25 +6,11 @@ "slot": "0", "type": "string" }, - { - "bytes": "288", - "label": "blueprint", - "offset": 0, - "slot": "1", - "type": "struct OPContractsManager.Blueprints" - }, - { - "bytes": "384", - "label": "implementation", - "offset": 0, - "slot": "10", - "type": "struct OPContractsManager.Implementations" - }, { "bytes": "1", "label": "isRC", "offset": 0, - "slot": "22", + "slot": "1", "type": "bool" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerInterop.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json similarity index 53% rename from packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerInterop.json rename to packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json index 3c1445a9829..98248abd1d8 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerInterop.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json @@ -1,30 +1,16 @@ [ - { - "bytes": "32", - "label": "L1_CONTRACTS_RELEASE", - "offset": 0, - "slot": "0", - "type": "string" - }, { "bytes": "288", "label": "blueprint", "offset": 0, - "slot": "1", + "slot": "0", "type": "struct OPContractsManager.Blueprints" }, { "bytes": "384", "label": "implementation", "offset": 0, - "slot": "10", + "slot": "9", "type": "struct OPContractsManager.Implementations" - }, - { - "bytes": "1", - "label": "isRC", - "offset": 0, - "slot": "22", - "type": "bool" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerDeployer.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerDeployer.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerDeployer.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerDeployerInterop.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerDeployerInterop.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerDeployerInterop.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerGameTypeAdder.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerGameTypeAdder.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerGameTypeAdder.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerUpgrader.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerUpgrader.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerUpgrader.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPPrestateUpdater.json b/packages/contracts-bedrock/snapshots/storageLayout/OPPrestateUpdater.json deleted file mode 100644 index 3c1445a9829..00000000000 --- a/packages/contracts-bedrock/snapshots/storageLayout/OPPrestateUpdater.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "bytes": "32", - "label": "L1_CONTRACTS_RELEASE", - "offset": 0, - "slot": "0", - "type": "string" - }, - { - "bytes": "288", - "label": "blueprint", - "offset": 0, - "slot": "1", - "type": "struct OPContractsManager.Blueprints" - }, - { - "bytes": "384", - "label": "implementation", - "offset": 0, - "slot": "10", - "type": "struct OPContractsManager.Implementations" - }, - { - "bytes": "1", - "label": "isRC", - "offset": 0, - "slot": "22", - "type": "bool" - } -] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 420532132e4..9279bbd312f 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -30,176 +30,237 @@ import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; import { IHasSuperchainConfig } from "interfaces/L1/IHasSuperchainConfig.sol"; +import { ISystemConfigInterop } from "interfaces/L1/ISystemConfigInterop.sol"; -contract OPContractsManager is ISemver { - // -------- Structs -------- +contract OPContractsManagerContractsContainer { + /// @notice Addresses of the Blueprint contracts. + /// This is internal because if public the autogenerated getter method would return a tuple of + /// addresses, but we want it to return a struct. + OPContractsManager.Blueprints internal blueprint; - /// @notice Represents the roles that can be set when deploying a standard OP Stack chain. - struct Roles { - address opChainProxyAdminOwner; - address systemConfigOwner; - address batcher; - address unsafeBlockSigner; - address proposer; - address challenger; - } + /// @notice Addresses of the latest implementation contracts. + OPContractsManager.Implementations internal implementation; - /// @notice The full set of inputs to deploy a new OP Stack chain. - struct DeployInput { - Roles roles; - uint32 basefeeScalar; - uint32 blobBasefeeScalar; - uint256 l2ChainId; - // The correct type is OutputRoot memory but OP Deployer does not yet support structs. - bytes startingAnchorRoot; - // The salt mixer is used as part of making the resulting salt unique. - string saltMixer; - uint64 gasLimit; - // Configurable dispute game parameters. - GameType disputeGameType; - Claim disputeAbsolutePrestate; - uint256 disputeMaxGameDepth; - uint256 disputeSplitDepth; - Duration disputeClockExtension; - Duration disputeMaxClockDuration; + constructor( + OPContractsManager.Blueprints memory _blueprints, + OPContractsManager.Implementations memory _implementations + ) { + blueprint = _blueprints; + implementation = _implementations; } - /// @notice The full set of outputs from deploying a new OP Stack chain. - struct DeployOutput { - IProxyAdmin opChainProxyAdmin; - IAddressManager addressManager; - IL1ERC721Bridge l1ERC721BridgeProxy; - ISystemConfig systemConfigProxy; - IOptimismMintableERC20Factory optimismMintableERC20FactoryProxy; - IL1StandardBridge l1StandardBridgeProxy; - IL1CrossDomainMessenger l1CrossDomainMessengerProxy; - // Fault proof contracts below. - IOptimismPortal2 optimismPortalProxy; - IDisputeGameFactory disputeGameFactoryProxy; - IAnchorStateRegistry anchorStateRegistryProxy; - IFaultDisputeGame faultDisputeGame; - IPermissionedDisputeGame permissionedDisputeGame; - IDelayedWETH delayedWETHPermissionedGameProxy; - IDelayedWETH delayedWETHPermissionlessGameProxy; + function blueprints() public view returns (OPContractsManager.Blueprints memory) { + return blueprint; } - /// @notice Addresses of ERC-5202 Blueprint contracts. There are used for deploying full size - /// contracts, to reduce the code size of this factory contract. If it deployed full contracts - /// using the `new Proxy()` syntax, the code size would get large fast, since this contract would - /// contain the bytecode of every contract it deploys. Therefore we instead use Blueprints to - /// reduce the code size of this contract. - struct Blueprints { - address addressManager; - address proxy; - address proxyAdmin; - address l1ChugSplashProxy; - address resolvedDelegateProxy; - address permissionedDisputeGame1; - address permissionedDisputeGame2; - address permissionlessDisputeGame1; - address permissionlessDisputeGame2; + function implementations() public view returns (OPContractsManager.Implementations memory) { + return implementation; } +} - /// @notice The latest implementation contracts for the OP Stack. - struct Implementations { - address superchainConfigImpl; - address protocolVersionsImpl; - address l1ERC721BridgeImpl; - address optimismPortalImpl; - address systemConfigImpl; - address optimismMintableERC20FactoryImpl; - address l1CrossDomainMessengerImpl; - address l1StandardBridgeImpl; - address disputeGameFactoryImpl; - address anchorStateRegistryImpl; - address delayedWETHImpl; - address mipsImpl; +abstract contract OPContractsManagerBase { + /// @notice The blueprint contract addresses contract. + OPContractsManagerContractsContainer public immutable contractsContainer; + + /// @notice The OPContractsManager contract that is currently being used. + OPContractsManagerBase internal immutable thisOPCM; + + /// @notice Constructor to initialize the immutable thisOPCM variable and contract addresses + /// @param _contractsContainer The blueprint contract addresses and implementation contract addresses + constructor(OPContractsManagerContractsContainer _contractsContainer) { + contractsContainer = _contractsContainer; + thisOPCM = this; } - /// @notice The input required to identify a chain for upgrading, along with new prestate hashes - struct OpChainConfig { - ISystemConfig systemConfigProxy; - IProxyAdmin proxyAdmin; - Claim absolutePrestate; + /// @notice Retrieves the implementation addresses stored in this OPCM contract + function getImplementations() internal view returns (OPContractsManager.Implementations memory) { + return thisOPCM.implementations(); } - struct AddGameInput { - string saltMixer; - ISystemConfig systemConfig; - IProxyAdmin proxyAdmin; - IDelayedWETH delayedWETH; - GameType disputeGameType; - Claim disputeAbsolutePrestate; - uint256 disputeMaxGameDepth; - uint256 disputeSplitDepth; - Duration disputeClockExtension; - Duration disputeMaxClockDuration; - uint256 initialBond; - IBigStepper vm; - bool permissioned; + /// @notice Retrieves the blueprint addresses stored in this OPCM contract + function getBlueprints() internal view returns (OPContractsManager.Blueprints memory) { + return thisOPCM.blueprints(); } - struct AddGameOutput { - IDelayedWETH delayedWETH; - IFaultDisputeGame faultDisputeGame; + /// @notice Retrieves the implementation addresses stored in this OPCM contract + function implementations() public view returns (OPContractsManager.Implementations memory) { + return contractsContainer.implementations(); } - // -------- Constants and Variables -------- + /// @notice Retrieves the blueprint addresses stored in this OPCM contract + function blueprints() public view returns (OPContractsManager.Blueprints memory) { + return contractsContainer.blueprints(); + } - /// @custom:semver 1.6.0 - function version() public pure virtual returns (string memory) { - return "1.6.0"; + /// @notice Maps an L2 chain ID to an L1 batch inbox address as defined by the standard + /// configuration's convention. This convention is `versionByte || keccak256(bytes32(chainId))[:19]`, + /// where || denotes concatenation`, versionByte is 0x00, and chainId is a uint256. + /// https://specs.optimism.io/protocol/configurability.html#consensus-parameters + function chainIdToBatchInboxAddress(uint256 _l2ChainId) public pure returns (address) { + bytes1 versionByte = 0x00; + bytes32 hashedChainId = keccak256(bytes.concat(bytes32(_l2ChainId))); + bytes19 first19Bytes = bytes19(hashedChainId); + return address(uint160(bytes20(bytes.concat(versionByte, first19Bytes)))); } - /// @notice Address of the SuperchainConfig contract shared by all chains. - ISuperchainConfig public immutable superchainConfig; + /// @notice Helper method for computing a salt that's used in CREATE2 deployments. + /// Including the contract name ensures that the resultant address from CREATE2 is unique + /// across our smart contract system. For example, we deploy multiple proxy contracts + /// with the same bytecode from this contract, so they each require a unique salt for determinism. + function computeSalt( + uint256 _l2ChainId, + string memory _saltMixer, + string memory _contractName + ) + internal + pure + returns (bytes32) + { + return keccak256(abi.encode(_l2ChainId, _saltMixer, _contractName)); + } - /// @notice Address of the ProtocolVersions contract shared by all chains. - IProtocolVersions public immutable protocolVersions; + /// @notice Helper method for computing a reusable salt mixer + /// This method should be used as the salt mixer when deploying contracts when there is no user + /// provided salt mixer. This protects against a situation where multiple chains with the same + /// L2 chain ID exist, which would otherwise result in address collisions. + function reusableSaltMixer(OPContractsManager.OpChainConfig memory _opChainConfig) + internal + pure + returns (string memory) + { + return string(bytes.concat(bytes32(uint256(uint160(address(_opChainConfig.systemConfigProxy)))))); + } - /// @notice Address of the SuperchainProxyAdmin contract shared by all chains. - IProxyAdmin public immutable superchainProxyAdmin; + /// @notice Deterministically deploys a new proxy contract owned by the provided ProxyAdmin. + /// The salt is computed as a function of the L2 chain ID, the salt mixer and the contract name. + /// This is required because we deploy many identical proxies, so they each require a unique salt for determinism. + function deployProxy( + uint256 _l2ChainId, + IProxyAdmin _proxyAdmin, + string memory _saltMixer, + string memory _contractName + ) + internal + returns (address) + { + bytes32 salt = computeSalt(_l2ChainId, _saltMixer, _contractName); + return Blueprint.deployFrom(getBlueprints().proxy, salt, abi.encode(_proxyAdmin)); + } - /// @notice L1 smart contracts release deployed by this version of OPCM. This is used in opcm to signal which - /// version of the L1 smart contracts is deployed. It takes the format of `op-contracts/vX.Y.Z`. - string internal L1_CONTRACTS_RELEASE; + /// @notice Makes an internal call to the target to initialize the proxy with the specified data. + /// First performs safety checks to ensure the target, implementation, and proxy admin are valid. + function upgradeToAndCall( + IProxyAdmin _proxyAdmin, + address _target, + address _implementation, + bytes memory _data + ) + internal + { + assertValidContractAddress(_implementation); - /// @notice Addresses of the Blueprint contracts. - /// This is internal because if public the autogenerated getter method would return a tuple of - /// addresses, but we want it to return a struct. - Blueprints internal blueprint; + _proxyAdmin.upgradeAndCall(payable(address(_target)), _implementation, _data); + } - /// @notice Addresses of the latest implementation contracts. - Implementations internal implementation; + function assertValidContractAddress(address _who) public view { + if (_who.code.length == 0) revert OPContractsManager.AddressHasNoCode(_who); + } - /// @notice The OPContractsManager contract that is currently being used. This is needed in the upgrade function - /// which is intended to be DELEGATECALLed. - OPContractsManager internal immutable thisOPCM; + function encodePermissionlessFDGConstructor(IFaultDisputeGame.GameConstructorParams memory _params) + internal + view + virtual + returns (bytes memory) + { + bytes memory dataWithSelector = abi.encodeCall(IFaultDisputeGame.__constructor__, (_params)); + return Bytes.slice(dataWithSelector, 4); + } - /// @notice The address of the upgrade controller. - address public immutable upgradeController; + function encodePermissionedFDGConstructor( + IFaultDisputeGame.GameConstructorParams memory _params, + address _proposer, + address _challenger + ) + internal + view + virtual + returns (bytes memory) + { + bytes memory dataWithSelector = + abi.encodeCall(IPermissionedDisputeGame.__constructor__, (_params, _proposer, _challenger)); + return Bytes.slice(dataWithSelector, 4); + } - /// @notice Whether this is a release candidate. - bool public isRC = true; + /// @notice Returns the implementation contract address for a given game type. + function getGameImplementation( + IDisputeGameFactory _disputeGameFactory, + GameType _gameType + ) + internal + view + returns (IDisputeGame) + { + return _disputeGameFactory.gameImpls(_gameType); + } - /// @notice Returns the release string. Appends "-rc" if this is a release candidate. - function l1ContractsRelease() external view virtual returns (string memory) { - return isRC ? string.concat(L1_CONTRACTS_RELEASE, "-rc") : L1_CONTRACTS_RELEASE; + /// @notice Retrieves the Anchor State Registry for a given game + function getAnchorStateRegistry(IFaultDisputeGame _disputeGame) internal view returns (IAnchorStateRegistry) { + return _disputeGame.anchorStateRegistry(); } - // -------- Events -------- + /// @notice Retrieves the L2 chain ID for a given game + function getL2ChainId(IFaultDisputeGame _disputeGame) internal view returns (uint256) { + return _disputeGame.l2ChainId(); + } - /// @notice Emitted when a new OP Stack chain is deployed. - /// @param l2ChainId Chain ID of the new chain. - /// @param deployer Address that deployed the chain. - /// @param deployOutput ABI-encoded output of the deployment. - event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); + /// @notice Retrieves the proposer address for a given game + function getProposer(IPermissionedDisputeGame _disputeGame) internal view returns (address) { + return _disputeGame.proposer(); + } - /// @notice Emitted when a chain is upgraded - /// @param systemConfig Address of the chain's SystemConfig contract - /// @param upgrader Address that initiated the upgrade - event Upgraded(uint256 indexed l2ChainId, ISystemConfig indexed systemConfig, address indexed upgrader); + /// @notice Retrieves the challenger address for a given game + function getChallenger(IPermissionedDisputeGame _disputeGame) internal view returns (address) { + return _disputeGame.challenger(); + } + + /// @notice Retrieves the DisputeGameFactory address for a given SystemConfig + function getDisputeGameFactory(ISystemConfig _systemConfig) internal view returns (IDisputeGameFactory) { + return IDisputeGameFactory(_systemConfig.disputeGameFactory()); + } + + /// @notice Retrieves the constructor params for a given game. + function getGameConstructorParams(IFaultDisputeGame _disputeGame) + internal + view + returns (IFaultDisputeGame.GameConstructorParams memory) + { + IFaultDisputeGame.GameConstructorParams memory params = IFaultDisputeGame.GameConstructorParams({ + gameType: _disputeGame.gameType(), + absolutePrestate: _disputeGame.absolutePrestate(), + maxGameDepth: _disputeGame.maxGameDepth(), + splitDepth: _disputeGame.splitDepth(), + clockExtension: _disputeGame.clockExtension(), + maxClockDuration: _disputeGame.maxClockDuration(), + vm: _disputeGame.vm(), + weth: getWETH(_disputeGame), + anchorStateRegistry: getAnchorStateRegistry(_disputeGame), + l2ChainId: getL2ChainId(_disputeGame) + }); + return params; + } + /// @notice Retrieves the DelayedWETH address for a given game + function getWETH(IFaultDisputeGame _disputeGame) internal view returns (IDelayedWETH) { + return _disputeGame.weth(); + } + + /// @notice Sets a game implementation on the dispute game factory + function setDGFImplementation(IDisputeGameFactory _dgf, GameType _gameType, IDisputeGame _newGame) internal { + _dgf.setImplementation(_gameType, _newGame); + } +} + +contract OPContractsManagerGameTypeAdder is OPContractsManagerBase { /// @notice Emitted when a new game type is added to a chain /// @param l2ChainId Chain ID of the chain /// @param gameType Type of the game being @@ -209,279 +270,289 @@ contract OPContractsManager is ISemver { uint256 indexed l2ChainId, GameType indexed gameType, IDisputeGame newDisputeGame, IDisputeGame oldDisputeGame ); - // -------- Errors -------- + /// @notice Constructor to initialize the immutable thisOPCM variable and contract addresses + /// @param _contractsContainer The blueprint contract addresses and implementation contract addresses + constructor(OPContractsManagerContractsContainer _contractsContainer) OPContractsManagerBase(_contractsContainer) { } - /// @notice Thrown when an address other than the upgrade controller calls the setRC function. - error OnlyUpgradeController(); + /// @notice addGameType deploys a new dispute game and links it to the DisputeGameFactory. The inputted _gameConfigs + /// must be added in ascending GameType order. + function addGameType( + OPContractsManager.AddGameInput[] memory _gameConfigs, + ISuperchainConfig _superchainConfig + ) + public + virtual + returns (OPContractsManager.AddGameOutput[] memory) + { + if (_gameConfigs.length == 0) revert OPContractsManager.InvalidGameConfigs(); - /// @notice Thrown when an address is the zero address. - error AddressNotFound(address who); + OPContractsManager.AddGameOutput[] memory outputs = new OPContractsManager.AddGameOutput[](_gameConfigs.length); + OPContractsManager.Blueprints memory bps = getBlueprints(); - /// @notice Throw when a contract address has no code. - error AddressHasNoCode(address who); + // Store last game config as an int256 so that we can ensure that the same game config is not added twice. + // Using int256 generates cheaper, simpler bytecode. + int256 lastGameConfig = -1; - /// @notice Thrown when a release version is already set. - error AlreadyReleased(); + for (uint256 i = 0; i < _gameConfigs.length; i++) { + OPContractsManager.AddGameInput memory gameConfig = _gameConfigs[i]; - /// @notice Thrown when an invalid `l2ChainId` is provided to `deploy`. - error InvalidChainId(); + // This conversion is safe because the GameType is a uint32, which will always fit in an int256. + int256 gameTypeInt = int256(uint256(gameConfig.disputeGameType.raw())); + // Ensure that the game configs are added in ascending order, and not duplicated. + if (lastGameConfig >= gameTypeInt) revert OPContractsManager.InvalidGameConfigs(); + lastGameConfig = gameTypeInt; - /// @notice Thrown when a role's address is not valid. - error InvalidRoleAddress(string role); + // Grab the permissioned and fault dispute games from the SystemConfig. + // We keep the FDG type as it reduces casting below. + IFaultDisputeGame pdg = IFaultDisputeGame( + address( + getGameImplementation(getDisputeGameFactory(gameConfig.systemConfig), GameTypes.PERMISSIONED_CANNON) + ) + ); + // Pull out the chain ID. + uint256 l2ChainId = getL2ChainId(pdg); - /// @notice Thrown when the latest release is not set upon initialization. - error LatestReleaseNotSet(); + // Deploy a new DelayedWETH proxy for this game if one hasn't already been specified. Leaving + /// gameConfig.delayedWETH as the zero address will cause a new DelayedWETH to be deployed for this game. + if (address(gameConfig.delayedWETH) == address(0)) { + string memory contractName = string.concat( + "DelayedWETH-", + // This is a safe cast because GameType is a uint256 under the hood and no operation has been done + // on it at this point + Strings.toString(uint256(gameTypeInt)) + ); + outputs[i].delayedWETH = IDelayedWETH( + payable(deployProxy(l2ChainId, gameConfig.proxyAdmin, gameConfig.saltMixer, contractName)) + ); - /// @notice Thrown when the starting anchor root is not provided. - error InvalidStartingAnchorRoot(); + // Initialize the proxy. + upgradeToAndCall( + gameConfig.proxyAdmin, + address(outputs[i].delayedWETH), + getImplementations().delayedWETHImpl, + abi.encodeCall(IDelayedWETH.initialize, (gameConfig.proxyAdmin.owner(), _superchainConfig)) + ); + } else { + outputs[i].delayedWETH = gameConfig.delayedWETH; + } - /// @notice Thrown when certain methods are called outside of a DELEGATECALL. - error OnlyDelegatecall(); + // The FDG is only used for the event below, and only if it is being replaced, + // so we declare it here, but only assign it below if needed. + IFaultDisputeGame fdg; - /// @notice Thrown when game configs passed to addGameType are invalid. - error InvalidGameConfigs(); + // The below sections are functionally the same. Both deploy a new dispute game. The dispute game type is + // either permissioned or permissionless depending on game config. + if (gameConfig.permissioned) { + outputs[i].faultDisputeGame = IFaultDisputeGame( + Blueprint.deployFrom( + bps.permissionedDisputeGame1, + bps.permissionedDisputeGame2, + computeSalt(l2ChainId, gameConfig.saltMixer, "PermissionedDisputeGame"), + encodePermissionedFDGConstructor( + IFaultDisputeGame.GameConstructorParams( + gameConfig.disputeGameType, + gameConfig.disputeAbsolutePrestate, + gameConfig.disputeMaxGameDepth, + gameConfig.disputeSplitDepth, + gameConfig.disputeClockExtension, + gameConfig.disputeMaxClockDuration, + gameConfig.vm, + outputs[i].delayedWETH, + getAnchorStateRegistry(pdg), + l2ChainId + ), + getProposer(IPermissionedDisputeGame(address(pdg))), + getChallenger(IPermissionedDisputeGame(address(pdg))) + ) + ) + ); + } else { + fdg = IFaultDisputeGame( + address(getGameImplementation(getDisputeGameFactory(gameConfig.systemConfig), GameTypes.CANNON)) + ); + outputs[i].faultDisputeGame = IFaultDisputeGame( + Blueprint.deployFrom( + bps.permissionlessDisputeGame1, + bps.permissionlessDisputeGame2, + computeSalt(l2ChainId, gameConfig.saltMixer, "PermissionlessDisputeGame"), + encodePermissionlessFDGConstructor( + IFaultDisputeGame.GameConstructorParams( + gameConfig.disputeGameType, + gameConfig.disputeAbsolutePrestate, + gameConfig.disputeMaxGameDepth, + gameConfig.disputeSplitDepth, + gameConfig.disputeClockExtension, + gameConfig.disputeMaxClockDuration, + gameConfig.vm, + outputs[i].delayedWETH, + // We can't assume that there is an existing fault dispute game, + // so get the Anchor State Registry from the permissioned game. + getAnchorStateRegistry(pdg), + l2ChainId + ) + ) + ) + ); + } - /// @notice Thrown when the SuperchainConfig of the chain does not match the SuperchainConfig of this OPCM. - error SuperchainConfigMismatch(ISystemConfig systemConfig); + // As a last step, register the new game type with the DisputeGameFactory. If the game type already exists, + // then its implementation will be overwritten. + IDisputeGameFactory dgf = getDisputeGameFactory(gameConfig.systemConfig); + setDGFImplementation(dgf, gameConfig.disputeGameType, IDisputeGame(address(outputs[i].faultDisputeGame))); + dgf.setInitBond(gameConfig.disputeGameType, gameConfig.initialBond); - /// @notice Thrown when the SuperchainProxyAdmin does not match the SuperchainConfig's admin. - error SuperchainProxyAdminMismatch(); + if (gameConfig.permissioned) { + // Emit event for the newly added game type with the old permissioned dispute game + emit GameTypeAdded( + l2ChainId, gameConfig.disputeGameType, outputs[i].faultDisputeGame, IDisputeGame(address(pdg)) + ); + } else { + // Emit event for the newly added game type with the old fault dispute game + emit GameTypeAdded( + l2ChainId, gameConfig.disputeGameType, outputs[i].faultDisputeGame, IDisputeGame(address(fdg)) + ); + } + } - /// @notice Thrown when a prestate is not set for a game. - error PrestateNotSet(); + return outputs; + } - // -------- Methods -------- + /// @notice Updates the prestate hash for a new game type while keeping all other parameters the same + /// @param _prestateUpdateInputs The new prestate hash to use + function updatePrestate( + OPContractsManager.OpChainConfig[] memory _prestateUpdateInputs, + ISuperchainConfig _superchainConfig + ) + public + { + // Loop through each chain and prestate hash + for (uint256 i = 0; i < _prestateUpdateInputs.length; i++) { + if (Claim.unwrap(_prestateUpdateInputs[i].absolutePrestate) == bytes32(0)) { + revert OPContractsManager.PrestateRequired(); + } - constructor( - ISuperchainConfig _superchainConfig, - IProtocolVersions _protocolVersions, - IProxyAdmin _superchainProxyAdmin, - string memory _l1ContractsRelease, - Blueprints memory _blueprints, - Implementations memory _implementations, - address _upgradeController - ) { - assertValidContractAddress(address(_superchainConfig)); - assertValidContractAddress(address(_protocolVersions)); - superchainConfig = _superchainConfig; - protocolVersions = _protocolVersions; - superchainProxyAdmin = _superchainProxyAdmin; - L1_CONTRACTS_RELEASE = _l1ContractsRelease; - blueprint = _blueprints; - implementation = _implementations; - thisOPCM = this; - upgradeController = _upgradeController; - } + // Get the DisputeGameFactory and existing game implementations + IDisputeGameFactory dgf = + IDisputeGameFactory(_prestateUpdateInputs[i].systemConfigProxy.disputeGameFactory()); + IFaultDisputeGame fdg = IFaultDisputeGame(address(getGameImplementation(dgf, GameTypes.CANNON))); + IPermissionedDisputeGame pdg = + IPermissionedDisputeGame(address(getGameImplementation(dgf, GameTypes.PERMISSIONED_CANNON))); + + // All chains must have a permissioned game, but not all chains must have a fault dispute game. + // Whether a chain has a fault dispute game determines how many AddGameInput objects are needed. + bool hasFDG = address(fdg) != address(0); + + OPContractsManager.AddGameInput[] memory inputs = new OPContractsManager.AddGameInput[](hasFDG ? 2 : 1); + OPContractsManager.AddGameInput memory pdgInput; + OPContractsManager.AddGameInput memory fdgInput; + + // Get the existing game parameters and init bond for the permissioned game + IFaultDisputeGame.GameConstructorParams memory pdgParams = + getGameConstructorParams(IFaultDisputeGame(address(pdg))); + uint256 initBond = dgf.initBonds(GameTypes.PERMISSIONED_CANNON); + + string memory saltMixer = reusableSaltMixer(_prestateUpdateInputs[i]); + // Create game input with updated prestate but same other params + pdgInput = OPContractsManager.AddGameInput({ + disputeAbsolutePrestate: _prestateUpdateInputs[i].absolutePrestate, + saltMixer: saltMixer, + systemConfig: _prestateUpdateInputs[i].systemConfigProxy, + proxyAdmin: _prestateUpdateInputs[i].proxyAdmin, + delayedWETH: IDelayedWETH(payable(address(pdgParams.weth))), + disputeGameType: pdgParams.gameType, + disputeMaxGameDepth: pdgParams.maxGameDepth, + disputeSplitDepth: pdgParams.splitDepth, + disputeClockExtension: pdgParams.clockExtension, + disputeMaxClockDuration: pdgParams.maxClockDuration, + initialBond: initBond, + vm: pdgParams.vm, + permissioned: true + }); - function deploy(DeployInput calldata _input) external virtual returns (DeployOutput memory) { - assertValidInputs(_input); - uint256 l2ChainId = _input.l2ChainId; - string memory saltMixer = _input.saltMixer; - DeployOutput memory output; + // If a fault dispute game exists, create a new game with the same parameters but updated prestate. + if (hasFDG) { + // Get the existing game parameters and init bond for the fault dispute game + IFaultDisputeGame.GameConstructorParams memory fdgParams = + getGameConstructorParams(IFaultDisputeGame(address(fdg))); + initBond = dgf.initBonds(GameTypes.CANNON); + + // Create game input with updated prestate but same other params + fdgInput = OPContractsManager.AddGameInput({ + disputeAbsolutePrestate: _prestateUpdateInputs[i].absolutePrestate, + saltMixer: saltMixer, + systemConfig: _prestateUpdateInputs[i].systemConfigProxy, + proxyAdmin: _prestateUpdateInputs[i].proxyAdmin, + delayedWETH: IDelayedWETH(payable(address(fdgParams.weth))), + disputeGameType: fdgParams.gameType, + disputeMaxGameDepth: fdgParams.maxGameDepth, + disputeSplitDepth: fdgParams.splitDepth, + disputeClockExtension: fdgParams.clockExtension, + disputeMaxClockDuration: fdgParams.maxClockDuration, + initialBond: initBond, + vm: fdgParams.vm, + permissioned: false + }); + } - // -------- Deploy Chain Singletons -------- + // Game inputs must be ordered with increasing game type values. So FDG is first if it exists. + if (hasFDG) { + inputs[0] = fdgInput; + inputs[1] = pdgInput; + } else { + inputs[0] = pdgInput; + } + // Add the new game type with updated prestate + addGameType(inputs, _superchainConfig); + } + } +} - // The AddressManager is used to store the implementation for the L1CrossDomainMessenger - // due to it's usage of the legacy ResolvedDelegateProxy. - output.addressManager = IAddressManager( - Blueprint.deployFrom( - blueprint.addressManager, computeSalt(l2ChainId, saltMixer, "AddressManager"), abi.encode() - ) - ); - // The ProxyAdmin is the owner of all proxies for the chain. We temporarily set the owner to - // this contract, and then transfer ownership to the specified owner at the end of deployment. - output.opChainProxyAdmin = IProxyAdmin( - Blueprint.deployFrom( - blueprint.proxyAdmin, computeSalt(l2ChainId, saltMixer, "ProxyAdmin"), abi.encode(address(this)) - ) - ); - // Set the AddressManager on the ProxyAdmin. - output.opChainProxyAdmin.setAddressManager(output.addressManager); - // Transfer ownership of the AddressManager to the ProxyAdmin. - transferOwnership(address(output.addressManager), address(output.opChainProxyAdmin)); +contract OPContractsManagerUpgrader is OPContractsManagerBase { + struct UpgradeInput { + ISuperchainConfig superchainConfig; + IProtocolVersions protocolVersions; + IProxyAdmin superchainProxyAdmin; + } - // -------- Deploy Proxy Contracts -------- + /// @notice Emitted when a chain is upgraded + /// @param systemConfig Address of the chain's SystemConfig contract + /// @param upgrader Address that initiated the upgrade + event Upgraded(uint256 indexed l2ChainId, ISystemConfig indexed systemConfig, address indexed upgrader); - // Deploy ERC-1967 proxied contracts. - output.l1ERC721BridgeProxy = - IL1ERC721Bridge(deployProxy(l2ChainId, output.opChainProxyAdmin, saltMixer, "L1ERC721Bridge")); - output.optimismPortalProxy = - IOptimismPortal2(payable(deployProxy(l2ChainId, output.opChainProxyAdmin, saltMixer, "OptimismPortal"))); - output.systemConfigProxy = - ISystemConfig(deployProxy(l2ChainId, output.opChainProxyAdmin, saltMixer, "SystemConfig")); - output.optimismMintableERC20FactoryProxy = IOptimismMintableERC20Factory( - deployProxy(l2ChainId, output.opChainProxyAdmin, saltMixer, "OptimismMintableERC20Factory") - ); - output.disputeGameFactoryProxy = - IDisputeGameFactory(deployProxy(l2ChainId, output.opChainProxyAdmin, saltMixer, "DisputeGameFactory")); - output.anchorStateRegistryProxy = - IAnchorStateRegistry(deployProxy(l2ChainId, output.opChainProxyAdmin, saltMixer, "AnchorStateRegistry")); - - // Deploy legacy proxied contracts. - output.l1StandardBridgeProxy = IL1StandardBridge( - payable( - Blueprint.deployFrom( - blueprint.l1ChugSplashProxy, - computeSalt(l2ChainId, saltMixer, "L1StandardBridge"), - abi.encode(output.opChainProxyAdmin) - ) - ) - ); - output.opChainProxyAdmin.setProxyType(address(output.l1StandardBridgeProxy), IProxyAdmin.ProxyType.CHUGSPLASH); - string memory contractName = "OVM_L1CrossDomainMessenger"; - output.l1CrossDomainMessengerProxy = IL1CrossDomainMessenger( - Blueprint.deployFrom( - blueprint.resolvedDelegateProxy, - computeSalt(l2ChainId, saltMixer, "L1CrossDomainMessenger"), - abi.encode(output.addressManager, contractName) - ) - ); - output.opChainProxyAdmin.setProxyType( - address(output.l1CrossDomainMessengerProxy), IProxyAdmin.ProxyType.RESOLVED - ); - output.opChainProxyAdmin.setImplementationName(address(output.l1CrossDomainMessengerProxy), contractName); - - // Eventually we will switch from DelayedWETHPermissionedGameProxy to DelayedWETHPermissionlessGameProxy. - output.delayedWETHPermissionedGameProxy = IDelayedWETH( - payable(deployProxy(l2ChainId, output.opChainProxyAdmin, saltMixer, "DelayedWETHPermissionedGame")) - ); - - // While not a proxy, we deploy the PermissionedDisputeGame here as well because it's bespoke per chain. - output.permissionedDisputeGame = IPermissionedDisputeGame( - Blueprint.deployFrom( - blueprint.permissionedDisputeGame1, - blueprint.permissionedDisputeGame2, - computeSalt(l2ChainId, saltMixer, "PermissionedDisputeGame"), - encodePermissionedFDGConstructor( - IFaultDisputeGame.GameConstructorParams({ - gameType: _input.disputeGameType, - absolutePrestate: _input.disputeAbsolutePrestate, - maxGameDepth: _input.disputeMaxGameDepth, - splitDepth: _input.disputeSplitDepth, - clockExtension: _input.disputeClockExtension, - maxClockDuration: _input.disputeMaxClockDuration, - vm: IBigStepper(implementation.mipsImpl), - weth: IDelayedWETH(payable(address(output.delayedWETHPermissionedGameProxy))), - anchorStateRegistry: IAnchorStateRegistry(address(output.anchorStateRegistryProxy)), - l2ChainId: _input.l2ChainId - }), - _input.roles.proposer, - _input.roles.challenger - ) - ) - ); - - // -------- Set and Initialize Proxy Implementations -------- - bytes memory data; - - data = encodeL1ERC721BridgeInitializer(output); - upgradeToAndCall( - output.opChainProxyAdmin, address(output.l1ERC721BridgeProxy), implementation.l1ERC721BridgeImpl, data - ); - - data = encodeOptimismPortalInitializer(output); - upgradeToAndCall( - output.opChainProxyAdmin, address(output.optimismPortalProxy), implementation.optimismPortalImpl, data - ); - - data = encodeSystemConfigInitializer(_input, output); - upgradeToAndCall( - output.opChainProxyAdmin, address(output.systemConfigProxy), implementation.systemConfigImpl, data - ); - - data = encodeOptimismMintableERC20FactoryInitializer(output); - upgradeToAndCall( - output.opChainProxyAdmin, - address(output.optimismMintableERC20FactoryProxy), - implementation.optimismMintableERC20FactoryImpl, - data - ); - - data = encodeL1CrossDomainMessengerInitializer(output); - upgradeToAndCall( - output.opChainProxyAdmin, - address(output.l1CrossDomainMessengerProxy), - implementation.l1CrossDomainMessengerImpl, - data - ); - - data = encodeL1StandardBridgeInitializer(output); - upgradeToAndCall( - output.opChainProxyAdmin, address(output.l1StandardBridgeProxy), implementation.l1StandardBridgeImpl, data - ); - - data = encodeDelayedWETHInitializer(_input); - // Eventually we will switch from DelayedWETHPermissionedGameProxy to DelayedWETHPermissionlessGameProxy. - upgradeToAndCall( - output.opChainProxyAdmin, - address(output.delayedWETHPermissionedGameProxy), - implementation.delayedWETHImpl, - data - ); - - // We set the initial owner to this contract, set game implementations, then transfer ownership. - data = encodeDisputeGameFactoryInitializer(); - upgradeToAndCall( - output.opChainProxyAdmin, - address(output.disputeGameFactoryProxy), - implementation.disputeGameFactoryImpl, - data - ); - setDGFImplementation( - output.disputeGameFactoryProxy, - GameTypes.PERMISSIONED_CANNON, - IDisputeGame(address(output.permissionedDisputeGame)) - ); - - transferOwnership(address(output.disputeGameFactoryProxy), address(_input.roles.opChainProxyAdminOwner)); - - data = encodeAnchorStateRegistryInitializer(_input, output); - upgradeToAndCall( - output.opChainProxyAdmin, - address(output.anchorStateRegistryProxy), - implementation.anchorStateRegistryImpl, - data - ); - - // -------- Finalize Deployment -------- - // Transfer ownership of the ProxyAdmin from this contract to the specified owner. - transferOwnership(address(output.opChainProxyAdmin), _input.roles.opChainProxyAdminOwner); - - emit Deployed(l2ChainId, msg.sender, abi.encode(output)); - return output; - } - - /// @notice Verifies that all OpChainConfig inputs are valid and reverts if any are invalid. - function assertValidOpChainConfig(OpChainConfig memory _config) internal view { - assertValidContractAddress(address(_config.systemConfigProxy)); - assertValidContractAddress(address(_config.proxyAdmin)); - } + constructor(OPContractsManagerContractsContainer _contractsContainer) OPContractsManagerBase(_contractsContainer) { } /// @notice Upgrades a set of chains to the latest implementation contracts /// @param _opChainConfigs Array of OpChain structs, one per chain to upgrade /// @dev This function is intended to be called via DELEGATECALL from the Upgrade Controller Safe - function upgrade(OpChainConfig[] memory _opChainConfigs) external virtual { - if (address(this) == address(thisOPCM)) revert OnlyDelegatecall(); - - // If this is delegatecalled by the upgrade controller, set isRC to false first, else, continue execution. - if (address(this) == upgradeController) { - // Set isRC to false. - // This function asserts that the caller is the upgrade controller. - thisOPCM.setRC(false); - } - - Implementations memory impls = getImplementations(); - Blueprints memory bps = getBlueprints(); + function upgrade( + OPContractsManager.OpChainConfig[] memory _opChainConfigs, + UpgradeInput memory _upgradeInput + ) + external + virtual + { + OPContractsManager.Implementations memory impls = getImplementations(); + OPContractsManager.Blueprints memory bps = getBlueprints(); // If the SuperchainConfig is not already upgraded, upgrade it. - if (superchainProxyAdmin.getProxyImplementation(address(superchainConfig)) != impls.superchainConfigImpl) { + if ( + _upgradeInput.superchainProxyAdmin.getProxyImplementation(address(_upgradeInput.superchainConfig)) + != impls.superchainConfigImpl + ) { // Attempt to upgrade. If the ProxyAdmin is not the SuperchainConfig's admin, this will revert. - upgradeTo(superchainProxyAdmin, address(superchainConfig), impls.superchainConfigImpl); + upgradeTo( + _upgradeInput.superchainProxyAdmin, address(_upgradeInput.superchainConfig), impls.superchainConfigImpl + ); } // If the ProtocolVersions contract is not already upgraded, upgrade it. - if (superchainProxyAdmin.getProxyImplementation(address(protocolVersions)) != impls.protocolVersionsImpl) { - upgradeTo(superchainProxyAdmin, address(protocolVersions), impls.protocolVersionsImpl); + if ( + _upgradeInput.superchainProxyAdmin.getProxyImplementation(address(_upgradeInput.protocolVersions)) + != impls.protocolVersionsImpl + ) { + upgradeTo( + _upgradeInput.superchainProxyAdmin, address(_upgradeInput.protocolVersions), impls.protocolVersionsImpl + ); } for (uint256 i = 0; i < _opChainConfigs.length; i++) { @@ -499,12 +570,12 @@ contract OPContractsManager is ISemver { // Check that all contracts have the correct superchainConfig if ( - getSuperchainConfig(opChainAddrs.optimismPortal) != superchainConfig - || getSuperchainConfig(opChainAddrs.l1CrossDomainMessenger) != superchainConfig - || getSuperchainConfig(opChainAddrs.l1ERC721Bridge) != superchainConfig - || getSuperchainConfig(opChainAddrs.l1StandardBridge) != superchainConfig + getSuperchainConfig(opChainAddrs.optimismPortal) != _upgradeInput.superchainConfig + || getSuperchainConfig(opChainAddrs.l1CrossDomainMessenger) != _upgradeInput.superchainConfig + || getSuperchainConfig(opChainAddrs.l1ERC721Bridge) != _upgradeInput.superchainConfig + || getSuperchainConfig(opChainAddrs.l1StandardBridge) != _upgradeInput.superchainConfig ) { - revert SuperchainConfigMismatch(_opChainConfigs[i].systemConfigProxy); + revert OPContractsManager.SuperchainConfigMismatch(_opChainConfigs[i].systemConfigProxy); } // -------- Upgrade Contracts Stored in SystemConfig -------- @@ -575,7 +646,7 @@ contract OPContractsManager is ISemver { abi.encodeCall( IAnchorStateRegistry.initialize, ( - superchainConfig, + _upgradeInput.superchainConfig, IDisputeGameFactory(opChainAddrs.disputeGameFactory), IOptimismPortal2(payable(opChainAddrs.optimismPortal)), startingAnchorRoot @@ -623,226 +694,375 @@ contract OPContractsManager is ISemver { } } - /// @notice addGameType deploys a new dispute game and links it to the DisputeGameFactory. The inputted _gameConfigs - /// must be added in ascending GameType order. - function addGameType(AddGameInput[] memory _gameConfigs) public virtual returns (AddGameOutput[] memory) { - if (address(this) == address(thisOPCM)) revert OnlyDelegatecall(); - if (_gameConfigs.length == 0) revert InvalidGameConfigs(); + /// @notice Retrieves the Superchain Config for a bridge contract + function getSuperchainConfig(address _hasSuperchainConfig) internal view returns (ISuperchainConfig) { + return IHasSuperchainConfig(_hasSuperchainConfig).superchainConfig(); + } - AddGameOutput[] memory outputs = new AddGameOutput[](_gameConfigs.length); - Blueprints memory bps = getBlueprints(); + /// @notice Updates the implementation of a proxy without calling the initializer. + /// First performs safety checks to ensure the target, implementation, and proxy admin are valid. + function upgradeTo(IProxyAdmin _proxyAdmin, address _target, address _implementation) internal { + assertValidContractAddress(_implementation); - // Store last game config as an int256 so that we can ensure that the same game config is not added twice. - // Using int256 generates cheaper, simpler bytecode. - int256 lastGameConfig = -1; + _proxyAdmin.upgrade(payable(address(_target)), _implementation); + } - for (uint256 i = 0; i < _gameConfigs.length; i++) { - AddGameInput memory gameConfig = _gameConfigs[i]; + /// @notice Verifies that all OpChainConfig inputs are valid and reverts if any are invalid. + function assertValidOpChainConfig(OPContractsManager.OpChainConfig memory _config) internal view { + assertValidContractAddress(address(_config.systemConfigProxy)); + assertValidContractAddress(address(_config.proxyAdmin)); + } - // This conversion is safe because the GameType is a uint32, which will always fit in an int256. - int256 gameTypeInt = int256(uint256(gameConfig.disputeGameType.raw())); - // Ensure that the game configs are added in ascending order, and not duplicated. - if (lastGameConfig >= gameTypeInt) revert InvalidGameConfigs(); - lastGameConfig = gameTypeInt; + /// @notice Deploys and sets a new dispute game implementation + /// @param _l2ChainId The L2 chain ID + /// @param _disputeGame The current dispute game implementation + /// @param _newAnchorStateRegistryProxy The new anchor state registry proxy + /// @param _gameType The type of game to deploy + /// @param _opChainConfig The OP chain configuration + /// @param _blueprints The blueprint addresses + /// @param _implementations The implementation addresses + /// @param _opChainAddrs The OP chain addresses + function deployAndSetNewGameImpl( + uint256 _l2ChainId, + IDisputeGame _disputeGame, + IAnchorStateRegistry _newAnchorStateRegistryProxy, + GameType _gameType, + OPContractsManager.OpChainConfig memory _opChainConfig, + OPContractsManager.Blueprints memory _blueprints, + OPContractsManager.Implementations memory _implementations, + ISystemConfig.Addresses memory _opChainAddrs + ) + internal + { + // independently scoped block to avoid stack too deep + { + // Get and upgrade the WETH proxy + IDelayedWETH delayedWethProxy = getWETH(IFaultDisputeGame(address(_disputeGame))); + upgradeTo(_opChainConfig.proxyAdmin, address(delayedWethProxy), _implementations.delayedWETHImpl); + } - // Grab the permissioned and fault dispute games from the SystemConfig. - // We keep the FDG type as it reduces casting below. - IFaultDisputeGame pdg = IFaultDisputeGame( - address( - getGameImplementation(getDisputeGameFactory(gameConfig.systemConfig), GameTypes.PERMISSIONED_CANNON) + // Get the constructor params for the game + IFaultDisputeGame.GameConstructorParams memory params = + getGameConstructorParams(IFaultDisputeGame(address(_disputeGame))); + + // Modify the params with the new anchorStateRegistry and vm values. + params.anchorStateRegistry = IAnchorStateRegistry(address(_newAnchorStateRegistryProxy)); + params.vm = IBigStepper(_implementations.mipsImpl); + if (Claim.unwrap(_opChainConfig.absolutePrestate) == bytes32(0)) { + revert OPContractsManager.PrestateNotSet(); + } + params.absolutePrestate = _opChainConfig.absolutePrestate; + + IDisputeGame newGame; + if (GameType.unwrap(_gameType) == GameType.unwrap(GameTypes.PERMISSIONED_CANNON)) { + address proposer = getProposer(IPermissionedDisputeGame(address(_disputeGame))); + address challenger = getChallenger(IPermissionedDisputeGame(address(_disputeGame))); + newGame = IDisputeGame( + Blueprint.deployFrom( + _blueprints.permissionedDisputeGame1, + _blueprints.permissionedDisputeGame2, + computeSalt(_l2ChainId, reusableSaltMixer(_opChainConfig), "PermissionedDisputeGame"), + encodePermissionedFDGConstructor(params, proposer, challenger) ) ); - // Pull out the chain ID. - uint256 l2ChainId = getL2ChainId(pdg); + } else { + newGame = IDisputeGame( + Blueprint.deployFrom( + _blueprints.permissionlessDisputeGame1, + _blueprints.permissionlessDisputeGame2, + computeSalt(_l2ChainId, reusableSaltMixer(_opChainConfig), "PermissionlessDisputeGame"), + encodePermissionlessFDGConstructor(params) + ) + ); + } + setDGFImplementation(IDisputeGameFactory(_opChainAddrs.disputeGameFactory), _gameType, IDisputeGame(newGame)); + } +} - // Deploy a new DelayedWETH proxy for this game if one hasn't already been specified. Leaving - /// gameConfig.delayedWETH as the zero address will cause a new DelayedWETH to be deployed for this game. - if (address(gameConfig.delayedWETH) == address(0)) { - string memory contractName = string.concat( - "DelayedWETH-", - // This is a safe cast because GameType is a uint256 under the hood and no operation has been done - // on it at this point - Strings.toString(uint256(gameTypeInt)) - ); - outputs[i].delayedWETH = IDelayedWETH( - payable(deployProxy(l2ChainId, gameConfig.proxyAdmin, gameConfig.saltMixer, contractName)) - ); +contract OPContractsManagerDeployer is OPContractsManagerBase { + /// @notice Emitted when a new OP Stack chain is deployed. + /// @param l2ChainId Chain ID of the new chain. + /// @param deployer Address that deployed the chain. + /// @param deployOutput ABI-encoded output of the deployment. + event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); - // Initialize the proxy. - upgradeToAndCall( - gameConfig.proxyAdmin, - address(outputs[i].delayedWETH), - getImplementations().delayedWETHImpl, - abi.encodeCall(IDelayedWETH.initialize, (gameConfig.proxyAdmin.owner(), superchainConfig)) - ); - } else { - outputs[i].delayedWETH = gameConfig.delayedWETH; - } + constructor(OPContractsManagerContractsContainer _contractsContainer) OPContractsManagerBase(_contractsContainer) { } - // The FDG is only used for the event below, and only if it is being replaced, - // so we declare it here, but only assign it below if needed. - IFaultDisputeGame fdg; + function deploy( + OPContractsManager.DeployInput calldata _input, + ISuperchainConfig _superchainConfig, + address _deployer + ) + external + virtual + returns (OPContractsManager.DeployOutput memory) + { + assertValidInputs(_input); + OPContractsManager.DeployOutput memory output; + OPContractsManager.Blueprints memory blueprint = getBlueprints(); + OPContractsManager.Implementations memory implementation = getImplementations(); - // The below sections are functionally the same. Both deploy a new dispute game. The dispute game type is - // either permissioned or permissionless depending on game config. - if (gameConfig.permissioned) { - outputs[i].faultDisputeGame = IFaultDisputeGame( - Blueprint.deployFrom( - bps.permissionedDisputeGame1, - bps.permissionedDisputeGame2, - computeSalt(l2ChainId, gameConfig.saltMixer, "PermissionedDisputeGame"), - encodePermissionedFDGConstructor( - IFaultDisputeGame.GameConstructorParams( - gameConfig.disputeGameType, - gameConfig.disputeAbsolutePrestate, - gameConfig.disputeMaxGameDepth, - gameConfig.disputeSplitDepth, - gameConfig.disputeClockExtension, - gameConfig.disputeMaxClockDuration, - gameConfig.vm, - outputs[i].delayedWETH, - getAnchorStateRegistry(pdg), - l2ChainId - ), - getProposer(IPermissionedDisputeGame(address(pdg))), - getChallenger(IPermissionedDisputeGame(address(pdg))) - ) - ) - ); - } else { - fdg = IFaultDisputeGame( - address(getGameImplementation(getDisputeGameFactory(gameConfig.systemConfig), GameTypes.CANNON)) - ); - outputs[i].faultDisputeGame = IFaultDisputeGame( - Blueprint.deployFrom( - bps.permissionlessDisputeGame1, - bps.permissionlessDisputeGame2, - computeSalt(l2ChainId, gameConfig.saltMixer, "PermissionlessDisputeGame"), - encodePermissionlessFDGConstructor( - IFaultDisputeGame.GameConstructorParams( - gameConfig.disputeGameType, - gameConfig.disputeAbsolutePrestate, - gameConfig.disputeMaxGameDepth, - gameConfig.disputeSplitDepth, - gameConfig.disputeClockExtension, - gameConfig.disputeMaxClockDuration, - gameConfig.vm, - outputs[i].delayedWETH, - // We can't assume that there is an existing fault dispute game, - // so get the Anchor State Registry from the permissioned game. - getAnchorStateRegistry(pdg), - l2ChainId - ) - ) - ) - ); - } + // -------- Deploy Chain Singletons -------- - // As a last step, register the new game type with the DisputeGameFactory. If the game type already exists, - // then its implementation will be overwritten. - IDisputeGameFactory dgf = getDisputeGameFactory(gameConfig.systemConfig); - setDGFImplementation(dgf, gameConfig.disputeGameType, IDisputeGame(address(outputs[i].faultDisputeGame))); - dgf.setInitBond(gameConfig.disputeGameType, gameConfig.initialBond); + // The AddressManager is used to store the implementation for the L1CrossDomainMessenger + // due to it's usage of the legacy ResolvedDelegateProxy. + output.addressManager = IAddressManager( + Blueprint.deployFrom( + blueprint.addressManager, + computeSalt(_input.l2ChainId, _input.saltMixer, "AddressManager"), + abi.encode() + ) + ); + // The ProxyAdmin is the owner of all proxies for the chain. We temporarily set the owner to + // this contract, and then transfer ownership to the specified owner at the end of deployment. + output.opChainProxyAdmin = IProxyAdmin( + Blueprint.deployFrom( + blueprint.proxyAdmin, + computeSalt(_input.l2ChainId, _input.saltMixer, "ProxyAdmin"), + abi.encode(address(this)) + ) + ); + // Set the AddressManager on the ProxyAdmin. + output.opChainProxyAdmin.setAddressManager(output.addressManager); + // Transfer ownership of the AddressManager to the ProxyAdmin. + transferOwnership(address(output.addressManager), address(output.opChainProxyAdmin)); - if (gameConfig.permissioned) { - // Emit event for the newly added game type with the old permissioned dispute game - emit GameTypeAdded( - l2ChainId, gameConfig.disputeGameType, outputs[i].faultDisputeGame, IDisputeGame(address(pdg)) - ); - } else { - // Emit event for the newly added game type with the old fault dispute game - emit GameTypeAdded( - l2ChainId, gameConfig.disputeGameType, outputs[i].faultDisputeGame, IDisputeGame(address(fdg)) - ); - } - } + // -------- Deploy Proxy Contracts -------- + + // Deploy ERC-1967 proxied contracts. + output.l1ERC721BridgeProxy = + IL1ERC721Bridge(deployProxy(_input.l2ChainId, output.opChainProxyAdmin, _input.saltMixer, "L1ERC721Bridge")); + output.optimismPortalProxy = IOptimismPortal2( + payable(deployProxy(_input.l2ChainId, output.opChainProxyAdmin, _input.saltMixer, "OptimismPortal")) + ); + output.systemConfigProxy = + ISystemConfig(deployProxy(_input.l2ChainId, output.opChainProxyAdmin, _input.saltMixer, "SystemConfig")); + output.optimismMintableERC20FactoryProxy = IOptimismMintableERC20Factory( + deployProxy(_input.l2ChainId, output.opChainProxyAdmin, _input.saltMixer, "OptimismMintableERC20Factory") + ); + output.disputeGameFactoryProxy = IDisputeGameFactory( + deployProxy(_input.l2ChainId, output.opChainProxyAdmin, _input.saltMixer, "DisputeGameFactory") + ); + output.anchorStateRegistryProxy = IAnchorStateRegistry( + deployProxy(_input.l2ChainId, output.opChainProxyAdmin, _input.saltMixer, "AnchorStateRegistry") + ); + + // Deploy legacy proxied contracts. + output.l1StandardBridgeProxy = IL1StandardBridge( + payable( + Blueprint.deployFrom( + blueprint.l1ChugSplashProxy, + computeSalt(_input.l2ChainId, _input.saltMixer, "L1StandardBridge"), + abi.encode(output.opChainProxyAdmin) + ) + ) + ); + output.opChainProxyAdmin.setProxyType(address(output.l1StandardBridgeProxy), IProxyAdmin.ProxyType.CHUGSPLASH); + string memory contractName = "OVM_L1CrossDomainMessenger"; + output.l1CrossDomainMessengerProxy = IL1CrossDomainMessenger( + Blueprint.deployFrom( + blueprint.resolvedDelegateProxy, + computeSalt(_input.l2ChainId, _input.saltMixer, "L1CrossDomainMessenger"), + abi.encode(output.addressManager, contractName) + ) + ); + output.opChainProxyAdmin.setProxyType( + address(output.l1CrossDomainMessengerProxy), IProxyAdmin.ProxyType.RESOLVED + ); + output.opChainProxyAdmin.setImplementationName(address(output.l1CrossDomainMessengerProxy), contractName); + + // Eventually we will switch from DelayedWETHPermissionedGameProxy to DelayedWETHPermissionlessGameProxy. + output.delayedWETHPermissionedGameProxy = IDelayedWETH( + payable( + deployProxy(_input.l2ChainId, output.opChainProxyAdmin, _input.saltMixer, "DelayedWETHPermissionedGame") + ) + ); + + // While not a proxy, we deploy the PermissionedDisputeGame here as well because it's bespoke per chain. + output.permissionedDisputeGame = IPermissionedDisputeGame( + Blueprint.deployFrom( + blueprint.permissionedDisputeGame1, + blueprint.permissionedDisputeGame2, + computeSalt(_input.l2ChainId, _input.saltMixer, "PermissionedDisputeGame"), + encodePermissionedFDGConstructor( + IFaultDisputeGame.GameConstructorParams({ + gameType: _input.disputeGameType, + absolutePrestate: _input.disputeAbsolutePrestate, + maxGameDepth: _input.disputeMaxGameDepth, + splitDepth: _input.disputeSplitDepth, + clockExtension: _input.disputeClockExtension, + maxClockDuration: _input.disputeMaxClockDuration, + vm: IBigStepper(implementation.mipsImpl), + weth: IDelayedWETH(payable(address(output.delayedWETHPermissionedGameProxy))), + anchorStateRegistry: IAnchorStateRegistry(address(output.anchorStateRegistryProxy)), + l2ChainId: _input.l2ChainId + }), + _input.roles.proposer, + _input.roles.challenger + ) + ) + ); + + // -------- Set and Initialize Proxy Implementations -------- + bytes memory data; + + data = encodeL1ERC721BridgeInitializer(output, _superchainConfig); + upgradeToAndCall( + output.opChainProxyAdmin, address(output.l1ERC721BridgeProxy), implementation.l1ERC721BridgeImpl, data + ); + + data = encodeOptimismPortalInitializer(output, _superchainConfig); + upgradeToAndCall( + output.opChainProxyAdmin, address(output.optimismPortalProxy), implementation.optimismPortalImpl, data + ); + + data = encodeSystemConfigInitializer(_input, output); + upgradeToAndCall( + output.opChainProxyAdmin, address(output.systemConfigProxy), implementation.systemConfigImpl, data + ); + + data = encodeOptimismMintableERC20FactoryInitializer(output); + upgradeToAndCall( + output.opChainProxyAdmin, + address(output.optimismMintableERC20FactoryProxy), + implementation.optimismMintableERC20FactoryImpl, + data + ); + + data = encodeL1CrossDomainMessengerInitializer(output, _superchainConfig); + upgradeToAndCall( + output.opChainProxyAdmin, + address(output.l1CrossDomainMessengerProxy), + implementation.l1CrossDomainMessengerImpl, + data + ); + + data = encodeL1StandardBridgeInitializer(output, _superchainConfig); + upgradeToAndCall( + output.opChainProxyAdmin, address(output.l1StandardBridgeProxy), implementation.l1StandardBridgeImpl, data + ); + + data = encodeDelayedWETHInitializer(_input, _superchainConfig); + // Eventually we will switch from DelayedWETHPermissionedGameProxy to DelayedWETHPermissionlessGameProxy. + upgradeToAndCall( + output.opChainProxyAdmin, + address(output.delayedWETHPermissionedGameProxy), + implementation.delayedWETHImpl, + data + ); + + // We set the initial owner to this contract, set game implementations, then transfer ownership. + data = encodeDisputeGameFactoryInitializer(); + upgradeToAndCall( + output.opChainProxyAdmin, + address(output.disputeGameFactoryProxy), + implementation.disputeGameFactoryImpl, + data + ); + setDGFImplementation( + output.disputeGameFactoryProxy, + GameTypes.PERMISSIONED_CANNON, + IDisputeGame(address(output.permissionedDisputeGame)) + ); + + transferOwnership(address(output.disputeGameFactoryProxy), address(_input.roles.opChainProxyAdminOwner)); + + data = encodeAnchorStateRegistryInitializer(_input, _superchainConfig, output); + upgradeToAndCall( + output.opChainProxyAdmin, + address(output.anchorStateRegistryProxy), + implementation.anchorStateRegistryImpl, + data + ); + + // -------- Finalize Deployment -------- + // Transfer ownership of the ProxyAdmin from this contract to the specified owner. + transferOwnership(address(output.opChainProxyAdmin), _input.roles.opChainProxyAdminOwner); + + emit Deployed(_input.l2ChainId, _deployer, abi.encode(output)); + return output; + } + + /// @notice Returns default, standard config arguments for the SystemConfig initializer. + /// This is used by subclasses to reduce code duplication. + function defaultSystemConfigParams( + OPContractsManager.DeployInput memory, /* _input */ + OPContractsManager.DeployOutput memory _output + ) + internal + view + virtual + returns (IResourceMetering.ResourceConfig memory resourceConfig_, ISystemConfig.Addresses memory opChainAddrs_) + { + resourceConfig_ = Constants.DEFAULT_RESOURCE_CONFIG(); + + opChainAddrs_ = ISystemConfig.Addresses({ + l1CrossDomainMessenger: address(_output.l1CrossDomainMessengerProxy), + l1ERC721Bridge: address(_output.l1ERC721BridgeProxy), + l1StandardBridge: address(_output.l1StandardBridgeProxy), + disputeGameFactory: address(_output.disputeGameFactoryProxy), + optimismPortal: address(_output.optimismPortalProxy), + optimismMintableERC20Factory: address(_output.optimismMintableERC20FactoryProxy) + }); - return outputs; + assertValidContractAddress(opChainAddrs_.l1CrossDomainMessenger); + assertValidContractAddress(opChainAddrs_.l1ERC721Bridge); + assertValidContractAddress(opChainAddrs_.l1StandardBridge); + assertValidContractAddress(opChainAddrs_.disputeGameFactory); + assertValidContractAddress(opChainAddrs_.optimismPortal); + assertValidContractAddress(opChainAddrs_.optimismMintableERC20Factory); } // -------- Utilities -------- /// @notice Verifies that all inputs are valid and reverts if any are invalid. /// Typically the proxy admin owner is expected to have code, but this is not enforced here. - function assertValidInputs(DeployInput calldata _input) internal view { - if (_input.l2ChainId == 0 || _input.l2ChainId == block.chainid) revert InvalidChainId(); - - if (_input.roles.opChainProxyAdminOwner == address(0)) revert InvalidRoleAddress("opChainProxyAdminOwner"); - if (_input.roles.systemConfigOwner == address(0)) revert InvalidRoleAddress("systemConfigOwner"); - if (_input.roles.batcher == address(0)) revert InvalidRoleAddress("batcher"); - if (_input.roles.unsafeBlockSigner == address(0)) revert InvalidRoleAddress("unsafeBlockSigner"); - if (_input.roles.proposer == address(0)) revert InvalidRoleAddress("proposer"); - if (_input.roles.challenger == address(0)) revert InvalidRoleAddress("challenger"); + function assertValidInputs(OPContractsManager.DeployInput calldata _input) internal view { + if (_input.l2ChainId == 0 || _input.l2ChainId == block.chainid) revert OPContractsManager.InvalidChainId(); - if (_input.startingAnchorRoot.length == 0) revert InvalidStartingAnchorRoot(); - if (bytes32(_input.startingAnchorRoot) == bytes32(0)) revert InvalidStartingAnchorRoot(); - } - - /// @notice Maps an L2 chain ID to an L1 batch inbox address as defined by the standard - /// configuration's convention. This convention is `versionByte || keccak256(bytes32(chainId))[:19]`, - /// where || denotes concatenation`, versionByte is 0x00, and chainId is a uint256. - /// https://specs.optimism.io/protocol/configurability.html#consensus-parameters - function chainIdToBatchInboxAddress(uint256 _l2ChainId) public pure returns (address) { - bytes1 versionByte = 0x00; - bytes32 hashedChainId = keccak256(bytes.concat(bytes32(_l2ChainId))); - bytes19 first19Bytes = bytes19(hashedChainId); - return address(uint160(bytes20(bytes.concat(versionByte, first19Bytes)))); - } - - /// @notice Helper method for computing a salt that's used in CREATE2 deployments. - /// Including the contract name ensures that the resultant address from CREATE2 is unique - /// across our smart contract system. For example, we deploy multiple proxy contracts - /// with the same bytecode from this contract, so they each require a unique salt for determinism. - function computeSalt( - uint256 _l2ChainId, - string memory _saltMixer, - string memory _contractName - ) - internal - pure - returns (bytes32) - { - return keccak256(abi.encode(_l2ChainId, _saltMixer, _contractName)); - } + if (_input.roles.opChainProxyAdminOwner == address(0)) { + revert OPContractsManager.InvalidRoleAddress("opChainProxyAdminOwner"); + } + if (_input.roles.systemConfigOwner == address(0)) { + revert OPContractsManager.InvalidRoleAddress("systemConfigOwner"); + } + if (_input.roles.batcher == address(0)) revert OPContractsManager.InvalidRoleAddress("batcher"); + if (_input.roles.unsafeBlockSigner == address(0)) { + revert OPContractsManager.InvalidRoleAddress("unsafeBlockSigner"); + } + if (_input.roles.proposer == address(0)) revert OPContractsManager.InvalidRoleAddress("proposer"); + if (_input.roles.challenger == address(0)) revert OPContractsManager.InvalidRoleAddress("challenger"); - /// @notice Helper method for computing a reusable salt mixer - /// This method should be used as the salt mixer when deploying contracts when there is no user - /// provided salt mixer. This protects against a situation where multiple chains with the same - /// L2 chain ID exist, which would otherwise result in address collisions. - function reusableSaltMixer(OpChainConfig memory _opChainConfig) internal pure returns (string memory) { - return string(bytes.concat(bytes32(uint256(uint160(address(_opChainConfig.systemConfigProxy)))))); + if (_input.startingAnchorRoot.length == 0) revert OPContractsManager.InvalidStartingAnchorRoot(); + if (bytes32(_input.startingAnchorRoot) == bytes32(0)) revert OPContractsManager.InvalidStartingAnchorRoot(); } - /// @notice Deterministically deploys a new proxy contract owned by the provided ProxyAdmin. - /// The salt is computed as a function of the L2 chain ID, the salt mixer and the contract name. - /// This is required because we deploy many identical proxies, so they each require a unique salt for determinism. - function deployProxy( - uint256 _l2ChainId, - IProxyAdmin _proxyAdmin, - string memory _saltMixer, - string memory _contractName - ) - internal - returns (address) - { - bytes32 salt = computeSalt(_l2ChainId, _saltMixer, _contractName); - return Blueprint.deployFrom(getBlueprints().proxy, salt, abi.encode(_proxyAdmin)); + /// @notice Transfers ownership + function transferOwnership(address _target, address _newOwner) internal { + // All transferOwnership targets have the same selector, so we just use IAddressManager + IAddressManager(_target).transferOwnership(_newOwner); } // -------- Initializer Encoding -------- /// @notice Helper method for encoding the L1ERC721Bridge initializer data. - function encodeL1ERC721BridgeInitializer(DeployOutput memory _output) + function encodeL1ERC721BridgeInitializer( + OPContractsManager.DeployOutput memory _output, + ISuperchainConfig _superchainConfig + ) internal view virtual returns (bytes memory) { - return abi.encodeCall(IL1ERC721Bridge.initialize, (_output.l1CrossDomainMessengerProxy, superchainConfig)); + return abi.encodeCall(IL1ERC721Bridge.initialize, (_output.l1CrossDomainMessengerProxy, _superchainConfig)); } /// @notice Helper method for encoding the OptimismPortal initializer data. - function encodeOptimismPortalInitializer(DeployOutput memory _output) + function encodeOptimismPortalInitializer( + OPContractsManager.DeployOutput memory _output, + ISuperchainConfig _superchainConfig + ) internal view virtual @@ -853,7 +1073,7 @@ contract OPContractsManager is ISemver { ( _output.disputeGameFactoryProxy, _output.systemConfigProxy, - superchainConfig, + _superchainConfig, GameTypes.PERMISSIONED_CANNON ) ); @@ -861,8 +1081,8 @@ contract OPContractsManager is ISemver { /// @notice Helper method for encoding the SystemConfig initializer data. function encodeSystemConfigInitializer( - DeployInput memory _input, - DeployOutput memory _output + OPContractsManager.DeployInput memory _input, + OPContractsManager.DeployOutput memory _output ) internal view @@ -889,7 +1109,7 @@ contract OPContractsManager is ISemver { } /// @notice Helper method for encoding the OptimismMintableERC20Factory initializer data. - function encodeOptimismMintableERC20FactoryInitializer(DeployOutput memory _output) + function encodeOptimismMintableERC20FactoryInitializer(OPContractsManager.DeployOutput memory _output) internal pure virtual @@ -899,23 +1119,29 @@ contract OPContractsManager is ISemver { } /// @notice Helper method for encoding the L1CrossDomainMessenger initializer data. - function encodeL1CrossDomainMessengerInitializer(DeployOutput memory _output) + function encodeL1CrossDomainMessengerInitializer( + OPContractsManager.DeployOutput memory _output, + ISuperchainConfig _superchainConfig + ) internal view virtual returns (bytes memory) { - return abi.encodeCall(IL1CrossDomainMessenger.initialize, (superchainConfig, _output.optimismPortalProxy)); + return abi.encodeCall(IL1CrossDomainMessenger.initialize, (_superchainConfig, _output.optimismPortalProxy)); } /// @notice Helper method for encoding the L1StandardBridge initializer data. - function encodeL1StandardBridgeInitializer(DeployOutput memory _output) + function encodeL1StandardBridgeInitializer( + OPContractsManager.DeployOutput memory _output, + ISuperchainConfig _superchainConfig + ) internal view virtual returns (bytes memory) { - return abi.encodeCall(IL1StandardBridge.initialize, (_output.l1CrossDomainMessengerProxy, superchainConfig)); + return abi.encodeCall(IL1StandardBridge.initialize, (_output.l1CrossDomainMessengerProxy, _superchainConfig)); } function encodeDisputeGameFactoryInitializer() internal view virtual returns (bytes memory) { @@ -925,8 +1151,9 @@ contract OPContractsManager is ISemver { } function encodeAnchorStateRegistryInitializer( - DeployInput memory _input, - DeployOutput memory _output + OPContractsManager.DeployInput memory _input, + ISuperchainConfig _superchainConfig, + OPContractsManager.DeployOutput memory _output ) internal view @@ -936,267 +1163,385 @@ contract OPContractsManager is ISemver { OutputRoot memory startingAnchorRoot = abi.decode(_input.startingAnchorRoot, (OutputRoot)); return abi.encodeCall( IAnchorStateRegistry.initialize, - (superchainConfig, _output.disputeGameFactoryProxy, _output.optimismPortalProxy, startingAnchorRoot) + (_superchainConfig, _output.disputeGameFactoryProxy, _output.optimismPortalProxy, startingAnchorRoot) ); } - function encodeDelayedWETHInitializer(DeployInput memory _input) internal view virtual returns (bytes memory) { - return abi.encodeCall(IDelayedWETH.initialize, (_input.roles.opChainProxyAdminOwner, superchainConfig)); - } - - function encodePermissionlessFDGConstructor(IFaultDisputeGame.GameConstructorParams memory _params) + function encodeDelayedWETHInitializer( + OPContractsManager.DeployInput memory _input, + ISuperchainConfig _superchainConfig + ) internal view virtual returns (bytes memory) { - bytes memory dataWithSelector = abi.encodeCall(IFaultDisputeGame.__constructor__, (_params)); - return Bytes.slice(dataWithSelector, 4); + return abi.encodeCall(IDelayedWETH.initialize, (_input.roles.opChainProxyAdminOwner, _superchainConfig)); } +} - function encodePermissionedFDGConstructor( - IFaultDisputeGame.GameConstructorParams memory _params, - address _proposer, - address _challenger +contract OPContractsManagerDeployerInterop is OPContractsManagerDeployer { + constructor(OPContractsManagerContractsContainer _contractsContainer) + OPContractsManagerDeployer(_contractsContainer) + { } + + // The `SystemConfigInterop` contract has an extra `address _dependencyManager` argument + // that we must account for. + function encodeSystemConfigInitializer( + OPContractsManager.DeployInput memory _input, + OPContractsManager.DeployOutput memory _output ) internal view virtual + override returns (bytes memory) { - bytes memory dataWithSelector = - abi.encodeCall(IPermissionedDisputeGame.__constructor__, (_params, _proposer, _challenger)); - return Bytes.slice(dataWithSelector, 4); + (IResourceMetering.ResourceConfig memory referenceResourceConfig, ISystemConfig.Addresses memory opChainAddrs) = + defaultSystemConfigParams(_input, _output); + + // TODO For now we assume that the dependency manager is the same as system config owner. + // This is currently undefined since it's not part of the standard config, so we may need + // to update where this value is pulled from in the future. To support a different dependency + // manager in this contract without an invasive change of redefining the `Roles` struct, + // we will make the change described in https://github.com/ethereum-optimism/optimism/issues/11783. + address dependencyManager = address(_input.roles.systemConfigOwner); + + return abi.encodeCall( + ISystemConfigInterop.initialize, + ( + _input.roles.systemConfigOwner, + _input.basefeeScalar, + _input.blobBasefeeScalar, + bytes32(uint256(uint160(_input.roles.batcher))), // batcherHash + _input.gasLimit, + _input.roles.unsafeBlockSigner, + referenceResourceConfig, + chainIdToBatchInboxAddress(_input.l2ChainId), + opChainAddrs, + dependencyManager + ) + ); } +} - /// @notice Returns default, standard config arguments for the SystemConfig initializer. - /// This is used by subclasses to reduce code duplication. - function defaultSystemConfigParams( - DeployInput memory, /* _input */ - DeployOutput memory _output - ) - internal - view - virtual - returns (IResourceMetering.ResourceConfig memory resourceConfig_, ISystemConfig.Addresses memory opChainAddrs_) - { - resourceConfig_ = Constants.DEFAULT_RESOURCE_CONFIG(); +contract OPContractsManager is ISemver { + // -------- Structs -------- + + /// @notice Represents the roles that can be set when deploying a standard OP Stack chain. + struct Roles { + address opChainProxyAdminOwner; + address systemConfigOwner; + address batcher; + address unsafeBlockSigner; + address proposer; + address challenger; + } + + /// @notice The full set of inputs to deploy a new OP Stack chain. + struct DeployInput { + Roles roles; + uint32 basefeeScalar; + uint32 blobBasefeeScalar; + uint256 l2ChainId; + // The correct type is OutputRoot memory but OP Deployer does not yet support structs. + bytes startingAnchorRoot; + // The salt mixer is used as part of making the resulting salt unique. + string saltMixer; + uint64 gasLimit; + // Configurable dispute game parameters. + GameType disputeGameType; + Claim disputeAbsolutePrestate; + uint256 disputeMaxGameDepth; + uint256 disputeSplitDepth; + Duration disputeClockExtension; + Duration disputeMaxClockDuration; + } + + /// @notice The full set of outputs from deploying a new OP Stack chain. + struct DeployOutput { + IProxyAdmin opChainProxyAdmin; + IAddressManager addressManager; + IL1ERC721Bridge l1ERC721BridgeProxy; + ISystemConfig systemConfigProxy; + IOptimismMintableERC20Factory optimismMintableERC20FactoryProxy; + IL1StandardBridge l1StandardBridgeProxy; + IL1CrossDomainMessenger l1CrossDomainMessengerProxy; + // Fault proof contracts below. + IOptimismPortal2 optimismPortalProxy; + IDisputeGameFactory disputeGameFactoryProxy; + IAnchorStateRegistry anchorStateRegistryProxy; + IFaultDisputeGame faultDisputeGame; + IPermissionedDisputeGame permissionedDisputeGame; + IDelayedWETH delayedWETHPermissionedGameProxy; + IDelayedWETH delayedWETHPermissionlessGameProxy; + } + + /// @notice Addresses of ERC-5202 Blueprint contracts. There are used for deploying full size + /// contracts, to reduce the code size of this factory contract. If it deployed full contracts + /// using the `new Proxy()` syntax, the code size would get large fast, since this contract would + /// contain the bytecode of every contract it deploys. Therefore we instead use Blueprints to + /// reduce the code size of this contract. + struct Blueprints { + address addressManager; + address proxy; + address proxyAdmin; + address l1ChugSplashProxy; + address resolvedDelegateProxy; + address permissionedDisputeGame1; + address permissionedDisputeGame2; + address permissionlessDisputeGame1; + address permissionlessDisputeGame2; + } + + /// @notice The latest implementation contracts for the OP Stack. + struct Implementations { + address superchainConfigImpl; + address protocolVersionsImpl; + address l1ERC721BridgeImpl; + address optimismPortalImpl; + address systemConfigImpl; + address optimismMintableERC20FactoryImpl; + address l1CrossDomainMessengerImpl; + address l1StandardBridgeImpl; + address disputeGameFactoryImpl; + address anchorStateRegistryImpl; + address delayedWETHImpl; + address mipsImpl; + } + + /// @notice The input required to identify a chain for upgrading, along with new prestate hashes + struct OpChainConfig { + ISystemConfig systemConfigProxy; + IProxyAdmin proxyAdmin; + Claim absolutePrestate; + } + + struct AddGameInput { + string saltMixer; + ISystemConfig systemConfig; + IProxyAdmin proxyAdmin; + IDelayedWETH delayedWETH; + GameType disputeGameType; + Claim disputeAbsolutePrestate; + uint256 disputeMaxGameDepth; + uint256 disputeSplitDepth; + Duration disputeClockExtension; + Duration disputeMaxClockDuration; + uint256 initialBond; + IBigStepper vm; + bool permissioned; + } + + struct AddGameOutput { + IDelayedWETH delayedWETH; + IFaultDisputeGame faultDisputeGame; + } + + // -------- Constants and Variables -------- + + /// @custom:semver 1.7.0-beta.1 + function version() public pure virtual returns (string memory) { + return "1.7.0-beta.1"; + } + + OPContractsManagerGameTypeAdder public immutable opcmGameTypeAdder; + + OPContractsManagerDeployer public immutable opcmDeployer; + + OPContractsManagerUpgrader public immutable opcmUpgrader; + + /// @notice Address of the SuperchainConfig contract shared by all chains. + ISuperchainConfig public immutable superchainConfig; + + /// @notice Address of the ProtocolVersions contract shared by all chains. + IProtocolVersions public immutable protocolVersions; + + /// @notice Address of the SuperchainProxyAdmin contract shared by all chains. + IProxyAdmin public immutable superchainProxyAdmin; + + /// @notice L1 smart contracts release deployed by this version of OPCM. This is used in opcm to signal which + /// version of the L1 smart contracts is deployed. It takes the format of `op-contracts/vX.Y.Z`. + string internal L1_CONTRACTS_RELEASE; + + /// @notice The OPContractsManager contract that is currently being used. This is needed in the upgrade function + /// which is intended to be DELEGATECALLed. + OPContractsManager internal immutable thisOPCM; + + /// @notice The address of the upgrade controller. + address public immutable upgradeController; - opChainAddrs_ = ISystemConfig.Addresses({ - l1CrossDomainMessenger: address(_output.l1CrossDomainMessengerProxy), - l1ERC721Bridge: address(_output.l1ERC721BridgeProxy), - l1StandardBridge: address(_output.l1StandardBridgeProxy), - disputeGameFactory: address(_output.disputeGameFactoryProxy), - optimismPortal: address(_output.optimismPortalProxy), - optimismMintableERC20Factory: address(_output.optimismMintableERC20FactoryProxy) - }); + /// @notice Whether this is a release candidate. + bool public isRC = true; - assertValidContractAddress(opChainAddrs_.l1CrossDomainMessenger); - assertValidContractAddress(opChainAddrs_.l1ERC721Bridge); - assertValidContractAddress(opChainAddrs_.l1StandardBridge); - assertValidContractAddress(opChainAddrs_.disputeGameFactory); - assertValidContractAddress(opChainAddrs_.optimismPortal); - assertValidContractAddress(opChainAddrs_.optimismMintableERC20Factory); + /// @notice Returns the release string. Appends "-rc" if this is a release candidate. + function l1ContractsRelease() external view virtual returns (string memory) { + return isRC ? string.concat(L1_CONTRACTS_RELEASE, "-rc") : L1_CONTRACTS_RELEASE; } - /// @notice Makes an external call to the target to initialize the proxy with the specified data. - /// First performs safety checks to ensure the target, implementation, and proxy admin are valid. - function upgradeToAndCall( - IProxyAdmin _proxyAdmin, - address _target, - address _implementation, - bytes memory _data - ) - internal - { - assertValidContractAddress(_implementation); + // -------- Errors -------- - _proxyAdmin.upgradeAndCall(payable(address(_target)), _implementation, _data); - } + /// @notice Thrown when an address other than the upgrade controller calls the setRC function. + error OnlyUpgradeController(); - /// @notice Updates the implementation of a proxy without calling the initializer. - /// First performs safety checks to ensure the target, implementation, and proxy admin are valid. - function upgradeTo(IProxyAdmin _proxyAdmin, address _target, address _implementation) internal { - assertValidContractAddress(_implementation); + /// @notice Thrown when an address is the zero address. + error AddressNotFound(address who); - _proxyAdmin.upgrade(payable(address(_target)), _implementation); - } + /// @notice Throw when a contract address has no code. + error AddressHasNoCode(address who); - function assertValidContractAddress(address _who) internal view { - if (_who.code.length == 0) revert AddressHasNoCode(_who); - } + /// @notice Thrown when a release version is already set. + error AlreadyReleased(); - /// @notice Returns the blueprint contract addresses. - function blueprints() public view returns (Blueprints memory) { - return blueprint; - } + /// @notice Thrown when an invalid `l2ChainId` is provided to `deploy`. + error InvalidChainId(); - /// @notice Returns the implementation contract addresses. - function implementations() public view returns (Implementations memory) { - return implementation; - } + /// @notice Thrown when a role's address is not valid. + error InvalidRoleAddress(string role); - /// @notice Returns the implementation contract address for a given game type. - function getGameImplementation( - IDisputeGameFactory _disputeGameFactory, - GameType _gameType - ) - internal - view - returns (IDisputeGame) - { - return _disputeGameFactory.gameImpls(_gameType); - } + /// @notice Thrown when the latest release is not set upon initialization. + error LatestReleaseNotSet(); - /// @notice Sets the RC flag. - function setRC(bool _isRC) external { - if (msg.sender != upgradeController) revert OnlyUpgradeController(); - isRC = _isRC; - } + /// @notice Thrown when the starting anchor root is not provided. + error InvalidStartingAnchorRoot(); - /// @notice Sets a game implementation on the dispute game factory - function setDGFImplementation(IDisputeGameFactory _dgf, GameType _gameType, IDisputeGame _newGame) internal { - _dgf.setImplementation(_gameType, _newGame); - } + /// @notice Thrown when certain methods are called outside of a DELEGATECALL. + error OnlyDelegatecall(); - /// @notice Transfers ownership - function transferOwnership(address _target, address _newOwner) internal { - // All transferOwnership targets have the same selector, so we just use IAddressManager - IAddressManager(_target).transferOwnership(_newOwner); - } + /// @notice Thrown when game configs passed to addGameType are invalid. + error InvalidGameConfigs(); - /// @notice Retrieves the constructor params for a given game. - function getGameConstructorParams(IFaultDisputeGame _disputeGame) - internal - view - returns (IFaultDisputeGame.GameConstructorParams memory) - { - IFaultDisputeGame.GameConstructorParams memory params = IFaultDisputeGame.GameConstructorParams({ - gameType: _disputeGame.gameType(), - absolutePrestate: _disputeGame.absolutePrestate(), - maxGameDepth: _disputeGame.maxGameDepth(), - splitDepth: _disputeGame.splitDepth(), - clockExtension: _disputeGame.clockExtension(), - maxClockDuration: _disputeGame.maxClockDuration(), - vm: _disputeGame.vm(), - weth: getWETH(_disputeGame), - anchorStateRegistry: getAnchorStateRegistry(_disputeGame), - l2ChainId: getL2ChainId(_disputeGame) - }); - return params; - } + /// @notice Thrown when the SuperchainConfig of the chain does not match the SuperchainConfig of this OPCM. + error SuperchainConfigMismatch(ISystemConfig systemConfig); - /// @notice Retrieves the Superchain Config for a bridge contract - function getSuperchainConfig(address _hasSuperchainConfig) internal view returns (ISuperchainConfig) { - return IHasSuperchainConfig(_hasSuperchainConfig).superchainConfig(); - } + /// @notice Thrown when the SuperchainProxyAdmin does not match the SuperchainConfig's admin. + error SuperchainProxyAdminMismatch(); - /// @notice Retrieves the Anchor State Registry for a given game - function getAnchorStateRegistry(IFaultDisputeGame _disputeGame) internal view returns (IAnchorStateRegistry) { - return _disputeGame.anchorStateRegistry(); - } + /// @notice Thrown when a prestate is not set for a game. + error PrestateNotSet(); - /// @notice Retrieves the DelayedWETH address for a given game - function getWETH(IFaultDisputeGame _disputeGame) internal view returns (IDelayedWETH) { - return _disputeGame.weth(); - } + /// @notice Thrown when the prestate of a permissioned disputed game is 0. + error PrestateRequired(); - /// @notice Retrieves the L2 chain ID for a given game - function getL2ChainId(IFaultDisputeGame _disputeGame) internal view returns (uint256) { - return _disputeGame.l2ChainId(); + // -------- Methods -------- + + constructor( + OPContractsManagerGameTypeAdder _opcmGameTypeAdder, + OPContractsManagerDeployer _opcmDeployer, + OPContractsManagerUpgrader _opcmUpgrader, + ISuperchainConfig _superchainConfig, + IProtocolVersions _protocolVersions, + IProxyAdmin _superchainProxyAdmin, + string memory _l1ContractsRelease, + address _upgradeController + ) { + _opcmDeployer.assertValidContractAddress(address(_superchainConfig)); + _opcmDeployer.assertValidContractAddress(address(_protocolVersions)); + _opcmDeployer.assertValidContractAddress(address(_opcmGameTypeAdder)); + _opcmDeployer.assertValidContractAddress(address(_opcmDeployer)); + _opcmDeployer.assertValidContractAddress(address(_opcmUpgrader)); + opcmGameTypeAdder = _opcmGameTypeAdder; + opcmDeployer = _opcmDeployer; + opcmUpgrader = _opcmUpgrader; + superchainConfig = _superchainConfig; + protocolVersions = _protocolVersions; + superchainProxyAdmin = _superchainProxyAdmin; + L1_CONTRACTS_RELEASE = _l1ContractsRelease; + thisOPCM = this; + upgradeController = _upgradeController; } - /// @notice Retrieves the proposer address for a given game - function getProposer(IPermissionedDisputeGame _disputeGame) internal view returns (address) { - return _disputeGame.proposer(); + function deploy(DeployInput calldata _input) external virtual returns (DeployOutput memory) { + return opcmDeployer.deploy(_input, superchainConfig, msg.sender); } - /// @notice Retrieves the challenger address for a given game - function getChallenger(IPermissionedDisputeGame _disputeGame) internal view returns (address) { - return _disputeGame.challenger(); + /// @notice Upgrades a set of chains to the latest implementation contracts + /// @param _opChainConfigs Array of OpChain structs, one per chain to upgrade + /// @dev This function is intended to be called via DELEGATECALL from the Upgrade Controller Safe + function upgrade(OpChainConfig[] memory _opChainConfigs) external virtual { + if (address(this) == address(thisOPCM)) revert OnlyDelegatecall(); + + // If this is delegatecalled by the upgrade controller, set isRC to false first, else, continue execution. + if (address(this) == upgradeController) { + // Set isRC to false. + // This function asserts that the caller is the upgrade controller. + thisOPCM.setRC(false); + } + + bytes memory data = abi.encodeWithSelector( + OPContractsManagerUpgrader.upgrade.selector, + _opChainConfigs, + OPContractsManagerUpgrader.UpgradeInput({ + superchainConfig: superchainConfig, + protocolVersions: protocolVersions, + superchainProxyAdmin: superchainProxyAdmin + }) + ); + _performDelegateCall(address(opcmUpgrader), data); } - /// @notice Retrieves the DisputeGameFactory address for a given SystemConfig - function getDisputeGameFactory(ISystemConfig _systemConfig) internal view returns (IDisputeGameFactory) { - return IDisputeGameFactory(_systemConfig.disputeGameFactory()); + /// @notice addGameType deploys a new dispute game and links it to the DisputeGameFactory. The inputted _gameConfigs + /// must be added in ascending GameType order. + function addGameType(AddGameInput[] memory _gameConfigs) public virtual returns (AddGameOutput[] memory) { + if (address(this) == address(thisOPCM)) revert OnlyDelegatecall(); + + bytes memory data = + abi.encodeWithSelector(OPContractsManagerGameTypeAdder.addGameType.selector, _gameConfigs, superchainConfig); + + bytes memory returnData = _performDelegateCall(address(opcmGameTypeAdder), data); + return abi.decode(returnData, (AddGameOutput[])); } - /// @notice Retrieves the implementation addresses stored in this OPCM contract - function getImplementations() internal view returns (Implementations memory) { - return thisOPCM.implementations(); + /// @notice Updates the prestate hash for a new game type while keeping all other parameters the same + /// @param _prestateUpdateInputs The new prestate hash to use + function updatePrestate(OpChainConfig[] memory _prestateUpdateInputs) public { + bytes memory data = abi.encodeWithSelector( + OPContractsManagerGameTypeAdder.updatePrestate.selector, _prestateUpdateInputs, superchainConfig + ); + + _performDelegateCall(address(opcmGameTypeAdder), data); } - /// @notice Retrieves the blueprint addresses stored in this OPCM contract - function getBlueprints() internal view returns (Blueprints memory) { - return thisOPCM.blueprints(); + /// @notice Maps an L2 chain ID to an L1 batch inbox address as defined by the standard + /// configuration's convention. This convention is `versionByte || keccak256(bytes32(chainId))[:19]`, + /// where || denotes concatenation`, versionByte is 0x00, and chainId is a uint256. + /// https://specs.optimism.io/protocol/configurability.html#consensus-parameters + function chainIdToBatchInboxAddress(uint256 _l2ChainId) public view returns (address) { + return opcmDeployer.chainIdToBatchInboxAddress(_l2ChainId); } - function getProxyImplementation(IProxyAdmin _proxyAdmin, address _proxy) internal view returns (address) { - return _proxyAdmin.getProxyImplementation(_proxy); + /// @notice Returns the blueprint contract addresses. + function blueprints() public view returns (Blueprints memory) { + return opcmDeployer.blueprints(); } - /// @notice Deploys and sets a new dispute game implementation - /// @param _l2ChainId The L2 chain ID - /// @param _disputeGame The current dispute game implementation - /// @param _newAnchorStateRegistryProxy The new anchor state registry proxy - /// @param _gameType The type of game to deploy - /// @param _opChainConfig The OP chain configuration - /// @param _blueprints The blueprint addresses - /// @param _implementations The implementation addresses - /// @param _opChainAddrs The OP chain addresses - function deployAndSetNewGameImpl( - uint256 _l2ChainId, - IDisputeGame _disputeGame, - IAnchorStateRegistry _newAnchorStateRegistryProxy, - GameType _gameType, - OpChainConfig memory _opChainConfig, - Blueprints memory _blueprints, - Implementations memory _implementations, - ISystemConfig.Addresses memory _opChainAddrs - ) - internal - { - // independently scoped block to avoid stack too deep - { - // Get and upgrade the WETH proxy - IDelayedWETH delayedWethProxy = getWETH(IFaultDisputeGame(address(_disputeGame))); - upgradeTo(_opChainConfig.proxyAdmin, address(delayedWethProxy), _implementations.delayedWETHImpl); - } + /// @notice Returns the implementation contract addresses. + function implementations() public view returns (Implementations memory) { + return opcmDeployer.implementations(); + } - // Get the constructor params for the game - IFaultDisputeGame.GameConstructorParams memory params = - getGameConstructorParams(IFaultDisputeGame(address(_disputeGame))); + /// @notice Sets the RC flag. + function setRC(bool _isRC) external { + if (msg.sender != upgradeController) revert OnlyUpgradeController(); + isRC = _isRC; + } - // Modify the params with the new anchorStateRegistry and vm values. - params.anchorStateRegistry = IAnchorStateRegistry(address(_newAnchorStateRegistryProxy)); - params.vm = IBigStepper(_implementations.mipsImpl); - if (Claim.unwrap(_opChainConfig.absolutePrestate) == bytes32(0)) { - revert PrestateNotSet(); + /// @notice Helper function to perform a delegatecall to a target contract + /// @param _target The target contract address + /// @param _data The calldata to send to the target + /// @return bytes The return data from the delegatecall + function _performDelegateCall(address _target, bytes memory _data) internal returns (bytes memory) { + // Perform the delegatecall + (bool success, bytes memory returnData) = _target.delegatecall(_data); + + // Check if the delegatecall was successful + if (!success) { + // If there was a revert message, bubble it up + assembly { + revert(add(returnData, 32), mload(returnData)) + } } - params.absolutePrestate = _opChainConfig.absolutePrestate; - IDisputeGame newGame; - if (GameType.unwrap(_gameType) == GameType.unwrap(GameTypes.PERMISSIONED_CANNON)) { - address proposer = getProposer(IPermissionedDisputeGame(address(_disputeGame))); - address challenger = getChallenger(IPermissionedDisputeGame(address(_disputeGame))); - newGame = IDisputeGame( - Blueprint.deployFrom( - _blueprints.permissionedDisputeGame1, - _blueprints.permissionedDisputeGame2, - computeSalt(_l2ChainId, reusableSaltMixer(_opChainConfig), "PermissionedDisputeGame"), - encodePermissionedFDGConstructor(params, proposer, challenger) - ) - ); - } else { - newGame = IDisputeGame( - Blueprint.deployFrom( - _blueprints.permissionlessDisputeGame1, - _blueprints.permissionlessDisputeGame2, - computeSalt(_l2ChainId, reusableSaltMixer(_opChainConfig), "PermissionlessDisputeGame"), - encodePermissionlessFDGConstructor(params) - ) - ); - } - setDGFImplementation(IDisputeGameFactory(_opChainAddrs.disputeGameFactory), _gameType, IDisputeGame(newGame)); + return returnData; } } diff --git a/packages/contracts-bedrock/src/L1/OPContractsManagerInterop.sol b/packages/contracts-bedrock/src/L1/OPContractsManagerInterop.sol deleted file mode 100644 index cd4fbcf9f53..00000000000 --- a/packages/contracts-bedrock/src/L1/OPContractsManagerInterop.sol +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -// Contracts -import { OPContractsManager } from "src/L1/OPContractsManager.sol"; - -// Interfaces -import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; -import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; -import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; -import { ISystemConfigInterop } from "interfaces/L1/ISystemConfigInterop.sol"; -import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; - -contract OPContractsManagerInterop is OPContractsManager { - /// @custom:semver +interop.10 - function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop.10"); - } - - constructor( - ISuperchainConfig _superchainConfig, - IProtocolVersions _protocolVersions, - IProxyAdmin _superchainProxyAdmin, - string memory _l1ContractsRelease, - Blueprints memory _blueprints, - Implementations memory _implementations, - address _upgradeController - ) - OPContractsManager( - _superchainConfig, - _protocolVersions, - _superchainProxyAdmin, - _l1ContractsRelease, - _blueprints, - _implementations, - _upgradeController - ) - { } - - // The `SystemConfigInterop` contract has an extra `address _dependencyManager` argument - // that we must account for. - function encodeSystemConfigInitializer( - DeployInput memory _input, - DeployOutput memory _output - ) - internal - view - virtual - override - returns (bytes memory) - { - (IResourceMetering.ResourceConfig memory referenceResourceConfig, ISystemConfig.Addresses memory opChainAddrs) = - defaultSystemConfigParams(_input, _output); - - // TODO For now we assume that the dependency manager is the same as system config owner. - // This is currently undefined since it's not part of the standard config, so we may need - // to update where this value is pulled from in the future. To support a different dependency - // manager in this contract without an invasive change of redefining the `Roles` struct, - // we will make the change described in https://github.com/ethereum-optimism/optimism/issues/11783. - address dependencyManager = address(_input.roles.systemConfigOwner); - - return abi.encodeCall( - ISystemConfigInterop.initialize, - ( - _input.roles.systemConfigOwner, - _input.basefeeScalar, - _input.blobBasefeeScalar, - bytes32(uint256(uint160(_input.roles.batcher))), // batcherHash - _input.gasLimit, - _input.roles.unsafeBlockSigner, - referenceResourceConfig, - chainIdToBatchInboxAddress(_input.l2ChainId), - opChainAddrs, - dependencyManager - ) - ); - } -} diff --git a/packages/contracts-bedrock/src/L1/OPPrestateUpdater.sol b/packages/contracts-bedrock/src/L1/OPPrestateUpdater.sol deleted file mode 100644 index 849e8454e68..00000000000 --- a/packages/contracts-bedrock/src/L1/OPPrestateUpdater.sol +++ /dev/null @@ -1,172 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -// Contracts -import { OPContractsManager } from "src/L1/OPContractsManager.sol"; - -// Interfaces -import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; -import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; -import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; -import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; -import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; -import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; - -// Libraries -import { Claim, GameTypes } from "src/dispute/lib/Types.sol"; - -/// @title OPPrestateUpdater -/// @notice A custom implementation of OPContractsManager that enables updating the prestate hash -/// for the permissioned and fault dispute games on a set of chains. -contract OPPrestateUpdater is OPContractsManager { - /// @notice Thrown when a function from the parent (OPCM) is not implemented. - error NotImplemented(); - - /// @notice Thrown when the prestate of a permissioned disputed game is 0. - error PrestateRequired(); - - // @return Version string - /// @custom:semver 1.7.0 - function version() public pure override returns (string memory) { - return "1.7.0"; - } - - // @notice Constructs the OPPrestateUpdater contract - // @param _superchainConfig Address of the SuperchainConfig contract - // @param _protocolVersions Address of the ProtocolVersions contract - // @param _blueprints Addresses of Blueprint contracts - constructor( - ISuperchainConfig _superchainConfig, - IProtocolVersions _protocolVersions, - Blueprints memory _blueprints - ) - OPContractsManager( - _superchainConfig, - _protocolVersions, - IProxyAdmin(address(0)), - "", - _blueprints, - Implementations( - address(0), //superchainConfigImpl - address(0), //protocolVersionsImpl - address(0), //l1ERC721BridgeImpl - address(0), //optimismPortalImpl - address(0), //systemConfigImpl - address(0), //optimismMintableERC20FactoryImpl - address(0), //l1CrossDomainMessengerImpl - address(0), //l1StandardBridgeImpl - address(0), //disputeGameFactoryImpl - address(0), //anchorStateRegistryImpl - address(0), //delayedWETHImpl - address(0) // mipsImpl - ), - address(0) - ) - { } - - /// @notice Overrides the l1ContractsRelease function to return "none", as this OPCM - /// is not releasing new contracts. - function l1ContractsRelease() external pure override returns (string memory) { - return "none"; - } - - function deploy(DeployInput memory _input) external pure override returns (DeployOutput memory) { - _input; // Silence warning - revert NotImplemented(); - } - - function upgrade(OpChainConfig[] memory _opChainConfigs) external pure override { - _opChainConfigs; // Silence warning - revert NotImplemented(); - } - - function addGameType(AddGameInput[] memory _gameConfigs) public pure override returns (AddGameOutput[] memory) { - _gameConfigs; // Silence warning - revert NotImplemented(); - } - - /// @notice Updates the prestate hash for a new game type while keeping all other parameters the same - /// @param _prestateUpdateInputs The new prestate hash to use - function updatePrestate(OpChainConfig[] memory _prestateUpdateInputs) external { - // Loop through each chain and prestate hash - for (uint256 i = 0; i < _prestateUpdateInputs.length; i++) { - if (Claim.unwrap(_prestateUpdateInputs[i].absolutePrestate) == bytes32(0)) { - revert PrestateRequired(); - } - - // Get the DisputeGameFactory and existing game implementations - IDisputeGameFactory dgf = - IDisputeGameFactory(_prestateUpdateInputs[i].systemConfigProxy.disputeGameFactory()); - IFaultDisputeGame fdg = IFaultDisputeGame(address(getGameImplementation(dgf, GameTypes.CANNON))); - IPermissionedDisputeGame pdg = - IPermissionedDisputeGame(address(getGameImplementation(dgf, GameTypes.PERMISSIONED_CANNON))); - - // All chains must have a permissioned game, but not all chains must have a fault dispute game. - // Whether a chain has a fault dispute game determines how many AddGameInput objects are needed. - bool hasFDG = address(fdg) != address(0); - - AddGameInput[] memory inputs = new AddGameInput[](hasFDG ? 2 : 1); - AddGameInput memory pdgInput; - AddGameInput memory fdgInput; - - // Get the existing game parameters and init bond for the permissioned game - IFaultDisputeGame.GameConstructorParams memory pdgParams = - getGameConstructorParams(IFaultDisputeGame(address(pdg))); - uint256 initBond = dgf.initBonds(GameTypes.PERMISSIONED_CANNON); - - string memory saltMixer = reusableSaltMixer(_prestateUpdateInputs[i]); - // Create game input with updated prestate but same other params - pdgInput = AddGameInput({ - disputeAbsolutePrestate: _prestateUpdateInputs[i].absolutePrestate, - saltMixer: saltMixer, - systemConfig: _prestateUpdateInputs[i].systemConfigProxy, - proxyAdmin: _prestateUpdateInputs[i].proxyAdmin, - delayedWETH: IDelayedWETH(payable(address(pdgParams.weth))), - disputeGameType: pdgParams.gameType, - disputeMaxGameDepth: pdgParams.maxGameDepth, - disputeSplitDepth: pdgParams.splitDepth, - disputeClockExtension: pdgParams.clockExtension, - disputeMaxClockDuration: pdgParams.maxClockDuration, - initialBond: initBond, - vm: pdgParams.vm, - permissioned: true - }); - - // If a fault dispute game exists, create a new game with the same parameters but updated prestate. - if (hasFDG) { - // Get the existing game parameters and init bond for the fault dispute game - IFaultDisputeGame.GameConstructorParams memory fdgParams = - getGameConstructorParams(IFaultDisputeGame(address(fdg))); - initBond = dgf.initBonds(GameTypes.CANNON); - - // Create game input with updated prestate but same other params - fdgInput = AddGameInput({ - disputeAbsolutePrestate: _prestateUpdateInputs[i].absolutePrestate, - saltMixer: saltMixer, - systemConfig: _prestateUpdateInputs[i].systemConfigProxy, - proxyAdmin: _prestateUpdateInputs[i].proxyAdmin, - delayedWETH: IDelayedWETH(payable(address(fdgParams.weth))), - disputeGameType: fdgParams.gameType, - disputeMaxGameDepth: fdgParams.maxGameDepth, - disputeSplitDepth: fdgParams.splitDepth, - disputeClockExtension: fdgParams.clockExtension, - disputeMaxClockDuration: fdgParams.maxClockDuration, - initialBond: initBond, - vm: fdgParams.vm, - permissioned: false - }); - } - - // Game inputs must be ordered with increasing game type values. So FDG is first if it exists. - if (hasFDG) { - inputs[0] = fdgInput; - inputs[1] = pdgInput; - } else { - inputs[0] = pdgInput; - } - // Add the new game type with updated prestate - super.addGameType(inputs); - } - } -} diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 0cf5ba4df88..ea718fd505b 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -37,11 +37,23 @@ import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; -import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; +import { + IOPContractsManager, + IOPContractsManagerGameTypeAdder, + IOPContractsManagerDeployer, + IOPContractsManagerUpgrader, + IOPContractsManagerContractsContainer +} from "interfaces/L1/IOPContractsManager.sol"; import { ISemver } from "interfaces/universal/ISemver.sol"; // Contracts -import { OPContractsManager } from "src/L1/OPContractsManager.sol"; +import { + OPContractsManager, + OPContractsManagerGameTypeAdder, + OPContractsManagerDeployer, + OPContractsManagerUpgrader, + OPContractsManagerContractsContainer +} from "src/L1/OPContractsManager.sol"; import { Blueprint } from "src/libraries/Blueprint.sol"; import { IBigStepper } from "interfaces/dispute/IBigStepper.sol"; import { GameType, Duration, Hash, Claim } from "src/dispute/lib/LibUDT.sol"; @@ -50,26 +62,28 @@ import { OutputRoot, GameTypes } from "src/dispute/lib/Types.sol"; // Exposes internal functions for testing. contract OPContractsManager_Harness is OPContractsManager { constructor( + OPContractsManagerGameTypeAdder _opcmGameTypeAdder, + OPContractsManagerDeployer _opcmDeployer, + OPContractsManagerUpgrader _opcmUpgrader, ISuperchainConfig _superchainConfig, IProtocolVersions _protocolVersions, IProxyAdmin _superchainProxyAdmin, string memory _l1ContractsRelease, - Blueprints memory _blueprints, - Implementations memory _implementations, address _upgradeController ) OPContractsManager( + _opcmGameTypeAdder, + _opcmDeployer, + _opcmUpgrader, _superchainConfig, _protocolVersions, _superchainProxyAdmin, _l1ContractsRelease, - _blueprints, - _implementations, _upgradeController ) { } - function chainIdToBatchInboxAddress_exposed(uint256 l2ChainId) public pure returns (address) { + function chainIdToBatchInboxAddress_exposed(uint256 l2ChainId) public view returns (address) { return super.chainIdToBatchInboxAddress(l2ChainId); } } @@ -175,13 +189,17 @@ contract OPContractsManager_InternalMethods_Test is Test { vm.etch(address(superchainConfigProxy), hex"01"); vm.etch(address(protocolVersionsProxy), hex"01"); + OPContractsManagerContractsContainer container = + new OPContractsManagerContractsContainer(emptyBlueprints, emptyImpls); + opcmHarness = new OPContractsManager_Harness({ + _opcmGameTypeAdder: new OPContractsManagerGameTypeAdder(container), + _opcmDeployer: new OPContractsManagerDeployer(container), + _opcmUpgrader: new OPContractsManagerUpgrader(container), _superchainConfig: superchainConfigProxy, _protocolVersions: protocolVersionsProxy, _superchainProxyAdmin: superchainProxyAdmin, _l1ContractsRelease: "dev", - _blueprints: emptyBlueprints, - _implementations: emptyImpls, _upgradeController: upgradeController }); } @@ -626,6 +644,51 @@ contract OPContractsManager_AddGameType_Test is Test { vm.etch(address(superchainConfigProxy), hex"01"); vm.etch(address(protocolVersionsProxy), hex"01"); + IOPContractsManagerGameTypeAdder opcmGameTypeAdder = IOPContractsManagerGameTypeAdder( + DeployUtils.createDeterministic({ + _name: "OPContractsManagerGameTypeAdder", + _args: DeployUtils.encodeConstructor( + abi.encodeCall( + IOPContractsManagerGameTypeAdder.__constructor__, + ( + IOPContractsManagerContractsContainer( + DeployUtils.createDeterministic({ + _name: "OPContractsManagerContractsContainer", + _args: DeployUtils.encodeConstructor( + abi.encodeCall( + IOPContractsManagerContractsContainer.__constructor__, (blueprints, impls) + ) + ), + _salt: DeployUtils.DEFAULT_SALT + }) + ) + ) + ) + ), + _salt: DeployUtils.DEFAULT_SALT + }) + ); + + IOPContractsManagerDeployer opcmDeployer = IOPContractsManagerDeployer( + DeployUtils.createDeterministic({ + _name: "OPContractsManagerDeployer", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IOPContractsManagerDeployer.__constructor__, (opcmGameTypeAdder.contractsContainer())) + ), + _salt: DeployUtils.DEFAULT_SALT + }) + ); + + IOPContractsManagerUpgrader opcmUpgrader = IOPContractsManagerUpgrader( + DeployUtils.createDeterministic({ + _name: "OPContractsManagerUpgrader", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IOPContractsManagerUpgrader.__constructor__, (opcmGameTypeAdder.contractsContainer())) + ), + _salt: DeployUtils.DEFAULT_SALT + }) + ); + opcm = IOPContractsManager( DeployUtils.createDeterministic({ _name: "OPContractsManager", @@ -633,12 +696,13 @@ contract OPContractsManager_AddGameType_Test is Test { abi.encodeCall( IOPContractsManager.__constructor__, ( + opcmGameTypeAdder, + opcmDeployer, + opcmUpgrader, superchainConfigProxy, protocolVersionsProxy, superchainProxyAdmin, "dev", - blueprints, - impls, address(this) ) ) @@ -855,3 +919,303 @@ contract OPContractsManager_AddGameType_Test is Test { ); } } + +contract OPContractsManager_UpdatePrestate_Test is Test { + IOPContractsManager internal opcm; + IOPContractsManager internal prestateUpdater; + + OPContractsManager.OpChainConfig[] internal opChainConfigs; + OPContractsManager.AddGameInput[] internal gameInput; + + IOPContractsManager.DeployOutput internal chainDeployOutput; + + function setUp() public { + IProxyAdmin superchainProxyAdmin = IProxyAdmin(makeAddr("superchainProxyAdmin")); + ISuperchainConfig superchainConfigProxy = ISuperchainConfig(makeAddr("superchainConfig")); + IProtocolVersions protocolVersionsProxy = IProtocolVersions(makeAddr("protocolVersions")); + bytes32 salt = hex"01"; + IOPContractsManager.Blueprints memory blueprints; + + (blueprints.addressManager,) = Blueprint.create(vm.getCode("AddressManager"), salt); + (blueprints.proxy,) = Blueprint.create(vm.getCode("Proxy"), salt); + (blueprints.proxyAdmin,) = Blueprint.create(vm.getCode("ProxyAdmin"), salt); + (blueprints.l1ChugSplashProxy,) = Blueprint.create(vm.getCode("L1ChugSplashProxy"), salt); + (blueprints.resolvedDelegateProxy,) = Blueprint.create(vm.getCode("ResolvedDelegateProxy"), salt); + (blueprints.permissionedDisputeGame1, blueprints.permissionedDisputeGame2) = + Blueprint.create(vm.getCode("PermissionedDisputeGame"), salt); + (blueprints.permissionlessDisputeGame1, blueprints.permissionlessDisputeGame2) = + Blueprint.create(vm.getCode("FaultDisputeGame"), salt); + + IPreimageOracle oracle = IPreimageOracle( + DeployUtils.create1({ + _name: "PreimageOracle", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IPreimageOracle.__constructor__, (126000, 86400))) + }) + ); + + IOPContractsManager.Implementations memory impls = IOPContractsManager.Implementations({ + superchainConfigImpl: DeployUtils.create1({ + _name: "SuperchainConfig", + _args: DeployUtils.encodeConstructor(abi.encodeCall(ISuperchainConfig.__constructor__, ())) + }), + protocolVersionsImpl: DeployUtils.create1({ + _name: "ProtocolVersions", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IProtocolVersions.__constructor__, ())) + }), + l1ERC721BridgeImpl: DeployUtils.create1({ + _name: "L1ERC721Bridge", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1ERC721Bridge.__constructor__, ())) + }), + optimismPortalImpl: DeployUtils.create1({ + _name: "OptimismPortal2", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IOptimismPortal2.__constructor__, (1, 1))) + }), + systemConfigImpl: DeployUtils.create1({ + _name: "SystemConfig", + _args: DeployUtils.encodeConstructor(abi.encodeCall(ISystemConfig.__constructor__, ())) + }), + optimismMintableERC20FactoryImpl: DeployUtils.create1({ + _name: "OptimismMintableERC20Factory", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IOptimismMintableERC20Factory.__constructor__, ())) + }), + l1CrossDomainMessengerImpl: DeployUtils.create1({ + _name: "L1CrossDomainMessenger", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1CrossDomainMessenger.__constructor__, ())) + }), + l1StandardBridgeImpl: DeployUtils.create1({ + _name: "L1StandardBridge", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1StandardBridge.__constructor__, ())) + }), + disputeGameFactoryImpl: DeployUtils.create1({ + _name: "DisputeGameFactory", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IDisputeGameFactory.__constructor__, ())) + }), + anchorStateRegistryImpl: DeployUtils.create1({ + _name: "AnchorStateRegistry", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IAnchorStateRegistry.__constructor__, ())) + }), + delayedWETHImpl: DeployUtils.create1({ + _name: "DelayedWETH", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IDelayedWETH.__constructor__, (3))) + }), + mipsImpl: DeployUtils.create1({ + _name: "MIPS", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IMIPS.__constructor__, (oracle))) + }) + }); + + vm.etch(address(superchainConfigProxy), hex"01"); + vm.etch(address(protocolVersionsProxy), hex"01"); + + IOPContractsManagerContractsContainer container = IOPContractsManagerContractsContainer( + DeployUtils.createDeterministic({ + _name: "OPContractsManagerContractsContainer", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IOPContractsManagerContractsContainer.__constructor__, (blueprints, impls)) + ), + _salt: DeployUtils.DEFAULT_SALT + }) + ); + + opcm = IOPContractsManager( + DeployUtils.createDeterministic({ + _name: "OPContractsManager", + _args: DeployUtils.encodeConstructor( + abi.encodeCall( + IOPContractsManager.__constructor__, + ( + IOPContractsManagerGameTypeAdder( + DeployUtils.createDeterministic({ + _name: "OPContractsManagerGameTypeAdder", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IOPContractsManagerGameTypeAdder.__constructor__, (container)) + ), + _salt: DeployUtils.DEFAULT_SALT + }) + ), + IOPContractsManagerDeployer( + DeployUtils.createDeterministic({ + _name: "OPContractsManagerDeployer", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IOPContractsManagerDeployer.__constructor__, (container)) + ), + _salt: DeployUtils.DEFAULT_SALT + }) + ), + IOPContractsManagerUpgrader( + DeployUtils.createDeterministic({ + _name: "OPContractsManagerUpgrader", + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IOPContractsManagerUpgrader.__constructor__, (container)) + ), + _salt: DeployUtils.DEFAULT_SALT + }) + ), + superchainConfigProxy, + protocolVersionsProxy, + superchainProxyAdmin, + "dev", + address(this) + ) + ) + ), + _salt: DeployUtils.DEFAULT_SALT + }) + ); + + chainDeployOutput = opcm.deploy( + IOPContractsManager.DeployInput({ + roles: IOPContractsManager.Roles({ + opChainProxyAdminOwner: address(this), + systemConfigOwner: address(this), + batcher: address(this), + unsafeBlockSigner: address(this), + proposer: address(this), + challenger: address(this) + }), + basefeeScalar: 1, + blobBasefeeScalar: 1, + startingAnchorRoot: abi.encode( + OutputRoot({ + root: Hash.wrap(0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef), + l2BlockNumber: 0 + }) + ), + l2ChainId: 100, + saltMixer: "hello", + gasLimit: 30_000_000, + disputeGameType: GameType.wrap(1), + disputeAbsolutePrestate: Claim.wrap( + bytes32(hex"038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c") + ), + disputeMaxGameDepth: 73, + disputeSplitDepth: 30, + disputeClockExtension: Duration.wrap(10800), + disputeMaxClockDuration: Duration.wrap(302400) + }) + ); + + prestateUpdater = opcm; + } + + function test_semver_works() public view { + assertNotEq(abi.encode(prestateUpdater.version()), abi.encode(0)); + } + + function test_updatePrestate_pdgOnlyWithValidInput_succeeds() public { + IOPContractsManager.OpChainConfig[] memory inputs = new IOPContractsManager.OpChainConfig[](1); + inputs[0] = IOPContractsManager.OpChainConfig( + chainDeployOutput.systemConfigProxy, chainDeployOutput.opChainProxyAdmin, Claim.wrap(bytes32(hex"ABBA")) + ); + address proxyAdminOwner = chainDeployOutput.opChainProxyAdmin.owner(); + + vm.etch(address(proxyAdminOwner), vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); + DelegateCaller(proxyAdminOwner).dcForward( + address(prestateUpdater), abi.encodeCall(IOPContractsManager.updatePrestate, (inputs)) + ); + + IPermissionedDisputeGame pdg = IPermissionedDisputeGame( + address( + IDisputeGameFactory(chainDeployOutput.systemConfigProxy.disputeGameFactory()).gameImpls( + GameTypes.PERMISSIONED_CANNON + ) + ) + ); + + assertEq(pdg.absolutePrestate().raw(), inputs[0].absolutePrestate.raw(), "pdg prestate mismatch"); + + // Ensure that the WETH contract is not reverting + pdg.weth().balanceOf(address(0)); + } + + function test_updatePrestate_bothGamesWithValidInput_succeeds() public { + // Also add a permissionless game + IOPContractsManager.AddGameInput memory input = newGameInputFactory({ permissioned: false }); + input.disputeGameType = GameTypes.CANNON; + addGameType(input); + + IOPContractsManager.OpChainConfig[] memory inputs = new IOPContractsManager.OpChainConfig[](1); + inputs[0] = IOPContractsManager.OpChainConfig( + chainDeployOutput.systemConfigProxy, chainDeployOutput.opChainProxyAdmin, Claim.wrap(bytes32(hex"ABBA")) + ); + address proxyAdminOwner = chainDeployOutput.opChainProxyAdmin.owner(); + + vm.etch(address(proxyAdminOwner), vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); + DelegateCaller(proxyAdminOwner).dcForward( + address(prestateUpdater), abi.encodeCall(IOPContractsManager.updatePrestate, (inputs)) + ); + + IPermissionedDisputeGame pdg = IPermissionedDisputeGame( + address( + IDisputeGameFactory(chainDeployOutput.systemConfigProxy.disputeGameFactory()).gameImpls( + GameTypes.PERMISSIONED_CANNON + ) + ) + ); + IPermissionedDisputeGame fdg = IPermissionedDisputeGame( + address( + IDisputeGameFactory(chainDeployOutput.systemConfigProxy.disputeGameFactory()).gameImpls( + GameTypes.CANNON + ) + ) + ); + + assertEq(pdg.absolutePrestate().raw(), inputs[0].absolutePrestate.raw(), "pdg prestate mismatch"); + assertEq(fdg.absolutePrestate().raw(), inputs[0].absolutePrestate.raw(), "fdg prestate mismatch"); + + // Ensure that the WETH contracts are not reverting + pdg.weth().balanceOf(address(0)); + fdg.weth().balanceOf(address(0)); + } + + function test_updatePrestate_whenPDGPrestateIsZero_reverts() public { + IOPContractsManager.OpChainConfig[] memory inputs = new IOPContractsManager.OpChainConfig[](1); + inputs[0] = IOPContractsManager.OpChainConfig({ + systemConfigProxy: chainDeployOutput.systemConfigProxy, + proxyAdmin: chainDeployOutput.opChainProxyAdmin, + absolutePrestate: Claim.wrap(bytes32(0)) + }); + + address proxyAdminOwner = chainDeployOutput.opChainProxyAdmin.owner(); + vm.etch(address(proxyAdminOwner), vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); + + vm.expectRevert(IOPContractsManager.PrestateRequired.selector); + DelegateCaller(proxyAdminOwner).dcForward( + address(prestateUpdater), abi.encodeCall(IOPContractsManager.updatePrestate, (inputs)) + ); + } + + function addGameType(IOPContractsManager.AddGameInput memory input) + internal + returns (IOPContractsManager.AddGameOutput memory) + { + IOPContractsManager.AddGameInput[] memory inputs = new IOPContractsManager.AddGameInput[](1); + inputs[0] = input; + + (bool success, bytes memory rawGameOut) = + address(opcm).delegatecall(abi.encodeCall(IOPContractsManager.addGameType, (inputs))); + assertTrue(success, "addGameType failed"); + + IOPContractsManager.AddGameOutput[] memory addGameOutAll = + abi.decode(rawGameOut, (IOPContractsManager.AddGameOutput[])); + return addGameOutAll[0]; + } + + function newGameInputFactory(bool permissioned) internal view returns (IOPContractsManager.AddGameInput memory) { + return IOPContractsManager.AddGameInput({ + saltMixer: "hello", + systemConfig: chainDeployOutput.systemConfigProxy, + proxyAdmin: chainDeployOutput.opChainProxyAdmin, + delayedWETH: IDelayedWETH(payable(address(0))), + disputeGameType: GameType.wrap(2000), + disputeAbsolutePrestate: Claim.wrap(bytes32(hex"deadbeef1234")), + disputeMaxGameDepth: 73, + disputeSplitDepth: 30, + disputeClockExtension: Duration.wrap(10800), + disputeMaxClockDuration: Duration.wrap(302400), + initialBond: 1 ether, + vm: IBigStepper(address(opcm.implementations().mipsImpl)), + permissioned: permissioned + }); + } +} diff --git a/packages/contracts-bedrock/test/L1/OPPrestateUpdater.t.sol b/packages/contracts-bedrock/test/L1/OPPrestateUpdater.t.sol deleted file mode 100644 index d700efa24e4..00000000000 --- a/packages/contracts-bedrock/test/L1/OPPrestateUpdater.t.sol +++ /dev/null @@ -1,385 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.15; - -// Testing -import { Test } from "forge-std/Test.sol"; -import { DelegateCaller } from "test/mocks/Callers.sol"; - -// Scripts -import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; - -// Libraries -import { Blueprint } from "src/libraries/Blueprint.sol"; - -// Interfaces -import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; -import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; -import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; -import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; -import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; -import { IMIPS } from "interfaces/cannon/IMIPS.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; -import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; -import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; -import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; -import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; -import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; -import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; -import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; -import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; -import { IOPPrestateUpdater } from "interfaces/L1/IOPPrestateUpdater.sol"; - -// Contracts -import { OPContractsManager } from "src/L1/OPContractsManager.sol"; -import { OPPrestateUpdater } from "src/L1/OPPrestateUpdater.sol"; -import { Blueprint } from "src/libraries/Blueprint.sol"; -import { IBigStepper } from "interfaces/dispute/IBigStepper.sol"; -import { GameType, Duration, Hash, Claim } from "src/dispute/lib/LibUDT.sol"; -import { OutputRoot, GameTypes } from "src/dispute/lib/Types.sol"; - -contract OPPrestateUpdater_Test is Test { - IOPContractsManager internal opcm; - OPPrestateUpdater internal prestateUpdater; - - OPContractsManager.OpChainConfig[] internal opChainConfigs; - OPContractsManager.AddGameInput[] internal gameInput; - - IOPContractsManager.DeployOutput internal chainDeployOutput; - - function setUp() public { - IProxyAdmin superchainProxyAdmin = IProxyAdmin(makeAddr("superchainProxyAdmin")); - ISuperchainConfig superchainConfigProxy = ISuperchainConfig(makeAddr("superchainConfig")); - IProtocolVersions protocolVersionsProxy = IProtocolVersions(makeAddr("protocolVersions")); - bytes32 salt = hex"01"; - IOPContractsManager.Blueprints memory blueprints; - - (blueprints.addressManager,) = Blueprint.create(vm.getCode("AddressManager"), salt); - (blueprints.proxy,) = Blueprint.create(vm.getCode("Proxy"), salt); - (blueprints.proxyAdmin,) = Blueprint.create(vm.getCode("ProxyAdmin"), salt); - (blueprints.l1ChugSplashProxy,) = Blueprint.create(vm.getCode("L1ChugSplashProxy"), salt); - (blueprints.resolvedDelegateProxy,) = Blueprint.create(vm.getCode("ResolvedDelegateProxy"), salt); - (blueprints.permissionedDisputeGame1, blueprints.permissionedDisputeGame2) = - Blueprint.create(vm.getCode("PermissionedDisputeGame"), salt); - (blueprints.permissionlessDisputeGame1, blueprints.permissionlessDisputeGame2) = - Blueprint.create(vm.getCode("FaultDisputeGame"), salt); - - IPreimageOracle oracle = IPreimageOracle( - DeployUtils.create1({ - _name: "PreimageOracle", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IPreimageOracle.__constructor__, (126000, 86400))) - }) - ); - - IOPContractsManager.Implementations memory impls = IOPContractsManager.Implementations({ - superchainConfigImpl: DeployUtils.create1({ - _name: "SuperchainConfig", - _args: DeployUtils.encodeConstructor(abi.encodeCall(ISuperchainConfig.__constructor__, ())) - }), - protocolVersionsImpl: DeployUtils.create1({ - _name: "ProtocolVersions", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IProtocolVersions.__constructor__, ())) - }), - l1ERC721BridgeImpl: DeployUtils.create1({ - _name: "L1ERC721Bridge", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1ERC721Bridge.__constructor__, ())) - }), - optimismPortalImpl: DeployUtils.create1({ - _name: "OptimismPortal2", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IOptimismPortal2.__constructor__, (1, 1))) - }), - systemConfigImpl: DeployUtils.create1({ - _name: "SystemConfig", - _args: DeployUtils.encodeConstructor(abi.encodeCall(ISystemConfig.__constructor__, ())) - }), - optimismMintableERC20FactoryImpl: DeployUtils.create1({ - _name: "OptimismMintableERC20Factory", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IOptimismMintableERC20Factory.__constructor__, ())) - }), - l1CrossDomainMessengerImpl: DeployUtils.create1({ - _name: "L1CrossDomainMessenger", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1CrossDomainMessenger.__constructor__, ())) - }), - l1StandardBridgeImpl: DeployUtils.create1({ - _name: "L1StandardBridge", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IL1StandardBridge.__constructor__, ())) - }), - disputeGameFactoryImpl: DeployUtils.create1({ - _name: "DisputeGameFactory", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IDisputeGameFactory.__constructor__, ())) - }), - anchorStateRegistryImpl: DeployUtils.create1({ - _name: "AnchorStateRegistry", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IAnchorStateRegistry.__constructor__, ())) - }), - delayedWETHImpl: DeployUtils.create1({ - _name: "DelayedWETH", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IDelayedWETH.__constructor__, (3))) - }), - mipsImpl: DeployUtils.create1({ - _name: "MIPS", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IMIPS.__constructor__, (oracle))) - }) - }); - - vm.etch(address(superchainConfigProxy), hex"01"); - vm.etch(address(protocolVersionsProxy), hex"01"); - - opcm = IOPContractsManager( - DeployUtils.createDeterministic({ - _name: "OPContractsManager", - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IOPContractsManager.__constructor__, - ( - superchainConfigProxy, - protocolVersionsProxy, - superchainProxyAdmin, - "dev", - blueprints, - impls, - address(this) - ) - ) - ), - _salt: DeployUtils.DEFAULT_SALT - }) - ); - - chainDeployOutput = opcm.deploy( - IOPContractsManager.DeployInput({ - roles: IOPContractsManager.Roles({ - opChainProxyAdminOwner: address(this), - systemConfigOwner: address(this), - batcher: address(this), - unsafeBlockSigner: address(this), - proposer: address(this), - challenger: address(this) - }), - basefeeScalar: 1, - blobBasefeeScalar: 1, - startingAnchorRoot: abi.encode( - OutputRoot({ - root: Hash.wrap(0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef), - l2BlockNumber: 0 - }) - ), - l2ChainId: 100, - saltMixer: "hello", - gasLimit: 30_000_000, - disputeGameType: GameType.wrap(1), - disputeAbsolutePrestate: Claim.wrap( - bytes32(hex"038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c") - ), - disputeMaxGameDepth: 73, - disputeSplitDepth: 30, - disputeClockExtension: Duration.wrap(10800), - disputeMaxClockDuration: Duration.wrap(302400) - }) - ); - - prestateUpdater = OPPrestateUpdater( - DeployUtils.createDeterministic({ - _name: "OPPrestateUpdater", - _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IOPPrestateUpdater.__constructor__, - (ISuperchainConfig(address(this)), IProtocolVersions(address(this)), blueprints) - ) - ), - _salt: DeployUtils.DEFAULT_SALT - }) - ); - } - - function test_semver_works() public view { - assertNotEq(abi.encode(prestateUpdater.version()), abi.encode(0)); - } - - function test_updatePrestate_pdgOnlyWithValidInput_succeeds() public { - OPContractsManager.OpChainConfig[] memory inputs = new OPContractsManager.OpChainConfig[](1); - inputs[0] = OPContractsManager.OpChainConfig( - chainDeployOutput.systemConfigProxy, chainDeployOutput.opChainProxyAdmin, Claim.wrap(bytes32(hex"ABBA")) - ); - address proxyAdminOwner = chainDeployOutput.opChainProxyAdmin.owner(); - - vm.etch(address(proxyAdminOwner), vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); - DelegateCaller(proxyAdminOwner).dcForward( - address(prestateUpdater), abi.encodeCall(OPPrestateUpdater.updatePrestate, (inputs)) - ); - - IPermissionedDisputeGame pdg = IPermissionedDisputeGame( - address( - IDisputeGameFactory(chainDeployOutput.systemConfigProxy.disputeGameFactory()).gameImpls( - GameTypes.PERMISSIONED_CANNON - ) - ) - ); - - assertEq(pdg.absolutePrestate().raw(), inputs[0].absolutePrestate.raw(), "pdg prestate mismatch"); - - // Ensure that the WETH contract is not reverting - pdg.weth().balanceOf(address(0)); - } - - function test_updatePrestate_bothGamesWithValidInput_succeeds() public { - // Also add a permissionless game - IOPContractsManager.AddGameInput memory input = newGameInputFactory({ permissioned: false }); - input.disputeGameType = GameTypes.CANNON; - addGameType(input); - - OPContractsManager.OpChainConfig[] memory inputs = new OPContractsManager.OpChainConfig[](1); - inputs[0] = OPContractsManager.OpChainConfig( - chainDeployOutput.systemConfigProxy, chainDeployOutput.opChainProxyAdmin, Claim.wrap(bytes32(hex"ABBA")) - ); - address proxyAdminOwner = chainDeployOutput.opChainProxyAdmin.owner(); - - vm.etch(address(proxyAdminOwner), vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); - DelegateCaller(proxyAdminOwner).dcForward( - address(prestateUpdater), abi.encodeCall(OPPrestateUpdater.updatePrestate, (inputs)) - ); - - IPermissionedDisputeGame pdg = IPermissionedDisputeGame( - address( - IDisputeGameFactory(chainDeployOutput.systemConfigProxy.disputeGameFactory()).gameImpls( - GameTypes.PERMISSIONED_CANNON - ) - ) - ); - IPermissionedDisputeGame fdg = IPermissionedDisputeGame( - address( - IDisputeGameFactory(chainDeployOutput.systemConfigProxy.disputeGameFactory()).gameImpls( - GameTypes.CANNON - ) - ) - ); - - assertEq(pdg.absolutePrestate().raw(), inputs[0].absolutePrestate.raw(), "pdg prestate mismatch"); - assertEq(fdg.absolutePrestate().raw(), inputs[0].absolutePrestate.raw(), "fdg prestate mismatch"); - - // Ensure that the WETH contracts are not reverting - pdg.weth().balanceOf(address(0)); - fdg.weth().balanceOf(address(0)); - } - - function test_updatePrestate_whenPDGPrestateIsZero_reverts() public { - OPPrestateUpdater.OpChainConfig[] memory inputs = new OPPrestateUpdater.OpChainConfig[](1); - inputs[0] = OPContractsManager.OpChainConfig({ - systemConfigProxy: chainDeployOutput.systemConfigProxy, - proxyAdmin: chainDeployOutput.opChainProxyAdmin, - absolutePrestate: Claim.wrap(bytes32(0)) - }); - - address proxyAdminOwner = chainDeployOutput.opChainProxyAdmin.owner(); - vm.etch(address(proxyAdminOwner), vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); - - vm.expectRevert(OPPrestateUpdater.PrestateRequired.selector); - DelegateCaller(proxyAdminOwner).dcForward( - address(prestateUpdater), abi.encodeCall(OPPrestateUpdater.updatePrestate, (inputs)) - ); - } - - function test_deploy_notImplemented_reverts() public { - OPContractsManager.DeployInput memory input = OPContractsManager.DeployInput({ - roles: OPContractsManager.Roles({ - opChainProxyAdminOwner: address(0), - systemConfigOwner: address(0), - batcher: address(0), - unsafeBlockSigner: address(0), - proposer: address(0), - challenger: address(0) - }), - basefeeScalar: 0, - blobBasefeeScalar: 0, - l2ChainId: 0, - startingAnchorRoot: bytes(abi.encode(0)), - saltMixer: "", - gasLimit: 0, - disputeGameType: GameType.wrap(0), - disputeAbsolutePrestate: Claim.wrap(0), - disputeMaxGameDepth: 0, - disputeSplitDepth: 0, - disputeClockExtension: Duration.wrap(0), - disputeMaxClockDuration: Duration.wrap(0) - }); - - vm.expectRevert(OPPrestateUpdater.NotImplemented.selector); - prestateUpdater.deploy(input); - } - - function test_upgrade_notImplemented_reverts() public { - opChainConfigs.push( - OPContractsManager.OpChainConfig({ - systemConfigProxy: ISystemConfig(address(0)), - proxyAdmin: IProxyAdmin(address(0)), - absolutePrestate: Claim.wrap(0) - }) - ); - - vm.expectRevert(OPPrestateUpdater.NotImplemented.selector); - prestateUpdater.upgrade(opChainConfigs); - } - - function test_addGameType_notImplemented_reverts() public { - gameInput.push( - OPContractsManager.AddGameInput({ - saltMixer: "hello", - systemConfig: ISystemConfig(address(0)), - proxyAdmin: IProxyAdmin(address(0)), - delayedWETH: IDelayedWETH(payable(address(0))), - disputeGameType: GameType.wrap(2000), - disputeAbsolutePrestate: Claim.wrap(bytes32(hex"deadbeef1234")), - disputeMaxGameDepth: 73, - disputeSplitDepth: 30, - disputeClockExtension: Duration.wrap(10800), - disputeMaxClockDuration: Duration.wrap(302400), - initialBond: 1 ether, - vm: IBigStepper(address(0)), - permissioned: true - }) - ); - - vm.expectRevert(OPPrestateUpdater.NotImplemented.selector); - prestateUpdater.addGameType(gameInput); - } - - function test_l1ContractsRelease_works() public view { - string memory result = "none"; - - assertEq(result, prestateUpdater.l1ContractsRelease()); - } - - function addGameType(IOPContractsManager.AddGameInput memory input) - internal - returns (IOPContractsManager.AddGameOutput memory) - { - IOPContractsManager.AddGameInput[] memory inputs = new IOPContractsManager.AddGameInput[](1); - inputs[0] = input; - - (bool success, bytes memory rawGameOut) = - address(opcm).delegatecall(abi.encodeCall(IOPContractsManager.addGameType, (inputs))); - assertTrue(success, "addGameType failed"); - - IOPContractsManager.AddGameOutput[] memory addGameOutAll = - abi.decode(rawGameOut, (IOPContractsManager.AddGameOutput[])); - return addGameOutAll[0]; - } - - function newGameInputFactory(bool permissioned) internal view returns (IOPContractsManager.AddGameInput memory) { - return IOPContractsManager.AddGameInput({ - saltMixer: "hello", - systemConfig: chainDeployOutput.systemConfigProxy, - proxyAdmin: chainDeployOutput.opChainProxyAdmin, - delayedWETH: IDelayedWETH(payable(address(0))), - disputeGameType: GameType.wrap(2000), - disputeAbsolutePrestate: Claim.wrap(bytes32(hex"deadbeef1234")), - disputeMaxGameDepth: 73, - disputeSplitDepth: 30, - disputeClockExtension: Duration.wrap(10800), - disputeMaxClockDuration: Duration.wrap(302400), - initialBond: 1 ether, - vm: IBigStepper(address(opcm.implementations().mipsImpl)), - permissioned: permissioned - }); - } -} diff --git a/packages/contracts-bedrock/test/universal/Specs.t.sol b/packages/contracts-bedrock/test/universal/Specs.t.sol index 61e1c6a4a05..63a0b733c0e 100644 --- a/packages/contracts-bedrock/test/universal/Specs.t.sol +++ b/packages/contracts-bedrock/test/universal/Specs.t.sol @@ -10,7 +10,6 @@ import { ForgeArtifacts, Abi, AbiEntry } from "scripts/libraries/ForgeArtifacts. // Interfaces import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; -import { IOPPrestateUpdater } from "interfaces/L1/IOPPrestateUpdater.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { IOptimismPortalInterop } from "interfaces/L1/IOptimismPortalInterop.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; @@ -896,49 +895,20 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "OPContractsManager", _sel: _getSel("protocolVersions()") }); _addSpec({ _name: "OPContractsManager", _sel: _getSel("superchainProxyAdmin()") }); _addSpec({ _name: "OPContractsManager", _sel: _getSel("l1ContractsRelease()") }); + _addSpec({ _name: "OPContractsManager", _sel: _getSel("opcmGameTypeAdder()") }); + _addSpec({ _name: "OPContractsManager", _sel: _getSel("opcmDeployer()") }); + _addSpec({ _name: "OPContractsManager", _sel: _getSel("opcmUpgrader()") }); _addSpec({ _name: "OPContractsManager", _sel: IOPContractsManager.deploy.selector }); _addSpec({ _name: "OPContractsManager", _sel: IOPContractsManager.blueprints.selector }); _addSpec({ _name: "OPContractsManager", _sel: IOPContractsManager.chainIdToBatchInboxAddress.selector }); _addSpec({ _name: "OPContractsManager", _sel: IOPContractsManager.implementations.selector }); _addSpec({ _name: "OPContractsManager", _sel: IOPContractsManager.upgrade.selector }); _addSpec({ _name: "OPContractsManager", _sel: IOPContractsManager.addGameType.selector }); + _addSpec({ _name: "OPContractsManager", _sel: IOPContractsManager.updatePrestate.selector }); _addSpec({ _name: "OPContractsManager", _sel: _getSel("isRC()") }); _addSpec({ _name: "OPContractsManager", _sel: _getSel("setRC(bool)") }); _addSpec({ _name: "OPContractsManager", _sel: _getSel("upgradeController()") }); - // OPPrestateUpdate - _addSpec({ _name: "OPPrestateUpdater", _sel: _getSel("version()") }); - _addSpec({ _name: "OPPrestateUpdater", _sel: _getSel("superchainConfig()") }); - _addSpec({ _name: "OPPrestateUpdater", _sel: _getSel("protocolVersions()") }); - _addSpec({ _name: "OPPrestateUpdater", _sel: _getSel("superchainProxyAdmin()") }); - _addSpec({ _name: "OPPrestateUpdater", _sel: _getSel("l1ContractsRelease()") }); - _addSpec({ _name: "OPPrestateUpdater", _sel: IOPContractsManager.deploy.selector }); - _addSpec({ _name: "OPPrestateUpdater", _sel: IOPContractsManager.blueprints.selector }); - _addSpec({ _name: "OPPrestateUpdater", _sel: IOPContractsManager.chainIdToBatchInboxAddress.selector }); - _addSpec({ _name: "OPPrestateUpdater", _sel: IOPContractsManager.implementations.selector }); - _addSpec({ _name: "OPPrestateUpdater", _sel: IOPContractsManager.upgrade.selector }); - _addSpec({ _name: "OPPrestateUpdater", _sel: IOPContractsManager.addGameType.selector }); - _addSpec({ _name: "OPPrestateUpdater", _sel: IOPPrestateUpdater.updatePrestate.selector }); - _addSpec({ _name: "OPPrestateUpdater", _sel: _getSel("isRC()") }); - _addSpec({ _name: "OPPrestateUpdater", _sel: _getSel("setRC(bool)") }); - _addSpec({ _name: "OPPrestateUpdater", _sel: _getSel("upgradeController()") }); - - // OPContractsManagerInterop - _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("version()") }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("superchainConfig()") }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("protocolVersions()") }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("superchainProxyAdmin()") }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("l1ContractsRelease()") }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: IOPContractsManager.deploy.selector }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: IOPContractsManager.blueprints.selector }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: IOPContractsManager.chainIdToBatchInboxAddress.selector }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: IOPContractsManager.implementations.selector }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: IOPContractsManager.upgrade.selector }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: IOPContractsManager.addGameType.selector }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("isRC()") }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("setRC(bool)") }); - _addSpec({ _name: "OPContractsManagerInterop", _sel: _getSel("upgradeController()") }); - // DeputyGuardianModule _addSpec({ _name: "DeputyGuardianModule", From 35d4fc19575976167c90103c07bb24b976bfe108 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Tue, 4 Mar 2025 15:33:16 -0700 Subject: [PATCH 054/130] ctb: Add upgrade 14 upgrader (#14612) --- .circleci/config.yml | 12 +- packages/contracts-bedrock/justfile | 3 +- .../deploy/DeployImplementations.s.sol | 4 +- .../abi/OPContractsManagerUpgrader.json | 33 ---- .../snapshots/semver-lock.json | 4 +- .../src/L1/OPContractsManager.sol | 170 ++---------------- .../test/L1/OPContractsManager.t.sol | 137 ++++++++------ .../test/opcm/DeployImplementations.t.sol | 6 +- .../test/setup/ForkLive.s.sol | 8 +- .../contracts-bedrock/test/setup/Setup.sol | 1 + 10 files changed, 133 insertions(+), 245 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1a61bc574bf..45e080ca9a3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -791,7 +791,8 @@ jobs: no_output_timeout: <> - run: name: Print failed test traces - command: just test-rerun + command: | + just test-rerun | tee failed-test-traces.log environment: FOUNDRY_PROFILE: <> ETH_RPC_URL: https://ci-mainnet-l1-archive.optimism.io @@ -801,6 +802,9 @@ jobs: disable_search: true files: ./packages/contracts-bedrock/lcov-all.info flags: contracts-bedrock-tests + - store_artifacts: + path: packages/contracts-bedrock/failed-test-traces.log + when: on_fail - notify-failures-on-develop contracts-bedrock-tests-upgrade: @@ -847,7 +851,8 @@ jobs: no_output_timeout: 15m - run: name: Print failed test traces - command: just test-upgrade-rerun + command: | + just test-upgrade-rerun | tee failed-test-traces.log environment: FOUNDRY_FUZZ_SEED: 42424242 FOUNDRY_FUZZ_RUNS: 1 @@ -866,6 +871,9 @@ jobs: when: always paths: - "/root/.foundry/cache" + - store_artifacts: + path: packages/contracts-bedrock/failed-test-traces.log + when: on_fail - notify-failures-on-develop contracts-bedrock-checks: diff --git a/packages/contracts-bedrock/justfile b/packages/contracts-bedrock/justfile index 9069958da79..53c1893db05 100644 --- a/packages/contracts-bedrock/justfile +++ b/packages/contracts-bedrock/justfile @@ -63,7 +63,7 @@ test-dev *ARGS: build-go-ffi FOUNDRY_PROFILE=lite forge test {{ARGS}} # Default block number for the forked upgrade path. -export pinnedBlockNumber := env_var_or_default('FORK_BLOCK_NUMBER', '21387830') +export pinnedBlockNumber := env_var_or_default('FORK_BLOCK_NUMBER', '21971446') print-pinned-block-number: echo $pinnedBlockNumber @@ -81,6 +81,7 @@ prepare-upgrade-env *ARGS : build-go-ffi export FORK_BLOCK_NUMBER=$pinnedBlockNumber export FORK_RPC_URL=$ETH_RPC_URL export FORK_TEST=true + export USE_MT_CANNON=true {{ARGS}} \ --match-path "test/{L1,dispute,cannon}/**" diff --git a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol index a8fd5a74300..5cd04147345 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol @@ -781,9 +781,9 @@ contract DeployImplementations is Script { IPreimageOracle preimageOracle = IPreimageOracle(address(_dio.preimageOracleSingleton())); // We want to ensure that the OPCM for upgrade 13 is deployed with Mips32 on production networks. - if (mipsVersion != 1) { + if (mipsVersion != 2) { if (block.chainid == Chains.Mainnet || block.chainid == Chains.Sepolia) { - revert("DeployImplementations: Only Mips32 should be deployed on Mainnet or Sepolia"); + revert("DeployImplementations: Only Mips64 should be deployed on Mainnet or Sepolia"); } } diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json index 0fd60a240f0..8efb6ad77e6 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json @@ -213,28 +213,6 @@ "internalType": "struct OPContractsManager.OpChainConfig[]", "name": "_opChainConfigs", "type": "tuple[]" - }, - { - "components": [ - { - "internalType": "contract ISuperchainConfig", - "name": "superchainConfig", - "type": "address" - }, - { - "internalType": "contract IProtocolVersions", - "name": "protocolVersions", - "type": "address" - }, - { - "internalType": "contract IProxyAdmin", - "name": "superchainProxyAdmin", - "type": "address" - } - ], - "internalType": "struct OPContractsManagerUpgrader.UpgradeInput", - "name": "_upgradeInput", - "type": "tuple" } ], "name": "upgrade", @@ -313,17 +291,6 @@ "name": "ReservedBitsSet", "type": "error" }, - { - "inputs": [ - { - "internalType": "contract ISystemConfig", - "name": "systemConfig", - "type": "address" - } - ], - "name": "SuperchainConfigMismatch", - "type": "error" - }, { "inputs": [ { diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index d5d937544c3..71146521dad 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -16,8 +16,8 @@ "sourceCodeHash": "0x7f614d0c5251365580c02dd898900a583ece2aa28579adea74a3808b575949b4" }, "src/L1/OPContractsManager.sol": { - "initCodeHash": "0xdc9f6153bfa79af8c0f0cffacb41d67cb037f334c221df1b306c235c3ceb299b", - "sourceCodeHash": "0x1b170e28f055c1f901c3709d193cbf2995527e66a2d9092ca51b155c8b293886" + "initCodeHash": "0x7ad29e286feabd0ef333f8969be8e766b3c68e97fe7b75e4c8a9699dd49e87f8", + "sourceCodeHash": "0xa412132a08809d03088690b7275c8118327d69d1e42b9d033c34cc283dea77a0" }, "src/L1/OptimismPortal2.sol": { "initCodeHash": "0x93c5e8ff52ff8b1cedd985b4a8890c12b56d5959832405c7622615c3541908f5", diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 9279bbd312f..b70b4179a09 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.15; import { Blueprint } from "src/libraries/Blueprint.sol"; import { Constants } from "src/libraries/Constants.sol"; import { Bytes } from "src/libraries/Bytes.sol"; -import { Claim, Hash, Duration, GameType, GameTypes, OutputRoot } from "src/dispute/lib/Types.sol"; +import { Claim, Duration, GameType, GameTypes, OutputRoot } from "src/dispute/lib/Types.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; // Interfaces @@ -524,82 +524,17 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { /// @notice Upgrades a set of chains to the latest implementation contracts /// @param _opChainConfigs Array of OpChain structs, one per chain to upgrade /// @dev This function is intended to be called via DELEGATECALL from the Upgrade Controller Safe - function upgrade( - OPContractsManager.OpChainConfig[] memory _opChainConfigs, - UpgradeInput memory _upgradeInput - ) - external - virtual - { + function upgrade(OPContractsManager.OpChainConfig[] memory _opChainConfigs) external virtual { OPContractsManager.Implementations memory impls = getImplementations(); OPContractsManager.Blueprints memory bps = getBlueprints(); - // If the SuperchainConfig is not already upgraded, upgrade it. - if ( - _upgradeInput.superchainProxyAdmin.getProxyImplementation(address(_upgradeInput.superchainConfig)) - != impls.superchainConfigImpl - ) { - // Attempt to upgrade. If the ProxyAdmin is not the SuperchainConfig's admin, this will revert. - upgradeTo( - _upgradeInput.superchainProxyAdmin, address(_upgradeInput.superchainConfig), impls.superchainConfigImpl - ); - } - - // If the ProtocolVersions contract is not already upgraded, upgrade it. - if ( - _upgradeInput.superchainProxyAdmin.getProxyImplementation(address(_upgradeInput.protocolVersions)) - != impls.protocolVersionsImpl - ) { - upgradeTo( - _upgradeInput.superchainProxyAdmin, address(_upgradeInput.protocolVersions), impls.protocolVersionsImpl - ); - } - for (uint256 i = 0; i < _opChainConfigs.length; i++) { assertValidOpChainConfig(_opChainConfigs[i]); - - // After Upgrade 13, we will be able to use systemConfigProxy.getAddresses() here. - ISystemConfig.Addresses memory opChainAddrs = ISystemConfig.Addresses({ - l1CrossDomainMessenger: _opChainConfigs[i].systemConfigProxy.l1CrossDomainMessenger(), - l1ERC721Bridge: _opChainConfigs[i].systemConfigProxy.l1ERC721Bridge(), - l1StandardBridge: _opChainConfigs[i].systemConfigProxy.l1StandardBridge(), - disputeGameFactory: address(getDisputeGameFactory(_opChainConfigs[i].systemConfigProxy)), - optimismPortal: _opChainConfigs[i].systemConfigProxy.optimismPortal(), - optimismMintableERC20Factory: _opChainConfigs[i].systemConfigProxy.optimismMintableERC20Factory() - }); - - // Check that all contracts have the correct superchainConfig - if ( - getSuperchainConfig(opChainAddrs.optimismPortal) != _upgradeInput.superchainConfig - || getSuperchainConfig(opChainAddrs.l1CrossDomainMessenger) != _upgradeInput.superchainConfig - || getSuperchainConfig(opChainAddrs.l1ERC721Bridge) != _upgradeInput.superchainConfig - || getSuperchainConfig(opChainAddrs.l1StandardBridge) != _upgradeInput.superchainConfig - ) { - revert OPContractsManager.SuperchainConfigMismatch(_opChainConfigs[i].systemConfigProxy); - } - - // -------- Upgrade Contracts Stored in SystemConfig -------- - upgradeTo( - _opChainConfigs[i].proxyAdmin, address(_opChainConfigs[i].systemConfigProxy), impls.systemConfigImpl - ); - upgradeTo( - _opChainConfigs[i].proxyAdmin, opChainAddrs.l1CrossDomainMessenger, impls.l1CrossDomainMessengerImpl - ); - upgradeTo(_opChainConfigs[i].proxyAdmin, opChainAddrs.l1ERC721Bridge, impls.l1ERC721BridgeImpl); - upgradeTo(_opChainConfigs[i].proxyAdmin, opChainAddrs.l1StandardBridge, impls.l1StandardBridgeImpl); - upgradeTo(_opChainConfigs[i].proxyAdmin, opChainAddrs.disputeGameFactory, impls.disputeGameFactoryImpl); - upgradeTo(_opChainConfigs[i].proxyAdmin, opChainAddrs.optimismPortal, impls.optimismPortalImpl); - upgradeTo( - _opChainConfigs[i].proxyAdmin, - opChainAddrs.optimismMintableERC20Factory, - impls.optimismMintableERC20FactoryImpl - ); + ISystemConfig.Addresses memory opChainAddrs = _opChainConfigs[i].systemConfigProxy.getAddresses(); // -------- Discover and Upgrade Proofs Contracts -------- - // Note that, the code below uses several independently scoped blocks to avoid stack too deep errors. - // All chains have the Permissioned Dispute Game. We get it first so that we can use it to - // retrieve its WETH and the Anchor State Registry when we need them. + // All chains have the Permissioned Dispute Game. IPermissionedDisputeGame permissionedDisputeGame = IPermissionedDisputeGame( address( getGameImplementation( @@ -607,69 +542,21 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { ) ) ); + // We're also going to need the l2ChainId below, so we cache it in the outer scope. uint256 l2ChainId = getL2ChainId(IFaultDisputeGame(address(permissionedDisputeGame))); - // Replace the Anchor State Registry Proxy with a new Proxy and Implementation - // For this upgrade, we are replacing the previous Anchor State Registry, thus we: - // 1. deploy a new Anchor State Registry proxy - // 2. get the starting anchor root corresponding to the currently respected game type. - // 3. initialize the proxy with that anchor root - IAnchorStateRegistry newAnchorStateRegistryProxy; - { - // Deploy a new proxy, because we're replacing the old one. - // Include the system config address in the salt to ensure that the new proxy is unique, - // even if another chains with the same L2 chain ID has been deployed by this contract. - newAnchorStateRegistryProxy = IAnchorStateRegistry( - deployProxy({ - _l2ChainId: l2ChainId, - _proxyAdmin: _opChainConfigs[i].proxyAdmin, - _saltMixer: reusableSaltMixer(_opChainConfigs[i]), - _contractName: "AnchorStateRegistry" - }) - ); - - // Get the starting anchor root by: - // 1. getting the anchor state registry from the Permissioned Dispute Game. - // 2. getting the respected game type from the OptimismPortal. - // 3. getting the anchor root for the respected game type from the Anchor State Registry. - { - GameType gameType = IOptimismPortal2(payable(opChainAddrs.optimismPortal)).respectedGameType(); - (Hash root, uint256 l2BlockNumber) = - getAnchorStateRegistry(IFaultDisputeGame(address(permissionedDisputeGame))).anchors(gameType); - OutputRoot memory startingAnchorRoot = OutputRoot({ root: root, l2BlockNumber: l2BlockNumber }); - - upgradeToAndCall( - _opChainConfigs[i].proxyAdmin, - address(newAnchorStateRegistryProxy), - impls.anchorStateRegistryImpl, - abi.encodeCall( - IAnchorStateRegistry.initialize, - ( - _upgradeInput.superchainConfig, - IDisputeGameFactory(opChainAddrs.disputeGameFactory), - IOptimismPortal2(payable(opChainAddrs.optimismPortal)), - startingAnchorRoot - ) - ) - ); - } - - // Deploy and set a new permissioned game to update its prestate - - deployAndSetNewGameImpl({ - _l2ChainId: l2ChainId, - _disputeGame: IDisputeGame(address(permissionedDisputeGame)), - _newAnchorStateRegistryProxy: newAnchorStateRegistryProxy, - _gameType: GameTypes.PERMISSIONED_CANNON, - _opChainConfig: _opChainConfigs[i], - _implementations: impls, - _blueprints: bps, - _opChainAddrs: opChainAddrs - }); - } + deployAndSetNewGameImpl({ + _l2ChainId: l2ChainId, + _disputeGame: IDisputeGame(address(permissionedDisputeGame)), + _gameType: GameTypes.PERMISSIONED_CANNON, + _opChainConfig: _opChainConfigs[i], + _implementations: impls, + _blueprints: bps, + _opChainAddrs: opChainAddrs + }); - // Now retrieve the permissionless game. If it exists, upgrade its weth and replace its implementation. + // Now retrieve the permissionless game. If it exists, replace its implementation. IFaultDisputeGame permissionlessDisputeGame = IFaultDisputeGame( address(getGameImplementation(IDisputeGameFactory(opChainAddrs.disputeGameFactory), GameTypes.CANNON)) ); @@ -679,7 +566,6 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { deployAndSetNewGameImpl({ _l2ChainId: l2ChainId, _disputeGame: IDisputeGame(address(permissionlessDisputeGame)), - _newAnchorStateRegistryProxy: newAnchorStateRegistryProxy, _gameType: GameTypes.CANNON, _opChainConfig: _opChainConfigs[i], _implementations: impls, @@ -716,7 +602,6 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { /// @notice Deploys and sets a new dispute game implementation /// @param _l2ChainId The L2 chain ID /// @param _disputeGame The current dispute game implementation - /// @param _newAnchorStateRegistryProxy The new anchor state registry proxy /// @param _gameType The type of game to deploy /// @param _opChainConfig The OP chain configuration /// @param _blueprints The blueprint addresses @@ -725,7 +610,6 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { function deployAndSetNewGameImpl( uint256 _l2ChainId, IDisputeGame _disputeGame, - IAnchorStateRegistry _newAnchorStateRegistryProxy, GameType _gameType, OPContractsManager.OpChainConfig memory _opChainConfig, OPContractsManager.Blueprints memory _blueprints, @@ -734,19 +618,11 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { ) internal { - // independently scoped block to avoid stack too deep - { - // Get and upgrade the WETH proxy - IDelayedWETH delayedWethProxy = getWETH(IFaultDisputeGame(address(_disputeGame))); - upgradeTo(_opChainConfig.proxyAdmin, address(delayedWethProxy), _implementations.delayedWETHImpl); - } - // Get the constructor params for the game IFaultDisputeGame.GameConstructorParams memory params = getGameConstructorParams(IFaultDisputeGame(address(_disputeGame))); - // Modify the params with the new anchorStateRegistry and vm values. - params.anchorStateRegistry = IAnchorStateRegistry(address(_newAnchorStateRegistryProxy)); + // Modify the params with the new vm values. params.vm = IBigStepper(_implementations.mipsImpl); if (Claim.unwrap(_opChainConfig.absolutePrestate) == bytes32(0)) { revert OPContractsManager.PrestateNotSet(); @@ -1340,9 +1216,9 @@ contract OPContractsManager is ISemver { // -------- Constants and Variables -------- - /// @custom:semver 1.7.0-beta.1 + /// @custom:semver 1.7.0 function version() public pure virtual returns (string memory) { - return "1.7.0-beta.1"; + return "1.7.0"; } OPContractsManagerGameTypeAdder public immutable opcmGameTypeAdder; @@ -1468,15 +1344,7 @@ contract OPContractsManager is ISemver { thisOPCM.setRC(false); } - bytes memory data = abi.encodeWithSelector( - OPContractsManagerUpgrader.upgrade.selector, - _opChainConfigs, - OPContractsManagerUpgrader.UpgradeInput({ - superchainConfig: superchainConfig, - protocolVersions: protocolVersions, - superchainProxyAdmin: superchainProxyAdmin - }) - ); + bytes memory data = abi.encodeWithSelector(OPContractsManagerUpgrader.upgrade.selector, _opChainConfigs); _performDelegateCall(address(opcmUpgrader), data); } diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index ea718fd505b..e33e00c0a95 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -288,8 +288,10 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { emit Upgraded(impl); } - function runUpgradeTestAndChecks(address _delegateCaller) public { - IOPContractsManager.Implementations memory impls = opcm.implementations(); + function runV200UpgradeAndChecks(address _delegateCaller) public { + // The address below corresponds with the address of the v2.0.0-rc.1 OPCM on mainnet. + IOPContractsManager deployedOPCM = IOPContractsManager(address(0x026b2F158255Beac46c1E7c6b8BbF29A4b6A7B76)); + IOPContractsManager.Implementations memory impls = deployedOPCM.implementations(); // Cache the old L1xDM address so we can look for it in the AddressManager's event address oldL1CrossDomainMessenger = addressManager.getAddress("OVM_L1CrossDomainMessenger"); @@ -304,7 +306,9 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { "AnchorStateRegistry" ) ); - bytes memory initCode = bytes.concat(vm.getCode("Proxy"), abi.encode(proxyAdmin)); + address proxyBp = deployedOPCM.blueprints().proxy; + Blueprint.Preamble memory preamble = Blueprint.parseBlueprintPreamble(proxyBp.code); + bytes memory initCode = bytes.concat(preamble.initcode, abi.encode(proxyAdmin)); address newAnchorStateRegistryProxy = vm.computeCreate2Address(salt, keccak256(initCode), _delegateCaller); vm.label(newAnchorStateRegistryProxy, "NewAnchorStateRegistryProxy"); @@ -326,8 +330,11 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { // OPContractsManager.upgrade() call, so ignore the first topic. vm.expectEmit(false, true, true, true, address(disputeGameFactory)); emit ImplementationSet(address(0), GameTypes.PERMISSIONED_CANNON); - if (address(delayedWeth) != address(0)) { - expectEmitUpgraded(impls.delayedWETHImpl, address(delayedWeth)); + + IFaultDisputeGame oldFDG = IFaultDisputeGame(address(disputeGameFactory.gameImpls(GameTypes.CANNON))); + if (address(oldFDG) != address(0)) { + IDelayedWETH weth = oldFDG.weth(); + expectEmitUpgraded(impls.delayedWETHImpl, address(weth)); // Ignore the first topic for the same reason as the previous comment. vm.expectEmit(false, true, true, true, address(disputeGameFactory)); @@ -342,7 +349,7 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { vm.etch(_delegateCaller, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); DelegateCaller(_delegateCaller).dcForward( - address(opcm), abi.encodeCall(IOPContractsManager.upgrade, (opChainConfigs)) + address(deployedOPCM), abi.encodeCall(IOPContractsManager.upgrade, (opChainConfigs)) ); VmSafe.Gas memory gas = vm.lastCallGas(); @@ -376,32 +383,84 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { assertEq(address(pdg.anchorStateRegistry()), address(newAnchorStateRegistryProxy)); assertEq(address(pdg.vm()), impls.mipsImpl); - if (address(delayedWeth) != address(0)) { + if (address(oldFDG) != address(0)) { + // Check that the PermissionlessDisputeGame is upgraded to the expected version + IFaultDisputeGame newFDG = IFaultDisputeGame(address(disputeGameFactory.gameImpls(GameTypes.CANNON))); // Check that the PermissionlessDisputeGame is upgraded to the expected version, references // the correct anchor state and has the mipsImpl. - assertEq(impls.delayedWETHImpl, EIP1967Helper.getImplementation(address(delayedWeth))); - // Check that the PermissionlessDisputeGame is upgraded to the expected version - IFaultDisputeGame fdg = IFaultDisputeGame(address(disputeGameFactory.gameImpls(GameTypes.CANNON))); - assertEq(ISemver(address(fdg)).version(), "1.4.1"); - assertEq(address(fdg.anchorStateRegistry()), address(newAnchorStateRegistryProxy)); - assertEq(address(fdg.vm()), impls.mipsImpl); + assertEq(impls.delayedWETHImpl, EIP1967Helper.getImplementation(address(newFDG.weth()))); + assertEq(ISemver(address(newFDG)).version(), "1.4.1"); + assertEq(address(newFDG.anchorStateRegistry()), address(newAnchorStateRegistryProxy)); + assertEq(address(newFDG.vm()), impls.mipsImpl); } } -} -contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { - function test_upgradeSuperchainAndOPChain_succeeds() public { - // wrap runUpgradeTestAndChecks with additional checks for superchainConfig and protocolVersions + function runUpgrade14UpgradeAndChecks(address _delegateCaller) public { IOPContractsManager.Implementations memory impls = opcm.implementations(); - expectEmitUpgraded(impls.superchainConfigImpl, address(superchainConfig)); - expectEmitUpgraded(impls.protocolVersionsImpl, address(protocolVersions)); - runUpgradeTestAndChecks(upgrader); + // sanity check + IPermissionedDisputeGame oldPDG = + IPermissionedDisputeGame(address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON))); + IFaultDisputeGame oldFDG = IFaultDisputeGame(address(disputeGameFactory.gameImpls(GameTypes.CANNON))); + + // Sanity check that the mips IMPL is not MIPS64 + assertNotEq(address(oldPDG.vm()), impls.mipsImpl); + + // We don't yet know the address of the new permissionedGame which will be deployed by the + // OPContractsManager.upgrade() call, so ignore the first topic. + vm.expectEmit(false, true, true, true, address(disputeGameFactory)); + emit ImplementationSet(address(0), GameTypes.PERMISSIONED_CANNON); + + if (address(oldFDG) != address(0)) { + // Sanity check that the mips IMPL is not MIPS64 + assertNotEq(address(oldFDG.vm()), impls.mipsImpl); + // Ignore the first topic for the same reason as the previous comment. + vm.expectEmit(false, true, true, true, address(disputeGameFactory)); + emit ImplementationSet(address(0), GameTypes.CANNON); + } + vm.expectEmit(address(_delegateCaller)); + emit Upgraded(l2ChainId, opChainConfigs[0].systemConfigProxy, address(_delegateCaller)); + + // Temporarily replace the upgrader with a DelegateCaller so we can test the upgrade, + // then reset its code to the original code. + bytes memory delegateCallerCode = address(_delegateCaller).code; + vm.etch(_delegateCaller, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); + + DelegateCaller(_delegateCaller).dcForward( + address(opcm), abi.encodeCall(IOPContractsManager.upgrade, (opChainConfigs)) + ); + + VmSafe.Gas memory gas = vm.lastCallGas(); + + // Less than 90% of the gas target of 20M to account for the gas used by using Safe. + assertLt(gas.gasTotalUsed, 0.9 * 20_000_000, "Upgrade exceeds gas target of 15M"); - assertEq(impls.superchainConfigImpl, EIP1967Helper.getImplementation(address(superchainConfig))); - assertEq(impls.protocolVersionsImpl, EIP1967Helper.getImplementation(address(protocolVersions))); + vm.etch(_delegateCaller, delegateCallerCode); + + // Check that the PermissionedDisputeGame is upgraded to the expected version, references + // the correct anchor state and has the mipsImpl. + IPermissionedDisputeGame pdg = + IPermissionedDisputeGame(address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON))); + assertEq(ISemver(address(pdg)).version(), "1.4.1"); + assertEq(address(pdg.vm()), impls.mipsImpl); + + if (address(oldFDG) != address(0)) { + // Check that the PermissionlessDisputeGame is upgraded to the expected version + IFaultDisputeGame newFDG = IFaultDisputeGame(address(disputeGameFactory.gameImpls(GameTypes.CANNON))); + // Check that the PermissionlessDisputeGame is upgraded to the expected version, references + // the correct anchor state and has the mipsImpl. + assertEq(ISemver(address(newFDG)).version(), "1.4.1"); + assertEq(address(newFDG.vm()), impls.mipsImpl); + } } + function runUpgradeTestAndChecks(address _delegateCaller) public { + runV200UpgradeAndChecks(_delegateCaller); + runUpgrade14UpgradeAndChecks(_delegateCaller); + } +} + +contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { function test_upgradeOPChainOnly_succeeds() public { // Run the upgrade test and checks runUpgradeTestAndChecks(upgrader); @@ -465,40 +524,18 @@ contract OPContractsManager_Upgrade_Test is OPContractsManager_Upgrade_Harness { } contract OPContractsManager_Upgrade_TestFails is OPContractsManager_Upgrade_Harness { + // Upgrade to U14 first + function setUp() public override { + super.setUp(); + runV200UpgradeAndChecks(upgrader); + } + function test_upgrade_notDelegateCalled_reverts() public { vm.prank(upgrader); vm.expectRevert(IOPContractsManager.OnlyDelegatecall.selector); opcm.upgrade(opChainConfigs); } - function test_upgrade_superchainConfigMismatch_reverts() public { - upgrader = proxyAdmin.owner(); - // Set the superchainConfig to a different address in the OptimismPortal2 contract. - vm.store( - address(optimismPortal2), - bytes32(ForgeArtifacts.getSlot("OptimismPortal2", "superchainConfig").slot), - bytes32(abi.encode(bob)) - ); - - vm.expectRevert( - abi.encodeWithSelector(IOPContractsManager.SuperchainConfigMismatch.selector, address(systemConfig)) - ); - DelegateCaller(upgrader).dcForward(address(opcm), abi.encodeCall(IOPContractsManager.upgrade, (opChainConfigs))); - } - - function test_upgrade_notSuperchainProxyAdminOwner_reverts() public { - address delegateCaller = makeAddr("delegateCaller"); - vm.etch(delegateCaller, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); - - assertNotEq(superchainProxyAdmin.owner(), delegateCaller); - assertNotEq(proxyAdmin.owner(), delegateCaller); - - vm.expectRevert("Ownable: caller is not the owner"); - DelegateCaller(delegateCaller).dcForward( - address(opcm), abi.encodeCall(IOPContractsManager.upgrade, (opChainConfigs)) - ); - } - function test_upgrade_notProxyAdminOwner_reverts() public { address delegateCaller = makeAddr("delegateCaller"); vm.etch(delegateCaller, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); diff --git a/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol b/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol index d2fc138e91f..85680343ccc 100644 --- a/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol +++ b/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol @@ -432,14 +432,14 @@ contract DeployImplementations_Test is Test { function test_run_deployMipsV1OnMainnetOrSepolia_reverts() public { setDefaults(); - dii.set(dii.mipsVersion.selector, 2); + dii.set(dii.mipsVersion.selector, 1); vm.chainId(Chains.Mainnet); - vm.expectRevert("DeployImplementations: Only Mips32 should be deployed on Mainnet or Sepolia"); + vm.expectRevert("DeployImplementations: Only Mips64 should be deployed on Mainnet or Sepolia"); deployImplementations.run(dii, dio); vm.chainId(Chains.Sepolia); - vm.expectRevert("DeployImplementations: Only Mips32 should be deployed on Mainnet or Sepolia"); + vm.expectRevert("DeployImplementations: Only Mips64 should be deployed on Mainnet or Sepolia"); deployImplementations.run(dii, dio); } } diff --git a/packages/contracts-bedrock/test/setup/ForkLive.s.sol b/packages/contracts-bedrock/test/setup/ForkLive.s.sol index 45c31273428..3de0214c8ea 100644 --- a/packages/contracts-bedrock/test/setup/ForkLive.s.sol +++ b/packages/contracts-bedrock/test/setup/ForkLive.s.sol @@ -131,7 +131,6 @@ contract ForkLive is Deployer { // Fault proof proxied contracts saveProxyAndImpl("AnchorStateRegistry", opToml, ".addresses.AnchorStateRegistryProxy"); saveProxyAndImpl("DisputeGameFactory", opToml, ".addresses.DisputeGameFactoryProxy"); - saveProxyAndImpl("DelayedWETH", opToml, ".addresses.DelayedWETHProxy"); // Fault proof non-proxied contracts // For chains that don't have a permissionless game, we save the dispute game and WETH @@ -151,6 +150,10 @@ contract ForkLive is Deployer { IFaultDisputeGame(address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON))); artifacts.save("PermissionedDisputeGame", address(permissionedDisputeGame)); artifacts.save("PermissionedDelayedWETHProxy", address(permissionedDisputeGame.weth())); + + // The SR seems out-of-date, so pull the DelayedWETH addresses from the PermissionedDisputeGame. + artifacts.save("DelayedWETHProxy", address(permissionedDisputeGame.weth())); + artifacts.save("DelayedWETHImpl", EIP1967Helper.getImplementation(address(permissionedDisputeGame.weth()))); } /// @notice Calls to the Deploy.s.sol contract etched by Setup.sol to a deterministic address, sets up the @@ -181,6 +184,9 @@ contract ForkLive is Deployer { // then reset its code to the original code. bytes memory upgraderCode = address(upgrader).code; vm.etch(upgrader, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); + DelegateCaller(upgrader).dcForward( + address(0x026b2F158255Beac46c1E7c6b8BbF29A4b6A7B76), abi.encodeCall(IOPContractsManager.upgrade, (opChains)) + ); DelegateCaller(upgrader).dcForward(address(opcm), abi.encodeCall(IOPContractsManager.upgrade, (opChains))); vm.etch(upgrader, upgraderCode); diff --git a/packages/contracts-bedrock/test/setup/Setup.sol b/packages/contracts-bedrock/test/setup/Setup.sol index f984c72360b..8c6a3bd8a27 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -148,6 +148,7 @@ contract Setup { if (isForkTest()) { vm.createSelectFork(vm.envString("FORK_RPC_URL"), vm.envUint("FORK_BLOCK_NUMBER")); + console.log("Setup: fork selected!"); require( block.chainid == Chains.Sepolia || block.chainid == Chains.Mainnet, "Setup: ETH_RPC_URL must be set to a production (Sepolia or Mainnet) RPC URL" From 502f002f14e839252095c87156eb9a422d7974a9 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Wed, 5 Mar 2025 09:11:34 +1000 Subject: [PATCH 055/130] Remove redundant TODOs (#14610) * op-e2e: Remove completed TODO * op-test-sequencer: Remove TODO - ws support is not required here. * op-supervisor: Remove TODO - RPC() method is intended to only return HTTP URLs. * op-chain-ops: Remove TODO about making number of keys configurable. The issue for this was closed with the decision that it didn't need to be configurable. * op-program: Remove no longer relevant TODOs * Undo script changes. * Undo whitespace change. --- op-chain-ops/interopgen/recipe.go | 2 -- op-e2e/interop/supersystem.go | 1 - op-e2e/interop/supersystem_l2.go | 1 - op-program/client/interop/consolidate.go | 1 - op-program/host/config/config.go | 4 ++-- op-supervisor/supervisor/service.go | 1 - 6 files changed, 2 insertions(+), 8 deletions(-) diff --git a/op-chain-ops/interopgen/recipe.go b/op-chain-ops/interopgen/recipe.go index 56196584f73..fa72a1273f5 100644 --- a/op-chain-ops/interopgen/recipe.go +++ b/op-chain-ops/interopgen/recipe.go @@ -31,7 +31,6 @@ func (r *InteropDevRecipe) Build(addrs devkeys.Addresses) (*WorldConfig, error) Prefund: make(map[common.Address]*big.Int), } - // TODO(#11887): consider making the number of prefunded keys configurable. l1Users := devkeys.ChainUserKeys(l1Cfg.ChainID) for i := uint64(0); i < 20; i++ { userAddr, err := addrs.Address(l1Users(i)) @@ -264,7 +263,6 @@ func InteropL2DevConfig(l1ChainID, l2ChainID uint64, addrs devkeys.Addresses, me DisputeMaxClockDuration: 302400, // 3.5 days (input in seconds) } - // TODO(#11887): consider making the number of prefunded keys configurable. l2Users := devkeys.ChainUserKeys(new(big.Int).SetUint64(l2ChainID)) for i := uint64(0); i < 20; i++ { userAddr, err := addrs.Address(l2Users(i)) diff --git a/op-e2e/interop/supersystem.go b/op-e2e/interop/supersystem.go index 4012a124d85..bbdbd7cbfcb 100644 --- a/op-e2e/interop/supersystem.go +++ b/op-e2e/interop/supersystem.go @@ -347,7 +347,6 @@ func (s *interopE2ESystem) prepare(t *testing.T, w worldResourcePaths) { // but if in the future these maps can diverge, the indexes for username would also diverge // NOTE: The first 20 accounts are implicitly funded by the Recipe's World Deployment // see: op-chain-ops/interopgen/recipe.go -// TODO(#11887): make the funded account quantity specified in the recipe so SuperSystems can know which accounts are funded func (s *interopE2ESystem) AddUser(username string) { for id, l2 := range s.l2s { bigID, _ := big.NewInt(0).SetString(id, 10) diff --git a/op-e2e/interop/supersystem_l2.go b/op-e2e/interop/supersystem_l2.go index 7eb94c2ed70..bae49792e69 100644 --- a/op-e2e/interop/supersystem_l2.go +++ b/op-e2e/interop/supersystem_l2.go @@ -100,7 +100,6 @@ func (s *interopE2ESystem) newL2(id string, l2Out *interopgen.L2Output) l2Net { operatorKeys := s.newOperatorKeysForL2(l2Out) l2Geth := s.newGethForL2(id, "sequencer", l2Out) opNode := s.newNodeForL2(id, "sequencer", l2Out, operatorKeys, l2Geth, true) - // TODO(#11886): proposer does not work with the generated world as there is no DisputeGameFactoryProxy proposer := s.newProposerForL2(id, operatorKeys) batcher := s.newBatcherForL2(id, operatorKeys, l2Geth, opNode) diff --git a/op-program/client/interop/consolidate.go b/op-program/client/interop/consolidate.go index 141a7205245..6916d03066a 100644 --- a/op-program/client/interop/consolidate.go +++ b/op-program/client/interop/consolidate.go @@ -191,7 +191,6 @@ func newConsolidateCheckDeps( progress := transitionState.PendingProgress[i] // This is the optimistic head. It's OK if it's replaced by a deposits-only block. // Because by then the replacement block won't be used for hazard checks. - // TODO(#14012): for extra safety, ensure the l2 oracle used for checks isn't affected by block reexec. head := oracle.BlockByHash(progress.BlockHash, chain.ChainID) blockByHash := func(hash common.Hash) *ethtypes.Block { return oracle.BlockByHash(hash, chain.ChainID) diff --git a/op-program/host/config/config.go b/op-program/host/config/config.go index 33e6f0a2d4e..a1a768cc418 100644 --- a/op-program/host/config/config.go +++ b/op-program/host/config/config.go @@ -49,7 +49,7 @@ var ( ) type Config struct { - L2ChainID eth.ChainID // TODO: Forbid for interop + L2ChainID eth.ChainID Rollups []*rollup.Config // DataDir is the directory to read/write pre-image data from/to. // If not set, an in-memory key-value store is used and fetching data must be enabled @@ -66,7 +66,7 @@ type Config struct { L1RPCKind sources.RPCProviderKind // L2Head is the l2 block hash contained in the L2 Output referenced by the L2OutputRoot for pre-interop mode - L2Head common.Hash // TODO: Forbid for interop + L2Head common.Hash // L2OutputRoot is the agreed L2 output root to start derivation from L2OutputRoot common.Hash // L2URLs are the URLs of the L2 nodes to fetch L2 data from, these are the canonical URL for L2 data diff --git a/op-supervisor/supervisor/service.go b/op-supervisor/supervisor/service.go index 372abb1cc50..2f04d889183 100644 --- a/op-supervisor/supervisor/service.go +++ b/op-supervisor/supervisor/service.go @@ -257,6 +257,5 @@ func (su *SupervisorService) Stopped() bool { func (su *SupervisorService) RPC() string { // the RPC endpoint is assumed to be HTTP - // TODO(#11032): make this flexible for ws if the server supports it return "http://" + su.rpcServer.Endpoint() } From 8beb60326d149211805b79e770b28765ba3ac007 Mon Sep 17 00:00:00 2001 From: Inphi Date: Tue, 4 Mar 2025 19:32:03 -0500 Subject: [PATCH 056/130] op-e2e: cleanup unused MessageExpiryTime from interop recipe (#14634) --- op-chain-ops/interopgen/recipe.go | 11 +++++------ op-e2e/actions/interop/dsl/interop.go | 7 +++---- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/op-chain-ops/interopgen/recipe.go b/op-chain-ops/interopgen/recipe.go index fa72a1273f5..5c1d4ffac94 100644 --- a/op-chain-ops/interopgen/recipe.go +++ b/op-chain-ops/interopgen/recipe.go @@ -13,10 +13,9 @@ import ( ) type InteropDevRecipe struct { - L1ChainID uint64 - L2ChainIDs []uint64 - GenesisTimestamp uint64 - MessageExpiryTime uint64 + L1ChainID uint64 + L2ChainIDs []uint64 + GenesisTimestamp uint64 } func (r *InteropDevRecipe) Build(addrs devkeys.Addresses) (*WorldConfig, error) { @@ -90,7 +89,7 @@ func (r *InteropDevRecipe) Build(addrs devkeys.Addresses) (*WorldConfig, error) L2s: make(map[string]*L2Config), } for _, l2ChainID := range r.L2ChainIDs { - l2Cfg, err := InteropL2DevConfig(r.L1ChainID, l2ChainID, addrs, r.MessageExpiryTime) + l2Cfg, err := InteropL2DevConfig(r.L1ChainID, l2ChainID, addrs) if err != nil { return nil, fmt.Errorf("failed to generate L2 config for chain %d: %w", l2ChainID, err) } @@ -126,7 +125,7 @@ func prefundL2Accounts(l1Cfg *L1Config, l2Cfg *L2Config, addrs devkeys.Addresses return nil } -func InteropL2DevConfig(l1ChainID, l2ChainID uint64, addrs devkeys.Addresses, messageExpiryTime uint64) (*L2Config, error) { +func InteropL2DevConfig(l1ChainID, l2ChainID uint64, addrs devkeys.Addresses) (*L2Config, error) { // Padded chain ID, hex encoded, prefixed with 0xff like inboxes, then 0x02 to signify devnet. batchInboxAddress := common.HexToAddress(fmt.Sprintf("0xff02%016x", l2ChainID)) chainOps := devkeys.ChainOperatorKeys(new(big.Int).SetUint64(l2ChainID)) diff --git a/op-e2e/actions/interop/dsl/interop.go b/op-e2e/actions/interop/dsl/interop.go index 92c8d7feb8e..48bdde8e682 100644 --- a/op-e2e/actions/interop/dsl/interop.go +++ b/op-e2e/actions/interop/dsl/interop.go @@ -108,10 +108,9 @@ func SetupInterop(t helpers.Testing) *InteropSetup { logger := testlog.Logger(t, log.LevelDebug) recipe := interopgen.InteropDevRecipe{ - L1ChainID: 900100, - L2ChainIDs: []uint64{900200, 900201}, - GenesisTimestamp: uint64(time.Now().Unix() + 3), - MessageExpiryTime: messageExpiryTime, + L1ChainID: 900100, + L2ChainIDs: []uint64{900200, 900201}, + GenesisTimestamp: uint64(time.Now().Unix() + 3), } hdWallet, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) require.NoError(t, err) From b6ac64caed82edeab6a860a6fb9127cf8865392f Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Wed, 5 Mar 2025 10:43:45 +1000 Subject: [PATCH 057/130] op-dispute-mon: Support interop (#14598) * op-dispute-mon: Add support for super root game types. * op-dispute-mon: Switch to a single getter method for determining root type. * op-dispute-mon: Don't init rollup client if rollup-rpc is not set. --- op-dispute-mon/cmd/main_test.go | 21 +- op-dispute-mon/config/config.go | 22 +- op-dispute-mon/config/config_test.go | 20 +- op-dispute-mon/flags/flags.go | 14 +- ...richer.go => output_agreement_enricher.go} | 26 +- ...t.go => output_agreement_enricher_test.go} | 68 ++++- .../mon/extract/super_agreement_enricher.go | 65 +++++ .../extract/super_agreement_enricher_test.go | 236 ++++++++++++++++++ op-dispute-mon/mon/service.go | 38 ++- op-dispute-mon/mon/types/types.go | 10 + op-dispute-mon/mon/types/types_test.go | 32 +++ 11 files changed, 521 insertions(+), 31 deletions(-) rename op-dispute-mon/mon/extract/{agreement_enricher.go => output_agreement_enricher.go} (72%) rename op-dispute-mon/mon/extract/{agreement_enricher_test.go => output_agreement_enricher_test.go} (70%) create mode 100644 op-dispute-mon/mon/extract/super_agreement_enricher.go create mode 100644 op-dispute-mon/mon/extract/super_agreement_enricher_test.go create mode 100644 op-dispute-mon/mon/types/types_test.go diff --git a/op-dispute-mon/cmd/main_test.go b/op-dispute-mon/cmd/main_test.go index f3fa7cd7afe..9e58e1b95af 100644 --- a/op-dispute-mon/cmd/main_test.go +++ b/op-dispute-mon/cmd/main_test.go @@ -59,9 +59,13 @@ func TestL1EthRpc(t *testing.T) { }) } +func TestMustSpecifyEitherRollupRpcOrSupervisorRpc(t *testing.T) { + verifyArgsInvalid(t, "flag rollup-rpc or supervisor-rpc is required", addRequiredArgsExcept("--rollup-rpc")) +} + func TestRollupRpc(t *testing.T) { - t.Run("Required", func(t *testing.T) { - verifyArgsInvalid(t, "flag rollup-rpc is required", addRequiredArgsExcept("--rollup-rpc")) + t.Run("NotRequiredIfSupervisorRpcSupplied", func(t *testing.T) { + configForArgs(t, addRequiredArgsExcept("--rollup-rpc", "--supervisor-rpc", "http://localhost/supervisor")) }) t.Run("Valid", func(t *testing.T) { @@ -71,6 +75,19 @@ func TestRollupRpc(t *testing.T) { }) } +func TestSupervisorRpc(t *testing.T) { + t.Run("NotRequiredIfRollupRpcSupplied", func(t *testing.T) { + // rollup-rpc is in the default args. + configForArgs(t, addRequiredArgsExcept("--supervisor-rpc")) + }) + + t.Run("Valid", func(t *testing.T) { + url := "http://example.com:9999" + cfg := configForArgs(t, addRequiredArgsExcept("--rollup-rpc", "--supervisor-rpc", url)) + require.Equal(t, url, cfg.SupervisorRpc) + }) +} + func TestGameFactoryAddress(t *testing.T) { t.Run("RequiredIfNetworkNetSet", func(t *testing.T) { verifyArgsInvalid(t, "flag game-factory-address or network is required", addRequiredArgsExcept("--game-factory-address")) diff --git a/op-dispute-mon/config/config.go b/op-dispute-mon/config/config.go index 8329819d002..7cf631f5af5 100644 --- a/op-dispute-mon/config/config.go +++ b/op-dispute-mon/config/config.go @@ -12,10 +12,10 @@ import ( ) var ( - ErrMissingL1EthRPC = errors.New("missing l1 eth rpc url") - ErrMissingGameFactoryAddress = errors.New("missing game factory address") - ErrMissingRollupRpc = errors.New("missing rollup rpc url") - ErrMissingMaxConcurrency = errors.New("missing max concurrency") + ErrMissingL1EthRPC = errors.New("missing l1 eth rpc url") + ErrMissingGameFactoryAddress = errors.New("missing game factory address") + ErrMissingRollupAndSupervisorRpc = errors.New("must specify rollup rpc or supervisor rpc") + ErrMissingMaxConcurrency = errors.New("missing max concurrency") ) const ( @@ -40,6 +40,7 @@ type Config struct { HonestActors []common.Address // List of honest actors to monitor claims for. RollupRpc string // The rollup node RPC URL. + SupervisorRpc string // The supervisor RPC URL. MonitorInterval time.Duration // Frequency to check for new games to monitor. GameWindow time.Duration // Maximum window to look for games to monitor. IgnoredGames []common.Address // Games to exclude from monitoring @@ -49,10 +50,19 @@ type Config struct { PprofConfig oppprof.CLIConfig } +func NewInteropConfig(gameFactoryAddress common.Address, l1EthRpc string, supervisorRpc string) Config { + return NewCombinedConfig(gameFactoryAddress, l1EthRpc, "", supervisorRpc) +} + func NewConfig(gameFactoryAddress common.Address, l1EthRpc string, rollupRpc string) Config { + return NewCombinedConfig(gameFactoryAddress, l1EthRpc, rollupRpc, "") +} + +func NewCombinedConfig(gameFactoryAddress common.Address, l1EthRpc string, rollupRpc string, supervisorRpc string) Config { return Config{ L1EthRpc: l1EthRpc, RollupRpc: rollupRpc, + SupervisorRpc: supervisorRpc, GameFactoryAddress: gameFactoryAddress, MonitorInterval: DefaultMonitorInterval, @@ -68,8 +78,8 @@ func (c Config) Check() error { if c.L1EthRpc == "" { return ErrMissingL1EthRPC } - if c.RollupRpc == "" { - return ErrMissingRollupRpc + if c.RollupRpc == "" && c.SupervisorRpc == "" { + return ErrMissingRollupAndSupervisorRpc } if c.GameFactoryAddress == (common.Address{}) { return ErrMissingGameFactoryAddress diff --git a/op-dispute-mon/config/config_test.go b/op-dispute-mon/config/config_test.go index f199d8554a3..bc4e275cb97 100644 --- a/op-dispute-mon/config/config_test.go +++ b/op-dispute-mon/config/config_test.go @@ -12,6 +12,7 @@ var ( validL1EthRpc = "http://localhost:8545" validGameFactoryAddress = common.Address{0x23} validRollupRpc = "http://localhost:8555" + validSupervisorRpc = "http://localhost:8999" ) func validConfig() Config { @@ -34,10 +35,25 @@ func TestGameFactoryAddressRequired(t *testing.T) { require.ErrorIs(t, config.Check(), ErrMissingGameFactoryAddress) } -func TestRollupRpcRequired(t *testing.T) { +func TestRollupRpcOrSupervisorRpcRequired(t *testing.T) { config := validConfig() config.RollupRpc = "" - require.ErrorIs(t, config.Check(), ErrMissingRollupRpc) + config.SupervisorRpc = "" + require.ErrorIs(t, config.Check(), ErrMissingRollupAndSupervisorRpc) +} + +func TestRollupRpcNotRequiredWhenSupervisorRpcSet(t *testing.T) { + config := validConfig() + config.RollupRpc = "" + config.SupervisorRpc = validSupervisorRpc + require.NoError(t, config.Check()) +} + +func TestSupervisorRpcNotRequiredWhenRollupRpcSet(t *testing.T) { + config := validConfig() + config.RollupRpc = validRollupRpc + config.SupervisorRpc = "" + require.NoError(t, config.Check()) } func TestMaxConcurrencyRequired(t *testing.T) { diff --git a/op-dispute-mon/flags/flags.go b/op-dispute-mon/flags/flags.go index a8f534162f5..497c77e9798 100644 --- a/op-dispute-mon/flags/flags.go +++ b/op-dispute-mon/flags/flags.go @@ -32,12 +32,17 @@ var ( Usage: "HTTP provider URL for L1.", EnvVars: prefixEnvVars("L1_ETH_RPC"), } + // Optional Flags RollupRpcFlag = &cli.StringFlag{ Name: "rollup-rpc", Usage: "HTTP provider URL for the rollup node", EnvVars: prefixEnvVars("ROLLUP_RPC"), } - // Optional Flags + SupervisorRpcFlag = &cli.StringFlag{ + Name: "supervisor-rpc", + Usage: "HTTP provider URL for the supervisor node", + EnvVars: prefixEnvVars("SUPERVISOR_RPC"), + } GameFactoryAddressFlag = &cli.StringFlag{ Name: "game-factory-address", Usage: "Address of the fault game factory contract.", @@ -78,11 +83,12 @@ var ( // requiredFlags are checked by [CheckRequired] var requiredFlags = []cli.Flag{ L1EthRpcFlag, - RollupRpcFlag, } // optionalFlags is a list of unchecked cli flags var optionalFlags = []cli.Flag{ + RollupRpcFlag, + SupervisorRpcFlag, GameFactoryAddressFlag, NetworkFlag, HonestActorsFlag, @@ -109,6 +115,9 @@ func CheckRequired(ctx *cli.Context) error { return fmt.Errorf("flag %s is required", f.Names()[0]) } } + if !ctx.IsSet(RollupRpcFlag.Name) && !ctx.IsSet(SupervisorRpcFlag.Name) { + return fmt.Errorf("flag %s or %s is required", RollupRpcFlag.Name, SupervisorRpcFlag.Name) + } return nil } @@ -156,6 +165,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) { L1EthRpc: ctx.String(L1EthRpcFlag.Name), GameFactoryAddress: gameFactoryAddress, RollupRpc: ctx.String(RollupRpcFlag.Name), + SupervisorRpc: ctx.String(SupervisorRpcFlag.Name), HonestActors: actors, MonitorInterval: ctx.Duration(MonitorIntervalFlag.Name), diff --git a/op-dispute-mon/mon/extract/agreement_enricher.go b/op-dispute-mon/mon/extract/output_agreement_enricher.go similarity index 72% rename from op-dispute-mon/mon/extract/agreement_enricher.go rename to op-dispute-mon/mon/extract/output_agreement_enricher.go index dafa09cba93..378ce204659 100644 --- a/op-dispute-mon/mon/extract/agreement_enricher.go +++ b/op-dispute-mon/mon/extract/output_agreement_enricher.go @@ -2,16 +2,20 @@ package extract import ( "context" + "errors" "fmt" "strings" - "time" monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types" + "github.com/ethereum-optimism/optimism/op-service/clock" + "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" +) - "github.com/ethereum-optimism/optimism/op-service/eth" +var ( + ErrRollupRpcRequired = errors.New("rollup rpc required") ) type OutputRollupClient interface { @@ -23,22 +27,30 @@ type OutputMetrics interface { RecordOutputFetchTime(float64) } -type AgreementEnricher struct { +type OutputAgreementEnricher struct { log log.Logger metrics OutputMetrics client OutputRollupClient + clock clock.Clock } -func NewAgreementEnricher(logger log.Logger, metrics OutputMetrics, client OutputRollupClient) *AgreementEnricher { - return &AgreementEnricher{ +func NewOutputAgreementEnricher(logger log.Logger, metrics OutputMetrics, client OutputRollupClient, cl clock.Clock) *OutputAgreementEnricher { + return &OutputAgreementEnricher{ log: logger, metrics: metrics, client: client, + clock: cl, } } // Enrich validates the specified root claim against the output at the given block number. -func (o *AgreementEnricher) Enrich(ctx context.Context, block rpcblock.Block, caller GameCaller, game *monTypes.EnrichedGameData) error { +func (o *OutputAgreementEnricher) Enrich(ctx context.Context, block rpcblock.Block, caller GameCaller, game *monTypes.EnrichedGameData) error { + if !game.UsesOutputRoots() { + return nil + } + if o.client == nil { + return fmt.Errorf("%w but required for game type %v", ErrRollupRpcRequired, game.GameType) + } output, err := o.client.OutputAtBlock(ctx, game.L2BlockNumber) if err != nil { // string match as the error comes from the remote server so we can't use Errors.Is sadly. @@ -49,7 +61,7 @@ func (o *AgreementEnricher) Enrich(ctx context.Context, block rpcblock.Block, ca } return fmt.Errorf("failed to get output at block: %w", err) } - o.metrics.RecordOutputFetchTime(float64(time.Now().Unix())) + o.metrics.RecordOutputFetchTime(float64(o.clock.Now().Unix())) game.ExpectedRootClaim = common.Hash(output.OutputRoot) rootMatches := game.RootClaim == game.ExpectedRootClaim if !rootMatches { diff --git a/op-dispute-mon/mon/extract/agreement_enricher_test.go b/op-dispute-mon/mon/extract/output_agreement_enricher_test.go similarity index 70% rename from op-dispute-mon/mon/extract/agreement_enricher_test.go rename to op-dispute-mon/mon/extract/output_agreement_enricher_test.go index bd9d3853e79..ba9f64a5dbd 100644 --- a/op-dispute-mon/mon/extract/agreement_enricher_test.go +++ b/op-dispute-mon/mon/extract/output_agreement_enricher_test.go @@ -3,9 +3,13 @@ package extract import ( "context" "errors" + "fmt" "testing" + "time" + challengerTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types" + "github.com/ethereum-optimism/optimism/op-service/clock" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum-optimism/optimism/op-service/testlog" @@ -14,9 +18,67 @@ import ( "github.com/stretchr/testify/require" ) -func TestDetector_CheckRootAgreement(t *testing.T) { +func TestDetector_CheckOutputRootAgreement(t *testing.T) { t.Parallel() + t.Run("ErrorWhenNoRollupClient", func(t *testing.T) { + validator, _, _ := setupOutputValidatorTest(t) + validator.client = nil + game := &types.EnrichedGameData{ + GameMetadata: challengerTypes.GameMetadata{ + GameType: 0, + }, + L1HeadNum: 200, + L2BlockNumber: 0, + RootClaim: mockRootClaim, + } + err := validator.Enrich(context.Background(), rpcblock.Latest, nil, game) + require.ErrorIs(t, err, ErrRollupRpcRequired) + }) + + t.Run("SkipNonOutputRootGameTypes", func(t *testing.T) { + gameTypes := []uint32{4, 5, 7, 8, 10, 49812} + for _, gameType := range gameTypes { + gameType := gameType + t.Run(fmt.Sprintf("GameType_%d", gameType), func(t *testing.T) { + validator, _, metrics := setupOutputValidatorTest(t) + validator.client = nil // Should not error even though there's no rollup client + game := &types.EnrichedGameData{ + GameMetadata: challengerTypes.GameMetadata{ + GameType: gameType, + }, + L1HeadNum: 200, + L2BlockNumber: 0, + RootClaim: mockRootClaim, + } + err := validator.Enrich(context.Background(), rpcblock.Latest, nil, game) + require.NoError(t, err) + require.Zero(t, metrics.fetchTime) + }) + } + }) + + t.Run("FetchAllOutputRootGameTypes", func(t *testing.T) { + gameTypes := []uint32{0, 1, 2, 3, 6, 254, 255, 1337} + for _, gameType := range gameTypes { + gameType := gameType + t.Run(fmt.Sprintf("GameType_%d", gameType), func(t *testing.T) { + validator, _, metrics := setupOutputValidatorTest(t) + game := &types.EnrichedGameData{ + GameMetadata: challengerTypes.GameMetadata{ + GameType: gameType, + }, + L1HeadNum: 200, + L2BlockNumber: 0, + RootClaim: mockRootClaim, + } + err := validator.Enrich(context.Background(), rpcblock.Latest, nil, game) + require.NoError(t, err) + require.NotZero(t, metrics.fetchTime, "should have fetched output root") + }) + } + }) + t.Run("OutputFetchFails", func(t *testing.T) { validator, rollup, metrics := setupOutputValidatorTest(t) rollup.outputErr = errors.New("boom") @@ -136,11 +198,11 @@ func TestDetector_CheckRootAgreement(t *testing.T) { }) } -func setupOutputValidatorTest(t *testing.T) (*AgreementEnricher, *stubRollupClient, *stubOutputMetrics) { +func setupOutputValidatorTest(t *testing.T) (*OutputAgreementEnricher, *stubRollupClient, *stubOutputMetrics) { logger := testlog.Logger(t, log.LvlInfo) client := &stubRollupClient{safeHeadNum: 99999999999} metrics := &stubOutputMetrics{} - validator := NewAgreementEnricher(logger, metrics, client) + validator := NewOutputAgreementEnricher(logger, metrics, client, clock.NewDeterministicClock(time.Unix(9824924, 499))) return validator, client, metrics } diff --git a/op-dispute-mon/mon/extract/super_agreement_enricher.go b/op-dispute-mon/mon/extract/super_agreement_enricher.go new file mode 100644 index 00000000000..b202fc43a39 --- /dev/null +++ b/op-dispute-mon/mon/extract/super_agreement_enricher.go @@ -0,0 +1,65 @@ +package extract + +import ( + "context" + "errors" + "fmt" + + monTypes "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types" + "github.com/ethereum-optimism/optimism/op-service/clock" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/log" +) + +var ( + ErrSupervisorRpcRequired = errors.New("supervisor rpc required") +) + +type SuperRootProvider interface { + SuperRootAtTimestamp(ctx context.Context, timestamp hexutil.Uint64) (eth.SuperRootResponse, error) +} + +type SuperAgreementEnricher struct { + log log.Logger + metrics OutputMetrics + client SuperRootProvider + clock clock.Clock +} + +func NewSuperAgreementEnricher(logger log.Logger, metrics OutputMetrics, client SuperRootProvider, cl clock.Clock) *SuperAgreementEnricher { + return &SuperAgreementEnricher{ + log: logger, + metrics: metrics, + client: client, + clock: cl, + } +} + +func (e *SuperAgreementEnricher) Enrich(ctx context.Context, block rpcblock.Block, caller GameCaller, game *monTypes.EnrichedGameData) error { + if game.UsesOutputRoots() { + return nil + } + if e.client == nil { + return fmt.Errorf("%w but required for game type %v", ErrSupervisorRpcRequired, game.GameType) + } + response, err := e.client.SuperRootAtTimestamp(ctx, hexutil.Uint64(game.L2BlockNumber)) + if errors.Is(err, ethereum.NotFound) { + // Super root doesn't exist, so we must disagree with it. + game.AgreeWithClaim = false + return nil + } else if err != nil { + return fmt.Errorf("failed to retrieve super root at timestamp %v: %w", game.L2BlockNumber, err) + } + e.metrics.RecordOutputFetchTime(float64(e.clock.Now().Unix())) + game.ExpectedRootClaim = common.Hash(response.SuperRoot) + if game.RootClaim != game.ExpectedRootClaim { + game.AgreeWithClaim = false + return nil + } + game.AgreeWithClaim = response.CrossSafeDerivedFrom.Number <= game.L1HeadNum + return nil +} diff --git a/op-dispute-mon/mon/extract/super_agreement_enricher_test.go b/op-dispute-mon/mon/extract/super_agreement_enricher_test.go new file mode 100644 index 00000000000..e5c69b06fe6 --- /dev/null +++ b/op-dispute-mon/mon/extract/super_agreement_enricher_test.go @@ -0,0 +1,236 @@ +package extract + +import ( + "context" + "errors" + "fmt" + "testing" + "time" + + challengerTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" + "github.com/ethereum-optimism/optimism/op-dispute-mon/mon/types" + "github.com/ethereum-optimism/optimism/op-service/clock" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +func TestDetector_CheckSuperRootAgreement(t *testing.T) { + t.Parallel() + + t.Run("ErrorWhenNoSupervisorClient", func(t *testing.T) { + validator, _, _ := setupSuperValidatorTest(t) + validator.client = nil + game := &types.EnrichedGameData{ + GameMetadata: challengerTypes.GameMetadata{ + GameType: 999, + }, + L1HeadNum: 200, + L2BlockNumber: 0, + RootClaim: mockRootClaim, + } + err := validator.Enrich(context.Background(), rpcblock.Latest, nil, game) + require.ErrorIs(t, err, ErrSupervisorRpcRequired) + }) + + t.Run("SkipOutputRootGameTypes", func(t *testing.T) { + gameTypes := []uint32{0, 1, 2, 3, 6, 254, 255, 1337} + for _, gameType := range gameTypes { + gameType := gameType + t.Run(fmt.Sprintf("GameType_%d", gameType), func(t *testing.T) { + validator, _, metrics := setupSuperValidatorTest(t) + validator.client = nil // Should not error even though there's no rollup client + game := &types.EnrichedGameData{ + GameMetadata: challengerTypes.GameMetadata{ + GameType: gameType, + }, + L1HeadNum: 200, + L2BlockNumber: 0, + RootClaim: mockRootClaim, + } + err := validator.Enrich(context.Background(), rpcblock.Latest, nil, game) + require.NoError(t, err) + require.Zero(t, metrics.fetchTime) + }) + } + }) + + t.Run("FetchAllNonOutputRootGameTypes", func(t *testing.T) { + gameTypes := []uint32{4, 5, 7, 8, 10, 49812} // Treat unknown game types as using super roots + for _, gameType := range gameTypes { + gameType := gameType + t.Run(fmt.Sprintf("GameType_%d", gameType), func(t *testing.T) { + validator, _, metrics := setupSuperValidatorTest(t) + game := &types.EnrichedGameData{ + GameMetadata: challengerTypes.GameMetadata{ + GameType: gameType, + }, + L1HeadNum: 200, + L2BlockNumber: 0, + RootClaim: mockRootClaim, + } + err := validator.Enrich(context.Background(), rpcblock.Latest, nil, game) + require.NoError(t, err) + require.NotZero(t, metrics.fetchTime, "should have fetched output root") + }) + } + }) + + t.Run("OutputFetchFails", func(t *testing.T) { + validator, rollup, metrics := setupSuperValidatorTest(t) + rollup.outputErr = errors.New("boom") + game := &types.EnrichedGameData{ + GameMetadata: challengerTypes.GameMetadata{ + GameType: 999, + }, + L1HeadNum: 100, + L2BlockNumber: 0, + RootClaim: mockRootClaim, + } + err := validator.Enrich(context.Background(), rpcblock.Latest, nil, game) + require.ErrorIs(t, err, rollup.outputErr) + require.Equal(t, common.Hash{}, game.ExpectedRootClaim) + require.False(t, game.AgreeWithClaim) + require.Zero(t, metrics.fetchTime) + }) + + t.Run("OutputMismatch_Safe", func(t *testing.T) { + validator, _, metrics := setupSuperValidatorTest(t) + game := &types.EnrichedGameData{ + GameMetadata: challengerTypes.GameMetadata{ + GameType: 999, + }, + L1HeadNum: 100, + L2BlockNumber: 0, + RootClaim: common.Hash{}, + } + err := validator.Enrich(context.Background(), rpcblock.Latest, nil, game) + require.NoError(t, err) + require.Equal(t, mockRootClaim, game.ExpectedRootClaim) + require.False(t, game.AgreeWithClaim) + require.NotZero(t, metrics.fetchTime) + }) + + t.Run("OutputMatches_Safe_DerivedFromGameHead", func(t *testing.T) { + validator, client, metrics := setupSuperValidatorTest(t) + client.derivedFromL1BlockNum = 200 + game := &types.EnrichedGameData{ + GameMetadata: challengerTypes.GameMetadata{ + GameType: 999, + }, + L1HeadNum: 200, + L2BlockNumber: 0, + RootClaim: mockRootClaim, + } + err := validator.Enrich(context.Background(), rpcblock.Latest, nil, game) + require.NoError(t, err) + require.Equal(t, mockRootClaim, game.ExpectedRootClaim) + require.True(t, game.AgreeWithClaim) + require.NotZero(t, metrics.fetchTime) + }) + + t.Run("OutputMatches_Safe_DerivedFromBeforeGameHead", func(t *testing.T) { + validator, client, metrics := setupSuperValidatorTest(t) + client.derivedFromL1BlockNum = 199 + game := &types.EnrichedGameData{ + GameMetadata: challengerTypes.GameMetadata{ + GameType: 999, + }, + L1HeadNum: 200, + L2BlockNumber: 0, + RootClaim: mockRootClaim, + } + err := validator.Enrich(context.Background(), rpcblock.Latest, nil, game) + require.NoError(t, err) + require.Equal(t, mockRootClaim, game.ExpectedRootClaim) + require.True(t, game.AgreeWithClaim) + require.NotZero(t, metrics.fetchTime) + }) + + t.Run("OutputMismatch_NotSafe", func(t *testing.T) { + validator, client, metrics := setupSuperValidatorTest(t) + client.derivedFromL1BlockNum = 101 + game := &types.EnrichedGameData{ + GameMetadata: challengerTypes.GameMetadata{ + GameType: 999, + }, + L1HeadNum: 100, + L2BlockNumber: 0, + RootClaim: common.Hash{}, + } + err := validator.Enrich(context.Background(), rpcblock.Latest, nil, game) + require.NoError(t, err) + require.Equal(t, mockRootClaim, game.ExpectedRootClaim) + require.False(t, game.AgreeWithClaim) + require.NotZero(t, metrics.fetchTime) + }) + + t.Run("OutputMatches_NotSafe", func(t *testing.T) { + validator, client, metrics := setupSuperValidatorTest(t) + client.derivedFromL1BlockNum = 201 + game := &types.EnrichedGameData{ + GameMetadata: challengerTypes.GameMetadata{ + GameType: 999, + }, + L1HeadNum: 200, + L2BlockNumber: 100, + RootClaim: mockRootClaim, + } + err := validator.Enrich(context.Background(), rpcblock.Latest, nil, game) + require.NoError(t, err) + require.Equal(t, mockRootClaim, game.ExpectedRootClaim) + require.False(t, game.AgreeWithClaim) + require.NotZero(t, metrics.fetchTime) + }) + + t.Run("OutputNotFound", func(t *testing.T) { + validator, client, metrics := setupSuperValidatorTest(t) + // The supervisor client automatically translates RPC errors back to ethereum.NotFound for us + client.outputErr = ethereum.NotFound + game := &types.EnrichedGameData{ + GameMetadata: challengerTypes.GameMetadata{ + GameType: 999, + }, + L1HeadNum: 100, + L2BlockNumber: 42984924, + RootClaim: mockRootClaim, + } + err := validator.Enrich(context.Background(), rpcblock.Latest, nil, game) + require.NoError(t, err) + require.Equal(t, common.Hash{}, game.ExpectedRootClaim) + require.False(t, game.AgreeWithClaim) + require.Zero(t, metrics.fetchTime) + }) +} + +func setupSuperValidatorTest(t *testing.T) (*SuperAgreementEnricher, *stubSupervisorClient, *stubOutputMetrics) { + logger := testlog.Logger(t, log.LvlInfo) + client := &stubSupervisorClient{derivedFromL1BlockNum: 0} + metrics := &stubOutputMetrics{} + validator := NewSuperAgreementEnricher(logger, metrics, client, clock.NewDeterministicClock(time.Unix(9824924, 499))) + return validator, client, metrics +} + +type stubSupervisorClient struct { + requestedTimestamp uint64 + outputErr error + derivedFromL1BlockNum uint64 +} + +func (s *stubSupervisorClient) SuperRootAtTimestamp(_ context.Context, timestamp hexutil.Uint64) (eth.SuperRootResponse, error) { + s.requestedTimestamp = uint64(timestamp) + if s.outputErr != nil { + return eth.SuperRootResponse{}, s.outputErr + } + return eth.SuperRootResponse{ + CrossSafeDerivedFrom: eth.BlockID{Number: s.derivedFromL1BlockNum}, + Timestamp: uint64(timestamp), + SuperRoot: eth.Bytes32(mockRootClaim), + Version: eth.SuperRootVersionV1, + }, nil +} diff --git a/op-dispute-mon/mon/service.go b/op-dispute-mon/mon/service.go index c44f082d3a8..817501e3a59 100644 --- a/op-dispute-mon/mon/service.go +++ b/op-dispute-mon/mon/service.go @@ -38,14 +38,15 @@ type Service struct { cl clock.Clock - extractor *extract.Extractor - forecast *Forecast - bonds *bonds.Bonds - game *extract.GameCallerCreator - resolutions *ResolutionMonitor - claims *ClaimMonitor - withdrawals *WithdrawalMonitor - rollupClient *sources.RollupClient + extractor *extract.Extractor + forecast *Forecast + bonds *bonds.Bonds + game *extract.GameCallerCreator + resolutions *ResolutionMonitor + claims *ClaimMonitor + withdrawals *WithdrawalMonitor + rollupClient *sources.RollupClient + supervisorClient *sources.SupervisorClient l1RPC rpcclient.RPC l1Client *sources.L1Client @@ -89,6 +90,9 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error if err := s.initOutputRollupClient(ctx, cfg); err != nil { return fmt.Errorf("failed to init rollup client: %w", err) } + if err := s.initSupervisorClient(ctx, cfg); err != nil { + return fmt.Errorf("failed to init supervisor client: %w", err) + } s.initClaimMonitor(cfg) s.initResolutionMonitor() @@ -139,7 +143,8 @@ func (s *Service) initExtractor(cfg *config.Config) { extract.NewBondEnricher(), extract.NewBalanceEnricher(), extract.NewL1HeadBlockNumEnricher(s.l1Client), - extract.NewAgreementEnricher(s.logger, s.metrics, s.rollupClient), + extract.NewSuperAgreementEnricher(s.logger, s.metrics, s.supervisorClient, clock.SystemClock), + extract.NewOutputAgreementEnricher(s.logger, s.metrics, s.rollupClient, clock.SystemClock), ) } @@ -152,6 +157,9 @@ func (s *Service) initBonds() { } func (s *Service) initOutputRollupClient(ctx context.Context, cfg *config.Config) error { + if cfg.RollupRpc == "" { + return nil + } outputRollupClient, err := dial.DialRollupClientWithTimeout(ctx, dial.DefaultDialTimeout, s.logger, cfg.RollupRpc) if err != nil { return fmt.Errorf("failed to dial rollup client: %w", err) @@ -160,6 +168,18 @@ func (s *Service) initOutputRollupClient(ctx context.Context, cfg *config.Config return nil } +func (s *Service) initSupervisorClient(ctx context.Context, cfg *config.Config) error { + if cfg.SupervisorRpc == "" { + return nil + } + rpcClient, err := dial.DialRPCClientWithTimeout(ctx, dial.DefaultDialTimeout, s.logger, cfg.SupervisorRpc) + if err != nil { + return fmt.Errorf("failed to dial supervisor client: %w", err) + } + s.supervisorClient = sources.NewSupervisorClient(rpcclient.NewBaseRPCClient(rpcClient)) + return nil +} + func (s *Service) initL1Client(ctx context.Context, cfg *config.Config) error { l1RPC, err := dial.DialRPCClientWithTimeout(ctx, dial.DefaultDialTimeout, s.logger, cfg.L1EthRpc) if err != nil { diff --git a/op-dispute-mon/mon/types/types.go b/op-dispute-mon/mon/types/types.go index c1d5b258acf..6cbd31104cb 100644 --- a/op-dispute-mon/mon/types/types.go +++ b/op-dispute-mon/mon/types/types.go @@ -2,6 +2,7 @@ package types import ( "math/big" + "slices" "time" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" @@ -10,6 +11,10 @@ import ( "github.com/ethereum/go-ethereum/common" ) +// outputRootGameTypes lists the set of legacy game types that use output roots +// It is assumed that all other game types use super roots +var outputRootGameTypes = []uint32{0, 1, 2, 3, 6, 254, 255, 1337} + // EnrichedClaim extends the faultTypes.Claim with additional context. type EnrichedClaim struct { faultTypes.Claim @@ -56,6 +61,11 @@ type EnrichedGameData struct { ETHCollateral *big.Int } +// UsesOutputRoots returns true if the game type is one of the known types that use output roots as proposals. +func (g EnrichedGameData) UsesOutputRoots() bool { + return slices.Contains(outputRootGameTypes, g.GameType) +} + // BidirectionalTree is a tree of claims represented as a flat list of claims. // This keeps the tree structure identical to how claims are stored in the contract. type BidirectionalTree struct { diff --git a/op-dispute-mon/mon/types/types_test.go b/op-dispute-mon/mon/types/types_test.go new file mode 100644 index 00000000000..92ecf495ab2 --- /dev/null +++ b/op-dispute-mon/mon/types/types_test.go @@ -0,0 +1,32 @@ +package types + +import ( + "fmt" + "testing" + + "github.com/ethereum-optimism/optimism/op-challenger/game/types" + "github.com/stretchr/testify/require" +) + +func TestEnrichedGameData_UsesOutputRoots(t *testing.T) { + for _, gameType := range outputRootGameTypes { + gameType := gameType + t.Run(fmt.Sprintf("GameType-%v", gameType), func(t *testing.T) { + data := EnrichedGameData{ + GameMetadata: types.GameMetadata{GameType: gameType}, + } + require.True(t, data.UsesOutputRoots()) + }) + } + + nonOutputRootTypes := []uint32{4, 5, 9, 42982, 20013130} + for _, gameType := range nonOutputRootTypes { + gameType := gameType + t.Run(fmt.Sprintf("GameType-%v", gameType), func(t *testing.T) { + data := EnrichedGameData{ + GameMetadata: types.GameMetadata{GameType: gameType}, + } + require.False(t, data.UsesOutputRoots()) + }) + } +} From 989f8e043ddb0436831c180f397ec1f3b3be449c Mon Sep 17 00:00:00 2001 From: Inphi Date: Tue, 4 Mar 2025 20:00:04 -0500 Subject: [PATCH 058/130] contracts: Add solvency invariant test for Super FDG (#14555) * contracts: Add solvency invariant test for Super FDG * lint --- .../invariants/SuperFaultDisputeGame.t.sol | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 packages/contracts-bedrock/test/invariants/SuperFaultDisputeGame.t.sol diff --git a/packages/contracts-bedrock/test/invariants/SuperFaultDisputeGame.t.sol b/packages/contracts-bedrock/test/invariants/SuperFaultDisputeGame.t.sol new file mode 100644 index 00000000000..0b85caa0846 --- /dev/null +++ b/packages/contracts-bedrock/test/invariants/SuperFaultDisputeGame.t.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Testing +import { SuperFaultDisputeGame_Init } from "test/dispute/SuperFaultDisputeGame.t.sol"; +import { RandomClaimActor } from "test/invariants/FaultDisputeGame.t.sol"; + +// Libraries +import "src/dispute/lib/Types.sol"; +import "src/dispute/lib/Errors.sol"; + +contract SuperFaultDisputeGame_Solvency_Invariant is SuperFaultDisputeGame_Init { + Claim internal constant ROOT_CLAIM = Claim.wrap(bytes32(uint256(10))); + Claim internal constant ABSOLUTE_PRESTATE = Claim.wrap(bytes32((uint256(3) << 248) | uint256(0))); + + RandomClaimActor internal actor; + uint256 internal defaultSenderBalance; + + function setUp() public override { + super.setUp(); + super.init({ rootClaim: ROOT_CLAIM, absolutePrestate: ABSOLUTE_PRESTATE, l2BlockNumber: 0x10 }); + + actor = new RandomClaimActor(gameProxy, vm); + + targetContract(address(actor)); + vm.startPrank(address(actor)); + } + + /// @custom:invariant SuperFaultDisputeGame always returns all ETH on total resolution + /// + /// The SuperFaultDisputeGame contract should always return all ETH in the contract to the correct recipients upon + /// resolution of all outstanding claims. There may never be any ETH left in the contract after a full resolution. + function invariant_faultDisputeGame_solvency() public { + vm.warp(block.timestamp + 7 days + 1 seconds); + + (,,, uint256 rootBond,,,) = gameProxy.claimData(0); + + for (uint256 i = gameProxy.claimDataLen(); i > 0; i--) { + (bool success,) = address(gameProxy).call(abi.encodeCall(gameProxy.resolveClaim, (i - 1, 0))); + assertTrue(success); + } + gameProxy.resolve(); + + // Wait for finalization delay + vm.warp(block.timestamp + 3.5 days + 1 seconds); + + // Close the game. + gameProxy.closeGame(); + + // Claim credit once to trigger unlock period. + gameProxy.claimCredit(address(this)); + gameProxy.claimCredit(address(actor)); + + // Wait for the withdrawal delay. + vm.warp(block.timestamp + 7 days + 1 seconds); + + if (gameProxy.credit(address(this)) == 0) { + vm.expectRevert(NoCreditToClaim.selector); + gameProxy.claimCredit(address(this)); + } else { + gameProxy.claimCredit(address(this)); + } + + if (gameProxy.credit(address(actor)) == 0) { + vm.expectRevert(NoCreditToClaim.selector); + gameProxy.claimCredit(address(actor)); + } else { + gameProxy.claimCredit(address(actor)); + } + + if (gameProxy.status() == GameStatus.DEFENDER_WINS) { + assertEq(address(this).balance, type(uint96).max); + assertEq(address(actor).balance, actor.totalBonded() - rootBond); + } else if (gameProxy.status() == GameStatus.CHALLENGER_WINS) { + assertEq(DEFAULT_SENDER.balance, type(uint96).max - rootBond); + assertEq(address(actor).balance, actor.totalBonded() + rootBond); + } else { + revert("SuperFaultDisputeGame_Solvency_Invariant: unreachable"); + } + + assertEq(address(gameProxy).balance, 0); + } +} From 9795c853c4afc069c69611d9017e24aadcd97e66 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Wed, 5 Mar 2025 11:08:20 +1000 Subject: [PATCH 059/130] todo-checker: Fix handling of error codes. (#14611) Now fails if it hits rate limits or other errors instead of silently ignoring. Improves output to show all TODOs for closed issues. --- ops/scripts/todo-checker.sh | 94 ++++++++++++++++++++++++------------- 1 file changed, 62 insertions(+), 32 deletions(-) diff --git a/ops/scripts/todo-checker.sh b/ops/scripts/todo-checker.sh index 6b144ec200c..a437a7b83f4 100755 --- a/ops/scripts/todo-checker.sh +++ b/ops/scripts/todo-checker.sh @@ -22,7 +22,9 @@ REPO="optimism" NOT_FOUND_COUNT=0 MISMATCH_COUNT=0 OPEN_COUNT=0 +CLOSED_COUNT=0 declare -a OPEN_ISSUES +declare -a CLOSED_ISSUES # Colors RED='\033[0;31m' @@ -85,7 +87,7 @@ for todo in $todos; do ISSUE_NUM="${BASH_REMATCH[2]}" else if $FAIL_INVALID_FMT || $VERBOSE; then - echo -e "${YELLOW}[Warning]:${NC} Invalid TODO format: $todo" + echo -e "${YELLOW}[Warning]${NC} Invalid TODO format: $todo" if $FAIL_INVALID_FMT; then exit 1 fi @@ -96,63 +98,91 @@ for todo in $todos; do # Use GitHub API to fetch issue details GH_URL_PATH="$REPO_FULL/issues/$ISSUE_NUM" - RESPONSE=$(curl -sL -H "$AUTH" --request GET "https://api.github.com/repos/$GH_URL_PATH") + # Grab the status code and response as a two item array [response, status] + RESPONSE="[$(curl -sL -w ", %{http_code}" -H "$AUTH" --request GET "https://api.github.com/repos/$GH_URL_PATH")]" + # Split the two values out + STATUS=$(echo "$RESPONSE" | jq -r '.[1]') + RESPONSE=$(echo "$RESPONSE" | jq -r '.[0]') # Check if issue was found - if echo "$RESPONSE" | rg -q "Not Found"; then + if [[ "$STATUS" == "404" ]]; then if [[ $VERBOSE ]]; then - echo -e "${YELLOW}[Warning]:${NC} Issue not found: ${RED}$REPO_FULL/$ISSUE_NUM${NC}" + echo -e "${YELLOW}[Warning]${NC} Issue not found: ${RED}$REPO_FULL/$ISSUE_NUM${NC}" fi ((NOT_FOUND_COUNT++)) continue fi + if [[ "$STATUS" != "200" ]]; then + echo -e "${RED}[Error]${NC} Failed to retrieve issue ${YELLOW}$ISSUE_REFERENCE${NC}" + echo "Status: ${STATUS}" + echo "${RESPONSE}" + exit 1 + fi # Check issue state STATE=$(echo "$RESPONSE" | jq -r .state) - if [[ "$STATE" == "closed" ]] && $CHECK_CLOSED; then - echo -e "${RED}[Error]:${NC} Issue #$ISSUE_NUM is closed. Please remove the TODO in ${GREEN}$FILE:$LINE_NUM${NC} referencing ${YELLOW}$ISSUE_REFERENCE${NC} (${CYAN}https://github.com/$GH_URL_PATH${NC})" - exit 1 + TITLE=$(echo "$RESPONSE" | jq -r .title) + echo -e "${RED}[Error]${NC} Issue #$ISSUE_NUM is closed. Please remove the TODO in ${GREEN}$FILE:$LINE_NUM${NC} referencing ${YELLOW}$ISSUE_REFERENCE${NC} (${CYAN}https://github.com/$GH_URL_PATH${NC})" + ((CLOSED_COUNT++)) + CLOSED_ISSUES+=("$REPO_FULL #$ISSUE_NUM|$TITLE|$FILE:$LINE_NUM") fi if [[ "$STATE" == "open" ]]; then ((OPEN_COUNT++)) TITLE=$(echo "$RESPONSE" | jq -r .title) - OPEN_ISSUES+=("$REPO_FULL/issues/$ISSUE_NUM|$TITLE|$FILE:$LINE_NUM") + OPEN_ISSUES+=("$REPO_FULL #$ISSUE_NUM|$TITLE|$FILE:$LINE_NUM") fi done +function printIssueTitle() { + printf "\n${PURPLE}%-40s${NC} ${GREY}|${NC} ${GREEN}%-65s${NC} ${GREY}|${NC} ${YELLOW}%-40s${NC}\n" "Repository & Issue" "Title" "Location" + echo -e "$GREY$(printf '%0.s-' {1..41})+$(printf '%0.s-' {1..67})+$(printf '%0.s-' {1..51})$NC" +} + +function printIssue() { + issue=${1} + REPO_ISSUE="${issue%%|*}" # up to the first | + REMAINING="${issue#*|}" # after the first | + TITLE="${REMAINING%%|*}" # up to the second | + LOC="${REMAINING#*|}" # after the second | + + # Truncate if necessary + if [ ${#REPO_ISSUE} -gt 37 ]; then + REPO_ISSUE=$(printf "%.37s..." "$REPO_ISSUE") + fi + if [ ${#TITLE} -gt 62 ]; then + TITLE=$(printf "%.62s..." "$TITLE") + fi + # Don't truncate LOC - we always want to be able to find the to do so need the full info here even if it wraps. + + printf "${CYAN}%-40s${NC} ${GREY}|${NC} %-65s ${GREY}|${NC} ${YELLOW}%-50s${NC}\n" "$REPO_ISSUE" "$TITLE" "$LOC" +} + # Print summary if [[ $NOT_FOUND_COUNT -gt 0 ]]; then - echo -e "${YELLOW}[Warning]:${NC} ${CYAN}$NOT_FOUND_COUNT${NC} TODOs referred to issues that were not found." + echo -e "${YELLOW}[Warning]${NC} ${CYAN}$NOT_FOUND_COUNT${NC} TODOs referred to issues that were not found." fi if [[ $MISMATCH_COUNT -gt 0 ]]; then - echo -e "${YELLOW}[Warning]:${NC} ${CYAN}$MISMATCH_COUNT${NC} TODOs did not match the expected pattern. Run with ${RED}\`--verbose\`${NC} to show details." + echo -e "${YELLOW}[Warning]${NC} ${CYAN}$MISMATCH_COUNT${NC} TODOs did not match the expected pattern. Run with ${RED}\`--verbose\`${NC} to show details." fi if [[ $OPEN_COUNT -gt 0 ]]; then - echo -e "${GREEN}[Info]:${NC} ${CYAN}$OPEN_COUNT${NC} TODOs refer to issues that are still open." - echo -e "${GREEN}[Info]:${NC} Open issue details:" - printf "\n${PURPLE}%-50s${NC} ${GREY}|${NC} ${GREEN}%-55s${NC} ${GREY}|${NC} ${YELLOW}%-30s${NC}\n" "Repository & Issue" "Title" "Location" - echo -e "$GREY$(printf '%0.s-' {1..51})+$(printf '%0.s-' {1..57})+$(printf '%0.s-' {1..31})$NC" + echo -e "${GREEN}[Info]${NC} ${CYAN}$OPEN_COUNT${NC} TODOs refer to issues that are still open." + echo -e "${GREEN}[Info]${NC} Open issue details:" + printIssueTitle for issue in "${OPEN_ISSUES[@]}"; do - REPO_ISSUE="https://github.com/${issue%%|*}" # up to the first | - REMAINING="${issue#*|}" # after the first | - TITLE="${REMAINING%%|*}" # up to the second | - LOC="${REMAINING#*|}" # after the second | - - # Truncate if necessary - if [ ${#REPO_ISSUE} -gt 47 ]; then - REPO_ISSUE=$(printf "%.47s..." "$REPO_ISSUE") - fi - if [ ${#TITLE} -gt 47 ]; then - TITLE=$(printf "%.52s..." "$TITLE") - fi - if [ ${#LOC} -gt 27 ]; then - LOC=$(printf "%.24s..." "$LOC") - fi - - printf "${CYAN}%-50s${NC} ${GREY}|${NC} %-55s ${GREY}|${NC} ${YELLOW}%-30s${NC}\n" "$REPO_ISSUE" "$TITLE" "$LOC" + printIssue "${issue}" done + echo fi -echo -e "${GREEN}[Info]:${NC} Done checking issues." +if [[ $CLOSED_COUNT -gt 0 ]]; then + echo -e "${RED}[Error]${NC} ${CYAN}$CLOSED_COUNT${NC} TODOs refer to issues that are closed." + echo -e "${RED}[Error]${NC} Closed issue details:" + printIssueTitle + for issue in "${CLOSED_ISSUES[@]}"; do + printIssue "${issue}" + done + exit 1 +fi +echo -e "${GREEN}[Info]${NC} Done checking issues." From 6553db0f60d5db8c4febdf1e10bd1e40e429ef66 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Wed, 5 Mar 2025 12:21:13 +1000 Subject: [PATCH 060/130] op-e2e: Update dispute game helpers to support SuperSystem and super cannon game types (#14574) * op-e2e: Update dispute game helpers to support SuperSystem and super cannon game types. * Switch back to permissioned cannon as default super system game type until OPCM supports super games Don't deploy super games to pre-interop system * Plural or genesis is actually geneses * Add extra info in top half of super games. * Move interop utils to new file. * Add nil check. --- op-e2e/e2eutils/challenger/helper.go | 53 +- op-e2e/e2eutils/disputegame/cannon_helper.go | 368 ++++++++++ op-e2e/e2eutils/disputegame/claim_helper.go | 4 +- op-e2e/e2eutils/disputegame/helper.go | 94 ++- .../disputegame/output_alphabet_helper.go | 2 +- .../disputegame/output_cannon_helper.go | 289 +------- .../disputegame/output_game_helper.go | 668 +----------------- .../disputegame/output_honest_helper.go | 4 +- .../e2eutils/disputegame/split_game_helper.go | 567 +++++++++++++++ .../disputegame/super_cannon_helper.go | 34 + .../disputegame/super_dispute_system.go | 98 +++ .../e2eutils/disputegame/super_game_helper.go | 48 ++ op-e2e/faultproofs/super_test.go | 20 + op-e2e/faultproofs/util_interop.go | 44 ++ op-e2e/interop/interop_test.go | 6 +- op-e2e/interop/supersystem.go | 57 +- op-e2e/interop/supersystem_l2.go | 10 + op-e2e/system/e2esys/setup.go | 26 + 18 files changed, 1426 insertions(+), 966 deletions(-) create mode 100644 op-e2e/e2eutils/disputegame/cannon_helper.go create mode 100644 op-e2e/e2eutils/disputegame/split_game_helper.go create mode 100644 op-e2e/e2eutils/disputegame/super_cannon_helper.go create mode 100644 op-e2e/e2eutils/disputegame/super_dispute_system.go create mode 100644 op-e2e/e2eutils/disputegame/super_game_helper.go create mode 100644 op-e2e/faultproofs/super_test.go create mode 100644 op-e2e/faultproofs/util_interop.go diff --git a/op-e2e/e2eutils/challenger/helper.go b/op-e2e/e2eutils/challenger/helper.go index f1559127749..c7a7687ec12 100644 --- a/op-e2e/e2eutils/challenger/helper.go +++ b/op-e2e/e2eutils/challenger/helper.go @@ -11,7 +11,6 @@ import ( "testing" "time" - e2econfig "github.com/ethereum-optimism/optimism/op-e2e/config" "github.com/ethereum-optimism/optimism/op-service/crypto" "github.com/ethereum/go-ethereum/ethclient" @@ -32,6 +31,14 @@ import ( "github.com/ethereum-optimism/optimism/op-service/testlog" ) +type PrestateVariant string + +const ( + STCannonVariant PrestateVariant = "" + MTCannonVariant PrestateVariant = "mt64" + InteropVariant PrestateVariant = "interop" +) + type EndpointProvider interface { NodeEndpoint(name string) endpoint.RPC RollupEndpoint(name string) endpoint.RPC @@ -39,9 +46,9 @@ type EndpointProvider interface { } type System interface { - RollupCfg() *rollup.Config - L2Genesis() *core.Genesis - AllocType() e2econfig.AllocType + RollupCfgs() []*rollup.Config + L2Geneses() []*core.Genesis + PrestateVariant() PrestateVariant } type Helper struct { log log.Logger @@ -120,43 +127,47 @@ func FindMonorepoRoot(t *testing.T) string { return "" } -func applyCannonConfig(c *config.Config, t *testing.T, rollupCfg *rollup.Config, l2Genesis *core.Genesis, allocType e2econfig.AllocType) { +func applyCannonConfig(c *config.Config, t *testing.T, rollupCfgs []*rollup.Config, l2Geneses []*core.Genesis, prestateVariant PrestateVariant) { require := require.New(t) root := FindMonorepoRoot(t) c.Cannon.VmBin = root + "cannon/bin/cannon" c.Cannon.Server = root + "op-program/bin/op-program" - if allocType == e2econfig.AllocTypeMTCannon { - t.Log("Using Cannon64 absolute prestate") - c.CannonAbsolutePreState = root + "op-program/bin/prestate-mt64.bin.gz" + t.Logf("Using absolute prestate variant %v", prestateVariant) + if prestateVariant != "" { + c.CannonAbsolutePreState = root + "op-program/bin/prestate-" + string(prestateVariant) + ".bin.gz" } else { c.CannonAbsolutePreState = root + "op-program/bin/prestate.bin.gz" } c.Cannon.SnapshotFreq = 10_000_000 - genesisBytes, err := json.Marshal(l2Genesis) - require.NoError(err, "marshall l2 genesis config") - genesisFile := filepath.Join(c.Datadir, "l2-genesis.json") - require.NoError(os.WriteFile(genesisFile, genesisBytes, 0o644)) - c.Cannon.L2GenesisPaths = []string{genesisFile} - - rollupBytes, err := json.Marshal(rollupCfg) - require.NoError(err, "marshall rollup config") - rollupFile := filepath.Join(c.Datadir, "rollup.json") - require.NoError(os.WriteFile(rollupFile, rollupBytes, 0o644)) - c.Cannon.RollupConfigPaths = []string{rollupFile} + for _, l2Genesis := range l2Geneses { + genesisBytes, err := json.Marshal(l2Genesis) + require.NoError(err, "marshall l2 genesis config") + genesisFile := filepath.Join(c.Datadir, "l2-genesis.json") + require.NoError(os.WriteFile(genesisFile, genesisBytes, 0o644)) + c.Cannon.L2GenesisPaths = append(c.Cannon.L2GenesisPaths, genesisFile) + } + + for _, rollupCfg := range rollupCfgs { + rollupBytes, err := json.Marshal(rollupCfg) + require.NoError(err, "marshall rollup config") + rollupFile := filepath.Join(c.Datadir, "rollup.json") + require.NoError(os.WriteFile(rollupFile, rollupBytes, 0o644)) + c.Cannon.RollupConfigPaths = append(c.Cannon.RollupConfigPaths, rollupFile) + } } func WithCannon(t *testing.T, system System) Option { return func(c *config.Config) { c.TraceTypes = append(c.TraceTypes, types.TraceTypeCannon) - applyCannonConfig(c, t, system.RollupCfg(), system.L2Genesis(), system.AllocType()) + applyCannonConfig(c, t, system.RollupCfgs(), system.L2Geneses(), system.PrestateVariant()) } } func WithPermissioned(t *testing.T, system System) Option { return func(c *config.Config) { c.TraceTypes = append(c.TraceTypes, types.TraceTypePermissioned) - applyCannonConfig(c, t, system.RollupCfg(), system.L2Genesis(), system.AllocType()) + applyCannonConfig(c, t, system.RollupCfgs(), system.L2Geneses(), system.PrestateVariant()) } } diff --git a/op-e2e/e2eutils/disputegame/cannon_helper.go b/op-e2e/e2eutils/disputegame/cannon_helper.go new file mode 100644 index 00000000000..b49c70c5bed --- /dev/null +++ b/op-e2e/e2eutils/disputegame/cannon_helper.go @@ -0,0 +1,368 @@ +package disputegame + +import ( + "context" + "crypto/ecdsa" + "errors" + "io" + "math/big" + "path/filepath" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/preimages" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/outputs" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/split" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types" + "github.com/ethereum-optimism/optimism/op-challenger/metrics" + "github.com/ethereum-optimism/optimism/op-e2e/bindings" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" + preimage "github.com/ethereum-optimism/optimism/op-preimage" + "github.com/ethereum-optimism/optimism/op-service/sources/batching" + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +type CannonHelper struct { + t *testing.T + require *require.Assertions + client *ethclient.Client + privKey *ecdsa.PrivateKey + system DisputeSystem + splitGame *SplitGameHelper + defaultChallengerOptions func() []challenger.Option +} + +func NewCannonHelper(splitGameHelper *SplitGameHelper, defaultChallengerOptions func() []challenger.Option) *CannonHelper { + return &CannonHelper{ + t: splitGameHelper.T, + require: splitGameHelper.Require, + client: splitGameHelper.Client, + privKey: splitGameHelper.PrivKey, + splitGame: splitGameHelper, + system: splitGameHelper.System, + defaultChallengerOptions: defaultChallengerOptions, + } +} + +func (g *CannonHelper) StartChallenger(ctx context.Context, name string, options ...challenger.Option) *challenger.Helper { + opts := g.defaultChallengerOptions() + opts = append(opts, options...) + c := challenger.NewChallenger(g.t, ctx, g.system, name, opts...) + g.t.Cleanup(func() { + _ = c.Close() + }) + return c +} + +// ChallengePeriod returns the challenge period fetched from the PreimageOracle contract. +// The returned uint64 value is the number of seconds for the challenge period. +func (g *CannonHelper) ChallengePeriod(ctx context.Context) uint64 { + oracle := g.oracle(ctx) + period, err := oracle.ChallengePeriod(ctx) + g.require.NoError(err, "Failed to get challenge period") + return period +} + +// WaitForChallengePeriodStart waits for the challenge period to start for a given large preimage claim. +func (g *CannonHelper) WaitForChallengePeriodStart(ctx context.Context, sender common.Address, data *types.PreimageOracleData) { + timedCtx, cancel := context.WithTimeout(ctx, defaultTimeout) + defer cancel() + err := wait.For(timedCtx, time.Second, func() (bool, error) { + ctx, cancel := context.WithTimeout(timedCtx, 30*time.Second) + defer cancel() + timestamp := g.ChallengePeriodStartTime(ctx, sender, data) + g.t.Log("Waiting for challenge period start", "timestamp", timestamp, "key", data.OracleKey, "game", g.splitGame.Addr) + return timestamp > 0, nil + }) + if err != nil { + g.splitGame.LogGameData(ctx) + g.require.NoErrorf(err, "Failed to get challenge start period for preimage data %v", data) + } +} + +// ChallengePeriodStartTime returns the start time of the challenge period for a given large preimage claim. +// If the returned start time is 0, the challenge period has not started. +func (g *CannonHelper) ChallengePeriodStartTime(ctx context.Context, sender common.Address, data *types.PreimageOracleData) uint64 { + oracle := g.oracle(ctx) + uuid := preimages.NewUUID(sender, data) + metadata, err := oracle.GetProposalMetadata(ctx, rpcblock.Latest, keccakTypes.LargePreimageIdent{ + Claimant: sender, + UUID: uuid, + }) + g.require.NoError(err, "Failed to get proposal metadata") + if len(metadata) == 0 { + return 0 + } + return metadata[0].Timestamp +} + +func (g *CannonHelper) WaitForPreimageInOracle(ctx context.Context, data *types.PreimageOracleData) { + timedCtx, cancel := context.WithTimeout(ctx, defaultTimeout) + defer cancel() + oracle := g.oracle(ctx) + err := wait.For(timedCtx, time.Second, func() (bool, error) { + g.t.Logf("Waiting for preimage (%v) to be present in oracle", common.Bytes2Hex(data.OracleKey)) + return oracle.GlobalDataExists(ctx, data) + }) + g.require.NoErrorf(err, "Did not find preimage (%v) in oracle", common.Bytes2Hex(data.OracleKey)) +} + +func (g *CannonHelper) UploadPreimage(ctx context.Context, data *types.PreimageOracleData) { + oracle := g.oracle(ctx) + tx, err := oracle.AddGlobalDataTx(data) + g.require.NoError(err, "Failed to create preimage upload tx") + transactions.RequireSendTx(g.t, ctx, g.client, tx, g.privKey) +} + +func (g *CannonHelper) oracle(ctx context.Context) contracts.PreimageOracleContract { + oracle, err := g.splitGame.Game.GetOracle(ctx) + g.require.NoError(err, "Failed to create oracle contract") + return oracle +} + +type PreimageLoadCheck func(types.TraceProvider, uint64) error + +func (g *CannonHelper) CreateStepLargePreimageLoadCheck(ctx context.Context, sender common.Address) PreimageLoadCheck { + return func(provider types.TraceProvider, targetTraceIndex uint64) error { + // Fetch the challenge period + challengePeriod := g.ChallengePeriod(ctx) + + // Get the preimage data + execDepth := g.splitGame.ExecDepth(ctx) + _, _, preimageData, err := provider.GetStepData(ctx, types.NewPosition(execDepth, big.NewInt(int64(targetTraceIndex)))) + g.require.NoError(err) + + // Wait until the challenge period has started by checking until the challenge + // period start time is not zero by calling the ChallengePeriodStartTime method + g.WaitForChallengePeriodStart(ctx, sender, preimageData) + + challengePeriodStart := g.ChallengePeriodStartTime(ctx, sender, preimageData) + challengePeriodEnd := challengePeriodStart + challengePeriod + + // Time travel past the challenge period. + g.system.AdvanceTime(time.Duration(challengePeriod) * time.Second) + g.require.NoError(wait.ForBlockWithTimestamp(ctx, g.system.NodeClient("l1"), challengePeriodEnd)) + + // Assert that the preimage was indeed loaded by an honest challenger + g.WaitForPreimageInOracle(ctx, preimageData) + return nil + } +} + +func (g *CannonHelper) CreateStepPreimageLoadCheck(ctx context.Context) PreimageLoadCheck { + return func(provider types.TraceProvider, targetTraceIndex uint64) error { + execDepth := g.splitGame.ExecDepth(ctx) + _, _, preimageData, err := provider.GetStepData(ctx, types.NewPosition(execDepth, big.NewInt(int64(targetTraceIndex)))) + g.require.NoError(err) + g.WaitForPreimageInOracle(ctx, preimageData) + return nil + } +} + +// ChallengeToPreimageLoad challenges the supplied execution root claim by inducing a step that requires a preimage to be loaded +// It does this by: +// 1. Identifying the first state transition that loads a global preimage +// 2. Descending the execution game tree to reach the step that loads the preimage +// 3. Asserting that the preimage was indeed loaded by an honest challenger (assuming the preimage is not preloaded) +// This expects an odd execution game depth in order for the honest challenger to step on our leaf claim +func (g *CannonHelper) ChallengeToPreimageLoad(ctx context.Context, outputRootClaim *ClaimHelper, challengerKey *ecdsa.PrivateKey, preimage utils.PreimageOpt, preimageCheck PreimageLoadCheck, preloadPreimage bool) { + // Identifying the first state transition that loads a global preimage + provider, _ := g.createCannonTraceProvider(ctx, "sequencer", outputRootClaim, challenger.WithPrivKey(challengerKey)) + targetTraceIndex, err := provider.FindStep(ctx, 0, preimage) + g.require.NoError(err) + + splitDepth := g.splitGame.SplitDepth(ctx) + execDepth := g.splitGame.ExecDepth(ctx) + g.require.NotEqual(outputRootClaim.Position.TraceIndex(execDepth).Uint64(), targetTraceIndex, "cannot move to defend a terminal trace index") + g.require.EqualValues(splitDepth+1, outputRootClaim.Depth(), "supplied claim must be the root of an execution game") + g.require.EqualValues(execDepth%2, 1, "execution game depth must be odd") // since we're challenging the execution root claim + + if preloadPreimage { + _, _, preimageData, err := provider.GetStepData(ctx, types.NewPosition(execDepth, big.NewInt(int64(targetTraceIndex)))) + g.require.NoError(err) + g.UploadPreimage(ctx, preimageData) + g.WaitForPreimageInOracle(ctx, preimageData) + } + + // Descending the execution game tree to reach the step that loads the preimage + bisectTraceIndex := func(claim *ClaimHelper) *ClaimHelper { + execClaimPosition, err := claim.Position.RelativeToAncestorAtDepth(splitDepth + 1) + g.require.NoError(err) + + claimTraceIndex := execClaimPosition.TraceIndex(execDepth).Uint64() + g.t.Logf("Bisecting: Into targetTraceIndex %v: claimIndex=%v at depth=%v. claimPosition=%v execClaimPosition=%v claimTraceIndex=%v", + targetTraceIndex, claim.Index, claim.Depth(), claim.Position, execClaimPosition, claimTraceIndex) + + // We always want to position ourselves such that the challenger generates proofs for the targetTraceIndex as prestate + if execClaimPosition.Depth() == execDepth-1 { + if execClaimPosition.TraceIndex(execDepth).Uint64() == targetTraceIndex { + newPosition := execClaimPosition.Attack() + correct, err := provider.Get(ctx, newPosition) + g.require.NoError(err) + g.t.Logf("Bisecting: Attack correctly for step at newPosition=%v execIndexAtDepth=%v", newPosition, newPosition.TraceIndex(execDepth)) + return claim.Attack(ctx, correct) + } else if execClaimPosition.TraceIndex(execDepth).Uint64() > targetTraceIndex { + g.t.Logf("Bisecting: Attack incorrectly for step") + return claim.Attack(ctx, common.Hash{0xdd}) + } else if execClaimPosition.TraceIndex(execDepth).Uint64()+1 == targetTraceIndex { + g.t.Logf("Bisecting: Defend incorrectly for step") + return claim.Defend(ctx, common.Hash{0xcc}) + } else { + newPosition := execClaimPosition.Defend() + correct, err := provider.Get(ctx, newPosition) + g.require.NoError(err) + g.t.Logf("Bisecting: Defend correctly for step at newPosition=%v execIndexAtDepth=%v", newPosition, newPosition.TraceIndex(execDepth)) + return claim.Defend(ctx, correct) + } + } + + // Attack or Defend depending on whether the claim we're responding to is to the left or right of the trace index + // Induce the honest challenger to attack or defend depending on whether our new position will be to the left or right of the trace index + if execClaimPosition.TraceIndex(execDepth).Uint64() < targetTraceIndex && claim.Depth() != splitDepth+1 { + newPosition := execClaimPosition.Defend() + if newPosition.TraceIndex(execDepth).Uint64() < targetTraceIndex { + g.t.Logf("Bisecting: Defend correct. newPosition=%v execIndexAtDepth=%v", newPosition, newPosition.TraceIndex(execDepth)) + correct, err := provider.Get(ctx, newPosition) + g.require.NoError(err) + return claim.Defend(ctx, correct) + } else { + g.t.Logf("Bisecting: Defend incorrect. newPosition=%v execIndexAtDepth=%v", newPosition, newPosition.TraceIndex(execDepth)) + return claim.Defend(ctx, common.Hash{0xaa}) + } + } else { + newPosition := execClaimPosition.Attack() + if newPosition.TraceIndex(execDepth).Uint64() < targetTraceIndex { + g.t.Logf("Bisecting: Attack correct. newPosition=%v execIndexAtDepth=%v", newPosition, newPosition.TraceIndex(execDepth)) + correct, err := provider.Get(ctx, newPosition) + g.require.NoError(err) + return claim.Attack(ctx, correct) + } else { + g.t.Logf("Bisecting: Attack incorrect. newPosition=%v execIndexAtDepth=%v", newPosition, newPosition.TraceIndex(execDepth)) + return claim.Attack(ctx, common.Hash{0xbb}) + } + } + } + + g.splitGame.LogGameData(ctx) + // Initial bisect to put us on defense + mover := bisectTraceIndex(outputRootClaim) + leafClaim := g.splitGame.DefendClaim(ctx, mover, bisectTraceIndex, WithoutWaitingForStep()) + + // Validate that the preimage was loaded correctly + g.require.NoError(preimageCheck(provider, targetTraceIndex)) + + // Now the preimage is available wait for the step call to succeed. + leafClaim.WaitForCountered(ctx) + g.splitGame.LogGameData(ctx) +} + +func (g *CannonHelper) VerifyPreimage(ctx context.Context, outputRootClaim *ClaimHelper, preimageKey preimage.Key) { + execDepth := g.splitGame.ExecDepth(ctx) + + // Identifying the first state transition that loads a global preimage + provider, localContext := g.createCannonTraceProvider(ctx, "sequencer", outputRootClaim, challenger.WithPrivKey(TestKey)) + start := uint64(0) + found := false + for offset := uint32(0); ; offset += 4 { + preimageOpt := utils.PreimageLoad(preimageKey, offset) + g.t.Logf("Searching for step with key %x and offset %v", preimageKey.PreimageKey(), offset) + targetTraceIndex, err := provider.FindStep(ctx, start, preimageOpt) + if errors.Is(err, io.EOF) { + // Did not find any more reads + g.require.True(found, "Should have found at least one preimage read") + g.t.Logf("Searching for step with key %x and offset %v did not find another read", preimageKey.PreimageKey(), offset) + return + } + g.require.NoError(err, "Failed to find step that loads requested preimage") + start = targetTraceIndex + found = true + + g.t.Logf("Target trace index: %v", targetTraceIndex) + pos := types.NewPosition(execDepth, new(big.Int).SetUint64(targetTraceIndex)) + g.require.Equal(targetTraceIndex, pos.TraceIndex(execDepth).Uint64()) + + prestate, proof, oracleData, err := provider.GetStepData(ctx, pos) + g.require.NoError(err, "Failed to get step data") + g.require.NotNil(oracleData, "Should have had required preimage oracle data") + g.require.Equal(common.Hash(preimageKey.PreimageKey()).Bytes(), oracleData.OracleKey, "Must have correct preimage key") + + candidate, err := g.splitGame.Game.UpdateOracleTx(ctx, uint64(outputRootClaim.Index), oracleData) + g.require.NoError(err, "failed to get oracle") + transactions.RequireSendTx(g.t, ctx, g.client, candidate, g.privKey) + + expectedPostState, err := provider.Get(ctx, pos) + g.require.NoError(err, "Failed to get expected post state") + + vm, err := g.splitGame.Game.Vm(ctx) + g.require.NoError(err, "Failed to get VM address") + + abi, err := bindings.MIPSMetaData.GetAbi() + g.require.NoError(err, "Failed to load MIPS ABI") + caller := batching.NewMultiCaller(g.client.Client(), batching.DefaultBatchSize) + result, err := caller.SingleCall(ctx, rpcblock.Latest, &batching.ContractCall{ + Abi: abi, + Addr: vm.Addr(), + Method: "step", + Args: []interface{}{ + prestate, proof, localContext, + }, + From: g.splitGame.Addr, + }) + g.require.NoError(err, "Failed to call step") + actualPostState := result.GetBytes32(0) + g.require.Equal(expectedPostState, common.Hash(actualPostState)) + } +} + +func (g *CannonHelper) createCannonTraceProvider(ctx context.Context, l2Node string, outputRootClaim *ClaimHelper, options ...challenger.Option) (*cannon.CannonTraceProviderForTest, common.Hash) { + splitDepth := g.splitGame.SplitDepth(ctx) + g.require.EqualValues(outputRootClaim.Depth(), splitDepth+1, "outputRootClaim must be the root of an execution game") + + logger := testlog.Logger(g.t, log.LevelInfo).New("role", "CannonTraceProvider", "game", g.splitGame.Addr) + opt := g.defaultChallengerOptions() + opt = append(opt, options...) + cfg := challenger.NewChallengerConfig(g.t, g.system, l2Node, opt...) + + l2Client := g.system.NodeClient(l2Node) + + prestateBlock, poststateBlock, err := g.splitGame.Game.GetBlockRange(ctx) + g.require.NoError(err, "Failed to load block range") + rollupClient := g.system.RollupClient(l2Node) + prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock) + l1Head := g.splitGame.GetL1Head(ctx) + outputProvider := outputs.NewTraceProvider(logger, prestateProvider, rollupClient, l2Client, l1Head, splitDepth, prestateBlock, poststateBlock) + + var localContext common.Hash + selector := split.NewSplitProviderSelector(outputProvider, splitDepth, func(ctx context.Context, depth types.Depth, pre types.Claim, post types.Claim) (types.TraceProvider, error) { + agreed, disputed, err := outputs.FetchProposals(ctx, outputProvider, pre, post) + g.require.NoError(err) + g.t.Logf("Using trace between blocks %v and %v\n", agreed.L2BlockNumber, disputed.L2BlockNumber) + localInputs, err := utils.FetchLocalInputsFromProposals(ctx, l1Head.Hash, l2Client, agreed, disputed) + g.require.NoError(err, "Failed to fetch local inputs") + localContext = split.CreateLocalContext(pre, post) + dir := filepath.Join(cfg.Datadir, "cannon-trace") + subdir := filepath.Join(dir, localContext.Hex()) + return cannon.NewTraceProviderForTest(logger, metrics.NoopMetrics.ToTypedVmMetrics(types.TraceTypeCannon.String()), cfg, localInputs, subdir, g.splitGame.MaxDepth(ctx)-splitDepth-1), nil + }) + + claims, err := g.splitGame.Game.GetAllClaims(ctx, rpcblock.Latest) + g.require.NoError(err) + game := types.NewGameState(claims, g.splitGame.MaxDepth(ctx)) + + provider, err := selector(ctx, game, game.Claims()[outputRootClaim.ParentIndex], outputRootClaim.Position) + g.require.NoError(err) + translatingProvider := provider.(*trace.TranslatingProvider) + return translatingProvider.Original().(*cannon.CannonTraceProviderForTest), localContext +} diff --git a/op-e2e/e2eutils/disputegame/claim_helper.go b/op-e2e/e2eutils/disputegame/claim_helper.go index a3abf2b79e7..46b2958c11d 100644 --- a/op-e2e/e2eutils/disputegame/claim_helper.go +++ b/op-e2e/e2eutils/disputegame/claim_helper.go @@ -15,14 +15,14 @@ import ( type ClaimHelper struct { require *require.Assertions - game *OutputGameHelper + game *SplitGameHelper Index int64 ParentIndex int Position types.Position claim common.Hash } -func newClaimHelper(game *OutputGameHelper, idx int64, claim types.Claim) *ClaimHelper { +func newClaimHelper(game *SplitGameHelper, idx int64, claim types.Claim) *ClaimHelper { return &ClaimHelper{ require: game.Require, game: game, diff --git a/op-e2e/e2eutils/disputegame/helper.go b/op-e2e/e2eutils/disputegame/helper.go index 658eedffc51..50d9cd9c4f1 100644 --- a/op-e2e/e2eutils/disputegame/helper.go +++ b/op-e2e/e2eutils/disputegame/helper.go @@ -8,12 +8,10 @@ import ( "testing" "time" - "github.com/ethereum-optimism/optimism/op-e2e/config" - - "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/outputs" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/super" "github.com/ethereum-optimism/optimism/op-e2e/bindings" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/disputegame/preimage" @@ -43,9 +41,11 @@ var ( ) const ( - cannonGameType uint32 = 0 - permissionedGameType uint32 = 1 - alphabetGameType uint32 = 255 + cannonGameType uint32 = 0 + permissionedGameType uint32 = 1 + superCannonGameType uint32 = 4 + superPermissionedGameType uint32 = 5 + alphabetGameType uint32 = 255 ) type GameCfg struct { @@ -75,15 +75,16 @@ func WithFutureProposal() GameOpt { type DisputeSystem interface { L1BeaconEndpoint() endpoint.RestHTTP + SupervisorClient() *sources.SupervisorClient NodeEndpoint(name string) endpoint.RPC NodeClient(name string) *ethclient.Client RollupEndpoint(name string) endpoint.RPC RollupClient(name string) *sources.RollupClient - L1Deployments() *genesis.L1Deployments - RollupCfg() *rollup.Config - L2Genesis() *core.Genesis - AllocType() config.AllocType + DisputeGameFactoryAddr() common.Address + RollupCfgs() []*rollup.Config + L2Geneses() []*core.Genesis + PrestateVariant() challenger.PrestateVariant AdvanceTime(time.Duration) } @@ -97,7 +98,6 @@ type FactoryHelper struct { PrivKey *ecdsa.PrivateKey FactoryAddr common.Address Factory *bindings.DisputeGameFactory - AllocType config.AllocType } type FactoryCfg struct { @@ -118,9 +118,6 @@ func NewFactoryHelper(t *testing.T, ctx context.Context, system DisputeSystem, o chainID, err := client.ChainID(ctx) require.NoError(err) - allocType := system.AllocType() - require.True(allocType.UsesProofs(), "AllocType %v does not support proofs", allocType) - factoryCfg := &FactoryCfg{PrivKey: TestKey} for _, opt := range opts { opt(factoryCfg) @@ -128,8 +125,7 @@ func NewFactoryHelper(t *testing.T, ctx context.Context, system DisputeSystem, o txOpts, err := bind.NewKeyedTransactorWithChainID(factoryCfg.PrivKey, chainID) require.NoError(err) - l1Deployments := system.L1Deployments() - factoryAddr := l1Deployments.DisputeGameFactoryProxy + factoryAddr := system.DisputeGameFactoryAddr() factory, err := bindings.NewDisputeGameFactory(factoryAddr, client) require.NoError(err) @@ -142,7 +138,6 @@ func NewFactoryHelper(t *testing.T, ctx context.Context, system DisputeSystem, o PrivKey: factoryCfg.PrivKey, Factory: factory, FactoryAddr: factoryAddr, - AllocType: allocType, } } @@ -216,9 +211,47 @@ func (h *FactoryHelper) startOutputCannonGameOfType(ctx context.Context, l2Node prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock) provider := outputs.NewTraceProvider(logger, prestateProvider, rollupClient, l2Client, l1Head, splitDepth, prestateBlock, poststateBlock) - return &OutputCannonGameHelper{ - OutputGameHelper: *NewOutputGameHelper(h.T, h.Require, h.Client, h.Opts, h.PrivKey, game, h.FactoryAddr, createdEvent.DisputeProxy, provider, h.System, h.AllocType), - } + return NewOutputCannonGameHelper(h.T, h.Client, h.Opts, h.PrivKey, game, h.FactoryAddr, createdEvent.DisputeProxy, provider, h.System) +} + +func (h *FactoryHelper) StartSuperCannonGame(ctx context.Context, timestamp uint64, rootClaim common.Hash, opts ...GameOpt) *SuperCannonGameHelper { + return h.startSuperCannonGameOfType(ctx, timestamp, rootClaim, superCannonGameType, opts...) +} + +func (h *FactoryHelper) startSuperCannonGameOfType(ctx context.Context, timestamp uint64, rootClaim common.Hash, gameType uint32, opts ...GameOpt) *SuperCannonGameHelper { + cfg := NewGameCfg(opts...) + logger := testlog.Logger(h.T, log.LevelInfo).New("role", "OutputCannonGameHelper") + rootProvider := h.System.SupervisorClient() + + extraData := h.CreateSuperGameExtraData(ctx, rootProvider, timestamp, cfg) + + ctx, cancel := context.WithTimeout(ctx, 1*time.Minute) + defer cancel() + + tx, err := transactions.PadGasEstimate(h.Opts, 2, func(opts *bind.TransactOpts) (*types.Transaction, error) { + return h.Factory.Create(opts, gameType, rootClaim, extraData) + }) + h.Require.NoError(err, "create fault dispute game") + rcpt, err := wait.ForReceiptOK(ctx, h.Client, tx.Hash()) + h.Require.NoError(err, "wait for create fault dispute game receipt to be OK") + h.Require.Len(rcpt.Logs, 2, "should have emitted a single DisputeGameCreated event") + createdEvent, err := h.Factory.ParseDisputeGameCreated(*rcpt.Logs[1]) + h.Require.NoError(err) + game, err := contracts.NewFaultDisputeGameContract(ctx, metrics.NoopContractMetrics, createdEvent.DisputeProxy, batching.NewMultiCaller(h.Client.Client(), batching.DefaultBatchSize)) + h.Require.NoError(err) + + prestateTimestamp, poststateTimestamp, err := game.GetBlockRange(ctx) + h.Require.NoError(err, "Failed to load starting block number") + splitDepth, err := game.GetSplitDepth(ctx) + h.Require.NoError(err, "Failed to load split depth") + l1Head := h.GetL1Head(ctx, game) + + prestateProvider := super.NewSuperRootPrestateProvider(rootProvider, prestateTimestamp) + rollupCfgs, err := super.NewRollupConfigsFromParsed(h.System.RollupCfgs()...) + require.NoError(h.T, err, "failed to create rollup configs") + provider := super.NewSuperTraceProvider(logger, rollupCfgs, prestateProvider, rootProvider, l1Head, splitDepth, prestateTimestamp, poststateTimestamp) + + return NewSuperCannonGameHelper(h.T, h.Client, h.Opts, h.PrivKey, game, h.FactoryAddr, createdEvent.DisputeProxy, provider, h.System) } func (h *FactoryHelper) GetL1Head(ctx context.Context, game contracts.FaultDisputeGameContract) eth.BlockID { @@ -271,7 +304,7 @@ func (h *FactoryHelper) StartOutputAlphabetGame(ctx context.Context, l2Node stri provider := outputs.NewTraceProvider(logger, prestateProvider, rollupClient, l2Client, l1Head, splitDepth, prestateBlock, poststateBlock) return &OutputAlphabetGameHelper{ - OutputGameHelper: *NewOutputGameHelper(h.T, h.Require, h.Client, h.Opts, h.PrivKey, game, h.FactoryAddr, createdEvent.DisputeProxy, provider, h.System, h.AllocType), + OutputGameHelper: *NewOutputGameHelper(h.T, h.Require, h.Client, h.Opts, h.PrivKey, game, h.FactoryAddr, createdEvent.DisputeProxy, provider, h.System), } } @@ -283,6 +316,25 @@ func (h *FactoryHelper) CreateBisectionGameExtraData(l2Node string, l2BlockNumbe return extraData } +func (h *FactoryHelper) CreateSuperGameExtraData(ctx context.Context, supervisor *sources.SupervisorClient, timestamp uint64, cfg *GameCfg) []byte { + if !cfg.allowFuture { + timedCtx, cancel := context.WithTimeout(ctx, time.Minute) + defer cancel() + err := wait.For(timedCtx, time.Second, func() (bool, error) { + status, err := supervisor.SyncStatus(ctx) + if err != nil { + return false, err + } + return status.SafeTimestamp >= timestamp, nil + }) + require.NoError(h.T, err, "Safe head did not reach proposal timestamp") + } + h.T.Logf("Creating game with l2 timestamp: %v", timestamp) + extraData := make([]byte, 32) + binary.BigEndian.PutUint64(extraData[24:], timestamp) + return extraData +} + func (h *FactoryHelper) WaitForBlock(l2Node string, l2BlockNumber uint64, cfg *GameCfg) { if cfg.allowFuture { // Proposing a block that doesn't exist yet, so don't perform any checks diff --git a/op-e2e/e2eutils/disputegame/output_alphabet_helper.go b/op-e2e/e2eutils/disputegame/output_alphabet_helper.go index d2e7ec8fe8f..b9acf24f2fa 100644 --- a/op-e2e/e2eutils/disputegame/output_alphabet_helper.go +++ b/op-e2e/e2eutils/disputegame/output_alphabet_helper.go @@ -44,7 +44,7 @@ func (g *OutputAlphabetGameHelper) CreateHonestActor(ctx context.Context, l2Node prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock) correctTrace, err := outputs.NewOutputAlphabetTraceAccessor(logger, metrics.NoopMetrics, prestateProvider, rollupClient, l2Client, l1Head, splitDepth, prestateBlock, poststateBlock) g.Require.NoError(err, "Create trace accessor") - return NewOutputHonestHelper(g.T, g.Require, &g.OutputGameHelper, g.Game, correctTrace) + return NewOutputHonestHelper(g.T, g.Require, &g.OutputGameHelper.SplitGameHelper, g.Game, correctTrace) } func (g *OutputAlphabetGameHelper) CreateDishonestHelper(ctx context.Context, l2Node string, defender bool) *DishonestHelper { diff --git a/op-e2e/e2eutils/disputegame/output_cannon_helper.go b/op-e2e/e2eutils/disputegame/output_cannon_helper.go index 1ced9717589..533e4f779f4 100644 --- a/op-e2e/e2eutils/disputegame/output_cannon_helper.go +++ b/op-e2e/e2eutils/disputegame/output_cannon_helper.go @@ -3,49 +3,41 @@ package disputegame import ( "context" "crypto/ecdsa" - "errors" - "io" - "math/big" "path/filepath" - "time" + "testing" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/cannon" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/outputs" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/split" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" "github.com/ethereum-optimism/optimism/op-challenger/metrics" - "github.com/ethereum-optimism/optimism/op-e2e/bindings" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" - "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions" - "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" - preimage "github.com/ethereum-optimism/optimism/op-preimage" - "github.com/ethereum-optimism/optimism/op-service/sources/batching" - "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum-optimism/optimism/op-service/testlog" ) type OutputCannonGameHelper struct { OutputGameHelper + CannonHelper } -func (g *OutputCannonGameHelper) StartChallenger(ctx context.Context, name string, options ...challenger.Option) *challenger.Helper { - opts := []challenger.Option{ - challenger.WithCannon(g.T, g.System), - challenger.WithFactoryAddress(g.FactoryAddr), - challenger.WithGameAddress(g.Addr), +func NewOutputCannonGameHelper(t *testing.T, client *ethclient.Client, opts *bind.TransactOpts, key *ecdsa.PrivateKey, game contracts.FaultDisputeGameContract, factoryAddr common.Address, gameAddr common.Address, provider *outputs.OutputTraceProvider, system DisputeSystem) *OutputCannonGameHelper { + outputGameHelper := NewOutputGameHelper(t, require.New(t), client, opts, key, game, factoryAddr, gameAddr, provider, system) + defaultChallengerOptions := func() []challenger.Option { + return []challenger.Option{ + challenger.WithCannon(t, system), + challenger.WithFactoryAddress(factoryAddr), + challenger.WithGameAddress(gameAddr), + } + } + return &OutputCannonGameHelper{ + OutputGameHelper: *outputGameHelper, + CannonHelper: *NewCannonHelper(&outputGameHelper.SplitGameHelper, defaultChallengerOptions), } - opts = append(opts, options...) - c := challenger.NewChallenger(g.T, ctx, g.System, name, opts...) - g.T.Cleanup(func() { - _ = c.Close() - }) - return c } type HonestActorConfig struct { @@ -92,248 +84,5 @@ func (g *OutputCannonGameHelper) CreateHonestActor(ctx context.Context, l2Node s accessor, err := outputs.NewOutputCannonTraceAccessor( logger, metrics.NoopMetrics, cfg.Cannon, vm.NewOpProgramServerExecutor(logger), l2Client, prestateProvider, cfg.CannonAbsolutePreState, rollupClient, dir, l1Head, splitDepth, actorCfg.PrestateBlock, actorCfg.PoststateBlock) g.Require.NoError(err, "Failed to create output cannon trace accessor") - return NewOutputHonestHelper(g.T, g.Require, &g.OutputGameHelper, g.Game, accessor) -} - -type PreimageLoadCheck func(types.TraceProvider, uint64) error - -func (g *OutputCannonGameHelper) CreateStepLargePreimageLoadCheck(ctx context.Context, sender common.Address) PreimageLoadCheck { - return func(provider types.TraceProvider, targetTraceIndex uint64) error { - // Fetch the challenge period - challengePeriod := g.ChallengePeriod(ctx) - - // Get the preimage data - execDepth := g.ExecDepth(ctx) - _, _, preimageData, err := provider.GetStepData(ctx, types.NewPosition(execDepth, big.NewInt(int64(targetTraceIndex)))) - g.Require.NoError(err) - - // Wait until the challenge period has started by checking until the challenge - // period start time is not zero by calling the ChallengePeriodStartTime method - g.WaitForChallengePeriodStart(ctx, sender, preimageData) - - challengePeriodStart := g.ChallengePeriodStartTime(ctx, sender, preimageData) - challengePeriodEnd := challengePeriodStart + challengePeriod - - // Time travel past the challenge period. - g.System.AdvanceTime(time.Duration(challengePeriod) * time.Second) - g.Require.NoError(wait.ForBlockWithTimestamp(ctx, g.System.NodeClient("l1"), challengePeriodEnd)) - - // Assert that the preimage was indeed loaded by an honest challenger - g.WaitForPreimageInOracle(ctx, preimageData) - return nil - } -} - -func (g *OutputCannonGameHelper) CreateStepPreimageLoadCheck(ctx context.Context) PreimageLoadCheck { - return func(provider types.TraceProvider, targetTraceIndex uint64) error { - execDepth := g.ExecDepth(ctx) - _, _, preimageData, err := provider.GetStepData(ctx, types.NewPosition(execDepth, big.NewInt(int64(targetTraceIndex)))) - g.Require.NoError(err) - g.WaitForPreimageInOracle(ctx, preimageData) - return nil - } -} - -// ChallengeToPreimageLoad challenges the supplied execution root claim by inducing a step that requires a preimage to be loaded -// It does this by: -// 1. Identifying the first state transition that loads a global preimage -// 2. Descending the execution game tree to reach the step that loads the preimage -// 3. Asserting that the preimage was indeed loaded by an honest challenger (assuming the preimage is not preloaded) -// This expects an odd execution game depth in order for the honest challenger to step on our leaf claim -func (g *OutputCannonGameHelper) ChallengeToPreimageLoad(ctx context.Context, outputRootClaim *ClaimHelper, challengerKey *ecdsa.PrivateKey, preimage utils.PreimageOpt, preimageCheck PreimageLoadCheck, preloadPreimage bool) { - // Identifying the first state transition that loads a global preimage - provider, _ := g.createCannonTraceProvider(ctx, "sequencer", outputRootClaim, challenger.WithPrivKey(challengerKey)) - targetTraceIndex, err := provider.FindStep(ctx, 0, preimage) - g.Require.NoError(err) - - splitDepth := g.SplitDepth(ctx) - execDepth := g.ExecDepth(ctx) - g.Require.NotEqual(outputRootClaim.Position.TraceIndex(execDepth).Uint64(), targetTraceIndex, "cannot move to defend a terminal trace index") - g.Require.EqualValues(splitDepth+1, outputRootClaim.Depth(), "supplied claim must be the root of an execution game") - g.Require.EqualValues(execDepth%2, 1, "execution game depth must be odd") // since we're challenging the execution root claim - - if preloadPreimage { - _, _, preimageData, err := provider.GetStepData(ctx, types.NewPosition(execDepth, big.NewInt(int64(targetTraceIndex)))) - g.Require.NoError(err) - g.UploadPreimage(ctx, preimageData) - g.WaitForPreimageInOracle(ctx, preimageData) - } - - // Descending the execution game tree to reach the step that loads the preimage - bisectTraceIndex := func(claim *ClaimHelper) *ClaimHelper { - execClaimPosition, err := claim.Position.RelativeToAncestorAtDepth(splitDepth + 1) - g.Require.NoError(err) - - claimTraceIndex := execClaimPosition.TraceIndex(execDepth).Uint64() - g.T.Logf("Bisecting: Into targetTraceIndex %v: claimIndex=%v at depth=%v. claimPosition=%v execClaimPosition=%v claimTraceIndex=%v", - targetTraceIndex, claim.Index, claim.Depth(), claim.Position, execClaimPosition, claimTraceIndex) - - // We always want to position ourselves such that the challenger generates proofs for the targetTraceIndex as prestate - if execClaimPosition.Depth() == execDepth-1 { - if execClaimPosition.TraceIndex(execDepth).Uint64() == targetTraceIndex { - newPosition := execClaimPosition.Attack() - correct, err := provider.Get(ctx, newPosition) - g.Require.NoError(err) - g.T.Logf("Bisecting: Attack correctly for step at newPosition=%v execIndexAtDepth=%v", newPosition, newPosition.TraceIndex(execDepth)) - return claim.Attack(ctx, correct) - } else if execClaimPosition.TraceIndex(execDepth).Uint64() > targetTraceIndex { - g.T.Logf("Bisecting: Attack incorrectly for step") - return claim.Attack(ctx, common.Hash{0xdd}) - } else if execClaimPosition.TraceIndex(execDepth).Uint64()+1 == targetTraceIndex { - g.T.Logf("Bisecting: Defend incorrectly for step") - return claim.Defend(ctx, common.Hash{0xcc}) - } else { - newPosition := execClaimPosition.Defend() - correct, err := provider.Get(ctx, newPosition) - g.Require.NoError(err) - g.T.Logf("Bisecting: Defend correctly for step at newPosition=%v execIndexAtDepth=%v", newPosition, newPosition.TraceIndex(execDepth)) - return claim.Defend(ctx, correct) - } - } - - // Attack or Defend depending on whether the claim we're responding to is to the left or right of the trace index - // Induce the honest challenger to attack or defend depending on whether our new position will be to the left or right of the trace index - if execClaimPosition.TraceIndex(execDepth).Uint64() < targetTraceIndex && claim.Depth() != splitDepth+1 { - newPosition := execClaimPosition.Defend() - if newPosition.TraceIndex(execDepth).Uint64() < targetTraceIndex { - g.T.Logf("Bisecting: Defend correct. newPosition=%v execIndexAtDepth=%v", newPosition, newPosition.TraceIndex(execDepth)) - correct, err := provider.Get(ctx, newPosition) - g.Require.NoError(err) - return claim.Defend(ctx, correct) - } else { - g.T.Logf("Bisecting: Defend incorrect. newPosition=%v execIndexAtDepth=%v", newPosition, newPosition.TraceIndex(execDepth)) - return claim.Defend(ctx, common.Hash{0xaa}) - } - } else { - newPosition := execClaimPosition.Attack() - if newPosition.TraceIndex(execDepth).Uint64() < targetTraceIndex { - g.T.Logf("Bisecting: Attack correct. newPosition=%v execIndexAtDepth=%v", newPosition, newPosition.TraceIndex(execDepth)) - correct, err := provider.Get(ctx, newPosition) - g.Require.NoError(err) - return claim.Attack(ctx, correct) - } else { - g.T.Logf("Bisecting: Attack incorrect. newPosition=%v execIndexAtDepth=%v", newPosition, newPosition.TraceIndex(execDepth)) - return claim.Attack(ctx, common.Hash{0xbb}) - } - } - } - - g.LogGameData(ctx) - // Initial bisect to put us on defense - mover := bisectTraceIndex(outputRootClaim) - leafClaim := g.DefendClaim(ctx, mover, bisectTraceIndex, WithoutWaitingForStep()) - - // Validate that the preimage was loaded correctly - g.Require.NoError(preimageCheck(provider, targetTraceIndex)) - - // Now the preimage is available wait for the step call to succeed. - leafClaim.WaitForCountered(ctx) - g.LogGameData(ctx) -} - -func (g *OutputCannonGameHelper) VerifyPreimage(ctx context.Context, outputRootClaim *ClaimHelper, preimageKey preimage.Key) { - execDepth := g.ExecDepth(ctx) - - // Identifying the first state transition that loads a global preimage - provider, localContext := g.createCannonTraceProvider(ctx, "sequencer", outputRootClaim, challenger.WithPrivKey(TestKey)) - start := uint64(0) - found := false - for offset := uint32(0); ; offset += 4 { - preimageOpt := utils.PreimageLoad(preimageKey, offset) - g.T.Logf("Searching for step with key %x and offset %v", preimageKey.PreimageKey(), offset) - targetTraceIndex, err := provider.FindStep(ctx, start, preimageOpt) - if errors.Is(err, io.EOF) { - // Did not find any more reads - g.Require.True(found, "Should have found at least one preimage read") - g.T.Logf("Searching for step with key %x and offset %v did not find another read", preimageKey.PreimageKey(), offset) - return - } - g.Require.NoError(err, "Failed to find step that loads requested preimage") - start = targetTraceIndex - found = true - - g.T.Logf("Target trace index: %v", targetTraceIndex) - pos := types.NewPosition(execDepth, new(big.Int).SetUint64(targetTraceIndex)) - g.Require.Equal(targetTraceIndex, pos.TraceIndex(execDepth).Uint64()) - - prestate, proof, oracleData, err := provider.GetStepData(ctx, pos) - g.Require.NoError(err, "Failed to get step data") - g.Require.NotNil(oracleData, "Should have had required preimage oracle data") - g.Require.Equal(common.Hash(preimageKey.PreimageKey()).Bytes(), oracleData.OracleKey, "Must have correct preimage key") - - candidate, err := g.Game.UpdateOracleTx(ctx, uint64(outputRootClaim.Index), oracleData) - g.Require.NoError(err, "failed to get oracle") - transactions.RequireSendTx(g.T, ctx, g.Client, candidate, g.PrivKey) - - expectedPostState, err := provider.Get(ctx, pos) - g.Require.NoError(err, "Failed to get expected post state") - - vm, err := g.Game.Vm(ctx) - g.Require.NoError(err, "Failed to get VM address") - - abi, err := bindings.MIPSMetaData.GetAbi() - g.Require.NoError(err, "Failed to load MIPS ABI") - caller := batching.NewMultiCaller(g.Client.Client(), batching.DefaultBatchSize) - result, err := caller.SingleCall(ctx, rpcblock.Latest, &batching.ContractCall{ - Abi: abi, - Addr: vm.Addr(), - Method: "step", - Args: []interface{}{ - prestate, proof, localContext, - }, - From: g.Addr, - }) - g.Require.NoError(err, "Failed to call step") - actualPostState := result.GetBytes32(0) - g.Require.Equal(expectedPostState, common.Hash(actualPostState)) - } -} - -func (g *OutputCannonGameHelper) createCannonTraceProvider(ctx context.Context, l2Node string, outputRootClaim *ClaimHelper, options ...challenger.Option) (*cannon.CannonTraceProviderForTest, common.Hash) { - splitDepth := g.SplitDepth(ctx) - g.Require.EqualValues(outputRootClaim.Depth(), splitDepth+1, "outputRootClaim must be the root of an execution game") - - logger := testlog.Logger(g.T, log.LevelInfo).New("role", "CannonTraceProvider", "game", g.Addr) - opt := g.defaultChallengerOptions() - opt = append(opt, options...) - cfg := challenger.NewChallengerConfig(g.T, g.System, l2Node, opt...) - - l2Client := g.System.NodeClient(l2Node) - - prestateBlock, poststateBlock, err := g.Game.GetBlockRange(ctx) - g.Require.NoError(err, "Failed to load block range") - rollupClient := g.System.RollupClient(l2Node) - prestateProvider := outputs.NewPrestateProvider(rollupClient, prestateBlock) - l1Head := g.GetL1Head(ctx) - outputProvider := outputs.NewTraceProvider(logger, prestateProvider, rollupClient, l2Client, l1Head, splitDepth, prestateBlock, poststateBlock) - - var localContext common.Hash - selector := split.NewSplitProviderSelector(outputProvider, splitDepth, func(ctx context.Context, depth types.Depth, pre types.Claim, post types.Claim) (types.TraceProvider, error) { - agreed, disputed, err := outputs.FetchProposals(ctx, outputProvider, pre, post) - g.Require.NoError(err) - g.T.Logf("Using trace between blocks %v and %v\n", agreed.L2BlockNumber, disputed.L2BlockNumber) - localInputs, err := utils.FetchLocalInputsFromProposals(ctx, l1Head.Hash, l2Client, agreed, disputed) - g.Require.NoError(err, "Failed to fetch local inputs") - localContext = split.CreateLocalContext(pre, post) - dir := filepath.Join(cfg.Datadir, "cannon-trace") - subdir := filepath.Join(dir, localContext.Hex()) - return cannon.NewTraceProviderForTest(logger, metrics.NoopMetrics.ToTypedVmMetrics(types.TraceTypeCannon.String()), cfg, localInputs, subdir, g.MaxDepth(ctx)-splitDepth-1), nil - }) - - claims, err := g.Game.GetAllClaims(ctx, rpcblock.Latest) - g.Require.NoError(err) - game := types.NewGameState(claims, g.MaxDepth(ctx)) - - provider, err := selector(ctx, game, game.Claims()[outputRootClaim.ParentIndex], outputRootClaim.Position) - g.Require.NoError(err) - translatingProvider := provider.(*trace.TranslatingProvider) - return translatingProvider.Original().(*cannon.CannonTraceProviderForTest), localContext -} - -func (g *OutputCannonGameHelper) defaultChallengerOptions() []challenger.Option { - return []challenger.Option{ - challenger.WithCannon(g.T, g.System), - challenger.WithFactoryAddress(g.FactoryAddr), - challenger.WithGameAddress(g.Addr), - } + return NewOutputHonestHelper(g.T, g.Require, &g.OutputGameHelper.SplitGameHelper, g.Game, accessor) } diff --git a/op-e2e/e2eutils/disputegame/output_game_helper.go b/op-e2e/e2eutils/disputegame/output_game_helper.go index 090d1c8fdb2..a969c593094 100644 --- a/op-e2e/e2eutils/disputegame/output_game_helper.go +++ b/op-e2e/e2eutils/disputegame/output_game_helper.go @@ -5,104 +5,54 @@ import ( "crypto/ecdsa" "fmt" "math/big" - "strings" "testing" "time" - "github.com/ethereum-optimism/optimism/op-e2e/config" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" - "github.com/ethereum-optimism/optimism/op-challenger/game/fault/preimages" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/outputs" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" - keccakTypes "github.com/ethereum-optimism/optimism/op-challenger/game/keccak/types" - gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" - "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" - "github.com/ethereum-optimism/optimism/op-service/errutil" - "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - gethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/stretchr/testify/require" ) -const defaultTimeout = 5 * time.Minute - type OutputGameHelper struct { - T *testing.T - Require *require.Assertions - Client *ethclient.Client - Opts *bind.TransactOpts - PrivKey *ecdsa.PrivateKey - Game contracts.FaultDisputeGameContract - FactoryAddr common.Address - Addr common.Address - CorrectOutputProvider *outputs.OutputTraceProvider - System DisputeSystem + SplitGameHelper + ClaimedBlockNumber func(pos types.Position) (uint64, error) } func NewOutputGameHelper(t *testing.T, require *require.Assertions, client *ethclient.Client, opts *bind.TransactOpts, privKey *ecdsa.PrivateKey, - game contracts.FaultDisputeGameContract, factoryAddr common.Address, addr common.Address, correctOutputProvider *outputs.OutputTraceProvider, system DisputeSystem, allocType config.AllocType) *OutputGameHelper { + game contracts.FaultDisputeGameContract, factoryAddr common.Address, addr common.Address, correctOutputProvider *outputs.OutputTraceProvider, system DisputeSystem) *OutputGameHelper { return &OutputGameHelper{ - T: t, - Require: require, - Client: client, - Opts: opts, - PrivKey: privKey, - Game: game, - FactoryAddr: factoryAddr, - Addr: addr, - CorrectOutputProvider: correctOutputProvider, - System: system, + SplitGameHelper: SplitGameHelper{ + T: t, + Require: require, + Client: client, + Opts: opts, + PrivKey: privKey, + Game: game, + FactoryAddr: factoryAddr, + Addr: addr, + CorrectOutputProvider: correctOutputProvider, + System: system, + DescribePosition: func(pos types.Position, splitDepth types.Depth) string { + if pos.Depth() > splitDepth { + return "" + } + blockNum, err := correctOutputProvider.ClaimedBlockNumber(pos) + if err != nil { + return "" + } + return fmt.Sprintf("Block num: %v", blockNum) + }, + }, + ClaimedBlockNumber: correctOutputProvider.ClaimedBlockNumber, } } -type moveCfg struct { - Opts *bind.TransactOpts - ignoreDupes bool -} - -type MoveOpt interface { - Apply(cfg *moveCfg) -} - -type moveOptFn func(c *moveCfg) - -func (f moveOptFn) Apply(c *moveCfg) { - f(c) -} - -func WithTransactOpts(Opts *bind.TransactOpts) MoveOpt { - return moveOptFn(func(c *moveCfg) { - c.Opts = Opts - }) -} - -func WithIgnoreDuplicates() MoveOpt { - return moveOptFn(func(c *moveCfg) { - c.ignoreDupes = true - }) -} - -func (g *OutputGameHelper) SplitDepth(ctx context.Context) types.Depth { - splitDepth, err := g.Game.GetSplitDepth(ctx) - g.Require.NoError(err, "failed to load split depth") - return splitDepth -} - -func (g *OutputGameHelper) ExecDepth(ctx context.Context) types.Depth { - return g.MaxDepth(ctx) - g.SplitDepth(ctx) - 1 -} - -func (g *OutputGameHelper) L2BlockNum(ctx context.Context) uint64 { - _, blockNum, err := g.Game.GetBlockRange(ctx) - g.Require.NoError(err, "failed to load l2 block number") - return blockNum -} - func (g *OutputGameHelper) StartingBlockNum(ctx context.Context) uint64 { blockNum, _, err := g.Game.GetBlockRange(ctx) g.Require.NoError(err, "failed to load starting block number") @@ -118,7 +68,7 @@ func (g *OutputGameHelper) DisputeLastBlock(ctx context.Context) *ClaimHelper { // to execute cannon on. ie the first block the honest and dishonest actors disagree about is the l2 block of the game. func (g *OutputGameHelper) DisputeBlock(ctx context.Context, disputeBlockNum uint64) *ClaimHelper { dishonestValue := g.GetClaimValue(ctx, 0) - correctRootClaim := g.correctOutputRoot(ctx, types.NewPositionFromGIndex(big.NewInt(1))) + correctRootClaim := g.correctClaimValue(ctx, types.NewPositionFromGIndex(big.NewInt(1))) rootIsValid := dishonestValue == correctRootClaim if rootIsValid { // Ensure that the dishonest actor is actually posting invalid roots. @@ -127,25 +77,25 @@ func (g *OutputGameHelper) DisputeBlock(ctx context.Context, disputeBlockNum uin } pos := types.NewPositionFromGIndex(big.NewInt(1)) getClaimValue := func(parentClaim *ClaimHelper, claimPos types.Position) common.Hash { - claimBlockNum, err := g.CorrectOutputProvider.ClaimedBlockNumber(claimPos) + claimBlockNum, err := g.ClaimedBlockNumber(claimPos) g.Require.NoError(err, "failed to calculate claim block number") if claimBlockNum < disputeBlockNum { // Use the correct output root for all claims prior to the dispute block number // This pushes the game to dispute the last block in the range - return g.correctOutputRoot(ctx, claimPos) + return g.correctClaimValue(ctx, claimPos) } if rootIsValid == parentClaim.AgreesWithOutputRoot() { // We are responding to a parent claim that agrees with a valid root, so we're being dishonest return dishonestValue } else { // Otherwise we must be the honest actor so use the correct root - return g.correctOutputRoot(ctx, claimPos) + return g.correctClaimValue(ctx, claimPos) } } claim := g.RootClaim(ctx) for !claim.IsOutputRootLeaf(ctx) { - parentClaimBlockNum, err := g.CorrectOutputProvider.ClaimedBlockNumber(pos) + parentClaimBlockNum, err := g.ClaimedBlockNumber(pos) g.Require.NoError(err, "failed to calculate parent claim block number") if parentClaimBlockNum >= disputeBlockNum { pos = pos.Attack() @@ -158,285 +108,6 @@ func (g *OutputGameHelper) DisputeBlock(ctx context.Context, disputeBlockNum uin return claim } -func (g *OutputGameHelper) RootClaim(ctx context.Context) *ClaimHelper { - claim := g.getClaim(ctx, 0) - return newClaimHelper(g, 0, claim) -} - -func (g *OutputGameHelper) WaitForCorrectOutputRoot(ctx context.Context, claimIdx int64) { - g.WaitForClaimCount(ctx, claimIdx+1) - claim := g.getClaim(ctx, claimIdx) - output := g.correctOutputRoot(ctx, claim.Position) - g.Require.EqualValuesf(output, claim.Value, "Incorrect output root at claim %v at position %v", claimIdx, claim.Position.ToGIndex().Uint64()) -} - -func (g *OutputGameHelper) correctOutputRoot(ctx context.Context, pos types.Position) common.Hash { - outputRoot, err := g.CorrectOutputProvider.Get(ctx, pos) - g.Require.NoErrorf(err, "Failed to get correct output for position %v", pos) - return outputRoot -} - -func (g *OutputGameHelper) MaxClockDuration(ctx context.Context) time.Duration { - duration, err := g.Game.GetMaxClockDuration(ctx) - g.Require.NoError(err, "failed to get max clock duration") - return duration -} - -func (g *OutputGameHelper) WaitForNoAvailableCredit(ctx context.Context, addr common.Address) { - timedCtx, cancel := context.WithTimeout(ctx, defaultTimeout) - defer cancel() - err := wait.For(timedCtx, time.Second, func() (bool, error) { - bal, _, err := g.Game.GetCredit(timedCtx, addr) - if err != nil { - return false, err - } - g.T.Log("Waiting for zero available credit", "current", bal, "addr", addr) - return bal.Cmp(big.NewInt(0)) == 0, nil - }) - if err != nil { - g.LogGameData(ctx) - g.Require.NoError(err, "Failed to wait for zero available credit") - } -} - -func (g *OutputGameHelper) AvailableCredit(ctx context.Context, addr common.Address) *big.Int { - credit, _, err := g.Game.GetCredit(ctx, addr) - g.Require.NoErrorf(err, "Failed to fetch available credit for %v", addr) - return credit -} - -func (g *OutputGameHelper) CreditUnlockDuration(ctx context.Context) time.Duration { - _, delay, _, err := g.Game.GetBalanceAndDelay(ctx, rpcblock.Latest) - g.Require.NoError(err, "Failed to get withdrawal delay") - return delay -} - -func (g *OutputGameHelper) WethBalance(ctx context.Context, addr common.Address) *big.Int { - balance, _, _, err := g.Game.GetBalanceAndDelay(ctx, rpcblock.Latest) - g.Require.NoError(err, "Failed to get WETH balance") - return balance -} - -// WaitForClaimCount waits until there are at least count claims in the game. -// This does not check that the number of claims is exactly the specified count to avoid intermittent failures -// where a challenger posts an additional claim before this method sees the number of claims it was waiting for. -func (g *OutputGameHelper) WaitForClaimCount(ctx context.Context, count int64) { - timedCtx, cancel := context.WithTimeout(ctx, defaultTimeout) - defer cancel() - err := wait.For(timedCtx, time.Second, func() (bool, error) { - actual, err := g.Game.GetClaimCount(timedCtx) - if err != nil { - return false, err - } - g.T.Log("Waiting for claim count", "current", actual, "expected", count, "game", g.Addr) - return int64(actual) >= count, nil - }) - if err != nil { - g.LogGameData(ctx) - g.Require.NoErrorf(err, "Did not find expected claim count %v", count) - } -} - -type ContractClaim struct { - ParentIndex uint32 - CounteredBy common.Address - Claimant common.Address - Bond *big.Int - Claim [32]byte - Position *big.Int - Clock *big.Int -} - -func (g *OutputGameHelper) MaxDepth(ctx context.Context) types.Depth { - depth, err := g.Game.GetMaxGameDepth(ctx) - g.Require.NoError(err, "Failed to load game depth") - return depth -} - -func (g *OutputGameHelper) waitForClaim(ctx context.Context, timeout time.Duration, errorMsg string, predicate func(claimIdx int64, claim types.Claim) bool) (int64, types.Claim) { - timedCtx, cancel := context.WithTimeout(ctx, timeout) - defer cancel() - var matchedClaim types.Claim - var matchClaimIdx int64 - err := wait.For(timedCtx, time.Second, func() (bool, error) { - claims, err := g.Game.GetAllClaims(ctx, rpcblock.Latest) - if err != nil { - return false, fmt.Errorf("retrieve all claims: %w", err) - } - // Search backwards because the new claims are at the end and more likely the ones we want. - for i := len(claims) - 1; i >= 0; i-- { - claim := claims[i] - if predicate(int64(i), claim) { - matchClaimIdx = int64(i) - matchedClaim = claim - return true, nil - } - } - return false, nil - }) - if err != nil { // Avoid waiting time capturing game data when there's no error - g.Require.NoErrorf(err, "%v\n%v", errorMsg, g.GameData(ctx)) - } - return matchClaimIdx, matchedClaim -} - -func (g *OutputGameHelper) waitForNoClaim(ctx context.Context, errorMsg string, predicate func(claim types.Claim) bool) { - timedCtx, cancel := context.WithTimeout(ctx, defaultTimeout) - defer cancel() - err := wait.For(timedCtx, time.Second, func() (bool, error) { - claims, err := g.Game.GetAllClaims(ctx, rpcblock.Latest) - if err != nil { - return false, fmt.Errorf("retrieve all claims: %w", err) - } - // Search backwards because the new claims are at the end and more likely the ones we want. - for i := len(claims) - 1; i >= 0; i-- { - claim := claims[i] - if predicate(claim) { - return false, nil - } - } - return true, nil - }) - if err != nil { // Avoid waiting time capturing game data when there's no error - g.Require.NoErrorf(err, "%v\n%v", errorMsg, g.GameData(ctx)) - } -} - -func (g *OutputGameHelper) GetClaimValue(ctx context.Context, claimIdx int64) common.Hash { - g.WaitForClaimCount(ctx, claimIdx+1) - claim := g.getClaim(ctx, claimIdx) - return claim.Value -} - -func (g *OutputGameHelper) getAllClaims(ctx context.Context) []types.Claim { - claims, err := g.Game.GetAllClaims(ctx, rpcblock.Latest) - g.Require.NoError(err, "Failed to get all claims") - return claims -} - -// getClaim retrieves the claim data for a specific index. -// Note that it is deliberately not exported as tests should use WaitForClaim to avoid race conditions. -func (g *OutputGameHelper) getClaim(ctx context.Context, claimIdx int64) types.Claim { - claimData, err := g.Game.GetClaim(ctx, uint64(claimIdx)) - if err != nil { - g.Require.NoErrorf(err, "retrieve claim %v", claimIdx) - } - return claimData -} - -func (g *OutputGameHelper) WaitForClaimAtDepth(ctx context.Context, depth types.Depth) { - g.waitForClaim( - ctx, - defaultTimeout, - fmt.Sprintf("Could not find claim depth %v", depth), - func(_ int64, claim types.Claim) bool { - return claim.Depth() == depth - }) -} - -func (g *OutputGameHelper) WaitForClaimAtMaxDepth(ctx context.Context, countered bool) { - maxDepth := g.MaxDepth(ctx) - g.waitForClaim( - ctx, - defaultTimeout, - fmt.Sprintf("Could not find claim depth %v with countered=%v", maxDepth, countered), - func(_ int64, claim types.Claim) bool { - return claim.Depth() == maxDepth && (claim.CounteredBy != common.Address{}) == countered - }) -} - -func (g *OutputGameHelper) WaitForAllClaimsCountered(ctx context.Context) { - g.waitForNoClaim( - ctx, - "Did not find all claims countered", - func(claim types.Claim) bool { - return claim.CounteredBy == common.Address{} - }) -} - -func (g *OutputGameHelper) Resolve(ctx context.Context) { - ctx, cancel := context.WithTimeout(ctx, time.Minute) - defer cancel() - candidate, err := g.Game.ResolveTx() - g.Require.NoError(err) - transactions.RequireSendTx(g.T, ctx, g.Client, candidate, g.PrivKey) -} - -func (g *OutputGameHelper) Status(ctx context.Context) gameTypes.GameStatus { - status, err := g.Game.GetStatus(ctx) - g.Require.NoError(err) - return status -} - -func (g *OutputGameHelper) WaitForBondModeDecided(ctx context.Context) { - g.T.Logf("Waiting for game %v to have bond mode set", g.Addr) - timedCtx, cancel := context.WithTimeout(ctx, defaultTimeout) - defer cancel() - err := wait.For(timedCtx, time.Second, func() (bool, error) { - bondMode, err := g.Game.GetBondDistributionMode(ctx, rpcblock.Latest) - g.Require.NoError(err) - return bondMode != types.UndecidedDistributionMode, nil - }) - g.Require.NoError(err, "Failed to wait for bond mode to be set") -} - -func (g *OutputGameHelper) WaitForGameStatus(ctx context.Context, expected gameTypes.GameStatus) { - g.T.Logf("Waiting for game %v to have status %v", g.Addr, expected) - timedCtx, cancel := context.WithTimeout(ctx, defaultTimeout) - defer cancel() - err := wait.For(timedCtx, time.Second, func() (bool, error) { - ctx, cancel := context.WithTimeout(timedCtx, 30*time.Second) - defer cancel() - status, err := g.Game.GetStatus(ctx) - if err != nil { - return false, fmt.Errorf("game status unavailable: %w", err) - } - g.T.Logf("Game %v has state %v, waiting for state %v", g.Addr, status, expected) - return expected == status, nil - }) - g.Require.NoErrorf(err, "wait for Game status. Game state: \n%v", g.GameData(ctx)) -} - -func (g *OutputGameHelper) WaitForInactivity(ctx context.Context, numInactiveBlocks int, untilGameEnds bool) { - g.T.Logf("Waiting for game %v to have no activity for %v blocks", g.Addr, numInactiveBlocks) - headCh := make(chan *gethtypes.Header, 100) - headSub, err := g.Client.SubscribeNewHead(ctx, headCh) - g.Require.NoError(err) - defer headSub.Unsubscribe() - - var lastActiveBlock uint64 - for { - if untilGameEnds && g.Status(ctx) != gameTypes.GameStatusInProgress { - break - } - select { - case head := <-headCh: - if lastActiveBlock == 0 { - lastActiveBlock = head.Number.Uint64() - continue - } else if lastActiveBlock+uint64(numInactiveBlocks) < head.Number.Uint64() { - return - } - block, err := g.Client.BlockByNumber(ctx, head.Number) - g.Require.NoError(err) - numActions := 0 - for _, tx := range block.Transactions() { - if tx.To().Hex() == g.Addr.Hex() { - numActions++ - } - } - if numActions != 0 { - g.T.Logf("Game %v has %v actions in block %d. Resetting inactivity timeout", g.Addr, numActions, block.NumberU64()) - lastActiveBlock = head.Number.Uint64() - } - case err := <-headSub.Err(): - g.Require.NoError(err) - case <-ctx.Done(): - g.Require.Fail("Context canceled", ctx.Err()) - } - } -} - func (g *OutputGameHelper) WaitForL2BlockNumberChallenged(ctx context.Context) { g.T.Logf("Waiting for game %v to have L2 block number challenged", g.Addr) timedCtx, cancel := context.WithTimeout(ctx, 30*time.Second) @@ -446,280 +117,3 @@ func (g *OutputGameHelper) WaitForL2BlockNumberChallenged(ctx context.Context) { }) g.Require.NoError(err, "L2 block number was not challenged in time") } - -// Mover is a function that either attacks or defends the claim at parentClaimIdx -type Mover func(parent *ClaimHelper) *ClaimHelper - -// Stepper is a function that attempts to perform a step against the claim at parentClaimIdx -type Stepper func(parentClaimIdx int64) - -type defendClaimCfg struct { - skipWaitingForStep bool -} - -type DefendClaimOpt func(cfg *defendClaimCfg) - -func WithoutWaitingForStep() DefendClaimOpt { - return func(cfg *defendClaimCfg) { - cfg.skipWaitingForStep = true - } -} - -// DefendClaim uses the supplied Mover to perform moves in an attempt to defend the supplied claim. -// It is assumed that the specified claim is invalid and that an honest op-challenger is already running. -// When the game has reached the maximum depth it waits for the honest challenger to counter the leaf claim with step. -// Returns the final leaf claim -func (g *OutputGameHelper) DefendClaim(ctx context.Context, claim *ClaimHelper, performMove Mover, Opts ...DefendClaimOpt) *ClaimHelper { - g.T.Logf("Defending claim %v at depth %v", claim.Index, claim.Depth()) - cfg := &defendClaimCfg{} - for _, opt := range Opts { - opt(cfg) - } - for !claim.IsMaxDepth(ctx) { - g.LogGameData(ctx) - // Wait for the challenger to counter - claim = claim.WaitForCounterClaim(ctx) - g.LogGameData(ctx) - - // Respond with our own move - claim = performMove(claim) - } - - if !cfg.skipWaitingForStep { - claim.WaitForCountered(ctx) - } - return claim -} - -// ChallengeClaim uses the supplied functions to perform moves and steps in an attempt to challenge the supplied claim. -// It is assumed that the claim being disputed is valid and that an honest op-challenger is already running. -// When the game has reached the maximum depth it calls the Stepper to attempt to counter the leaf claim. -// Since the output root is valid, it should not be possible for the Stepper to call step successfully. -func (g *OutputGameHelper) ChallengeClaim(ctx context.Context, claim *ClaimHelper, performMove Mover, attemptStep Stepper) { - for !claim.IsMaxDepth(ctx) { - g.LogGameData(ctx) - // Perform our move - claim = performMove(claim) - - // Wait for the challenger to counter - g.LogGameData(ctx) - claim = claim.WaitForCounterClaim(ctx) - } - - // Confirm the game has reached max depth and the last claim hasn't been countered - g.WaitForClaimAtMaxDepth(ctx, false) - g.LogGameData(ctx) - - // It's on us to call step if we want to win but shouldn't be possible - attemptStep(claim.Index) -} - -func (g *OutputGameHelper) WaitForNewClaim(ctx context.Context, checkPoint int64) (int64, error) { - return g.waitForNewClaim(ctx, checkPoint, defaultTimeout) -} - -func (g *OutputGameHelper) waitForNewClaim(ctx context.Context, checkPoint int64, timeout time.Duration) (int64, error) { - timedCtx, cancel := context.WithTimeout(ctx, timeout) - defer cancel() - var newClaimLen int64 - err := wait.For(timedCtx, time.Second, func() (bool, error) { - actual, err := g.Game.GetClaimCount(ctx) - if err != nil { - return false, err - } - newClaimLen = int64(actual) - return int64(actual) > checkPoint, nil - }) - return newClaimLen, err -} - -func (g *OutputGameHelper) moveCfg(Opts ...MoveOpt) *moveCfg { - cfg := &moveCfg{ - Opts: g.Opts, - } - for _, opt := range Opts { - opt.Apply(cfg) - } - return cfg -} - -func (g *OutputGameHelper) Attack(ctx context.Context, claimIdx int64, claim common.Hash, Opts ...MoveOpt) { - g.T.Logf("Attacking claim %v with value %v", claimIdx, claim) - cfg := g.moveCfg(Opts...) - - claimData, err := g.Game.GetClaim(ctx, uint64(claimIdx)) - g.Require.NoError(err, "Failed to get claim data") - attackPos := claimData.Position.Attack() - - candidate, err := g.Game.AttackTx(ctx, claimData, claim) - g.Require.NoError(err, "Failed to create tx candidate") - _, _, err = transactions.SendTx(ctx, g.Client, candidate, g.PrivKey) - if err != nil { - if cfg.ignoreDupes && g.hasClaim(ctx, claimIdx, attackPos, claim) { - return - } - g.Require.NoErrorf(err, "Defend transaction failed. Game state: \n%v", g.GameData(ctx)) - } -} - -func (g *OutputGameHelper) Defend(ctx context.Context, claimIdx int64, claim common.Hash, Opts ...MoveOpt) { - g.T.Logf("Defending claim %v with value %v", claimIdx, claim) - cfg := g.moveCfg(Opts...) - - claimData, err := g.Game.GetClaim(ctx, uint64(claimIdx)) - g.Require.NoError(err, "Failed to get claim data") - defendPos := claimData.Position.Defend() - - candidate, err := g.Game.DefendTx(ctx, claimData, claim) - g.Require.NoError(err, "Failed to create tx candidate") - _, _, err = transactions.SendTx(ctx, g.Client, candidate, g.PrivKey) - if err != nil { - if cfg.ignoreDupes && g.hasClaim(ctx, claimIdx, defendPos, claim) { - return - } - g.Require.NoErrorf(err, "Defend transaction failed. Game state: \n%v", g.GameData(ctx)) - } -} - -func (g *OutputGameHelper) hasClaim(ctx context.Context, parentIdx int64, pos types.Position, value common.Hash) bool { - claims := g.getAllClaims(ctx) - for _, claim := range claims { - if int64(claim.ParentContractIndex) == parentIdx && claim.Position.ToGIndex().Cmp(pos.ToGIndex()) == 0 && claim.Value == value { - return true - } - } - return false -} - -// StepFails attempts to call step and verifies that it fails with ValidStep() -func (g *OutputGameHelper) StepFails(ctx context.Context, claimIdx int64, isAttack bool, stateData []byte, proof []byte) { - g.T.Logf("Attempting step against claim %v isAttack: %v", claimIdx, isAttack) - candidate, err := g.Game.StepTx(uint64(claimIdx), isAttack, stateData, proof) - g.Require.NoError(err, "Failed to create tx candidate") - _, _, err = transactions.SendTx(ctx, g.Client, candidate, g.PrivKey, transactions.WithReceiptFail()) - err = errutil.TryAddRevertReason(err) - g.Require.Error(err, "Transaction should fail") - validStepErr := "0xfb4e40dd" - invalidPrestateErr := "0x696550ff" - if !strings.Contains(err.Error(), validStepErr) && !strings.Contains(err.Error(), invalidPrestateErr) { - g.Require.Failf("Revert reason should be abi encoded ValidStep() or InvalidPrestate() but was: %v", err.Error()) - } -} - -// ResolveClaim resolves a single subgame -func (g *OutputGameHelper) ResolveClaim(ctx context.Context, claimIdx int64) { - candidate, err := g.Game.ResolveClaimTx(uint64(claimIdx)) - g.Require.NoError(err, "Failed to create resolve claim candidate tx") - transactions.RequireSendTx(g.T, ctx, g.Client, candidate, g.PrivKey) -} - -// ChallengePeriod returns the challenge period fetched from the PreimageOracle contract. -// The returned uint64 value is the number of seconds for the challenge period. -func (g *OutputGameHelper) ChallengePeriod(ctx context.Context) uint64 { - oracle := g.oracle(ctx) - period, err := oracle.ChallengePeriod(ctx) - g.Require.NoError(err, "Failed to get challenge period") - return period -} - -// WaitForChallengePeriodStart waits for the challenge period to start for a given large preimage claim. -func (g *OutputGameHelper) WaitForChallengePeriodStart(ctx context.Context, sender common.Address, data *types.PreimageOracleData) { - timedCtx, cancel := context.WithTimeout(ctx, defaultTimeout) - defer cancel() - err := wait.For(timedCtx, time.Second, func() (bool, error) { - ctx, cancel := context.WithTimeout(timedCtx, 30*time.Second) - defer cancel() - timestamp := g.ChallengePeriodStartTime(ctx, sender, data) - g.T.Log("Waiting for challenge period start", "timestamp", timestamp, "key", data.OracleKey, "game", g.Addr) - return timestamp > 0, nil - }) - if err != nil { - g.LogGameData(ctx) - g.Require.NoErrorf(err, "Failed to get challenge start period for preimage data %v", data) - } -} - -// ChallengePeriodStartTime returns the start time of the challenge period for a given large preimage claim. -// If the returned start time is 0, the challenge period has not started. -func (g *OutputGameHelper) ChallengePeriodStartTime(ctx context.Context, sender common.Address, data *types.PreimageOracleData) uint64 { - oracle := g.oracle(ctx) - uuid := preimages.NewUUID(sender, data) - metadata, err := oracle.GetProposalMetadata(ctx, rpcblock.Latest, keccakTypes.LargePreimageIdent{ - Claimant: sender, - UUID: uuid, - }) - g.Require.NoError(err, "Failed to get proposal metadata") - if len(metadata) == 0 { - return 0 - } - return metadata[0].Timestamp -} - -func (g *OutputGameHelper) WaitForPreimageInOracle(ctx context.Context, data *types.PreimageOracleData) { - timedCtx, cancel := context.WithTimeout(ctx, defaultTimeout) - defer cancel() - oracle := g.oracle(ctx) - err := wait.For(timedCtx, time.Second, func() (bool, error) { - g.T.Logf("Waiting for preimage (%v) to be present in oracle", common.Bytes2Hex(data.OracleKey)) - return oracle.GlobalDataExists(ctx, data) - }) - g.Require.NoErrorf(err, "Did not find preimage (%v) in oracle", common.Bytes2Hex(data.OracleKey)) -} - -func (g *OutputGameHelper) UploadPreimage(ctx context.Context, data *types.PreimageOracleData) { - oracle := g.oracle(ctx) - tx, err := oracle.AddGlobalDataTx(data) - g.Require.NoError(err, "Failed to create preimage upload tx") - transactions.RequireSendTx(g.T, ctx, g.Client, tx, g.PrivKey) -} - -func (g *OutputGameHelper) oracle(ctx context.Context) contracts.PreimageOracleContract { - oracle, err := g.Game.GetOracle(ctx) - g.Require.NoError(err, "Failed to create oracle contract") - return oracle -} - -func (g *OutputGameHelper) GameData(ctx context.Context) string { - maxDepth := g.MaxDepth(ctx) - splitDepth := g.SplitDepth(ctx) - claims, err := g.Game.GetAllClaims(ctx, rpcblock.Latest) - g.Require.NoError(err, "Fetching claims") - info := fmt.Sprintf("Claim count: %v\n", len(claims)) - for i, claim := range claims { - pos := claim.Position - extra := "" - if pos.Depth() <= splitDepth { - blockNum, err := g.CorrectOutputProvider.ClaimedBlockNumber(pos) - if err != nil { - } else { - extra = fmt.Sprintf("Block num: %v", blockNum) - } - } - info = info + fmt.Sprintf("%v - Position: %v, Depth: %v, IndexAtDepth: %v Trace Index: %v, ClaimHash: %v, Countered By: %v, ParentIndex: %v Claimant: %v Bond: %v %v\n", - i, claim.Position.ToGIndex().Int64(), pos.Depth(), pos.IndexAtDepth(), pos.TraceIndex(maxDepth), claim.Value.Hex(), claim.CounteredBy, claim.ParentContractIndex, claim.Claimant, claim.Bond, extra) - } - l2BlockNum := g.L2BlockNum(ctx) - status, err := g.Game.GetStatus(ctx) - g.Require.NoError(err, "Load game status") - return fmt.Sprintf("Game %v - %v - L2 Block: %v - Split Depth: %v - Max Depth: %v:\n%v\n", - g.Addr, status, l2BlockNum, splitDepth, maxDepth, info) -} - -func (g *OutputGameHelper) LogGameData(ctx context.Context) { - g.T.Log(g.GameData(ctx)) -} - -func (g *OutputGameHelper) Credit(ctx context.Context, addr common.Address) *big.Int { - amt, _, err := g.Game.GetCredit(ctx, addr) - g.Require.NoError(err) - return amt -} - -func (g *OutputGameHelper) GetL1Head(ctx context.Context) eth.BlockID { - l1HeadHash, err := g.Game.GetL1Head(ctx) - g.Require.NoError(err, "Failed to load L1 head") - l1Header, err := g.Client.HeaderByHash(ctx, l1HeadHash) - g.Require.NoError(err, "Failed to load L1 header") - l1Head := eth.HeaderBlockID(l1Header) - return l1Head -} diff --git a/op-e2e/e2eutils/disputegame/output_honest_helper.go b/op-e2e/e2eutils/disputegame/output_honest_helper.go index 917b05912d5..e2fc406ffb9 100644 --- a/op-e2e/e2eutils/disputegame/output_honest_helper.go +++ b/op-e2e/e2eutils/disputegame/output_honest_helper.go @@ -17,12 +17,12 @@ const getTraceTimeout = 10 * time.Minute type OutputHonestHelper struct { t *testing.T require *require.Assertions - game *OutputGameHelper + game *SplitGameHelper contract contracts.FaultDisputeGameContract correctTrace types.TraceAccessor } -func NewOutputHonestHelper(t *testing.T, require *require.Assertions, game *OutputGameHelper, contract contracts.FaultDisputeGameContract, correctTrace types.TraceAccessor) *OutputHonestHelper { +func NewOutputHonestHelper(t *testing.T, require *require.Assertions, game *SplitGameHelper, contract contracts.FaultDisputeGameContract, correctTrace types.TraceAccessor) *OutputHonestHelper { return &OutputHonestHelper{ t: t, require: require, diff --git a/op-e2e/e2eutils/disputegame/split_game_helper.go b/op-e2e/e2eutils/disputegame/split_game_helper.go new file mode 100644 index 00000000000..bb9dbc15608 --- /dev/null +++ b/op-e2e/e2eutils/disputegame/split_game_helper.go @@ -0,0 +1,567 @@ +package disputegame + +import ( + "context" + "crypto/ecdsa" + "fmt" + "math/big" + "strings" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/transactions" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" + "github.com/ethereum-optimism/optimism/op-service/errutil" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/require" +) + +const defaultTimeout = 5 * time.Minute + +type SplitGameHelper struct { + T *testing.T + Require *require.Assertions + Client *ethclient.Client + Opts *bind.TransactOpts + PrivKey *ecdsa.PrivateKey + Game contracts.FaultDisputeGameContract + FactoryAddr common.Address + Addr common.Address + CorrectOutputProvider types.TraceProvider + System DisputeSystem + DescribePosition func(pos types.Position, splitDepth types.Depth) string +} + +type moveCfg struct { + Opts *bind.TransactOpts + ignoreDupes bool +} + +type MoveOpt interface { + Apply(cfg *moveCfg) +} + +type moveOptFn func(c *moveCfg) + +func (f moveOptFn) Apply(c *moveCfg) { + f(c) +} + +func WithTransactOpts(Opts *bind.TransactOpts) MoveOpt { + return moveOptFn(func(c *moveCfg) { + c.Opts = Opts + }) +} + +func WithIgnoreDuplicates() MoveOpt { + return moveOptFn(func(c *moveCfg) { + c.ignoreDupes = true + }) +} + +func (g *SplitGameHelper) L2BlockNum(ctx context.Context) uint64 { + _, blockNum, err := g.Game.GetBlockRange(ctx) + g.Require.NoError(err, "failed to load l2 block number") + return blockNum +} + +func (g *SplitGameHelper) SplitDepth(ctx context.Context) types.Depth { + splitDepth, err := g.Game.GetSplitDepth(ctx) + g.Require.NoError(err, "failed to load split depth") + return splitDepth +} + +func (g *SplitGameHelper) ExecDepth(ctx context.Context) types.Depth { + return g.MaxDepth(ctx) - g.SplitDepth(ctx) - 1 +} + +func (g *SplitGameHelper) RootClaim(ctx context.Context) *ClaimHelper { + claim := g.getClaim(ctx, 0) + return newClaimHelper(g, 0, claim) +} + +func (g *SplitGameHelper) WaitForCorrectClaim(ctx context.Context, claimIdx int64) { + g.WaitForClaimCount(ctx, claimIdx+1) + claim := g.getClaim(ctx, claimIdx) + value := g.correctClaimValue(ctx, claim.Position) + g.Require.EqualValuesf(value, claim.Value, "Incorrect claim value at claim %v at position %v", claimIdx, claim.Position.ToGIndex().Uint64()) +} + +func (g *SplitGameHelper) correctClaimValue(ctx context.Context, pos types.Position) common.Hash { + claim, err := g.CorrectOutputProvider.Get(ctx, pos) + g.Require.NoErrorf(err, "Failed to get correct claim for position %v", pos) + return claim +} + +func (g *SplitGameHelper) MaxClockDuration(ctx context.Context) time.Duration { + duration, err := g.Game.GetMaxClockDuration(ctx) + g.Require.NoError(err, "failed to get max clock duration") + return duration +} + +func (g *SplitGameHelper) WaitForNoAvailableCredit(ctx context.Context, addr common.Address) { + timedCtx, cancel := context.WithTimeout(ctx, defaultTimeout) + defer cancel() + err := wait.For(timedCtx, time.Second, func() (bool, error) { + bal, _, err := g.Game.GetCredit(timedCtx, addr) + if err != nil { + return false, err + } + g.T.Log("Waiting for zero available credit", "current", bal, "addr", addr) + return bal.Cmp(big.NewInt(0)) == 0, nil + }) + if err != nil { + g.LogGameData(ctx) + g.Require.NoError(err, "Failed to wait for zero available credit") + } +} + +func (g *SplitGameHelper) AvailableCredit(ctx context.Context, addr common.Address) *big.Int { + credit, _, err := g.Game.GetCredit(ctx, addr) + g.Require.NoErrorf(err, "Failed to fetch available credit for %v", addr) + return credit +} + +func (g *SplitGameHelper) CreditUnlockDuration(ctx context.Context) time.Duration { + _, delay, _, err := g.Game.GetBalanceAndDelay(ctx, rpcblock.Latest) + g.Require.NoError(err, "Failed to get withdrawal delay") + return delay +} + +func (g *SplitGameHelper) WethBalance(ctx context.Context, addr common.Address) *big.Int { + balance, _, _, err := g.Game.GetBalanceAndDelay(ctx, rpcblock.Latest) + g.Require.NoError(err, "Failed to get WETH balance") + return balance +} + +// WaitForClaimCount waits until there are at least count claims in the game. +// This does not check that the number of claims is exactly the specified count to avoid intermittent failures +// where a challenger posts an additional claim before this method sees the number of claims it was waiting for. +func (g *SplitGameHelper) WaitForClaimCount(ctx context.Context, count int64) { + timedCtx, cancel := context.WithTimeout(ctx, defaultTimeout) + defer cancel() + err := wait.For(timedCtx, time.Second, func() (bool, error) { + actual, err := g.Game.GetClaimCount(timedCtx) + if err != nil { + return false, err + } + g.T.Log("Waiting for claim count", "current", actual, "expected", count, "game", g.Addr) + return int64(actual) >= count, nil + }) + if err != nil { + g.LogGameData(ctx) + g.Require.NoErrorf(err, "Did not find expected claim count %v", count) + } +} + +type ContractClaim struct { + ParentIndex uint32 + CounteredBy common.Address + Claimant common.Address + Bond *big.Int + Claim [32]byte + Position *big.Int + Clock *big.Int +} + +func (g *SplitGameHelper) MaxDepth(ctx context.Context) types.Depth { + depth, err := g.Game.GetMaxGameDepth(ctx) + g.Require.NoError(err, "Failed to load game depth") + return depth +} + +func (g *SplitGameHelper) waitForClaim(ctx context.Context, timeout time.Duration, errorMsg string, predicate func(claimIdx int64, claim types.Claim) bool) (int64, types.Claim) { + timedCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + var matchedClaim types.Claim + var matchClaimIdx int64 + err := wait.For(timedCtx, time.Second, func() (bool, error) { + claims, err := g.Game.GetAllClaims(ctx, rpcblock.Latest) + if err != nil { + return false, fmt.Errorf("retrieve all claims: %w", err) + } + // Search backwards because the new claims are at the end and more likely the ones we want. + for i := len(claims) - 1; i >= 0; i-- { + claim := claims[i] + if predicate(int64(i), claim) { + matchClaimIdx = int64(i) + matchedClaim = claim + return true, nil + } + } + return false, nil + }) + if err != nil { // Avoid waiting time capturing game data when there's no error + g.Require.NoErrorf(err, "%v\n%v", errorMsg, g.GameData(ctx)) + } + return matchClaimIdx, matchedClaim +} + +func (g *SplitGameHelper) waitForNoClaim(ctx context.Context, errorMsg string, predicate func(claim types.Claim) bool) { + timedCtx, cancel := context.WithTimeout(ctx, defaultTimeout) + defer cancel() + err := wait.For(timedCtx, time.Second, func() (bool, error) { + claims, err := g.Game.GetAllClaims(ctx, rpcblock.Latest) + if err != nil { + return false, fmt.Errorf("retrieve all claims: %w", err) + } + // Search backwards because the new claims are at the end and more likely the ones we want. + for i := len(claims) - 1; i >= 0; i-- { + claim := claims[i] + if predicate(claim) { + return false, nil + } + } + return true, nil + }) + if err != nil { // Avoid waiting time capturing game data when there's no error + g.Require.NoErrorf(err, "%v\n%v", errorMsg, g.GameData(ctx)) + } +} + +func (g *SplitGameHelper) GetClaimValue(ctx context.Context, claimIdx int64) common.Hash { + g.WaitForClaimCount(ctx, claimIdx+1) + claim := g.getClaim(ctx, claimIdx) + return claim.Value +} + +func (g *SplitGameHelper) getAllClaims(ctx context.Context) []types.Claim { + claims, err := g.Game.GetAllClaims(ctx, rpcblock.Latest) + g.Require.NoError(err, "Failed to get all claims") + return claims +} + +// getClaim retrieves the claim data for a specific index. +// Note that it is deliberately not exported as tests should use WaitForClaim to avoid race conditions. +func (g *SplitGameHelper) getClaim(ctx context.Context, claimIdx int64) types.Claim { + claimData, err := g.Game.GetClaim(ctx, uint64(claimIdx)) + if err != nil { + g.Require.NoErrorf(err, "retrieve claim %v", claimIdx) + } + return claimData +} + +func (g *SplitGameHelper) WaitForClaimAtDepth(ctx context.Context, depth types.Depth) { + g.waitForClaim( + ctx, + defaultTimeout, + fmt.Sprintf("Could not find claim depth %v", depth), + func(_ int64, claim types.Claim) bool { + return claim.Depth() == depth + }) +} + +func (g *SplitGameHelper) WaitForClaimAtMaxDepth(ctx context.Context, countered bool) { + maxDepth := g.MaxDepth(ctx) + g.waitForClaim( + ctx, + defaultTimeout, + fmt.Sprintf("Could not find claim depth %v with countered=%v", maxDepth, countered), + func(_ int64, claim types.Claim) bool { + return claim.Depth() == maxDepth && (claim.CounteredBy != common.Address{}) == countered + }) +} + +func (g *SplitGameHelper) WaitForAllClaimsCountered(ctx context.Context) { + g.waitForNoClaim( + ctx, + "Did not find all claims countered", + func(claim types.Claim) bool { + return claim.CounteredBy == common.Address{} + }) +} + +func (g *SplitGameHelper) Resolve(ctx context.Context) { + ctx, cancel := context.WithTimeout(ctx, time.Minute) + defer cancel() + candidate, err := g.Game.ResolveTx() + g.Require.NoError(err) + transactions.RequireSendTx(g.T, ctx, g.Client, candidate, g.PrivKey) +} + +func (g *SplitGameHelper) Status(ctx context.Context) gameTypes.GameStatus { + status, err := g.Game.GetStatus(ctx) + g.Require.NoError(err) + return status +} + +func (g *SplitGameHelper) WaitForBondModeDecided(ctx context.Context) { + g.T.Logf("Waiting for game %v to have bond mode set", g.Addr) + timedCtx, cancel := context.WithTimeout(ctx, defaultTimeout) + defer cancel() + err := wait.For(timedCtx, time.Second, func() (bool, error) { + bondMode, err := g.Game.GetBondDistributionMode(ctx, rpcblock.Latest) + g.Require.NoError(err) + return bondMode != types.UndecidedDistributionMode, nil + }) + g.Require.NoError(err, "Failed to wait for bond mode to be set") +} + +func (g *SplitGameHelper) WaitForGameStatus(ctx context.Context, expected gameTypes.GameStatus) { + g.T.Logf("Waiting for game %v to have status %v", g.Addr, expected) + timedCtx, cancel := context.WithTimeout(ctx, defaultTimeout) + defer cancel() + err := wait.For(timedCtx, time.Second, func() (bool, error) { + ctx, cancel := context.WithTimeout(timedCtx, 30*time.Second) + defer cancel() + status, err := g.Game.GetStatus(ctx) + if err != nil { + return false, fmt.Errorf("game status unavailable: %w", err) + } + g.T.Logf("Game %v has state %v, waiting for state %v", g.Addr, status, expected) + return expected == status, nil + }) + g.Require.NoErrorf(err, "wait for Game status. Game state: \n%v", g.GameData(ctx)) +} + +func (g *SplitGameHelper) WaitForInactivity(ctx context.Context, numInactiveBlocks int, untilGameEnds bool) { + g.T.Logf("Waiting for game %v to have no activity for %v blocks", g.Addr, numInactiveBlocks) + headCh := make(chan *gethtypes.Header, 100) + headSub, err := g.Client.SubscribeNewHead(ctx, headCh) + g.Require.NoError(err) + defer headSub.Unsubscribe() + + var lastActiveBlock uint64 + for { + if untilGameEnds && g.Status(ctx) != gameTypes.GameStatusInProgress { + break + } + select { + case head := <-headCh: + if lastActiveBlock == 0 { + lastActiveBlock = head.Number.Uint64() + continue + } else if lastActiveBlock+uint64(numInactiveBlocks) < head.Number.Uint64() { + return + } + block, err := g.Client.BlockByNumber(ctx, head.Number) + g.Require.NoError(err) + numActions := 0 + for _, tx := range block.Transactions() { + if tx.To().Hex() == g.Addr.Hex() { + numActions++ + } + } + if numActions != 0 { + g.T.Logf("Game %v has %v actions in block %d. Resetting inactivity timeout", g.Addr, numActions, block.NumberU64()) + lastActiveBlock = head.Number.Uint64() + } + case err := <-headSub.Err(): + g.Require.NoError(err) + case <-ctx.Done(): + g.Require.Fail("Context canceled", ctx.Err()) + } + } +} + +func (g *SplitGameHelper) GameData(ctx context.Context) string { + maxDepth := g.MaxDepth(ctx) + splitDepth := g.SplitDepth(ctx) + claims, err := g.Game.GetAllClaims(ctx, rpcblock.Latest) + g.Require.NoError(err, "Fetching claims") + info := fmt.Sprintf("Claim count: %v\n", len(claims)) + for i, claim := range claims { + pos := claim.Position + extra := g.DescribePosition(pos, splitDepth) + info = info + fmt.Sprintf("%v - Position: %v, Depth: %v, IndexAtDepth: %v Trace Index: %v, ClaimHash: %v, Countered By: %v, ParentIndex: %v Claimant: %v Bond: %v %v\n", + i, claim.Position.ToGIndex().Int64(), pos.Depth(), pos.IndexAtDepth(), pos.TraceIndex(maxDepth), claim.Value.Hex(), claim.CounteredBy, claim.ParentContractIndex, claim.Claimant, claim.Bond, extra) + } + l2BlockNum := g.L2BlockNum(ctx) + status, err := g.Game.GetStatus(ctx) + g.Require.NoError(err, "Load game status") + return fmt.Sprintf("Game %v - %v - L2 Block: %v - Split Depth: %v - Max Depth: %v:\n%v\n", + g.Addr, status, l2BlockNum, splitDepth, maxDepth, info) +} + +func (g *SplitGameHelper) LogGameData(ctx context.Context) { + g.T.Log(g.GameData(ctx)) +} + +func (g *SplitGameHelper) Credit(ctx context.Context, addr common.Address) *big.Int { + amt, _, err := g.Game.GetCredit(ctx, addr) + g.Require.NoError(err) + return amt +} + +func (g *SplitGameHelper) GetL1Head(ctx context.Context) eth.BlockID { + l1HeadHash, err := g.Game.GetL1Head(ctx) + g.Require.NoError(err, "Failed to load L1 head") + l1Header, err := g.Client.HeaderByHash(ctx, l1HeadHash) + g.Require.NoError(err, "Failed to load L1 header") + l1Head := eth.HeaderBlockID(l1Header) + return l1Head +} + +// Mover is a function that either attacks or defends the claim at parentClaimIdx +type Mover func(parent *ClaimHelper) *ClaimHelper + +// Stepper is a function that attempts to perform a step against the claim at parentClaimIdx +type Stepper func(parentClaimIdx int64) + +type defendClaimCfg struct { + skipWaitingForStep bool +} + +type DefendClaimOpt func(cfg *defendClaimCfg) + +func WithoutWaitingForStep() DefendClaimOpt { + return func(cfg *defendClaimCfg) { + cfg.skipWaitingForStep = true + } +} + +// DefendClaim uses the supplied Mover to perform moves in an attempt to defend the supplied claim. +// It is assumed that the specified claim is invalid and that an honest op-challenger is already running. +// When the game has reached the maximum depth it waits for the honest challenger to counter the leaf claim with step. +// Returns the final leaf claim +func (g *SplitGameHelper) DefendClaim(ctx context.Context, claim *ClaimHelper, performMove Mover, Opts ...DefendClaimOpt) *ClaimHelper { + g.T.Logf("Defending claim %v at depth %v", claim.Index, claim.Depth()) + cfg := &defendClaimCfg{} + for _, opt := range Opts { + opt(cfg) + } + for !claim.IsMaxDepth(ctx) { + g.LogGameData(ctx) + // Wait for the challenger to counter + claim = claim.WaitForCounterClaim(ctx) + g.LogGameData(ctx) + + // Respond with our own move + claim = performMove(claim) + } + + if !cfg.skipWaitingForStep { + claim.WaitForCountered(ctx) + } + return claim +} + +// ChallengeClaim uses the supplied functions to perform moves and steps in an attempt to challenge the supplied claim. +// It is assumed that the claim being disputed is valid and that an honest op-challenger is already running. +// When the game has reached the maximum depth it calls the Stepper to attempt to counter the leaf claim. +// Since the output root is valid, it should not be possible for the Stepper to call step successfully. +func (g *SplitGameHelper) ChallengeClaim(ctx context.Context, claim *ClaimHelper, performMove Mover, attemptStep Stepper) { + for !claim.IsMaxDepth(ctx) { + g.LogGameData(ctx) + // Perform our move + claim = performMove(claim) + + // Wait for the challenger to counter + g.LogGameData(ctx) + claim = claim.WaitForCounterClaim(ctx) + } + + // Confirm the game has reached max depth and the last claim hasn't been countered + g.WaitForClaimAtMaxDepth(ctx, false) + g.LogGameData(ctx) + + // It's on us to call step if we want to win but shouldn't be possible + attemptStep(claim.Index) +} + +func (g *SplitGameHelper) WaitForNewClaim(ctx context.Context, checkPoint int64) (int64, error) { + return g.waitForNewClaim(ctx, checkPoint, defaultTimeout) +} + +func (g *SplitGameHelper) waitForNewClaim(ctx context.Context, checkPoint int64, timeout time.Duration) (int64, error) { + timedCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + var newClaimLen int64 + err := wait.For(timedCtx, time.Second, func() (bool, error) { + actual, err := g.Game.GetClaimCount(ctx) + if err != nil { + return false, err + } + newClaimLen = int64(actual) + return int64(actual) > checkPoint, nil + }) + return newClaimLen, err +} + +func (g *SplitGameHelper) moveCfg(Opts ...MoveOpt) *moveCfg { + cfg := &moveCfg{ + Opts: g.Opts, + } + for _, opt := range Opts { + opt.Apply(cfg) + } + return cfg +} + +func (g *SplitGameHelper) Attack(ctx context.Context, claimIdx int64, claim common.Hash, Opts ...MoveOpt) { + g.T.Logf("Attacking claim %v with value %v", claimIdx, claim) + cfg := g.moveCfg(Opts...) + + claimData, err := g.Game.GetClaim(ctx, uint64(claimIdx)) + g.Require.NoError(err, "Failed to get claim data") + attackPos := claimData.Position.Attack() + + candidate, err := g.Game.AttackTx(ctx, claimData, claim) + g.Require.NoError(err, "Failed to create tx candidate") + _, _, err = transactions.SendTx(ctx, g.Client, candidate, g.PrivKey) + if err != nil { + if cfg.ignoreDupes && g.hasClaim(ctx, claimIdx, attackPos, claim) { + return + } + g.Require.NoErrorf(err, "Defend transaction failed. Game state: \n%v", g.GameData(ctx)) + } +} + +func (g *SplitGameHelper) Defend(ctx context.Context, claimIdx int64, claim common.Hash, Opts ...MoveOpt) { + g.T.Logf("Defending claim %v with value %v", claimIdx, claim) + cfg := g.moveCfg(Opts...) + + claimData, err := g.Game.GetClaim(ctx, uint64(claimIdx)) + g.Require.NoError(err, "Failed to get claim data") + defendPos := claimData.Position.Defend() + + candidate, err := g.Game.DefendTx(ctx, claimData, claim) + g.Require.NoError(err, "Failed to create tx candidate") + _, _, err = transactions.SendTx(ctx, g.Client, candidate, g.PrivKey) + if err != nil { + if cfg.ignoreDupes && g.hasClaim(ctx, claimIdx, defendPos, claim) { + return + } + g.Require.NoErrorf(err, "Defend transaction failed. Game state: \n%v", g.GameData(ctx)) + } +} + +func (g *SplitGameHelper) hasClaim(ctx context.Context, parentIdx int64, pos types.Position, value common.Hash) bool { + claims := g.getAllClaims(ctx) + for _, claim := range claims { + if int64(claim.ParentContractIndex) == parentIdx && claim.Position.ToGIndex().Cmp(pos.ToGIndex()) == 0 && claim.Value == value { + return true + } + } + return false +} + +// StepFails attempts to call step and verifies that it fails with ValidStep() +func (g *SplitGameHelper) StepFails(ctx context.Context, claimIdx int64, isAttack bool, stateData []byte, proof []byte) { + g.T.Logf("Attempting step against claim %v isAttack: %v", claimIdx, isAttack) + candidate, err := g.Game.StepTx(uint64(claimIdx), isAttack, stateData, proof) + g.Require.NoError(err, "Failed to create tx candidate") + _, _, err = transactions.SendTx(ctx, g.Client, candidate, g.PrivKey, transactions.WithReceiptFail()) + err = errutil.TryAddRevertReason(err) + g.Require.Error(err, "Transaction should fail") + validStepErr := "0xfb4e40dd" + invalidPrestateErr := "0x696550ff" + if !strings.Contains(err.Error(), validStepErr) && !strings.Contains(err.Error(), invalidPrestateErr) { + g.Require.Failf("Revert reason should be abi encoded ValidStep() or InvalidPrestate() but was: %v", err.Error()) + } +} + +// ResolveClaim resolves a single subgame +func (g *SplitGameHelper) ResolveClaim(ctx context.Context, claimIdx int64) { + candidate, err := g.Game.ResolveClaimTx(uint64(claimIdx)) + g.Require.NoError(err, "Failed to create resolve claim candidate tx") + transactions.RequireSendTx(g.T, ctx, g.Client, candidate, g.PrivKey) +} diff --git a/op-e2e/e2eutils/disputegame/super_cannon_helper.go b/op-e2e/e2eutils/disputegame/super_cannon_helper.go new file mode 100644 index 00000000000..5d1c06be6f3 --- /dev/null +++ b/op-e2e/e2eutils/disputegame/super_cannon_helper.go @@ -0,0 +1,34 @@ +package disputegame + +import ( + "crypto/ecdsa" + "testing" + + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/super" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/require" +) + +type SuperCannonGameHelper struct { + SuperGameHelper + CannonHelper +} + +func NewSuperCannonGameHelper(t *testing.T, client *ethclient.Client, opts *bind.TransactOpts, key *ecdsa.PrivateKey, game contracts.FaultDisputeGameContract, factoryAddr common.Address, gameAddr common.Address, provider *super.SuperTraceProvider, system DisputeSystem) *SuperCannonGameHelper { + superGameHelper := NewSuperGameHelper(t, require.New(t), client, opts, key, game, factoryAddr, gameAddr, provider, system) + defaultChallengerOptions := func() []challenger.Option { + return []challenger.Option{ + challenger.WithCannon(t, system), + challenger.WithFactoryAddress(factoryAddr), + challenger.WithGameAddress(gameAddr), + } + } + return &SuperCannonGameHelper{ + SuperGameHelper: *superGameHelper, + CannonHelper: *NewCannonHelper(&superGameHelper.SplitGameHelper, defaultChallengerOptions), + } +} diff --git a/op-e2e/e2eutils/disputegame/super_dispute_system.go b/op-e2e/e2eutils/disputegame/super_dispute_system.go new file mode 100644 index 00000000000..b2aa4c445cb --- /dev/null +++ b/op-e2e/e2eutils/disputegame/super_dispute_system.go @@ -0,0 +1,98 @@ +package disputegame + +import ( + "strings" + "time" + + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" + "github.com/ethereum-optimism/optimism/op-e2e/interop" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-service/endpoint" + "github.com/ethereum-optimism/optimism/op-service/sources" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/ethclient" +) + +type SuperDisputeSystem struct { + sys interop.SuperSystem +} + +func (s *SuperDisputeSystem) SupervisorClient() *sources.SupervisorClient { + return s.sys.SupervisorClient() +} + +func NewSuperDisputeSystem(sys interop.SuperSystem) *SuperDisputeSystem { + return &SuperDisputeSystem{sys} +} + +func splitName(name string) (string, string) { + parts := strings.SplitN(name, "/", 2) + if len(parts) != 2 { + panic("Invalid super system name: " + name) + } + return parts[0], parts[1] +} + +func (s *SuperDisputeSystem) L1BeaconEndpoint() endpoint.RestHTTP { + beacon := s.sys.L1Beacon() + return endpoint.RestHTTPURL(beacon.BeaconAddr()) +} + +func (s *SuperDisputeSystem) NodeEndpoint(name string) endpoint.RPC { + if name == "l1" { + return s.sys.L1().UserRPC() + } + network, node := splitName(name) + return s.sys.L2GethEndpoint(network, node) +} + +func (s *SuperDisputeSystem) NodeClient(name string) *ethclient.Client { + if name == "l1" { + return s.sys.L1GethClient() + } + network, node := splitName(name) + return s.sys.L2GethClient(network, node) +} + +func (s *SuperDisputeSystem) RollupEndpoint(name string) endpoint.RPC { + network, node := splitName(name) + return s.sys.L2RollupEndpoint(network, node) +} + +func (s *SuperDisputeSystem) RollupClient(name string) *sources.RollupClient { + network, node := splitName(name) + return s.sys.L2RollupClient(network, node) +} + +func (s *SuperDisputeSystem) DisputeGameFactoryAddr() common.Address { + return s.sys.DisputeGameFactoryAddr() +} + +func (s *SuperDisputeSystem) RollupCfgs() []*rollup.Config { + networks := s.sys.L2IDs() + cfgs := make([]*rollup.Config, len(networks)) + for i, network := range networks { + cfgs[i] = s.sys.RollupConfig(network) + } + return cfgs +} + +func (s *SuperDisputeSystem) L2Geneses() []*core.Genesis { + networks := s.sys.L2IDs() + cfgs := make([]*core.Genesis, len(networks)) + for i, network := range networks { + cfgs[i] = s.sys.L2Genesis(network) + } + return cfgs +} + +func (s *SuperDisputeSystem) PrestateVariant() challenger.PrestateVariant { + return challenger.InteropVariant +} + +func (s *SuperDisputeSystem) AdvanceTime(duration time.Duration) { + s.sys.AdvanceL1Time(duration) +} + +var _ DisputeSystem = (*SuperDisputeSystem)(nil) diff --git a/op-e2e/e2eutils/disputegame/super_game_helper.go b/op-e2e/e2eutils/disputegame/super_game_helper.go new file mode 100644 index 00000000000..12c8e08bdd7 --- /dev/null +++ b/op-e2e/e2eutils/disputegame/super_game_helper.go @@ -0,0 +1,48 @@ +package disputegame + +import ( + "crypto/ecdsa" + "fmt" + "testing" + + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/super" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/require" +) + +type SuperGameHelper struct { + SplitGameHelper +} + +func NewSuperGameHelper(t *testing.T, require *require.Assertions, client *ethclient.Client, opts *bind.TransactOpts, privKey *ecdsa.PrivateKey, + game contracts.FaultDisputeGameContract, factoryAddr common.Address, addr common.Address, correctOutputProvider *super.SuperTraceProvider, system DisputeSystem) *SuperGameHelper { + return &SuperGameHelper{ + SplitGameHelper: SplitGameHelper{ + T: t, + Require: require, + Client: client, + Opts: opts, + PrivKey: privKey, + Game: game, + FactoryAddr: factoryAddr, + Addr: addr, + CorrectOutputProvider: correctOutputProvider, + System: system, + DescribePosition: func(pos types.Position, splitDepth types.Depth) string { + + if pos.Depth() > splitDepth { + return "" + } + timestamp, step, err := correctOutputProvider.ComputeStep(pos) + if err != nil { + return "" + } + return fmt.Sprintf("Timestamp: %v, Step: %v", timestamp, step) + }, + }, + } +} diff --git a/op-e2e/faultproofs/super_test.go b/op-e2e/faultproofs/super_test.go new file mode 100644 index 00000000000..354001ed71f --- /dev/null +++ b/op-e2e/faultproofs/super_test.go @@ -0,0 +1,20 @@ +package faultproofs + +import ( + "context" + "testing" + + op_e2e "github.com/ethereum-optimism/optimism/op-e2e" + "github.com/ethereum-optimism/optimism/op-e2e/config" + "github.com/ethereum/go-ethereum/common" +) + +func TestCreateSuperCannonGame(t *testing.T) { + t.Skip("Super cannon game can't yet be deployed with SuperSystem") + op_e2e.InitParallel(t, op_e2e.UsesCannon) + ctx := context.Background() + sys, disputeGameFactory, _ := StartInteropFaultDisputeSystem(t, WithAllocType(config.AllocTypeMTCannon)) + sys.L2IDs() + game := disputeGameFactory.StartSuperCannonGame(ctx, 4, common.Hash{0x01}) + game.LogGameData(ctx) +} diff --git a/op-e2e/faultproofs/util_interop.go b/op-e2e/faultproofs/util_interop.go new file mode 100644 index 00000000000..29afb3bc6d0 --- /dev/null +++ b/op-e2e/faultproofs/util_interop.go @@ -0,0 +1,44 @@ +package faultproofs + +import ( + "context" + "math/big" + "testing" + "time" + + "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" + "github.com/ethereum-optimism/optimism/op-chain-ops/interopgen" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/disputegame" + "github.com/ethereum-optimism/optimism/op-e2e/interop" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/require" +) + +func StartInteropFaultDisputeSystem(t *testing.T, opts ...faultDisputeConfigOpts) (interop.SuperSystem, *disputegame.FactoryHelper, *ethclient.Client) { + fdc := new(faultDisputeConfig) + for _, opt := range opts { + opt(fdc) + } + recipe := interopgen.InteropDevRecipe{ + L1ChainID: 900100, + L2ChainIDs: []uint64{900200, 900201}, + GenesisTimestamp: uint64(time.Now().Unix() + 3), // start chain 3 seconds from now + } + worldResources := interop.WorldResourcePaths{ + FoundryArtifacts: "../../packages/contracts-bedrock/forge-artifacts", + SourceMap: "../../packages/contracts-bedrock", + } + superCfg := interop.SuperSystemConfig{ + SupportTimeTravel: true, + } + + hdWallet, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) + require.NoError(t, err) + l1User := devkeys.ChainUserKeys(new(big.Int).SetUint64(recipe.L1ChainID))(0) + privKey, err := hdWallet.Secret(l1User) + require.NoError(t, err) + s2 := interop.NewSuperSystem(t, &recipe, worldResources, superCfg) + factory := disputegame.NewFactoryHelper(t, context.Background(), disputegame.NewSuperDisputeSystem(s2), + disputegame.WithFactoryPrivKey(privKey)) + return s2, factory, s2.L1GethClient() +} diff --git a/op-e2e/interop/interop_test.go b/op-e2e/interop/interop_test.go index 03168d05cc1..67013947f27 100644 --- a/op-e2e/interop/interop_test.go +++ b/op-e2e/interop/interop_test.go @@ -37,9 +37,9 @@ func setupAndRun(t *testing.T, config SuperSystemConfig, fn func(*testing.T, Sup L2ChainIDs: []uint64{900200, 900201}, GenesisTimestamp: uint64(time.Now().Unix() + 3), // start chain 3 seconds from now } - worldResources := worldResourcePaths{ - foundryArtifacts: "../../packages/contracts-bedrock/forge-artifacts", - sourceMap: "../../packages/contracts-bedrock", + worldResources := WorldResourcePaths{ + FoundryArtifacts: "../../packages/contracts-bedrock/forge-artifacts", + SourceMap: "../../packages/contracts-bedrock", } // create a super system from the recipe diff --git a/op-e2e/interop/supersystem.go b/op-e2e/interop/supersystem.go index bbdbd7cbfcb..1da8484bf1e 100644 --- a/op-e2e/interop/supersystem.go +++ b/op-e2e/interop/supersystem.go @@ -11,7 +11,9 @@ import ( "testing" "time" + "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum/core" "github.com/stretchr/testify/require" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -62,6 +64,10 @@ import ( // kurtosis or another testing framework could be implemented type SuperSystem interface { L1() *geth.GethInstance + L1GethClient() *ethclient.Client + L1Beacon() *fakebeacon.FakeBeacon + AdvanceL1Time(duration time.Duration) + DisputeGameFactoryAddr() common.Address // Superchain level L2IDs() []string @@ -72,7 +78,9 @@ type SuperSystem interface { SupervisorClient() *sources.SupervisorClient // L2 client specific + L2GethEndpoint(id string, name string) endpoint.RPC L2GethClient(network string, node string) *ethclient.Client + L2RollupEndpoint(network string, node string) endpoint.RPC L2RollupClient(network string, node string) *sources.RollupClient SendL2Tx(network string, node string, username string, applyTxOpts helpers.TxOptsFn) *types.Receipt EmitData(ctx context.Context, network string, node string, username string, data string) *types.Receipt @@ -80,6 +88,8 @@ type SuperSystem interface { // L2 level ChainID(network string) *big.Int + RollupConfig(network string) *rollup.Config + L2Genesis(network string) *core.Genesis UserKey(nework, username string) ecdsa.PrivateKey L2OperatorKey(network string, role devkeys.ChainOperatorRole) ecdsa.PrivateKey Address(network string, username string) common.Address @@ -97,11 +107,12 @@ type SuperSystem interface { // Access a contract on a network by name } type SuperSystemConfig struct { - mempoolFiltering bool + mempoolFiltering bool + SupportTimeTravel bool } // NewSuperSystem creates a new SuperSystem from a recipe. It creates an interopE2ESystem. -func NewSuperSystem(t *testing.T, recipe *interopgen.InteropDevRecipe, w worldResourcePaths, config SuperSystemConfig) SuperSystem { +func NewSuperSystem(t *testing.T, recipe *interopgen.InteropDevRecipe, w WorldResourcePaths, config SuperSystemConfig) SuperSystem { s2 := &interopE2ESystem{recipe: recipe, config: &config} s2.prepare(t, w) return s2 @@ -115,6 +126,7 @@ type interopE2ESystem struct { t *testing.T recipe *interopgen.InteropDevRecipe logger log.Logger + timeTravelClock *clock.AdvancingClock hdWallet *devkeys.MnemonicDevKeys worldDeployment *interopgen.WorldDeployment worldOutput *interopgen.WorldOutput @@ -132,6 +144,21 @@ func (s *interopE2ESystem) L1() *geth.GethInstance { return s.l1 } +func (s *interopE2ESystem) L1Beacon() *fakebeacon.FakeBeacon { + return s.beacon +} + +func (s *interopE2ESystem) AdvanceL1Time(duration time.Duration) { + require.NotNil(s.t, s.timeTravelClock, "Attempting to time travel without enabling it.") + s.timeTravelClock.AdvanceTime(duration) +} + +func (s *interopE2ESystem) DisputeGameFactoryAddr() common.Address { + // Currently uses the dispute game factory for the first L2 chain. + // Ultimately this should be a factory shared by all chains in the dependency set + return s.worldDeployment.L2s[s.L2IDs()[0]].DisputeGameFactoryProxy +} + // prepareHDWallet creates a new HD wallet to derive keys from func (s *interopE2ESystem) prepareHDWallet() *devkeys.MnemonicDevKeys { hdWallet, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) @@ -139,13 +166,13 @@ func (s *interopE2ESystem) prepareHDWallet() *devkeys.MnemonicDevKeys { return hdWallet } -type worldResourcePaths struct { - foundryArtifacts string - sourceMap string +type WorldResourcePaths struct { + FoundryArtifacts string + SourceMap string } // prepareWorld creates the world configuration from the recipe and deploys it -func (s *interopE2ESystem) prepareWorld(w worldResourcePaths) (*interopgen.WorldDeployment, *interopgen.WorldOutput) { +func (s *interopE2ESystem) prepareWorld(w WorldResourcePaths) (*interopgen.WorldDeployment, *interopgen.WorldOutput) { // Build the world configuration from the recipe and the HD wallet worldCfg, err := s.recipe.Build(s.hdWallet) require.NoError(s.t, err) @@ -155,8 +182,8 @@ func (s *interopE2ESystem) prepareWorld(w worldResourcePaths) (*interopgen.World require.NoError(s.t, worldCfg.Check(logger)) // create the foundry artifacts and source map - foundryArtifacts := foundry.OpenArtifactsDir(w.foundryArtifacts) - sourceMap := foundry.NewSourceMapFS(os.DirFS(w.sourceMap)) + foundryArtifacts := foundry.OpenArtifactsDir(w.FoundryArtifacts) + sourceMap := foundry.NewSourceMapFS(os.DirFS(w.SourceMap)) // deploy the world, using the logger, foundry artifacts, source map, and world configuration worldDeployment, worldOutput, err := interopgen.Deploy(logger, foundryArtifacts, sourceMap, worldCfg) @@ -182,6 +209,10 @@ func (s *interopE2ESystem) prepareL1() (*fakebeacon.FakeBeacon, *geth.GethInstan l1FinalizedDistance := uint64(3) l1Clock := clock.SystemClock + if s.config.SupportTimeTravel { + s.timeTravelClock = clock.NewAdvancingClock(100 * time.Millisecond) + l1Clock = s.timeTravelClock + } // Start the L1 chain l1Geth, err := geth.InitL1( blockTimeL1, @@ -234,6 +265,14 @@ func (s *interopE2ESystem) ChainID(network string) *big.Int { return s.l2s[network].chainID } +func (s *interopE2ESystem) RollupConfig(network string) *rollup.Config { + return s.l2s[network].l2Out.RollupCfg +} + +func (s *interopE2ESystem) L2Genesis(network string) *core.Genesis { + return s.l2s[network].l2Out.Genesis +} + // prepareSupervisor creates a new supervisor for the system func (s *interopE2ESystem) prepareSupervisor() *supervisor.SupervisorService { // Be verbose with op-supervisor, it's in early test phase @@ -306,7 +345,7 @@ func (s *interopE2ESystem) SupervisorClient() *sources.SupervisorClient { // prepare sets up the system for testing // components are built iteratively, so that they can be reused or modified // their creation can't be safely skipped or reordered at this time -func (s *interopE2ESystem) prepare(t *testing.T, w worldResourcePaths) { +func (s *interopE2ESystem) prepare(t *testing.T, w WorldResourcePaths) { s.t = t s.logger = testlog.Logger(s.t, log.LevelDebug) s.hdWallet = s.prepareHDWallet() diff --git a/op-e2e/interop/supersystem_l2.go b/op-e2e/interop/supersystem_l2.go index bae49792e69..3a5048beb6a 100644 --- a/op-e2e/interop/supersystem_l2.go +++ b/op-e2e/interop/supersystem_l2.go @@ -55,6 +55,10 @@ type l2Net struct { nodes map[string]*l2Node } +func (s *interopE2ESystem) L2GethEndpoint(id string, name string) endpoint.RPC { + net := s.l2s[id] + return net.nodes[name].l2Geth.UserRPC() +} func (s *interopE2ESystem) L2GethClient(id string, name string) *ethclient.Client { net := s.l2s[id] node := net.nodes[name] @@ -77,6 +81,12 @@ func (s *interopE2ESystem) L2GethClient(id string, name string) *ethclient.Clien return node.gethClient } +func (s *interopE2ESystem) L2RollupEndpoint(id string, name string) endpoint.RPC { + net := s.l2s[id] + node := net.nodes[name] + return node.opNode.UserRPC() +} + func (s *interopE2ESystem) L2RollupClient(id string, name string) *sources.RollupClient { net := s.l2s[id] node := net.nodes[name] diff --git a/op-e2e/system/e2esys/setup.go b/op-e2e/system/e2esys/setup.go index acd985d224c..8c63057660f 100644 --- a/op-e2e/system/e2esys/setup.go +++ b/op-e2e/system/e2esys/setup.go @@ -16,6 +16,7 @@ import ( "testing" "time" + "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait" "github.com/stretchr/testify/require" @@ -374,6 +375,23 @@ type System struct { clients map[string]*ethclient.Client } +func (sys *System) PrestateVariant() challenger.PrestateVariant { + switch sys.AllocType() { + case config.AllocTypeMTCannon: + return challenger.MTCannonVariant + default: + return challenger.STCannonVariant + } +} + +func (sys *System) DisputeGameFactoryAddr() common.Address { + return sys.L1Deployments().DisputeGameFactoryProxy +} + +func (sys *System) SupervisorClient() *sources.SupervisorClient { + panic("supervisor not supported for single chain system") +} + func (sys *System) Config() SystemConfig { return sys.Cfg } // AdvanceTime advances the system clock by the given duration. @@ -417,10 +435,18 @@ func (sys *System) RollupCfg() *rollup.Config { return sys.RollupConfig } +func (sys *System) RollupCfgs() []*rollup.Config { + return []*rollup.Config{sys.RollupConfig} +} + func (sys *System) L2Genesis() *core.Genesis { return sys.L2GenesisCfg } +func (sys *System) L2Geneses() []*core.Genesis { + return []*core.Genesis{sys.L2GenesisCfg} +} + func (sys *System) AllocType() config.AllocType { return sys.Cfg.AllocType } From 8d66746b4d8a17f89c04d20fefd10ffd942ebb51 Mon Sep 17 00:00:00 2001 From: Maurelian Date: Tue, 4 Mar 2025 23:04:00 -0500 Subject: [PATCH 061/130] Remove completed todo (#14640) --- packages/contracts-bedrock/justfile | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/contracts-bedrock/justfile b/packages/contracts-bedrock/justfile index 53c1893db05..4f1c59913b8 100644 --- a/packages/contracts-bedrock/justfile +++ b/packages/contracts-bedrock/justfile @@ -74,7 +74,6 @@ print-pinned-block-number: # Reusing the default block number greatly speeds up the test execution time by caching the # rpc call responses in ~/.foundry/cache/rpc. The default block will need to be updated # when the L1 chain is upgraded. -# TODO(opcm upgrades): unskip the "NMC" tests which fail on the forked upgrade path with "UnexpectedRootClaim" errors. prepare-upgrade-env *ARGS : build-go-ffi #!/bin/bash echo "Running upgrade tests at block $pinnedBlockNumber" From abaad4284e0e486138c2ab50d59e0e4baa444883 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Tue, 4 Mar 2025 21:05:03 -0700 Subject: [PATCH 062/130] ops: Add docs around bailiff re-runs (#14632) --- ops/book/src/ci/pr-authorization.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ops/book/src/ci/pr-authorization.md b/ops/book/src/ci/pr-authorization.md index 673781c9a2f..09e3573bcc7 100644 --- a/ops/book/src/ci/pr-authorization.md +++ b/ops/book/src/ci/pr-authorization.md @@ -41,6 +41,22 @@ All of the following must be true for a PR to be authorized: - The commit hash must match the head of the PR. - The PR must be on a fork. +## Re-Running Failed PRs + +If a PR fails due to a flake, you can re-run it from CircleCI's UI. However, you **must** re-run the commit that +Bailif pushed. For example, you may see the following in the CircleCI UI: + +- A pipeline from `pull/12345` (i.e., the one the user pushed to their fork) +- A pipeline from `external-fork/abcdef123456` (i.e., the one Bailiff pushed) + +Re-run the pipeline on the `external-fork` branch. If you re-run the wrong branch: + +1. The job will always fail, since OSS builds cannot be run on self-hosted runners. +2. The failing job's test results will override Bailiff's. + +You will have to push a new commit and re-authorize the PR to resolve the issue. You can see which branch Bailiff +pushed by looking at the Bailiff status check. + ## Troubleshooting If you're having trouble with Bailiff, see below. @@ -53,5 +69,7 @@ If you're having trouble with Bailiff, see below. Discord. [bailiff]: https://github.com/ethereum-optimism/bailiff + [bailiff-logs]: https://optimistic.grafana.net/goto/vCCT3AKNg?orgId=1 + [plat-gen]: https://discord.com/channels/1244729134312198194/1260624141497798706 \ No newline at end of file From 6cebf7a855584c00aee1946cdc4935f02d948274 Mon Sep 17 00:00:00 2001 From: Delweng Date: Wed, 5 Mar 2025 12:07:21 +0800 Subject: [PATCH 063/130] contracts: rm not used format (#14593) Signed-off-by: jsvisa --- packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol b/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol index c4734d89a0d..026cdb178f9 100644 --- a/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol +++ b/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol @@ -232,7 +232,7 @@ library ChainAssertions { /// @notice Asserts that the PreimageOracle is setup correctly function checkPreimageOracle(IPreimageOracle _oracle, DeployConfig _cfg) internal view { - console.log("Running chain assertions on the PreimageOracle %s at %s", address(_oracle)); + console.log("Running chain assertions on the PreimageOracle at %s", address(_oracle)); require(address(_oracle) != address(0), "CHECK-PIO-10"); require(_oracle.minProposalSize() == _cfg.preimageOracleMinProposalSize(), "CHECK-PIO-30"); @@ -241,7 +241,7 @@ library ChainAssertions { /// @notice Asserts that the MIPs contract is setup correctly function checkMIPS(IMIPS _mips, IPreimageOracle _oracle) internal view { - console.log("Running chain assertions on the MIPS %s at %s", address(_mips)); + console.log("Running chain assertions on the MIPS at %s", address(_mips)); require(address(_mips) != address(0), "CHECK-MIPS-10"); require(_mips.oracle() == _oracle, "CHECK-MIPS-20"); From 48adf2e6fbad0f2c16e4863dd8fc349aef014357 Mon Sep 17 00:00:00 2001 From: Inphi Date: Tue, 4 Mar 2025 23:22:24 -0500 Subject: [PATCH 064/130] op-program: Consolidate cascading block replacements (#14622) * op-program: Consolidate cascading block replacements Blocks containing invalid messages affect the cross-safety of its dependencies. This is because once the block is replaced with a deposits-only block, any messages it contained would no longer exist. And thus, executing messages that referenced the non-existent messages become invalid as well. To deal with this, consolidation runs until all cross-depedencies are reconciled against deposits-only updates. * consolidate oracle * op-e2e: intra-block cascade action case * add unit tests for consolidate oracle * always store state diff for the deposits-only block build * add output root to block build db * updated transition state view in consolidate oracle * lint --- op-e2e/actions/interop/proofs_test.go | 62 +++--- op-program/client/interop/consolidate.go | 162 +++++++++----- op-program/client/interop/interop.go | 3 + op-program/client/interop/interop_test.go | 90 ++++++-- op-program/client/interop/oracle.go | 154 +++++++++++++ op-program/client/interop/oracle_test.go | 253 ++++++++++++++++++++++ op-program/client/tasks/deposits_block.go | 22 +- op-program/client/tasks/derive.go | 10 +- 8 files changed, 642 insertions(+), 114 deletions(-) create mode 100644 op-program/client/interop/oracle.go create mode 100644 op-program/client/interop/oracle_test.go diff --git a/op-e2e/actions/interop/proofs_test.go b/op-e2e/actions/interop/proofs_test.go index 9164ef75fcd..cc6b88881a8 100644 --- a/op-e2e/actions/interop/proofs_test.go +++ b/op-e2e/actions/interop/proofs_test.go @@ -443,6 +443,8 @@ func TestInteropFaultProofs_Cycle(gt *testing.T) { } func TestInteropFaultProofs_CascadeInvalidBlock(gt *testing.T) { + // TODO(#14307): Support cascading block invalidations + gt.Skip("TODO(#14307): Support cascading block invalidations") t := helpers.NewDefaultTesting(gt) system := dsl.NewInteropDSL(t) @@ -458,31 +460,39 @@ func TestInteropFaultProofs_CascadeInvalidBlock(gt *testing.T) { emitterContract.Deploy(alice), )) - // Initiating messages on chain A - system.AddL2Block(actors.ChainA, dsl.WithL2BlockTransactions( - emitterContract.EmitMessage(alice, "chainA message"), - )) - chainAInitTx := emitterContract.LastEmittedMessage() - system.AddL2Block(actors.ChainB) - system.SubmitBatchData() + assertHeads(t, actors.ChainA, 1, 0, 1, 0) + assertHeads(t, actors.ChainB, 1, 0, 1, 0) - // Create a message with a conflicting payload on chain B, that also emits an initiating message - system.AddL2Block(actors.ChainB, dsl.WithL2BlockTransactions( - system.InboxContract.Execute(alice, chainAInitTx, dsl.WithPayload([]byte("this message was never emitted"))), - emitterContract.EmitMessage(alice, "chainB message"), - ), dsl.WithL1BlockCrossUnsafe()) - chainBExecTx := system.InboxContract.LastTransaction() - chainBExecTx.CheckIncluded() - chainBInitTx := emitterContract.LastEmittedMessage() - - // Create a message with a valid message on chain A, pointing to the initiating message on B from the same block - // as an invalid message. - system.AddL2Block(actors.ChainA, - dsl.WithL2BlockTransactions(system.InboxContract.Execute(alice, chainBInitTx)), - dsl.WithL1BlockCrossUnsafe(), + // Create initiating and executing messages within the same block + var ( + chainAExecTx *dsl.GeneratedTransaction + chainBExecTx *dsl.GeneratedTransaction + chainBInitTx *dsl.GeneratedTransaction ) - chainAExecTx := system.InboxContract.LastTransaction() - chainAExecTx.CheckIncluded() + { + actors.ChainA.Sequencer.ActL2StartBlock(t) + actors.ChainB.Sequencer.ActL2StartBlock(t) + + chainAInitTx := emitterContract.EmitMessage(alice, "chainA message")(actors.ChainA) + chainAInitTx.Include() + + // Create messages with a conflicting payload on chain B, while also emitting an initiating message + chainBExecTx := system.InboxContract.Execute(alice, chainAInitTx, + dsl.WithPayload([]byte("this message was never emitted")))(actors.ChainB) + chainBExecTx.Include() + chainBInitTx = emitterContract.EmitMessage(alice, "chainB message")(actors.ChainB) + chainBInitTx.Include() + + // Create a message with a valid message on chain A, pointing to the initiating message on B from the same block + // as an invalid message. + chainAExecTx = system.InboxContract.Execute(alice, chainBInitTx)(actors.ChainA) + chainAExecTx.Include() + + actors.ChainA.Sequencer.ActL2EndBlock(t) + actors.ChainB.Sequencer.ActL2EndBlock(t) + } + assertHeads(t, actors.ChainA, 2, 0, 1, 0) + assertHeads(t, actors.ChainB, 2, 0, 1, 0) system.SubmitBatchData(func(opts *dsl.SubmitBatchDataOpts) { opts.SkipCrossSafeUpdate = true @@ -513,9 +523,6 @@ func TestInteropFaultProofs_CascadeInvalidBlock(gt *testing.T) { disputedClaim: optimisticEnd.Marshal(), disputedTraceIndex: consolidateStep, expectValid: false, - // TODO(#14306): Support cascading re-orgs in op-program - skipProgram: true, - skipChallenger: true, }, { name: "Consolidate-ReplaceInvalidBlocks", @@ -523,9 +530,6 @@ func TestInteropFaultProofs_CascadeInvalidBlock(gt *testing.T) { disputedClaim: crossSafeEnd.Marshal(), disputedTraceIndex: consolidateStep, expectValid: true, - // TODO(#14306): Support cascading re-orgs in op-program - skipProgram: true, - skipChallenger: true, }, } runFppAndChallengerTests(gt, system, tests) diff --git a/op-program/client/interop/consolidate.go b/op-program/client/interop/consolidate.go index 6916d03066a..459012be07e 100644 --- a/op-program/client/interop/consolidate.go +++ b/op-program/client/interop/consolidate.go @@ -20,6 +20,8 @@ import ( "github.com/ethereum/go-ethereum/log" ) +var ErrInvalidBlockReplacement = errors.New("invalid block replacement error") + // ReceiptsToExecutingMessages returns the executing messages in the receipts indexed by their position in the log. func ReceiptsToExecutingMessages(depset depset.ChainIndexFromID, receipts ethtypes.Receipts) (map[uint32]*supervisortypes.ExecutingMessage, uint32, error) { execMsgs := make(map[uint32]*supervisortypes.ExecutingMessage) @@ -39,17 +41,19 @@ func ReceiptsToExecutingMessages(depset depset.ChainIndexFromID, receipts ethtyp return execMsgs, curr, nil } -func fetchAgreedBlockHashes(oracle l2.Oracle, superRoot *eth.SuperV1) ([]common.Hash, error) { - agreedBlockHashes := make([]common.Hash, len(superRoot.Chains)) - for i, chain := range superRoot.Chains { - output := oracle.OutputByRoot(common.Hash(chain.Output), chain.ChainID) - outputV0, ok := output.(*eth.OutputV0) - if !ok { - return nil, fmt.Errorf("unsupported L2 output version: %d", output.Version()) - } - agreedBlockHashes[i] = common.Hash(outputV0.BlockHash) - } - return agreedBlockHashes, nil +type consolidateState struct { + *types.TransitionState + replacedChains map[eth.ChainID]bool +} + +func (s *consolidateState) isReplaced(chainID eth.ChainID) bool { + return s.replacedChains[chainID] +} + +func (s *consolidateState) setReplaced(transitionStateIndex int, chainID eth.ChainID, outputRoot eth.Bytes32, replacementBlockHash common.Hash) { + s.PendingProgress[transitionStateIndex].OutputRoot = outputRoot + s.PendingProgress[transitionStateIndex].BlockHash = replacementBlockHash + s.replacedChains[chainID] = true } func RunConsolidation( @@ -61,60 +65,111 @@ func RunConsolidation( superRoot *eth.SuperV1, tasks taskExecutor, ) (eth.Bytes32, error) { + consolidateState := consolidateState{ + TransitionState: &types.TransitionState{ + PendingProgress: make([]types.OptimisticBlock, len(transitionState.PendingProgress)), + SuperRoot: transitionState.SuperRoot, + Step: transitionState.Step, + }, + replacedChains: make(map[eth.ChainID]bool), + } + // We will be updating the transition state as blocks are replaced, so make a copy + copy(consolidateState.PendingProgress, transitionState.PendingProgress) + // Use a reference to the transition state so the consolidate oracle has a recent view. + // The TransitionStateByRoot method isn't expected to be used during consolidation, + // but we pass the state for safety in case this changes in the future. + consolidateOracle := NewConsolidateOracle(l2PreimageOracle, consolidateState.TransitionState) + + // Keep consolidating until there are no more invalid blocks to replace +loop: + for { + err := singleRoundConsolidation(logger, bootInfo, l1PreimageOracle, consolidateOracle, &consolidateState, superRoot, tasks) + switch { + case err == nil: + break loop + case errors.Is(err, ErrInvalidBlockReplacement): + continue + default: + return eth.Bytes32{}, err + } + } + + var consolidatedChains []eth.ChainIDAndOutput + for i, chain := range superRoot.Chains { + consolidatedChains = append(consolidatedChains, eth.ChainIDAndOutput{ + ChainID: chain.ChainID, + Output: consolidateState.PendingProgress[i].OutputRoot, + }) + } + consolidatedSuper := ð.SuperV1{ + Timestamp: superRoot.Timestamp + 1, + Chains: consolidatedChains, + } + return eth.SuperRoot(consolidatedSuper), nil +} + +func singleRoundConsolidation( + logger log.Logger, + bootInfo *boot.BootInfoInterop, + l1PreimageOracle l1.Oracle, + l2PreimageOracle *ConsolidateOracle, + consolidateState *consolidateState, + superRoot *eth.SuperV1, + tasks taskExecutor, +) error { // The depset is the same for all chains. So it suffices to use any chain ID depset, err := bootInfo.Configs.DependencySet(superRoot.Chains[0].ChainID) if err != nil { - return eth.Bytes32{}, fmt.Errorf("failed to get dependency set: %w", err) - } - deps, err := newConsolidateCheckDeps(depset, bootInfo, transitionState, superRoot.Chains, l2PreimageOracle) - if err != nil { - return eth.Bytes32{}, fmt.Errorf("failed to create consolidate check deps: %w", err) + return fmt.Errorf("failed to get dependency set: %w", err) } - agreedBlockHashes, err := fetchAgreedBlockHashes(l2PreimageOracle, superRoot) + deps, err := newConsolidateCheckDeps(depset, bootInfo, consolidateState.TransitionState, superRoot.Chains, l2PreimageOracle) if err != nil { - return eth.Bytes32{}, err + return fmt.Errorf("failed to create consolidate check deps: %w", err) } - // TODO(#14306): Handle cascading reorgs - // invalidChains tracks blocks that need to be replaced with a deposits-only block. - // The replacement is done after a first pass on all chains to avoid "contaminating" the caonical block - // oracle in a way that alters the result of hazard checks after a reorg. invalidChains := make(map[eth.ChainID]*ethtypes.Block) for i, chain := range superRoot.Chains { - progress := transitionState.PendingProgress[i] + // Do not check chains that have been replaced with a deposits-only block. + // They are already cross-safe because deposits-only blocks cannot contain executing messages. + if consolidateState.isReplaced(chain.ChainID) { + continue + } + agreedOutput := l2PreimageOracle.OutputByRoot(common.Hash(chain.Output), chain.ChainID) + agreedOutputV0, ok := agreedOutput.(*eth.OutputV0) + if !ok { + return fmt.Errorf("unsupported L2 output version: %d", agreedOutput.Version()) + } + agreedBlockHash := common.Hash(agreedOutputV0.BlockHash) + + progress := consolidateState.PendingProgress[i] // It's possible that the optimistic block is not canonical. // So we use the blockDataByHash hint to trigger a block rebuild to ensure that the block data, including receipts, are available. - _ = l2PreimageOracle.BlockDataByHash(agreedBlockHashes[i], progress.BlockHash, chain.ChainID) + _ = l2PreimageOracle.BlockDataByHash(agreedBlockHash, progress.BlockHash, chain.ChainID) - optimisticBlock, receipts := l2PreimageOracle.ReceiptsByBlockHash(progress.BlockHash, chain.ChainID) - execMsgs, _, err := ReceiptsToExecutingMessages(deps.DependencySet(), receipts) - switch { - case errors.Is(err, supervisortypes.ErrUnknownChain): - invalidChains[chain.ChainID] = optimisticBlock - continue - case err != nil: - return eth.Bytes32{}, err - } + optimisticBlock, _ := l2PreimageOracle.ReceiptsByBlockHash(progress.BlockHash, chain.ChainID) candidate := supervisortypes.BlockSeal{ Hash: progress.BlockHash, Number: optimisticBlock.NumberU64(), Timestamp: optimisticBlock.Time(), } - if err := checkHazards(logger, deps, candidate, chain.ChainID, execMsgs); err != nil { + if err := checkHazards(logger, deps, candidate, chain.ChainID); err != nil { if !isInvalidMessageError(err) { - return eth.Bytes32{}, err + return err } invalidChains[chain.ChainID] = optimisticBlock } } - var consolidatedChains []eth.ChainIDAndOutput + if len(invalidChains) == 0 { + return nil + } + for i, chain := range superRoot.Chains { if optimisticBlock, ok := invalidChains[chain.ChainID]; ok { chainAgreedPrestate := superRoot.Chains[i] - _, outputRoot, err := buildDepositOnlyBlock( + replacementBlockHash, outputRoot, err := buildDepositOnlyBlock( logger, bootInfo, l1PreimageOracle, @@ -122,26 +177,25 @@ func RunConsolidation( chainAgreedPrestate, tasks, optimisticBlock, + // Update the preimage oracle database with the replaced block data + l2PreimageOracle.KeyValueStore(), ) if err != nil { - return eth.Bytes32{}, err + return err } - consolidatedChains = append(consolidatedChains, eth.ChainIDAndOutput{ - ChainID: chain.ChainID, - Output: outputRoot, - }) - } else { - consolidatedChains = append(consolidatedChains, eth.ChainIDAndOutput{ - ChainID: chain.ChainID, - Output: transitionState.PendingProgress[i].OutputRoot, - }) + logger.Info( + "Replaced block", + "chain", chain.ChainID, + "replacedBlock", eth.ToBlockID(optimisticBlock), + "replacementBlockHash", replacementBlockHash, + "outputRoot", outputRoot, + "replacedOutputRoot", superRoot.Chains[i].Output, + ) + superRoot.Chains[i].Output = outputRoot + consolidateState.setReplaced(i, chain.ChainID, outputRoot, replacementBlockHash) } } - consolidatedSuper := ð.SuperV1{ - Timestamp: superRoot.Timestamp + 1, - Chains: consolidatedChains, - } - return eth.SuperRoot(consolidatedSuper), nil + return ErrInvalidBlockReplacement } func isInvalidMessageError(err error) bool { @@ -158,7 +212,7 @@ type ConsolidateCheckDeps interface { cross.UnsafeStartDeps } -func checkHazards(logger log.Logger, deps ConsolidateCheckDeps, candidate supervisortypes.BlockSeal, chainID eth.ChainID, execMsgs map[uint32]*supervisortypes.ExecutingMessage) error { +func checkHazards(logger log.Logger, deps ConsolidateCheckDeps, candidate supervisortypes.BlockSeal, chainID eth.ChainID) error { hazards, err := cross.CrossUnsafeHazards(deps, logger, chainID, candidate) if err != nil { return err @@ -308,6 +362,7 @@ func buildDepositOnlyBlock( chainAgreedPrestate eth.ChainIDAndOutput, tasks taskExecutor, optimisticBlock *ethtypes.Block, + db l2.KeyValueStore, ) (common.Hash, eth.Bytes32, error) { rollupCfg, err := bootInfo.Configs.RollupConfig(chainAgreedPrestate.ChainID) if err != nil { @@ -326,6 +381,7 @@ func buildDepositOnlyBlock( l1PreimageOracle, l2PreimageOracle, optimisticBlock, + db, ) if err != nil { return common.Hash{}, eth.Bytes32{}, err diff --git a/op-program/client/interop/interop.go b/op-program/client/interop/interop.go index 3f5be634a48..ac977793806 100644 --- a/op-program/client/interop/interop.go +++ b/op-program/client/interop/interop.go @@ -53,6 +53,7 @@ type taskExecutor interface { l1Oracle l1.Oracle, l2Oracle l2.Oracle, optimisticBlock *ethtypes.Block, + db l2.KeyValueStore, ) (blockHash common.Hash, outputRoot eth.Bytes32, err error) } @@ -214,6 +215,7 @@ func (t *interopTaskExecutor) BuildDepositOnlyBlock( l1Oracle l1.Oracle, l2Oracle l2.Oracle, optimisticBlock *ethtypes.Block, + db l2.KeyValueStore, ) (common.Hash, eth.Bytes32, error) { return tasks.BuildDepositOnlyBlock( logger, @@ -224,5 +226,6 @@ func (t *interopTaskExecutor) BuildDepositOnlyBlock( agreedL2OutputRoot, l1Oracle, l2Oracle, + db, ) } diff --git a/op-program/client/interop/interop_test.go b/op-program/client/interop/interop_test.go index ba5886ffa55..993f81408e4 100644 --- a/op-program/client/interop/interop_test.go +++ b/op-program/client/interop/interop_test.go @@ -146,19 +146,20 @@ const ( ) func TestDeriveBlockForConsolidateStep(t *testing.T) { - createExecMessage := func(initIncludedIn uint64, config *staticConfigSource) interoptypes.Message { + createExecMessage := func(initIncludedIn uint64, config *staticConfigSource, initChainIndex supervisortypes.ChainIndex) interoptypes.Message { exec := interoptypes.Message{ Identifier: interoptypes.Identifier{ Origin: initiatingMessageOrigin, BlockNumber: initIncludedIn, LogIndex: 0, - Timestamp: initIncludedIn * config.rollupCfgs[chainA].BlockTime, - ChainID: uint256.Int(eth.ChainIDFromBig(config.rollupCfgs[chainA].L2ChainID)), + Timestamp: initIncludedIn * config.rollupCfgs[initChainIndex].BlockTime, + ChainID: uint256.Int(eth.ChainIDFromBig(config.rollupCfgs[initChainIndex].L2ChainID)), }, PayloadHash: initPayloadHash, } return exec } + createInitLog := func() *gethTypes.Log { return &gethTypes.Log{ Address: initiatingMessageOrigin, @@ -179,7 +180,7 @@ func TestDeriveBlockForConsolidateStep(t *testing.T) { testCase: consolidationTestCase{ logBuilderFn: func(includeBlockNumbers map[supervisortypes.ChainIndex]uint64, config *staticConfigSource) map[supervisortypes.ChainIndex][]*gethTypes.Log { init := createInitLog() - exec := createExecMessage(includeBlockNumbers[chainA], config) + exec := createExecMessage(includeBlockNumbers[chainA], config, chainA) return map[supervisortypes.ChainIndex][]*gethTypes.Log{chainA: {init}, chainB: {convertExecutingMessageToLog(t, exec)}} }, }, @@ -189,17 +190,7 @@ func TestDeriveBlockForConsolidateStep(t *testing.T) { testCase: consolidationTestCase{ logBuilderFn: func(includeBlockNumbers map[supervisortypes.ChainIndex]uint64, config *staticConfigSource) map[supervisortypes.ChainIndex][]*gethTypes.Log { init := createInitLog() - initPayloadHash := crypto.Keccak256Hash(initiatingMessageTopic[:]) - execMsg := interoptypes.Message{ - Identifier: interoptypes.Identifier{ - Origin: init.Address, - BlockNumber: includeBlockNumbers[chainB], - LogIndex: 0, - Timestamp: includeBlockNumbers[chainB] * config.rollupCfgs[chainB].BlockTime, - ChainID: uint256.Int(eth.ChainIDFromBig(config.rollupCfgs[chainB].L2ChainID)), - }, - PayloadHash: initPayloadHash, - } + execMsg := createExecMessage(includeBlockNumbers[chainB], config, chainB) exec := convertExecutingMessageToLog(t, execMsg) return map[supervisortypes.ChainIndex][]*gethTypes.Log{chainA: {exec}, chainB: {init}} }, @@ -217,7 +208,7 @@ func TestDeriveBlockForConsolidateStep(t *testing.T) { Address: initiatingMessageOrigin2, Topics: []common.Hash{initiatingMessageTopic}, } - exec := createExecMessage(includeBlockNumbers[chainA], config) + exec := createExecMessage(includeBlockNumbers[chainA], config, chainA) exec.Identifier.Origin = init2.Address exec.Identifier.LogIndex = 1 return map[supervisortypes.ChainIndex][]*gethTypes.Log{ @@ -227,12 +218,30 @@ func TestDeriveBlockForConsolidateStep(t *testing.T) { }, }, }, + { + name: "HappyPathWithValidMessages-IntraBlockCycle", + testCase: consolidationTestCase{ + logBuilderFn: func(includeBlockNumbers map[supervisortypes.ChainIndex]uint64, config *staticConfigSource) map[supervisortypes.ChainIndex][]*gethTypes.Log { + initA := createInitLog() + initB := createInitLog() + + execMsgA := createExecMessage(includeBlockNumbers[chainB], config, chainB) + execA := convertExecutingMessageToLog(t, execMsgA) + execMsgB := createExecMessage(includeBlockNumbers[chainA], config, chainA) + execB := convertExecutingMessageToLog(t, execMsgB) + return map[supervisortypes.ChainIndex][]*gethTypes.Log{ + chainA: {initA, execA}, + chainB: {initB, execB}, + } + }, + }, + }, { name: "ReplaceChainB-UnknownChainID", testCase: consolidationTestCase{ logBuilderFn: func(includeBlockNumbers map[supervisortypes.ChainIndex]uint64, config *staticConfigSource) map[supervisortypes.ChainIndex][]*gethTypes.Log { init := createInitLog() - exec := createExecMessage(includeBlockNumbers[chainA], config) + exec := createExecMessage(includeBlockNumbers[chainA], config, chainA) exec.Identifier.ChainID = uint256.Int(eth.ChainIDFromUInt64(0xdeadbeef)) return map[supervisortypes.ChainIndex][]*gethTypes.Log{chainA: {init}, chainB: {convertExecutingMessageToLog(t, exec)}} }, @@ -253,7 +262,7 @@ func TestDeriveBlockForConsolidateStep(t *testing.T) { Address: initiatingMessageOrigin2, Topics: []common.Hash{initiatingMessageTopic}, } - exec := createExecMessage(includeBlockNumbers[chainA], config) + exec := createExecMessage(includeBlockNumbers[chainA], config, chainA) exec.Identifier.Origin = init2.Address exec.Identifier.LogIndex = 0 return map[supervisortypes.ChainIndex][]*gethTypes.Log{ @@ -271,7 +280,7 @@ func TestDeriveBlockForConsolidateStep(t *testing.T) { testCase: consolidationTestCase{ logBuilderFn: func(includeBlockNumbers map[supervisortypes.ChainIndex]uint64, config *staticConfigSource) map[supervisortypes.ChainIndex][]*gethTypes.Log { init := createInitLog() - execMsg := createExecMessage(includeBlockNumbers[chainA], config) + execMsg := createExecMessage(includeBlockNumbers[chainA], config, chainA) execMsg.PayloadHash = crypto.Keccak256Hash([]byte("invalid hash")) return map[supervisortypes.ChainIndex][]*gethTypes.Log{chainA: {init}, chainB: {convertExecutingMessageToLog(t, execMsg)}} }, @@ -285,7 +294,7 @@ func TestDeriveBlockForConsolidateStep(t *testing.T) { testCase: consolidationTestCase{ logBuilderFn: func(includeBlockNumbers map[supervisortypes.ChainIndex]uint64, config *staticConfigSource) map[supervisortypes.ChainIndex][]*gethTypes.Log { init := createInitLog() - execMsg := createExecMessage(includeBlockNumbers[chainA], config) + execMsg := createExecMessage(includeBlockNumbers[chainA], config, chainA) execMsg.Identifier.Timestamp = execMsg.Identifier.Timestamp - 1 return map[supervisortypes.ChainIndex][]*gethTypes.Log{chainA: {init}, chainB: {convertExecutingMessageToLog(t, execMsg)}} }, @@ -298,7 +307,7 @@ func TestDeriveBlockForConsolidateStep(t *testing.T) { name: "ReplaceBothChains", testCase: consolidationTestCase{ logBuilderFn: func(includeBlockNumbers map[supervisortypes.ChainIndex]uint64, config *staticConfigSource) map[supervisortypes.ChainIndex][]*gethTypes.Log { - invalidExecMsg := createExecMessage(includeBlockNumbers[chainA], config) + invalidExecMsg := createExecMessage(includeBlockNumbers[chainA], config, chainA) invalidExecMsg.PayloadHash = crypto.Keccak256Hash([]byte("invalid hash")) log := convertExecutingMessageToLog(t, invalidExecMsg) return map[supervisortypes.ChainIndex][]*gethTypes.Log{chainA: {log}, chainB: {log}} @@ -308,10 +317,36 @@ func TestDeriveBlockForConsolidateStep(t *testing.T) { }, }, }, + { + name: "ReplaceBothChains-CascadingReorg", + testCase: consolidationTestCase{ + logBuilderFn: func(includeBlockNumbers map[supervisortypes.ChainIndex]uint64, config *staticConfigSource) map[supervisortypes.ChainIndex][]*gethTypes.Log { + initA := createInitLog() + initB := createInitLog() + + execMsgA := createExecMessage(includeBlockNumbers[chainB], config, chainB) + execA := convertExecutingMessageToLog(t, execMsgA) + execMsgB := createExecMessage(includeBlockNumbers[chainA], config, chainA) + execMsgB.PayloadHash = crypto.Keccak256Hash([]byte("invalid hash")) + execB := convertExecutingMessageToLog(t, execMsgB) + + return map[supervisortypes.ChainIndex][]*gethTypes.Log{ + chainA: {initA, execA}, + chainB: {initB, execB}, + } + }, + expectBlockReplacements: func(config *staticConfigSource) []supervisortypes.ChainIndex { + return []supervisortypes.ChainIndex{chainA, chainB} + }, + }, + }, } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { + if tt.name != "ReplaceBothChains-CascadingReorg" { + t.Skip() + } runConsolidationTestCase(t, tt.testCase) }) } @@ -391,10 +426,16 @@ func runConsolidationTestCase(t *testing.T, testCase consolidationTestCase) { replacedBlockOutputRoot := common.Hash(eth.OutputRoot(replacedBlockOutput)) l2PreimageOracle.Outputs[replacedBlockOutputRoot] = replacedBlockOutput - depositsOnlyBlock, _ := createBlock(rng, configSource.rollupCfgs[chainIndexToReplace], 2, nil) - depositsOnlyOutputRoot := eth.OutputRoot(createOutput(depositsOnlyBlock.Hash())) + depositsOnlyBlock, depositsOnlyBlockReceipts := createBlock(rng, configSource.rollupCfgs[chainIndexToReplace], 2, nil) + depositsOnlyOutput := createOutput(depositsOnlyBlock.Hash()) + depositsOnlyOutputRoot := eth.OutputRoot(depositsOnlyOutput) tasksStub.ExpectBuildDepositOnlyBlock(common.Hash{}, agreedSuperRoot.Chains[chainIndexToReplace].Output, depositsOnlyBlock.Hash(), depositsOnlyOutputRoot) finalRoots[chainIndexToReplace] = depositsOnlyOutputRoot + // stub the preimages in the replacement block + l2PreimageOracle.Blocks[depositsOnlyBlock.Hash()] = depositsOnlyBlock + l2PreimageOracle.BlockData[depositsOnlyBlock.Hash()] = depositsOnlyBlock + l2PreimageOracle.Outputs[common.Hash(depositsOnlyOutputRoot)] = depositsOnlyOutput + l2PreimageOracle.Receipts[depositsOnlyBlock.Hash()] = depositsOnlyBlockReceipts } } expectedClaim := common.Hash(eth.SuperRoot(ð.SuperV1{ @@ -538,6 +579,7 @@ func (t *stubTasks) BuildDepositOnlyBlock( l1Oracle l1.Oracle, l2Oracle l2.Oracle, optimisticBlock *gethTypes.Block, + db l2.KeyValueStore, ) (common.Hash, eth.Bytes32, error) { out := t.Mock.Called( logger, @@ -548,6 +590,7 @@ func (t *stubTasks) BuildDepositOnlyBlock( l1Oracle, l2Oracle, optimisticBlock, + db, ) return out.Get(0).(common.Hash), out.Get(1).(eth.Bytes32), nil } @@ -568,6 +611,7 @@ func (t *stubTasks) ExpectBuildDepositOnlyBlock( mock.Anything, mock.Anything, mock.Anything, + mock.Anything, ).Once().Return(depositOnlyBlockHash, depositOnlyOutputRoot, nil) } diff --git a/op-program/client/interop/oracle.go b/op-program/client/interop/oracle.go new file mode 100644 index 00000000000..26c18445350 --- /dev/null +++ b/op-program/client/interop/oracle.go @@ -0,0 +1,154 @@ +package interop + +import ( + "fmt" + + preimage "github.com/ethereum-optimism/optimism/op-preimage" + interopTypes "github.com/ethereum-optimism/optimism/op-program/client/interop/types" + "github.com/ethereum-optimism/optimism/op-program/client/l2" + l2Types "github.com/ethereum-optimism/optimism/op-program/client/l2/types" + "github.com/ethereum-optimism/optimism/op-program/client/mpt" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/rlp" +) + +// ConsolidateOracle extends another l2.Oracle with consolidated state data. +// The consolidated state data includes data from deposits-only replacement blocks. +type ConsolidateOracle struct { + o l2.Oracle + db l2.KeyValueStore + ts *interopTypes.TransitionState +} + +var _ l2.Oracle = &ConsolidateOracle{} + +func NewConsolidateOracle(oracle l2.Oracle, transitionState *interopTypes.TransitionState) *ConsolidateOracle { + return &ConsolidateOracle{ + o: oracle, + db: memorydb.New(), + ts: transitionState, + } +} + +func (o *ConsolidateOracle) BlockByHash(blockHash common.Hash, chainID eth.ChainID) *types.Block { + block := o.consolidatedBlockByHash(blockHash) + if block != nil { + return block + } + return o.o.BlockByHash(blockHash, chainID) +} + +func (o *ConsolidateOracle) OutputByRoot(root common.Hash, chainID eth.ChainID) eth.Output { + key := preimage.Keccak256Key(root).PreimageKey() + b, err := o.db.Get(key[:]) + if err == nil { + output, err := eth.UnmarshalOutput(b) + if err != nil { + panic(fmt.Errorf("invalid output %s: %w", root, err)) + } + return output + } + return o.o.OutputByRoot(root, chainID) +} + +func (o *ConsolidateOracle) BlockDataByHash(agreedBlockHash, blockHash common.Hash, chainID eth.ChainID) *types.Block { + block := o.consolidatedBlockByHash(blockHash) + if block != nil { + return block + } + return o.o.BlockDataByHash(agreedBlockHash, blockHash, chainID) +} + +func (o *ConsolidateOracle) ReceiptsByBlockHash(blockHash common.Hash, chainID eth.ChainID) (*types.Block, types.Receipts) { + block := o.consolidatedBlockByHash(blockHash) + if block != nil { + opaqueReceipts := mpt.ReadTrie(block.ReceiptHash(), func(key common.Hash) []byte { + k := preimage.Keccak256Key(key).PreimageKey() + b, err := o.db.Get(k[:]) + if err != nil { + panic(fmt.Errorf("missing receipt trie node %s: %w", key, err)) + } + return b + }) + txHashes := make([]common.Hash, len(block.Transactions())) + for i, tx := range block.Transactions() { + txHashes[i] = tx.Hash() + } + receipts, err := eth.DecodeRawReceipts(eth.ToBlockID(block), opaqueReceipts, txHashes) + if err != nil { + panic(fmt.Errorf("failed to decode receipts for block %v: %w", block.Hash(), err)) + } + return block, receipts + } + + return o.o.ReceiptsByBlockHash(blockHash, chainID) +} + +func (o *ConsolidateOracle) NodeByHash(nodeHash common.Hash, chainID eth.ChainID) []byte { + node, err := o.db.Get(nodeHash[:]) + if err == nil { + return node + } + return o.o.NodeByHash(nodeHash, chainID) +} + +func (o *ConsolidateOracle) CodeByHash(codeHash common.Hash, chainID eth.ChainID) []byte { + code, err := o.db.Get(codeHash[:]) + if err == nil { + return code + } + return o.o.CodeByHash(codeHash, chainID) +} + +func (o *ConsolidateOracle) Hinter() l2Types.OracleHinter { + return o.o.Hinter() +} + +func (o *ConsolidateOracle) TransitionStateByRoot(root common.Hash) *interopTypes.TransitionState { + return o.ts +} + +func (o *ConsolidateOracle) headerByBlockHash(blockHash common.Hash) *types.Header { + blockHashKey := preimage.Keccak256Key(blockHash).PreimageKey() + headerRlp, err := o.db.Get(blockHashKey[:]) + if err != nil { + return nil + } + var header types.Header + if err := rlp.DecodeBytes(headerRlp, &header); err != nil { + panic(fmt.Errorf("invalid block header %s: %w", blockHash, err)) + } + return &header +} + +func (o *ConsolidateOracle) loadTransactions(txHash common.Hash) []*types.Transaction { + opaqueTxs := mpt.ReadTrie(txHash, func(key common.Hash) []byte { + k := preimage.Keccak256Key(key).PreimageKey() + b, err := o.db.Get(k[:]) + if err != nil { + panic(fmt.Errorf("missing tx trie node %s", key)) + } + return b + }) + txs, err := eth.DecodeTransactions(opaqueTxs) + if err != nil { + panic(fmt.Errorf("failed to decode list of txs: %w", err)) + } + return txs +} + +func (o *ConsolidateOracle) consolidatedBlockByHash(blockHash common.Hash) *types.Block { + header := o.headerByBlockHash(blockHash) + if header == nil { + return nil + } + txs := o.loadTransactions(header.TxHash) + return types.NewBlockWithHeader(header).WithBody(types.Body{Transactions: txs}) +} + +func (o *ConsolidateOracle) KeyValueStore() l2.KeyValueStore { + return o.db +} diff --git a/op-program/client/interop/oracle_test.go b/op-program/client/interop/oracle_test.go new file mode 100644 index 00000000000..abcfb10e336 --- /dev/null +++ b/op-program/client/interop/oracle_test.go @@ -0,0 +1,253 @@ +package interop + +import ( + "math/rand" + "testing" + + preimage "github.com/ethereum-optimism/optimism/op-preimage" + interopTypes "github.com/ethereum-optimism/optimism/op-program/client/interop/types" + "github.com/ethereum-optimism/optimism/op-program/client/l2" + l2Types "github.com/ethereum-optimism/optimism/op-program/client/l2/types" + "github.com/ethereum-optimism/optimism/op-program/client/mpt" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testutils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + gethTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "github.com/stretchr/testify/require" + + "github.com/stretchr/testify/mock" +) + +func TestConsolidateOracle_NoConsolidatedData(t *testing.T) { + chainID := uint64(48294) + rng := rand.New(rand.NewSource(1)) + + t.Run("BlockByHash", func(t *testing.T) { + mock := new(OracleMock) + oracle := NewConsolidateOracle(mock, &interopTypes.TransitionState{}) + block, _ := testutils.RandomBlock(rng, 1) + mock.On("BlockByHash", block.Hash(), eth.ChainIDFromUInt64(chainID)).Return(block) + actual := oracle.BlockByHash(block.Hash(), eth.ChainIDFromUInt64(chainID)) + require.Equal(t, block, actual) + mock.AssertExpectations(t) + }) + t.Run("OutputByRoot", func(t *testing.T) { + mock := new(OracleMock) + oracle := NewConsolidateOracle(mock, &interopTypes.TransitionState{}) + root := common.Hash{0xaa} + output := testutils.RandomOutputV0(rng) + mock.On("OutputByRoot", root, eth.ChainIDFromUInt64(chainID)).Return(output) + actual := oracle.OutputByRoot(root, eth.ChainIDFromUInt64(chainID)) + require.Equal(t, output, actual) + mock.AssertExpectations(t) + }) + t.Run("BlockDataByHash", func(t *testing.T) { + mock := new(OracleMock) + oracle := NewConsolidateOracle(mock, &interopTypes.TransitionState{}) + block, _ := testutils.RandomBlock(rng, 1) + mock.On("BlockDataByHash", block.Hash(), block.Hash(), eth.ChainIDFromUInt64(chainID)).Return(block) + actual := oracle.BlockDataByHash(block.Hash(), block.Hash(), eth.ChainIDFromUInt64(chainID)) + require.Equal(t, block, actual) + mock.AssertExpectations(t) + }) + t.Run("ReceiptsByBlockHash", func(t *testing.T) { + mock := new(OracleMock) + oracle := NewConsolidateOracle(mock, &interopTypes.TransitionState{}) + block, receipts := testutils.RandomBlock(rng, 1) + mock.On("ReceiptsByBlockHash", block.Hash(), eth.ChainIDFromUInt64(chainID)).Return(block, types.Receipts(receipts)) + actual, actualReceipts := oracle.ReceiptsByBlockHash(block.Hash(), eth.ChainIDFromUInt64(chainID)) + require.Equal(t, block, actual) + require.Equal(t, types.Receipts(receipts), actualReceipts) + mock.AssertExpectations(t) + }) + t.Run("NodeByHash", func(t *testing.T) { + mock := new(OracleMock) + oracle := NewConsolidateOracle(mock, &interopTypes.TransitionState{}) + node := []byte{12, 3, 4} + hash := common.Hash{0xaa} + mock.On("NodeByHash", hash, eth.ChainIDFromUInt64(chainID)).Return(node) + actual := oracle.NodeByHash(hash, eth.ChainIDFromUInt64(chainID)) + require.Equal(t, node, actual) + mock.AssertExpectations(t) + }) + t.Run("CodeByHash", func(t *testing.T) { + mock := new(OracleMock) + oracle := NewConsolidateOracle(mock, &interopTypes.TransitionState{}) + code := []byte{12, 3, 4} + hash := common.Hash{0xaa} + mock.On("CodeByHash", hash, eth.ChainIDFromUInt64(chainID)).Return(code) + actual := oracle.CodeByHash(hash, eth.ChainIDFromUInt64(chainID)) + require.Equal(t, code, actual) + mock.AssertExpectations(t) + }) + t.Run("TransitionStateByRoot", func(t *testing.T) { + mock := new(OracleMock) + ts := &interopTypes.TransitionState{SuperRoot: []byte{0xbb}} + oracle := NewConsolidateOracle(mock, ts) + root := common.Hash{0xaa} + actual := oracle.TransitionStateByRoot(root) + require.Equal(t, ts, actual) + mock.AssertExpectations(t) + }) + t.Run("Hinter", func(t *testing.T) { + mock := new(OracleMock) + oracle := NewConsolidateOracle(mock, &interopTypes.TransitionState{}) + mock.On("Hinter").Return(new(OracleHinterStub)) + actual := oracle.Hinter() + require.Equal(t, new(OracleHinterStub), actual) + mock.AssertExpectations(t) + }) +} + +func TestConsolidateOracle_WithConsolidatedData(t *testing.T) { + chainID := eth.ChainIDFromUInt64(48294) + rng := rand.New(rand.NewSource(1)) + block, receipts := testutils.RandomBlock(rng, 1) + mock := new(OracleMock) + defer mock.AssertExpectations(t) + + t.Run("BlockByHash", func(t *testing.T) { + oracle := NewConsolidateOracle(mock, &interopTypes.TransitionState{}) + db := oracle.KeyValueStore() + storeBlock(t, db, block, receipts) + + actual := oracle.BlockByHash(block.Hash(), chainID) + require.Equal(t, block.Hash(), actual.Hash()) + + require.Equal(t, len(block.Transactions()), len(actual.Transactions())) + for i := range block.Transactions() { + require.Equal(t, block.Transactions()[i].Hash(), actual.Transactions()[i].Hash()) + } + }) + t.Run("ReceiptsByBlockHash", func(t *testing.T) { + oracle := NewConsolidateOracle(mock, &interopTypes.TransitionState{}) + db := oracle.KeyValueStore() + storeBlock(t, db, block, receipts) + + actual, actualReceipts := oracle.ReceiptsByBlockHash(block.Hash(), chainID) + require.Equal(t, block.Hash(), actual.Hash()) + require.Equal(t, len(receipts), len(actualReceipts)) + for i := range receipts { + // compare only consensus fields + a, err := receipts[i].MarshalBinary() + require.NoError(t, err) + b, err := actualReceipts[i].MarshalBinary() + require.NoError(t, err) + require.Equal(t, a, b) + } + }) + t.Run("OutputByRoot", func(t *testing.T) { + oracle := NewConsolidateOracle(mock, &interopTypes.TransitionState{}) + db := oracle.KeyValueStore() + key := common.Hash{0xaa} + dbKey := preimage.Keccak256Key(key).PreimageKey() + output := testutils.RandomOutputV0(rng).Marshal() + require.NoError(t, db.Put(dbKey[:], output)) + + actual := oracle.OutputByRoot(key, chainID) + require.Equal(t, output, actual.Marshal()) + }) + t.Run("NodeByHash", func(t *testing.T) { + oracle := NewConsolidateOracle(mock, &interopTypes.TransitionState{}) + db := oracle.KeyValueStore() + key := common.Hash{0xaa} + require.NoError(t, db.Put(key[:], []byte{1, 2, 3})) + storeBlock(t, db, block, receipts) + + actual := oracle.NodeByHash(key, chainID) + require.Equal(t, []byte{1, 2, 3}, actual) + }) + t.Run("CodeByHash", func(t *testing.T) { + oracle := NewConsolidateOracle(mock, &interopTypes.TransitionState{}) + db := oracle.KeyValueStore() + key := common.Hash{0xaa} + require.NoError(t, db.Put(key[:], []byte{1, 2, 3})) + storeBlock(t, db, block, receipts) + + actual := oracle.CodeByHash(key, chainID) + require.Equal(t, []byte{1, 2, 3}, actual) + }) +} + +func storeBlock(t *testing.T, kv l2.KeyValueStore, block *types.Block, receipts types.Receipts) { + opaqueRcpts, err := eth.EncodeReceipts(receipts) + require.NoError(t, err) + _, nodes := mpt.WriteTrie(opaqueRcpts) + for _, node := range nodes { + key := preimage.Keccak256Key(crypto.Keccak256Hash(node)).PreimageKey() + require.NoError(t, kv.Put(key[:], node)) + } + + opaqueTxs, err := eth.EncodeTransactions(block.Transactions()) + require.NoError(t, err) + _, txsNodes := mpt.WriteTrie(opaqueTxs) + for _, p := range txsNodes { + key := preimage.Keccak256Key(crypto.Keccak256Hash(p)).PreimageKey() + require.NoError(t, kv.Put(key[:], p)) + } + + headerRlp, err := rlp.EncodeToBytes(block.Header()) + require.NoError(t, err) + key := preimage.Keccak256Key(block.Hash()).PreimageKey() + require.NoError(t, kv.Put(key[:], headerRlp)) +} + +type OracleMock struct { + mock.Mock +} + +var _ l2.Oracle = &OracleMock{} + +func (o *OracleMock) BlockByHash(blockHash common.Hash, chainID eth.ChainID) *gethTypes.Block { + args := o.Called(blockHash, chainID) + return args.Get(0).(*gethTypes.Block) +} + +func (o *OracleMock) OutputByRoot(root common.Hash, chainID eth.ChainID) eth.Output { + args := o.Called(root, chainID) + return args.Get(0).(eth.Output) +} + +func (o *OracleMock) BlockDataByHash(agreedBlockHash, blockHash common.Hash, chainID eth.ChainID) *gethTypes.Block { + args := o.Called(agreedBlockHash, blockHash, chainID) + return args.Get(0).(*gethTypes.Block) +} + +func (o *OracleMock) TransitionStateByRoot(root common.Hash) *interopTypes.TransitionState { + args := o.Called(root) + return args.Get(0).(*interopTypes.TransitionState) +} + +func (o *OracleMock) ReceiptsByBlockHash(blockHash common.Hash, chainID eth.ChainID) (*gethTypes.Block, gethTypes.Receipts) { + args := o.Called(blockHash, chainID) + return args.Get(0).(*gethTypes.Block), args.Get(1).(gethTypes.Receipts) +} + +func (o *OracleMock) NodeByHash(nodeHash common.Hash, chainID eth.ChainID) []byte { + args := o.Called(nodeHash, chainID) + return args.Get(0).([]byte) +} + +func (o *OracleMock) CodeByHash(codeHash common.Hash, chainID eth.ChainID) []byte { + args := o.Called(codeHash, chainID) + return args.Get(0).([]byte) +} + +func (o *OracleMock) Hinter() l2Types.OracleHinter { + args := o.Called() + return args.Get(0).(l2Types.OracleHinter) +} + +type OracleHinterStub struct { +} + +var _ l2Types.OracleHinter = &OracleHinterStub{} + +func (o *OracleHinterStub) HintBlockExecution(parentBlockHash common.Hash, attr eth.PayloadAttributes, chainID eth.ChainID) { +} + +func (o *OracleHinterStub) HintWithdrawalsRoot(blockHash common.Hash, chainID eth.ChainID) { +} diff --git a/op-program/client/tasks/deposits_block.go b/op-program/client/tasks/deposits_block.go index 4c3fd0c99c6..fceaaf485a9 100644 --- a/op-program/client/tasks/deposits_block.go +++ b/op-program/client/tasks/deposits_block.go @@ -6,12 +6,14 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/interop/managed" + preimage "github.com/ethereum-optimism/optimism/op-preimage" "github.com/ethereum-optimism/optimism/op-program/client/l1" "github.com/ethereum-optimism/optimism/op-program/client/l2" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/misc/eip1559" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" @@ -20,6 +22,7 @@ import ( // BuildDepositOnlyBlock builds a deposits-only block replacement for the specified optimistic block and returns the block hash and output root // for the new block. // The specified l2OutputRoot must be the output root of the optimistic block's parent. +// The provided l2.KeyValueStore is used to store state diff that's applied to the deposits-only block, which also includes the output root and any transaction and receipt trie nodes of the new block. func BuildDepositOnlyBlock( logger log.Logger, cfg *rollup.Config, @@ -29,8 +32,9 @@ func BuildDepositOnlyBlock( agreedL2OutputRoot eth.Bytes32, l1Oracle l1.Oracle, l2Oracle l2.Oracle, + db l2.KeyValueStore, ) (common.Hash, eth.Bytes32, error) { - engineBackend, err := l2.NewOracleBackedL2Chain(logger, l2Oracle, l1Oracle, l2Cfg, common.Hash(agreedL2OutputRoot), memorydb.New()) + engineBackend, err := l2.NewOracleBackedL2Chain(logger, l2Oracle, l1Oracle, l2Cfg, common.Hash(agreedL2OutputRoot), db) if err != nil { return common.Hash{}, eth.Bytes32{}, fmt.Errorf("failed to create oracle-backed L2 chain: %w", err) } @@ -81,11 +85,21 @@ func BuildDepositOnlyBlock( return common.Hash{}, eth.Bytes32{}, fmt.Errorf("failed to update forkchoice state (no build): %w", eth.ForkchoiceUpdateErr(result.PayloadStatus)) } - blockHash, outputRoot, err := l2Source.L2OutputRoot(uint64(payload.ExecutionPayload.BlockNumber)) + if err := storeBlockData(payload.ExecutionPayload.BlockHash, db, engineBackend); err != nil { + return common.Hash{}, eth.Bytes32{}, fmt.Errorf("failed to write tx/receipts trie nodes: %w", err) + } + output, err := l2Source.L2OutputAtBlockHash(payload.ExecutionPayload.BlockHash) if err != nil { - return common.Hash{}, eth.Bytes32{}, fmt.Errorf("failed to get L2 output root: %w", err) + return common.Hash{}, eth.Bytes32{}, fmt.Errorf("failed to get L2 output: %w", err) } - return blockHash, outputRoot, nil + marshaledOutput := output.Marshal() + outputRoot := eth.Bytes32(crypto.Keccak256Hash(marshaledOutput)) + outputRootKey := preimage.Keccak256Key(outputRoot).PreimageKey() + if err := db.Put(outputRootKey[:], marshaledOutput); err != nil { + return common.Hash{}, eth.Bytes32{}, fmt.Errorf("failed to store L2 output: %w", err) + } + + return payload.ExecutionPayload.BlockHash, outputRoot, nil } func getL2Output(logger log.Logger, cfg *rollup.Config, l2Cfg *params.ChainConfig, l2Oracle l2.Oracle, l1Oracle l1.Oracle, block *types.Block) (*eth.OutputV0, error) { diff --git a/op-program/client/tasks/derive.go b/op-program/client/tasks/derive.go index 2c2d24ec783..db251f50e2f 100644 --- a/op-program/client/tasks/derive.go +++ b/op-program/client/tasks/derive.go @@ -68,7 +68,7 @@ func RunDerivation( logger.Info("Derivation complete", "head", result) if options.StoreBlockData { - if err := storeBlockData(result, db, engineBackend); err != nil { + if err := storeBlockData(result.Hash, db, engineBackend); err != nil { return DerivationResult{}, fmt.Errorf("failed to write trie nodes: %w", err) } logger.Info("Trie nodes written") @@ -88,16 +88,16 @@ func loadOutputRoot(l2ClaimBlockNum uint64, head eth.L2BlockRef, src L2Source) ( }, nil } -func storeBlockData(derived eth.L2BlockRef, db l2.KeyValueStore, backend engineapi.CachingEngineBackend) error { - block := backend.GetBlockByHash(derived.Hash) +func storeBlockData(derivedBlockHash common.Hash, db l2.KeyValueStore, backend engineapi.CachingEngineBackend) error { + block := backend.GetBlockByHash(derivedBlockHash) if block == nil { - return fmt.Errorf("derived block %v is missing", derived.Hash) + return fmt.Errorf("derived block %v is missing", derivedBlockHash) } headerRLP, err := rlp.EncodeToBytes(block.Header()) if err != nil { return fmt.Errorf("failed to encode block header: %w", err) } - blockHashKey := preimage.Keccak256Key(derived.Hash).PreimageKey() + blockHashKey := preimage.Keccak256Key(derivedBlockHash).PreimageKey() if err := db.Put(blockHashKey[:], headerRLP); err != nil { return fmt.Errorf("failed to store block header: %w", err) } From 580ad4bd3de3849a7d066bddebcead0c29390d57 Mon Sep 17 00:00:00 2001 From: Yann Hodique Date: Wed, 5 Mar 2025 04:12:54 -0600 Subject: [PATCH 065/130] fix(devnet-sdk): allow in-place redeployment (#14412) Currently, the last step of the deployment (storing the devnet descriptor into the enclave) fails the 2nd time around. As kurtosis API doesn't allow us to overwrite an artifact, we sidestep the issue by storing successive generations of the descriptor. Also adjust the environment fetcher to detect and load the last generation. --- devnet-sdk/kt/fs/fs.go | 58 ++++++ devnet-sdk/kt/fs/fs_test.go | 7 + devnet-sdk/shell/env/kt_fetch.go | 58 +++++- devnet-sdk/shell/env/kt_fetch_test.go | 92 ++++++++- devnet-sdk/testing/systest/provider.go | 3 - devnet-sdk/testing/systest/systest.go | 68 ++++--- devnet-sdk/testing/systest/systest_test.go | 193 ++++++++++++------- devnet-sdk/testing/systest/testing_test.go | 210 ++++++++++++++------- kurtosis-devnet/pkg/deploy/deploy.go | 45 ++++- kurtosis-devnet/pkg/deploy/deploy_test.go | 107 +++++++++++ 10 files changed, 678 insertions(+), 163 deletions(-) diff --git a/devnet-sdk/kt/fs/fs.go b/devnet-sdk/kt/fs/fs.go index cd4841c1f71..e1e4d9d3f5d 100644 --- a/devnet-sdk/kt/fs/fs.go +++ b/devnet-sdk/kt/fs/fs.go @@ -5,16 +5,19 @@ import ( "bytes" "compress/gzip" "context" + "fmt" "io" "os" "path/filepath" + "github.com/kurtosis-tech/kurtosis/api/golang/core/kurtosis_core_rpc_api_bindings" "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/services" "github.com/kurtosis-tech/kurtosis/api/golang/engine/lib/kurtosis_context" ) // EnclaveContextIface abstracts the EnclaveContext for testing type EnclaveContextIface interface { + GetAllFilesArtifactNamesAndUuids(ctx context.Context) ([]*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid, error) DownloadFilesArtifact(ctx context.Context, name string) ([]byte, error) UploadFiles(pathToUpload string, artifactName string) (services.FilesArtifactUUID, services.FileArtifactName, error) } @@ -46,6 +49,20 @@ type Artifact struct { reader *tar.Reader } +func (fs *EnclaveFS) GetAllArtifactNames(ctx context.Context) ([]string, error) { + artifacts, err := fs.enclaveCtx.GetAllFilesArtifactNamesAndUuids(ctx) + if err != nil { + return nil, err + } + + names := make([]string, len(artifacts)) + for i, artifact := range artifacts { + names[i] = artifact.GetFileName() + } + + return names, nil +} + func (fs *EnclaveFS) GetArtifact(ctx context.Context, name string) (*Artifact, error) { artifact, err := fs.enclaveCtx.DownloadFilesArtifact(ctx, name) if err != nil { @@ -73,6 +90,47 @@ func NewArtifactFileWriter(path string, writer io.Writer) *ArtifactFileWriter { } } +func (a *Artifact) Download(path string) error { + for { + header, err := a.reader.Next() + if err == io.EOF { + return nil + } + if err != nil { + return fmt.Errorf("failed to read tar header: %w", err) + } + + fpath := filepath.Join(path, filepath.Clean(header.Name)) + + switch header.Typeflag { + case tar.TypeDir: + if err := os.MkdirAll(fpath, os.FileMode(header.Mode)); err != nil { + return fmt.Errorf("failed to create directory %s: %w", fpath, err) + } + case tar.TypeReg: + // Create parent directories if they don't exist + if err := os.MkdirAll(filepath.Dir(fpath), 0755); err != nil { + return fmt.Errorf("failed to create directory for %s: %w", fpath, err) + } + + // Create the file + f, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY, os.FileMode(header.Mode)) + if err != nil { + return fmt.Errorf("failed to create file %s: %w", fpath, err) + } + + // Copy contents from tar reader to file + if _, err := io.Copy(f, a.reader); err != nil { + f.Close() + return fmt.Errorf("failed to write contents to %s: %w", fpath, err) + } + f.Close() + default: + return fmt.Errorf("unsupported file type %d for %s", header.Typeflag, header.Name) + } + } +} + func (a *Artifact) ExtractFiles(writers ...*ArtifactFileWriter) error { paths := make(map[string]io.Writer) for _, writer := range writers { diff --git a/devnet-sdk/kt/fs/fs_test.go b/devnet-sdk/kt/fs/fs_test.go index 94c2401653c..d202e544b6f 100644 --- a/devnet-sdk/kt/fs/fs_test.go +++ b/devnet-sdk/kt/fs/fs_test.go @@ -9,6 +9,7 @@ import ( "path/filepath" "testing" + "github.com/kurtosis-tech/kurtosis/api/golang/core/kurtosis_core_rpc_api_bindings" "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/services" "github.com/stretchr/testify/require" ) @@ -53,6 +54,12 @@ func (m *mockEnclaveContext) UploadFiles(pathToUpload string, artifactName strin return "test-uuid", services.FileArtifactName(artifactName), err } +func (m *mockEnclaveContext) GetAllFilesArtifactNamesAndUuids(ctx context.Context) ([]*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid, error) { + return nil, nil +} + +var _ EnclaveContextIface = (*mockEnclaveContext)(nil) + func createTarGzArtifact(t *testing.T, files map[string]string) []byte { var buf bytes.Buffer gzWriter := gzip.NewWriter(&buf) diff --git a/devnet-sdk/shell/env/kt_fetch.go b/devnet-sdk/shell/env/kt_fetch.go index 2a8639ff1d8..d2a58b2aff0 100644 --- a/devnet-sdk/shell/env/kt_fetch.go +++ b/devnet-sdk/shell/env/kt_fetch.go @@ -11,13 +11,14 @@ import ( ) const ( - KurtosisDevnetEnvArtifactName = "devnet" - KurtosisDevnetEnvArtifactPath = "env.json" + KurtosisDevnetEnvArtifactNamePrefix = "devnet-descriptor-" + KurtosisDevnetEnvArtifactPath = "env.json" ) // EnclaveFS is an interface that both our mock and the real implementation satisfy type EnclaveFS interface { GetArtifact(ctx context.Context, name string) (*ktfs.Artifact, error) + GetAllArtifactNames(ctx context.Context) ([]string, error) Close() error } @@ -30,6 +31,10 @@ func (w *enclaveFSWrapper) GetArtifact(ctx context.Context, name string) (*ktfs. return w.fs.GetArtifact(ctx, name) } +func (w *enclaveFSWrapper) GetAllArtifactNames(ctx context.Context) ([]string, error) { + return w.fs.GetAllArtifactNames(ctx) +} + func (w *enclaveFSWrapper) Close() error { // The underlying EnclaveFS doesn't have a Close method, but we need it for our interface return nil @@ -49,11 +54,11 @@ var NewEnclaveFS NewEnclaveFSFunc = func(ctx context.Context, enclave string) (E } // parseKurtosisURL parses a Kurtosis URL of the form kt://enclave/artifact/file -// If artifact is omitted, it defaults to "devnet" +// If artifact is omitted, it defaults to "" // If file is omitted, it defaults to "env.json" func parseKurtosisURL(u *url.URL) (enclave, artifactName, fileName string) { enclave = u.Host - artifactName = KurtosisDevnetEnvArtifactName + artifactName = "" fileName = KurtosisDevnetEnvArtifactPath // Trim both prefix and suffix slashes before splitting @@ -69,6 +74,43 @@ func parseKurtosisURL(u *url.URL) (enclave, artifactName, fileName string) { return } +func getDefaultDescriptor(ctx context.Context, fs EnclaveFS) (string, error) { + prefix := KurtosisDevnetEnvArtifactNamePrefix + + names, err := fs.GetAllArtifactNames(ctx) + if err != nil { + return "", err + } + + var maxSuffix int + var maxName string + for _, name := range names { + if !strings.HasPrefix(name, prefix) { + continue + } + + // Extract the suffix after the prefix + suffix := name[len(prefix):] + // Parse the suffix as a number + var num int + if _, err := fmt.Sscanf(suffix, "%d", &num); err != nil { + continue // Skip if suffix is not a valid number + } + + // Update maxName if this number is larger + if maxName == "" || num > maxSuffix { + maxSuffix = num + maxName = name + } + } + + if maxName == "" { + return "", fmt.Errorf("no descriptor found with valid numerical suffix") + } + + return maxName, nil +} + // fetchKurtosisData reads data from a Kurtosis artifact func fetchKurtosisData(u *url.URL) (string, []byte, error) { enclave, artifactName, fileName := parseKurtosisURL(u) @@ -78,6 +120,14 @@ func fetchKurtosisData(u *url.URL) (string, []byte, error) { return "", nil, fmt.Errorf("error creating enclave fs: %w", err) } + if artifactName == "" { + artifactName, err = getDefaultDescriptor(context.Background(), fs) + if err != nil { + return "", nil, fmt.Errorf("error getting default descriptor: %w", err) + } + fmt.Printf("Using default descriptor: %s\n", artifactName) + } + art, err := fs.GetArtifact(context.Background(), artifactName) if err != nil { return "", nil, fmt.Errorf("error getting artifact: %w", err) diff --git a/devnet-sdk/shell/env/kt_fetch_test.go b/devnet-sdk/shell/env/kt_fetch_test.go index 8580e5ee352..9ef67d37dbf 100644 --- a/devnet-sdk/shell/env/kt_fetch_test.go +++ b/devnet-sdk/shell/env/kt_fetch_test.go @@ -12,20 +12,38 @@ import ( ) // testFS implements EnclaveFS for testing -type testFS struct{} +type testFS struct { + artifacts map[string]bool +} func (m *testFS) GetArtifact(_ context.Context, name string) (*ktfs.Artifact, error) { if name == "error" { return nil, fmt.Errorf("mock error") } + if !m.artifacts[name] { + return nil, fmt.Errorf("artifact %s not found", name) + } // We don't need to return a real artifact since we're only testing error cases return nil, nil } +func (m *testFS) GetAllArtifactNames(_ context.Context) ([]string, error) { + if m.artifacts == nil { + return nil, nil + } + names := make([]string, 0, len(m.artifacts)) + for name := range m.artifacts { + names = append(names, name) + } + return names, nil +} + func (m *testFS) Close() error { return nil } +var _ EnclaveFS = (*testFS)(nil) + func TestParseKurtosisURL(t *testing.T) { tests := []struct { name string @@ -39,7 +57,7 @@ func TestParseKurtosisURL(t *testing.T) { name: "basic url", urlStr: "kt://myenclave", wantEnclave: "myenclave", - wantArtifact: "devnet", + wantArtifact: "", wantFile: "env.json", }, { @@ -106,11 +124,20 @@ func TestFetchKurtosisDataErrors(t *testing.T) { name: "error getting artifact", setupMock: func() { NewEnclaveFS = func(_ context.Context, _ string) (EnclaveFS, error) { - return &testFS{}, nil + return &testFS{artifacts: map[string]bool{"error": true}}, nil } }, urlStr: "kt://myenclave/error", }, + { + name: "no default descriptor", + setupMock: func() { + NewEnclaveFS = func(_ context.Context, _ string) (EnclaveFS, error) { + return &testFS{artifacts: map[string]bool{}}, nil + } + }, + urlStr: "kt://myenclave", + }, } for _, tt := range tests { @@ -128,3 +155,62 @@ func TestFetchKurtosisDataErrors(t *testing.T) { }) } } + +func TestGetDefaultDescriptor(t *testing.T) { + tests := []struct { + name string + artifacts map[string]bool + wantName string + wantErrText string + }{ + { + name: "finds highest numbered descriptor", + artifacts: map[string]bool{ + "devnet-descriptor-1": true, + "devnet-descriptor-5": true, + "devnet-descriptor-10": true, + "other": true, + }, + wantName: "devnet-descriptor-10", + }, + { + name: "handles non-numeric suffixes", + artifacts: map[string]bool{ + "devnet-descriptor-1": true, + "devnet-descriptor-5": true, + "devnet-descriptor-abc": true, + "devnet-descriptor-10": true, + "other": true, + "devnet-descriptor-def": true, + }, + wantName: "devnet-descriptor-10", + }, + { + name: "no descriptors", + artifacts: map[string]bool{}, + wantErrText: "no descriptor found with valid numerical suffix", + }, + { + name: "no valid descriptors", + artifacts: map[string]bool{ + "other": true, + "devnet-abc": true, + }, + wantErrText: "no descriptor found with valid numerical suffix", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fs := &testFS{artifacts: tt.artifacts} + got, err := getDefaultDescriptor(context.Background(), fs) + if tt.wantErrText != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErrText) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantName, got) + }) + } +} diff --git a/devnet-sdk/testing/systest/provider.go b/devnet-sdk/testing/systest/provider.go index 76974c54c50..87da920d0d0 100644 --- a/devnet-sdk/testing/systest/provider.go +++ b/devnet-sdk/testing/systest/provider.go @@ -13,6 +13,3 @@ type defaultProvider struct{} func (p *defaultProvider) NewSystemFromURL(url string) (system.System, error) { return system.NewSystemFromURL(url) } - -// currentPackage is the current package implementation -var currentPackage systemProvider = &defaultProvider{} diff --git a/devnet-sdk/testing/systest/systest.go b/devnet-sdk/testing/systest/systest.go index 587b35a8985..1291fe8683b 100644 --- a/devnet-sdk/testing/systest/systest.go +++ b/devnet-sdk/testing/systest/systest.go @@ -47,12 +47,6 @@ func (e *PreconditionError) Unwrap() error { // Any other result indicates this acquirer was selected and its result (success or failure) should be used. type SystemAcquirer func(t BasicT) (system.System, error) -// systemAcquirers is the list of ways to acquire a system, tried in order -var systemAcquirers = []SystemAcquirer{ - acquireFromEnvURL, - // Add more acquirers here as needed -} - // tryAcquirers attempts to acquire a system using the provided acquirers in order. // Each acquirer is tried in sequence until one returns a non-(nil,nil) result. // If an acquirer returns (nil, nil), it is skipped and the next one is tried. @@ -70,19 +64,6 @@ func tryAcquirers(t BasicT, acquirers []SystemAcquirer) (system.System, error) { return nil, fmt.Errorf("no acquirer was able to create a system") } -// acquireFromEnvURL attempts to create a system from the URL specified in the environment variable. -func acquireFromEnvURL(t BasicT) (system.System, error) { - url := os.Getenv(env.EnvURLVar) - if url == "" { - return nil, nil // Skip this acquirer - } - sys, err := currentPackage.NewSystemFromURL(url) - if err != nil { - return nil, fmt.Errorf("failed to create system from URL %q: %w", url, err) - } - return sys, nil -} - type PreconditionValidator func(t T, sys system.System) (context.Context, error) type SystemTestFunc func(t T, sys system.System) type InteropSystemTestFunc func(t T, sys system.InteropSystem) @@ -91,11 +72,29 @@ type InteropSystemTestFunc func(t T, sys system.InteropSystem) type systemTestHelper interface { SystemTest(t BasicT, f SystemTestFunc, validators ...PreconditionValidator) InteropSystemTest(t BasicT, f InteropSystemTestFunc, validators ...PreconditionValidator) + WithAcquirers(acquirers []SystemAcquirer) *basicSystemTestHelper + WithProvider(provider systemProvider) *basicSystemTestHelper } // basicSystemTestHelper provides a basic implementation of systemTestHelper using environment variables type basicSystemTestHelper struct { expectPreconditionsMet bool + acquirers []SystemAcquirer + provider systemProvider + envGetter envGetter +} + +// acquireFromEnvURL attempts to create a system from the URL specified in the environment variable. +func (h *basicSystemTestHelper) acquireFromEnvURL(t BasicT) (system.System, error) { + url := h.envGetter.Getenv(env.EnvURLVar) + if url == "" { + return nil, nil // Skip this acquirer + } + sys, err := h.provider.NewSystemFromURL(url) + if err != nil { + return nil, fmt.Errorf("failed to create system from URL %q: %w", url, err) + } + return sys, nil } func (h *basicSystemTestHelper) handlePreconditionError(t BasicT, err error) { @@ -117,9 +116,10 @@ func (h *basicSystemTestHelper) SystemTest(t BasicT, f SystemTestFunc, validator wt = wt.WithContext(ctx) - sys, err := tryAcquirers(t, systemAcquirers) + sys, err := tryAcquirers(t, h.acquirers) if err != nil { - t.Fatalf("failed to acquire system: %v", err) + h.handlePreconditionError(t, err) + return } for _, validator := range validators { @@ -151,9 +151,33 @@ func newBasicSystemTestHelper(envGetter envGetter) *basicSystemTestHelper { if err != nil { expectPreconditionsMet = false // empty string or invalid value returns false } - return &basicSystemTestHelper{ + + helper := &basicSystemTestHelper{ expectPreconditionsMet: expectPreconditionsMet, + provider: &defaultProvider{}, + envGetter: envGetter, } + + // Set up acquirers after helper is constructed so we can use the method + helper.acquirers = []SystemAcquirer{ + helper.acquireFromEnvURL, + } + + return helper +} + +// WithAcquirers returns a new helper with the specified acquirers +func (h *basicSystemTestHelper) WithAcquirers(acquirers []SystemAcquirer) *basicSystemTestHelper { + newHelper := *h + newHelper.acquirers = acquirers + return &newHelper +} + +// WithProvider returns a new helper with the specified provider +func (h *basicSystemTestHelper) WithProvider(provider systemProvider) *basicSystemTestHelper { + newHelper := *h + newHelper.provider = provider + return &newHelper } // SystemTest delegates to the default helper diff --git a/devnet-sdk/testing/systest/systest_test.go b/devnet-sdk/testing/systest/systest_test.go index ae82f911939..2f657d3306a 100644 --- a/devnet-sdk/testing/systest/systest_test.go +++ b/devnet-sdk/testing/systest/systest_test.go @@ -16,6 +16,7 @@ type mockSystemTestHelper struct { systemTestCalls int interopTestCalls int preconditionErrors []error + systemAcquirer func() (system.System, error) } func (h *mockSystemTestHelper) handlePreconditionError(t BasicT, err error) { @@ -30,12 +31,17 @@ func (h *mockSystemTestHelper) handlePreconditionError(t BasicT, err error) { func (h *mockSystemTestHelper) SystemTest(t BasicT, f SystemTestFunc, validators ...PreconditionValidator) { h.systemTestCalls++ wt := NewT(t) - sys := newMockSystem() ctx, cancel := context.WithCancel(wt.Context()) defer cancel() wt = wt.WithContext(ctx) + sys, err := h.systemAcquirer() + if err != nil { + h.handlePreconditionError(t, err) + return + } + for _, validator := range validators { ctx, err := validator(wt, sys) if err != nil { @@ -50,23 +56,13 @@ func (h *mockSystemTestHelper) SystemTest(t BasicT, f SystemTestFunc, validators func (h *mockSystemTestHelper) InteropSystemTest(t BasicT, f InteropSystemTestFunc, validators ...PreconditionValidator) { h.interopTestCalls++ - wt := NewT(t) - sys := newMockInteropSystem() - - ctx, cancel := context.WithCancel(wt.Context()) - defer cancel() - wt = wt.WithContext(ctx) - - for _, validator := range validators { - ctx, err := validator(wt, sys) - if err != nil { - h.handlePreconditionError(t, err) - return + h.SystemTest(t, func(t T, sys system.System) { + if sys, ok := sys.(system.InteropSystem); ok { + f(t, sys) + } else { + h.handlePreconditionError(t, fmt.Errorf("interop test requested, but system is not an interop system")) } - wt = wt.WithContext(ctx) - } - - f(wt, sys) + }, validators...) } // mockEnvGetter implements envGetter for testing @@ -118,74 +114,138 @@ func TestSystemTestHelper(t *testing.T) { // TestSystemTest tests the main SystemTest function func TestSystemTest(t *testing.T) { - withTestSystem(t, func() (system.System, error) { - return newMockSystem(), nil - }, func(t *testing.T) { - t.Run("basic system test", func(t *testing.T) { - called := false - SystemTest(t, func(t T, sys system.System) { - called = true - require.NotNil(t, sys) + t.Run("system acquisition failure", func(t *testing.T) { + testCases := []struct { + name string + expectMet bool + expectSkip bool + expectFatal bool + }{ + { + name: "preconditions not expected skips test", + expectMet: false, + expectSkip: true, + expectFatal: false, + }, + { + name: "preconditions expected fails test", + expectMet: true, + expectSkip: false, + expectFatal: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + helper := &mockSystemTestHelper{ + expectPreconditionsMet: tc.expectMet, + systemAcquirer: func() (system.System, error) { + return nil, fmt.Errorf("failed to acquire system") + }, + } + + recorder := &mockTBRecorder{mockTB: mockTB{name: "test"}} + helper.SystemTest(recorder, func(t T, sys system.System) { + t.Fatal("test function should not be called") + }) + + require.Equal(t, tc.expectSkip, recorder.skipped, "unexpected skip state") + require.Equal(t, tc.expectFatal, recorder.failed, "unexpected fatal state") + require.Len(t, helper.preconditionErrors, 1, "expected one precondition error") + require.Contains(t, helper.preconditionErrors[0].Error(), "failed to acquire system") }) - require.True(t, called) + } + }) + + t.Run("successful system acquisition", func(t *testing.T) { + helper := &mockSystemTestHelper{ + systemAcquirer: func() (system.System, error) { + return newMockSystem(), nil + }, + } + + called := false + helper.SystemTest(t, func(t T, sys system.System) { + called = true + require.NotNil(t, sys) }) + require.True(t, called) + }) - t.Run("with validator", func(t *testing.T) { - validatorCalled := false - testCalled := false + t.Run("with validator", func(t *testing.T) { + helper := &mockSystemTestHelper{ + systemAcquirer: func() (system.System, error) { + return newMockSystem(), nil + }, + } - validator := func(t T, sys system.System) (context.Context, error) { - validatorCalled = true - return t.Context(), nil - } + validatorCalled := false + testCalled := false - SystemTest(t, func(t T, sys system.System) { - testCalled = true - }, validator) + validator := func(t T, sys system.System) (context.Context, error) { + validatorCalled = true + return t.Context(), nil + } - require.True(t, validatorCalled) - require.True(t, testCalled) - }) + helper.SystemTest(t, func(t T, sys system.System) { + testCalled = true + }, validator) - t.Run("multiple validators", func(t *testing.T) { - validatorCount := 0 + require.True(t, validatorCalled) + require.True(t, testCalled) + }) - validator := func(t T, sys system.System) (context.Context, error) { - validatorCount++ - return t.Context(), nil - } + t.Run("multiple validators", func(t *testing.T) { + helper := &mockSystemTestHelper{ + systemAcquirer: func() (system.System, error) { + return newMockSystem(), nil + }, + } - SystemTest(t, func(t T, sys system.System) {}, validator, validator, validator) - require.Equal(t, 3, validatorCount) - }) + validatorCount := 0 + validator := func(t T, sys system.System) (context.Context, error) { + validatorCount++ + return t.Context(), nil + } + + helper.SystemTest(t, func(t T, sys system.System) {}, validator, validator, validator) + require.Equal(t, 3, validatorCount) }) } // TestInteropSystemTest tests the InteropSystemTest function func TestInteropSystemTest(t *testing.T) { t.Run("skips non-interop system", func(t *testing.T) { - withTestSystem(t, func() (system.System, error) { - return newMockSystem(), nil - }, func(t *testing.T) { - called := false - InteropSystemTest(t, func(t T, sys system.InteropSystem) { - called = true - }) - require.False(t, called) + helper := &mockSystemTestHelper{ + systemAcquirer: func() (system.System, error) { + return newMockSystem(), nil + }, + } + + recorder := &mockTBRecorder{mockTB: mockTB{name: "test"}} + called := false + helper.InteropSystemTest(recorder, func(t T, sys system.InteropSystem) { + called = true }) + require.False(t, called) + require.Len(t, helper.preconditionErrors, 1) + require.Contains(t, helper.preconditionErrors[0].Error(), "interop test requested") }) t.Run("runs with interop system", func(t *testing.T) { - withTestSystem(t, func() (system.System, error) { - return newMockInteropSystem(), nil - }, func(t *testing.T) { - called := false - InteropSystemTest(t, func(t T, sys system.InteropSystem) { - called = true - require.NotNil(t, sys.InteropSet()) - }) - require.True(t, called) + helper := &mockSystemTestHelper{ + systemAcquirer: func() (system.System, error) { + return newMockInteropSystem(), nil + }, + } + + called := false + helper.InteropSystemTest(t, func(t T, sys system.InteropSystem) { + called = true + require.NotNil(t, sys.InteropSet()) }) + require.True(t, called) + require.Empty(t, helper.preconditionErrors) }) } @@ -226,6 +286,9 @@ func TestPreconditionHandling(t *testing.T) { t.Run(tc.name, func(t *testing.T) { helper := &mockSystemTestHelper{ expectPreconditionsMet: tc.expectMet, + systemAcquirer: func() (system.System, error) { + return newMockSystem(), nil + }, } recorder := &mockTBRecorder{mockTB: mockTB{name: "test"}} diff --git a/devnet-sdk/testing/systest/testing_test.go b/devnet-sdk/testing/systest/testing_test.go index 62f2b5f1645..64c407233ae 100644 --- a/devnet-sdk/testing/systest/testing_test.go +++ b/devnet-sdk/testing/systest/testing_test.go @@ -140,24 +140,6 @@ func (p *testPackage) NewSystemFromURL(string) (system.System, error) { return p.creator() } -// withTestSystem runs a test with a custom system creator -func withTestSystem(t *testing.T, creator testSystemCreator, f func(t *testing.T)) { - // Save original acquirers and restore after test - origAcquirers := systemAcquirers - defer func() { - systemAcquirers = origAcquirers - }() - - // Replace acquirers with just our test creator - systemAcquirers = []SystemAcquirer{ - func(t BasicT) (system.System, error) { - return creator() - }, - } - - f(t) -} - // TestNewT tests the creation and basic functionality of the test wrapper func TestNewT(t *testing.T) { t.Run("wraps *testing.T correctly", func(t *testing.T) { @@ -290,14 +272,8 @@ func TestTryAcquirers(t *testing.T) { }) } -// Update TestSystemAcquisition to match new behavior +// TestSystemAcquisition tests the system acquisition functionality func TestSystemAcquisition(t *testing.T) { - // Save original acquirers and restore after test - origAcquirers := systemAcquirers - defer func() { - systemAcquirers = origAcquirers - }() - t.Run("uses first non-skip acquirer (success)", func(t *testing.T) { sys1, sys2 := newMockSystem(), newMockSystem() acquirers := []SystemAcquirer{ @@ -305,78 +281,184 @@ func TestSystemAcquisition(t *testing.T) { mockAcquirer(sys1, nil), // selected and succeeds mockAcquirer(sys2, nil), // not reached } - systemAcquirers = acquirers + + helper := newBasicSystemTestHelper(&mockEnvGetter{}). + WithAcquirers(acquirers) var acquiredSys system.System - SystemTest(t, func(t T, sys system.System) { + helper.SystemTest(t, func(t T, sys system.System) { acquiredSys = sys }) require.Equal(t, sys1, acquiredSys) }) t.Run("fails when selected acquirer fails", func(t *testing.T) { - expectedErr := fmt.Errorf("selected acquirer failed") - systemAcquirers = []SystemAcquirer{ - mockAcquirer(nil, nil), // skipped - mockAcquirer(nil, expectedErr), // selected and fails + testCases := []struct { + name string + expectMet bool + expectSkip bool + expectFatal bool + }{ + { + name: "preconditions not expected skips test", + expectMet: false, + expectSkip: true, + expectFatal: false, + }, + { + name: "preconditions expected fails test", + expectMet: true, + expectSkip: false, + expectFatal: true, + }, } - mock := &mockTB{name: "mock"} - SystemTest(mock, func(t T, sys system.System) { - require.Fail(t, "should not reach here") - }) - require.True(t, mock.failed) - require.Contains(t, mock.lastError, expectedErr.Error()) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + expectedErr := fmt.Errorf("selected acquirer failed") + acquirers := []SystemAcquirer{ + mockAcquirer(nil, nil), // skipped + mockAcquirer(nil, expectedErr), // selected and fails + } + + // Create a new helper with the right configuration + helper := newBasicSystemTestHelper(&mockEnvGetter{}). + WithAcquirers(acquirers) + helper.expectPreconditionsMet = tc.expectMet + + recorder := &mockTBRecorder{mockTB: mockTB{name: "test"}} + helper.SystemTest(recorder, func(t T, sys system.System) { + require.Fail(t, "should not reach here") + }) + + require.Equal(t, tc.expectSkip, recorder.skipped, "unexpected skip state") + require.Equal(t, tc.expectFatal, recorder.failed, "unexpected fatal state") + if tc.expectSkip { + require.Contains(t, recorder.skipMsg, expectedErr.Error()) + } + if tc.expectFatal { + require.Contains(t, recorder.fatalMsg, expectedErr.Error()) + } + }) + } }) t.Run("fails when all acquirers skip", func(t *testing.T) { - systemAcquirers = []SystemAcquirer{ - mockAcquirer(nil, nil), - mockAcquirer(nil, nil), + testCases := []struct { + name string + expectMet bool + expectSkip bool + expectFatal bool + }{ + { + name: "preconditions not expected skips test", + expectMet: false, + expectSkip: true, + expectFatal: false, + }, + { + name: "preconditions expected fails test", + expectMet: true, + expectSkip: false, + expectFatal: true, + }, } - mock := &mockTB{name: "mock"} - SystemTest(mock, func(t T, sys system.System) { - require.Fail(t, "should not reach here") - }) - require.True(t, mock.failed) - require.Contains(t, mock.lastError, "no acquirer was able to create a system") + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + acquirers := []SystemAcquirer{ + mockAcquirer(nil, nil), + mockAcquirer(nil, nil), + } + + // Create a new helper with the right configuration + helper := newBasicSystemTestHelper(&mockEnvGetter{}). + WithAcquirers(acquirers) + helper.expectPreconditionsMet = tc.expectMet + + recorder := &mockTBRecorder{mockTB: mockTB{name: "test"}} + helper.SystemTest(recorder, func(t T, sys system.System) { + require.Fail(t, "should not reach here") + }) + + require.Equal(t, tc.expectSkip, recorder.skipped, "unexpected skip state") + require.Equal(t, tc.expectFatal, recorder.failed, "unexpected fatal state") + if tc.expectSkip { + require.Contains(t, recorder.skipMsg, "no acquirer was able to create a system") + } + if tc.expectFatal { + require.Contains(t, recorder.fatalMsg, "no acquirer was able to create a system") + } + }) + } }) t.Run("acquireFromEnvURL behavior", func(t *testing.T) { - // Save original env var and package - origEnvFile := os.Getenv(env.EnvURLVar) - origPkg := currentPackage - defer func() { - os.Setenv(env.EnvURLVar, origEnvFile) - currentPackage = origPkg - }() + // Create a mockEnvGetter with the original env value + origEnv := &mockEnvGetter{ + values: map[string]string{ + env.EnvURLVar: os.Getenv(env.EnvURLVar), + }, + } t.Run("skips when env var not set", func(t *testing.T) { - os.Unsetenv(env.EnvURLVar) - sys, err := acquireFromEnvURL(t) + helper := newBasicSystemTestHelper(&mockEnvGetter{ + values: make(map[string]string), + }) + sys, err := helper.acquireFromEnvURL(t) require.NoError(t, err) require.Nil(t, sys) }) t.Run("fails with error for invalid URL", func(t *testing.T) { - os.Setenv(env.EnvURLVar, "invalid://url") - sys, err := acquireFromEnvURL(t) + helper := newBasicSystemTestHelper(&mockEnvGetter{ + values: map[string]string{ + env.EnvURLVar: "invalid://url", + }, + }).WithProvider(&testPackage{ + creator: func() (system.System, error) { + return nil, fmt.Errorf("invalid URL") + }, + }) + sys, err := helper.acquireFromEnvURL(t) require.Error(t, err) require.Nil(t, sys) }) t.Run("succeeds with valid URL", func(t *testing.T) { - // Set up test package that returns a mock system - currentPackage = &testPackage{ + mockSys := newMockSystem() + helper := newBasicSystemTestHelper(&mockEnvGetter{ + values: map[string]string{ + env.EnvURLVar: "file:///valid/url", + }, + }).WithProvider(&testPackage{ creator: func() (system.System, error) { - return newMockSystem(), nil + return mockSys, nil }, - } - os.Setenv(env.EnvURLVar, "file:///valid/url") - sys, err := acquireFromEnvURL(t) + }) + sys, err := helper.acquireFromEnvURL(t) require.NoError(t, err) - require.NotNil(t, sys) + require.Equal(t, mockSys, sys) + }) + + // Verify original environment is preserved by running a test with the original env + t.Run("preserves original environment", func(t *testing.T) { + helper := newBasicSystemTestHelper(origEnv) + sys, err := helper.acquireFromEnvURL(t) + if origEnv.values[env.EnvURLVar] == "" { + require.NoError(t, err) + require.Nil(t, sys) + } else { + // If there was a value, we'd need a provider to handle it properly + helper = helper.WithProvider(&testPackage{ + creator: func() (system.System, error) { + return newMockSystem(), nil + }, + }) + sys, err = helper.acquireFromEnvURL(t) + require.NoError(t, err) + require.NotNil(t, sys) + } }) }) } diff --git a/kurtosis-devnet/pkg/deploy/deploy.go b/kurtosis-devnet/pkg/deploy/deploy.go index af7339b0ba2..6bbf328f9a4 100644 --- a/kurtosis-devnet/pkg/deploy/deploy.go +++ b/kurtosis-devnet/pkg/deploy/deploy.go @@ -8,6 +8,7 @@ import ( "io" "log" "os" + "strings" ktfs "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" "github.com/ethereum-optimism/optimism/devnet-sdk/shell/env" @@ -39,6 +40,7 @@ type Deployer struct { engineManager EngineManager templateFile string dataFile string + newEnclaveFS func(ctx context.Context, enclave string) (*ktfs.EnclaveFS, error) } func WithKurtosisDeployer(ktDeployer DeployerFunc) DeployerOption { @@ -95,12 +97,19 @@ func WithEnclave(enclave string) DeployerOption { } } +func WithNewEnclaveFSFunc(newEnclaveFS func(ctx context.Context, enclave string) (*ktfs.EnclaveFS, error)) DeployerOption { + return func(d *Deployer) { + d.newEnclaveFS = newEnclaveFS + } +} + func NewDeployer(opts ...DeployerOption) *Deployer { d := &Deployer{ kurtosisBinary: "kurtosis", ktDeployer: func(opts ...kurtosis.KurtosisDeployerOptions) (deployer, error) { return kurtosis.NewKurtosisDeployer(opts...) }, + newEnclaveFS: ktfs.NewEnclaveFS, } for _, opt := range opts { opt(d) @@ -146,7 +155,7 @@ func (d *Deployer) deployEnvironment(ctx context.Context, r io.Reader) (*kurtosi } // Upload the environment info to the enclave. - fs, err := ktfs.NewEnclaveFS(ctx, d.enclave) + fs, err := d.newEnclaveFS(ctx, d.enclave) if err != nil { return nil, fmt.Errorf("error getting enclave fs: %w", err) } @@ -158,13 +167,45 @@ func (d *Deployer) deployEnvironment(ctx context.Context, r io.Reader) (*kurtosi return nil, fmt.Errorf("error encoding environment: %w", err) } - if err := fs.PutArtifact(ctx, env.KurtosisDevnetEnvArtifactName, ktfs.NewArtifactFileReader(env.KurtosisDevnetEnvArtifactPath, envBuf)); err != nil { + descName, err := getNextDevnetDescriptor(ctx, fs) + if err != nil { + return nil, fmt.Errorf("error getting next devnet descriptor: %w", err) + } + + if err := fs.PutArtifact(ctx, descName, ktfs.NewArtifactFileReader(env.KurtosisDevnetEnvArtifactPath, envBuf)); err != nil { return nil, fmt.Errorf("error putting environment artifact: %w", err) } return info, nil } +func getNextDevnetDescriptor(ctx context.Context, fs *ktfs.EnclaveFS) (string, error) { + artifactNames, err := fs.GetAllArtifactNames(ctx) + if err != nil { + return "", fmt.Errorf("error getting artifact names: %w", err) + } + + maxNum := -1 + for _, artifactName := range artifactNames { + if !strings.HasPrefix(artifactName, env.KurtosisDevnetEnvArtifactNamePrefix) { + continue + } + + numStr := strings.TrimPrefix(artifactName, env.KurtosisDevnetEnvArtifactNamePrefix) + num := 0 + if _, err := fmt.Sscanf(numStr, "%d", &num); err != nil { + log.Printf("Warning: invalid devnet descriptor format: %s", artifactName) + continue + } + + if num > maxNum { + maxNum = num + } + } + + return fmt.Sprintf("%s%d", env.KurtosisDevnetEnvArtifactNamePrefix, maxNum+1), nil +} + func (d *Deployer) renderTemplate(buildDir string, urlBuilder func(path ...string) string) (*bytes.Buffer, error) { t := &Templater{ baseDir: d.baseDir, diff --git a/kurtosis-devnet/pkg/deploy/deploy_test.go b/kurtosis-devnet/pkg/deploy/deploy_test.go index 36ac00f6ff7..9b2b08237d6 100644 --- a/kurtosis-devnet/pkg/deploy/deploy_test.go +++ b/kurtosis-devnet/pkg/deploy/deploy_test.go @@ -9,8 +9,11 @@ import ( "path/filepath" "testing" + ktfs "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis" "github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/sources/spec" + "github.com/kurtosis-tech/kurtosis/api/golang/core/kurtosis_core_rpc_api_bindings" + "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/services" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -40,6 +43,30 @@ func (m *mockDeployerForTest) GetEnvironmentInfo(ctx context.Context, spec *spec return &kurtosis.KurtosisEnvironment{}, nil } +// mockEnclaveContext implements EnclaveContextIface for testing +type mockEnclaveContext struct { + artifacts []string +} + +func (m *mockEnclaveContext) GetAllFilesArtifactNamesAndUuids(ctx context.Context) ([]*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid, error) { + result := make([]*kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid, len(m.artifacts)) + for i, name := range m.artifacts { + result[i] = &kurtosis_core_rpc_api_bindings.FilesArtifactNameAndUuid{ + FileName: name, + FileUuid: "test-uuid", + } + } + return result, nil +} + +func (m *mockEnclaveContext) DownloadFilesArtifact(ctx context.Context, name string) ([]byte, error) { + return nil, nil +} + +func (m *mockEnclaveContext) UploadFiles(pathToUpload string, artifactName string) (services.FilesArtifactUUID, services.FileArtifactName, error) { + return "", "", nil +} + func TestDeploy(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -68,12 +95,24 @@ func TestDeploy(t *testing.T) { return &mockDeployerForTest{baseDir: tmpDir}, nil } + // Create a mock EnclaveFS function + mockEnclaveFSFunc := func(ctx context.Context, enclave string) (*ktfs.EnclaveFS, error) { + mockCtx := &mockEnclaveContext{ + artifacts: []string{ + "devnet-descriptor-1", + "devnet-descriptor-2", + }, + } + return ktfs.NewEnclaveFSWithContext(mockCtx), nil + } + d := NewDeployer( WithBaseDir(tmpDir), WithKurtosisDeployer(mockDeployerFunc), WithDryRun(true), WithTemplateFile(templatePath), WithDataFile(dataPath), + WithNewEnclaveFSFunc(mockEnclaveFSFunc), ) env, err := d.Deploy(ctx, deployConfig) @@ -92,3 +131,71 @@ func TestDeploy(t *testing.T) { require.NoError(t, err) assert.Equal(t, "value", envData["test"]) } + +func TestGetNextDevnetDescriptor(t *testing.T) { + tests := []struct { + name string + artifacts []string + wantName string + wantErrText string + }{ + { + name: "increments highest numbered descriptor", + artifacts: []string{ + "devnet-descriptor-1", + "devnet-descriptor-5", + "devnet-descriptor-10", + "other", + }, + wantName: "devnet-descriptor-11", + }, + { + name: "handles non-numeric suffixes", + artifacts: []string{ + "devnet-descriptor-1", + "devnet-descriptor-5", + "devnet-descriptor-abc", + "devnet-descriptor-10", + "other", + "devnet-descriptor-def", + }, + wantName: "devnet-descriptor-11", + }, + { + name: "no descriptors", + artifacts: []string{}, + wantName: "devnet-descriptor-0", + }, + { + name: "no valid descriptors", + artifacts: []string{ + "other", + "devnet-abc", + }, + wantName: "devnet-descriptor-0", + }, + { + name: "handles negative numbers", + artifacts: []string{ + "devnet-descriptor--1", + "devnet-descriptor-5", + }, + wantName: "devnet-descriptor-6", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockCtx := &mockEnclaveContext{artifacts: tt.artifacts} + fs := ktfs.NewEnclaveFSWithContext(mockCtx) + got, err := getNextDevnetDescriptor(context.Background(), fs) + if tt.wantErrText != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErrText) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantName, got) + }) + } +} From 5cc9f0017fc623c1fe3a4764f42008e6bb7de7e6 Mon Sep 17 00:00:00 2001 From: Park Changwan Date: Wed, 5 Mar 2025 21:39:53 +0900 Subject: [PATCH 066/130] devnet-sdk: Use geth logger instead of slog (#14644) * Use testlog and geth log instead of slog * Support devnet-sdk balance type for geth logging * Update README for using testlog --- devnet-sdk/book/src/testing.md | 7 ++++-- devnet-sdk/constraints/constraints.go | 4 ++-- devnet-sdk/system/txbuilder.go | 5 ++-- devnet-sdk/types/balance.go | 13 +++++------ devnet-sdk/types/balance_test.go | 3 +-- .../tests/interop/boilerplate_test.go | 6 +++-- .../tests/interop/interop_smoke_test.go | 23 +++++++++++-------- 7 files changed, 34 insertions(+), 27 deletions(-) diff --git a/devnet-sdk/book/src/testing.md b/devnet-sdk/book/src/testing.md index 815e25ebe68..ecd97c6f596 100644 --- a/devnet-sdk/book/src/testing.md +++ b/devnet-sdk/book/src/testing.md @@ -57,7 +57,6 @@ Here's a complete example showing these principles in action: ```go import ( - "log/slog" "math/big" "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/constants" @@ -65,6 +64,8 @@ import ( "github.com/ethereum-optimism/optimism/devnet-sdk/testing/systest" "github.com/ethereum-optimism/optimism/devnet-sdk/testing/testlib/validators" "github.com/ethereum-optimism/optimism/devnet-sdk/types" + "github.com/ethereum-optimism/optimism/op-service/testlog" + "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/require" ) @@ -72,7 +73,9 @@ import ( func wrapETHScenario(chainIdx uint64, walletGetter validators.WalletGetter) systest.SystemTestFunc { return func(t systest.T, sys system.System) { ctx := t.Context() - logger := slog.With("test", "WrapETH", "devnet", sys.Identifier()) + + logger := testlog.Logger(t, log.LevelInfo) + logger := logger.With("test", "WrapETH", "devnet", sys.Identifier()) // Get the L2 chain we want to test with chain := sys.L2(chainIdx) diff --git a/devnet-sdk/constraints/constraints.go b/devnet-sdk/constraints/constraints.go index c21514fdad4..c4566bb3504 100644 --- a/devnet-sdk/constraints/constraints.go +++ b/devnet-sdk/constraints/constraints.go @@ -1,7 +1,7 @@ package constraints import ( - "log/slog" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum-optimism/optimism/devnet-sdk/system" "github.com/ethereum-optimism/optimism/devnet-sdk/types" @@ -20,7 +20,7 @@ func (f WalletConstraintFunc) CheckWallet(wallet system.Wallet) bool { func WithBalance(amount types.Balance) WalletConstraint { return WalletConstraintFunc(func(wallet system.Wallet) bool { balance := wallet.Balance() - slog.Debug("checking balance", "wallet", wallet.Address(), "balance", balance, "needed", amount) + log.Debug("checking balance", "wallet", wallet.Address(), "balance", balance, "needed", amount) return balance.GreaterThan(amount) }) } diff --git a/devnet-sdk/system/txbuilder.go b/devnet-sdk/system/txbuilder.go index 8d7ce9656ad..c1e54aa37af 100644 --- a/devnet-sdk/system/txbuilder.go +++ b/devnet-sdk/system/txbuilder.go @@ -3,9 +3,10 @@ package system import ( "context" "fmt" - "log/slog" "math/big" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/holiman/uint256" @@ -78,7 +79,7 @@ func NewTxBuilder(ctx context.Context, chain Chain, opts ...TxBuilderOption) *Tx } } - slog.InfoContext(ctx, "Instantiated TxBuilder", + log.Info("Instantiated TxBuilder", "supportedTxTypes", builder.supportedTxTypes, "forcedTxType", builder.forcedTxType, "gasLimitMargin", builder.gasLimitMarginPercent, diff --git a/devnet-sdk/types/balance.go b/devnet-sdk/types/balance.go index 867b13700e1..bc2e4aa5942 100644 --- a/devnet-sdk/types/balance.go +++ b/devnet-sdk/types/balance.go @@ -2,7 +2,6 @@ package types import ( "fmt" - "log/slog" "math/big" ) @@ -66,10 +65,10 @@ func (b Balance) Equal(other Balance) bool { return b.Int.Cmp(other.Int) == 0 } -// LogValue implements slog.LogValuer to format Balance in the most readable unit -func (b Balance) LogValue() slog.Value { +// String implements fmt.Stringer to format Balance in the most readable unit +func (b Balance) String() string { if b.Int == nil { - return slog.StringValue("0 ETH") + return "0 ETH" } val := new(big.Float).SetInt(b.Int) @@ -78,16 +77,16 @@ func (b Balance) LogValue() slog.Value { // 1 ETH = 1e18 Wei if eth.Cmp(new(big.Float).SetFloat64(0.001)) >= 0 { str := eth.Text('f', 0) - return slog.StringValue(fmt.Sprintf("%s ETH", str)) + return fmt.Sprintf("%s ETH", str) } // 1 Gwei = 1e9 Wei gwei := new(big.Float).Quo(val, new(big.Float).SetInt64(1e9)) if gwei.Cmp(new(big.Float).SetFloat64(0.001)) >= 0 { str := gwei.Text('g', 3) - return slog.StringValue(fmt.Sprintf("%s Gwei", str)) + return fmt.Sprintf("%s Gwei", str) } // Wei - return slog.StringValue(fmt.Sprintf("%s Wei", b.Text(10))) + return fmt.Sprintf("%s Wei", b.Text(10)) } diff --git a/devnet-sdk/types/balance_test.go b/devnet-sdk/types/balance_test.go index 0db5437c5da..09b880f6062 100644 --- a/devnet-sdk/types/balance_test.go +++ b/devnet-sdk/types/balance_test.go @@ -261,8 +261,7 @@ func TestBalanceLogValue(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - logValue := tt.balance.LogValue() - assert.Equal(t, tt.expected, logValue.String()) + assert.Equal(t, tt.expected, tt.balance.String()) }) } } diff --git a/kurtosis-devnet/tests/interop/boilerplate_test.go b/kurtosis-devnet/tests/interop/boilerplate_test.go index 866b0801721..d8074a5e240 100644 --- a/kurtosis-devnet/tests/interop/boilerplate_test.go +++ b/kurtosis-devnet/tests/interop/boilerplate_test.go @@ -1,10 +1,12 @@ package interop import ( - "log/slog" "os" + + oplog "github.com/ethereum-optimism/optimism/op-service/log" + "github.com/ethereum/go-ethereum/log" ) func init() { - slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelDebug}))) + oplog.SetGlobalLogHandler(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelDebug, true)) } diff --git a/kurtosis-devnet/tests/interop/interop_smoke_test.go b/kurtosis-devnet/tests/interop/interop_smoke_test.go index f2db52a00cf..4046c849dcf 100644 --- a/kurtosis-devnet/tests/interop/interop_smoke_test.go +++ b/kurtosis-devnet/tests/interop/interop_smoke_test.go @@ -2,7 +2,6 @@ package interop import ( "context" - "log/slog" "math/big" "testing" @@ -11,18 +10,22 @@ import ( "github.com/ethereum-optimism/optimism/devnet-sdk/testing/systest" "github.com/ethereum-optimism/optimism/devnet-sdk/testing/testlib/validators" sdktypes "github.com/ethereum-optimism/optimism/devnet-sdk/types" + "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/require" ) func smokeTestScenario(chainIdx uint64, walletGetter validators.WalletGetter) systest.SystemTestFunc { return func(t systest.T, sys system.System) { ctx := t.Context() - logger := slog.With("test", "TestMinimal", "devnet", sys.Identifier()) + + logger := testlog.Logger(t, log.LevelInfo) + logger = logger.With("test", "TestMinimal", "devnet", sys.Identifier()) chain := sys.L2s()[chainIdx] logger = logger.With("chain", chain.ID()) - logger.InfoContext(ctx, "starting test") + logger.Info("starting test") funds := sdktypes.NewBalance(big.NewInt(0.5 * constants.ETH)) user := walletGetter(ctx) @@ -30,19 +33,19 @@ func smokeTestScenario(chainIdx uint64, walletGetter validators.WalletGetter) sy scw0Addr := constants.SuperchainWETH scw0, err := chain.ContractsRegistry().SuperchainWETH(scw0Addr) require.NoError(t, err) - logger.InfoContext(ctx, "using SuperchainWETH", "contract", scw0Addr) + logger.Info("using SuperchainWETH", "contract", scw0Addr) initialBalance, err := scw0.BalanceOf(user.Address()).Call(ctx) require.NoError(t, err) logger = logger.With("user", user.Address()) - logger.InfoContext(ctx, "initial balance retrieved", "balance", initialBalance) + logger.Info("initial balance retrieved", "balance", initialBalance) - logger.InfoContext(ctx, "sending ETH to contract", "amount", funds) + logger.Info("sending ETH to contract", "amount", funds) require.NoError(t, user.SendETH(scw0Addr, funds).Send(ctx).Wait()) balance, err := scw0.BalanceOf(user.Address()).Call(ctx) require.NoError(t, err) - logger.InfoContext(ctx, "final balance retrieved", "balance", balance) + logger.Info("final balance retrieved", "balance", balance) require.Equal(t, initialBalance.Add(funds), balance) } @@ -61,7 +64,7 @@ func TestSystemWrapETH(t *testing.T) { func TestInteropSystemNoop(t *testing.T) { systest.InteropSystemTest(t, func(t systest.T, sys system.InteropSystem) { - slog.Info("noop") + testlog.Logger(t, log.LevelInfo).Info("noop") }) } @@ -95,9 +98,9 @@ func TestSmokeTestFailure(t *testing.T) { func lowLevelSystemScenario(sysGetter validators.LowLevelSystemGetter) systest.SystemTestFunc { return func(t systest.T, sys system.System) { - logger := slog.With("test", "TestLowLevelSystem", "devnet", sys.Identifier()) + logger := testlog.Logger(t, log.LevelInfo).With("test", "TestLowLevelSystem", "devnet", sys.Identifier()) _ = sysGetter(t.Context()) - logger.InfoContext(t.Context(), "low level system acquired") + logger.Info("low level system acquired") } } From 24baeb1c87879ee1900551aabbb7c154dc058d14 Mon Sep 17 00:00:00 2001 From: zhiqiangxu <652732310@qq.com> Date: Wed, 5 Mar 2025 22:20:18 +0800 Subject: [PATCH 067/130] add flags: `txmgr.max-basefee` and `txmgr.max-tip-cap` (#13382) * add flags: txmgr.max-basefee and txmgr.max-tip-cap * add TestMaxFees * address comment * mod error msg --- op-service/txmgr/cli.go | 39 +++++++++++++++++++++++ op-service/txmgr/txmgr.go | 12 ++++++- op-service/txmgr/txmgr_test.go | 58 ++++++++++++++++++++++++++++++++-- 3 files changed, 105 insertions(+), 4 deletions(-) diff --git a/op-service/txmgr/cli.go b/op-service/txmgr/cli.go index 1f634015e39..ee664fa69ae 100644 --- a/op-service/txmgr/cli.go +++ b/op-service/txmgr/cli.go @@ -32,7 +32,9 @@ const ( FeeLimitMultiplierFlagName = "fee-limit-multiplier" FeeLimitThresholdFlagName = "txmgr.fee-limit-threshold" MinBaseFeeFlagName = "txmgr.min-basefee" + MaxBaseFeeFlagName = "txmgr.max-basefee" MinTipCapFlagName = "txmgr.min-tip-cap" + MaxTipCapFlagName = "txmgr.max-tip-cap" ResubmissionTimeoutFlagName = "resubmission-timeout" NetworkTimeoutFlagName = "network-timeout" TxSendTimeoutFlagName = "txmgr.send-timeout" @@ -156,12 +158,22 @@ func CLIFlagsWithDefaults(envPrefix string, defaults DefaultFlagValues) []cli.Fl Value: defaults.MinTipCapGwei, EnvVars: prefixEnvVars("TXMGR_MIN_TIP_CAP"), }, + &cli.Float64Flag{ + Name: MaxTipCapFlagName, + Usage: "Enforces a maximum tip cap (in GWei) to use when determining tx fees, `TxMgr` returns an error when exceeded. Disabled by default.", + EnvVars: prefixEnvVars("TXMGR_MAX_TIP_CAP"), + }, &cli.Float64Flag{ Name: MinBaseFeeFlagName, Usage: "Enforces a minimum base fee (in GWei) to assume when determining tx fees. 1 GWei by default.", Value: defaults.MinBaseFeeGwei, EnvVars: prefixEnvVars("TXMGR_MIN_BASEFEE"), }, + &cli.Float64Flag{ + Name: MaxBaseFeeFlagName, + Usage: "Enforces a maximum base fee (in GWei) to assume when determining tx fees, `TxMgr` returns an error when exceeded. Disabled by default.", + EnvVars: prefixEnvVars("TXMGR_MAX_BASEFEE"), + }, &cli.DurationFlag{ Name: ResubmissionTimeoutFlagName, Usage: "Duration we will wait before resubmitting a transaction to L1", @@ -214,6 +226,8 @@ type CLIConfig struct { FeeLimitThresholdGwei float64 MinBaseFeeGwei float64 MinTipCapGwei float64 + MaxBaseFeeGwei float64 + MaxTipCapGwei float64 ResubmissionTimeout time.Duration ReceiptQueryInterval time.Duration NetworkTimeout time.Duration @@ -289,7 +303,9 @@ func ReadCLIConfig(ctx *cli.Context) CLIConfig { FeeLimitMultiplier: ctx.Uint64(FeeLimitMultiplierFlagName), FeeLimitThresholdGwei: ctx.Float64(FeeLimitThresholdFlagName), MinBaseFeeGwei: ctx.Float64(MinBaseFeeFlagName), + MaxBaseFeeGwei: ctx.Float64(MaxBaseFeeFlagName), MinTipCapGwei: ctx.Float64(MinTipCapFlagName), + MaxTipCapGwei: ctx.Float64(MaxTipCapFlagName), ResubmissionTimeout: ctx.Duration(ResubmissionTimeoutFlagName), ReceiptQueryInterval: ctx.Duration(ReceiptQueryIntervalFlagName), NetworkTimeout: ctx.Duration(NetworkTimeoutFlagName), @@ -346,6 +362,23 @@ func NewConfig(cfg CLIConfig, l log.Logger) (*Config, error) { return nil, fmt.Errorf("invalid min tip cap: %w", err) } + var ( + maxBaseFee, maxTipCap *big.Int + ) + if cfg.MaxBaseFeeGwei > 0 { + maxBaseFee, err = eth.GweiToWei(cfg.MaxBaseFeeGwei) + if err != nil { + return nil, fmt.Errorf("invalid max base fee: %w", err) + } + } + + if cfg.MaxTipCapGwei > 0 { + maxTipCap, err = eth.GweiToWei(cfg.MaxTipCapGwei) + if err != nil { + return nil, fmt.Errorf("invalid max tip cap: %w", err) + } + } + res := Config{ Backend: l1, ChainID: chainID, @@ -365,7 +398,9 @@ func NewConfig(cfg CLIConfig, l log.Logger) (*Config, error) { res.FeeLimitThreshold.Store(feeLimitThreshold) res.FeeLimitMultiplier.Store(cfg.FeeLimitMultiplier) res.MinBaseFee.Store(minBaseFee) + res.MaxBaseFee.Store(maxBaseFee) res.MinTipCap.Store(minTipCap) + res.MaxTipCap.Store(maxTipCap) res.MinBlobTxFee.Store(defaultMinBlobTxFee) return &res, nil @@ -390,9 +425,13 @@ type Config struct { // Minimum base fee (in Wei) to assume when determining tx fees. MinBaseFee atomic.Pointer[big.Int] + // Maximum base fee (in Wei) to assume when determining tx fees. + MaxBaseFee atomic.Pointer[big.Int] // Minimum tip cap (in Wei) to enforce when determining tx fees. MinTipCap atomic.Pointer[big.Int] + // Maximum tip cap (in Wei) to enforce when determining tx fees. + MaxTipCap atomic.Pointer[big.Int] MinBlobTxFee atomic.Pointer[big.Int] diff --git a/op-service/txmgr/txmgr.go b/op-service/txmgr/txmgr.go index f439d086f07..0a8f12b5020 100644 --- a/op-service/txmgr/txmgr.go +++ b/op-service/txmgr/txmgr.go @@ -344,7 +344,7 @@ func (m *SimpleTxManager) craftTx(ctx context.Context, candidate TxCandidate) (* gasTipCap, baseFee, blobBaseFee, err := m.SuggestGasPriceCaps(ctx) if err != nil { m.metr.RPCError() - return nil, fmt.Errorf("failed to get gas price info: %w", err) + return nil, fmt.Errorf("failed to get gas price info or it's too high: %w", err) } gasFeeCap := calcGasFeeCap(baseFee, gasTipCap) @@ -892,6 +892,7 @@ func (m *SimpleTxManager) increaseGasPrice(ctx context.Context, tx *types.Transa // SuggestGasPriceCaps suggests what the new tip, base fee, and blob base fee should be based on // the current L1 conditions. `blobBaseFee` will be nil if 4844 is not yet active. +// Note that an error will be returned if MaxTipCap or MaxBaseFee is exceeded. func (m *SimpleTxManager) SuggestGasPriceCaps(ctx context.Context) (*big.Int, *big.Int, *big.Int, error) { cCtx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout) defer cancel() @@ -913,16 +914,25 @@ func (m *SimpleTxManager) SuggestGasPriceCaps(ctx context.Context) (*big.Int, *b // Enforce minimum base fee and tip cap minTipCap := m.cfg.MinTipCap.Load() + maxTipCap := m.cfg.MaxTipCap.Load() minBaseFee := m.cfg.MinBaseFee.Load() + maxBaseFee := m.cfg.MaxBaseFee.Load() if minTipCap != nil && tip.Cmp(minTipCap) == -1 { m.l.Debug("Enforcing min tip cap", "minTipCap", minTipCap, "origTipCap", tip) tip = new(big.Int).Set(minTipCap) } + if maxTipCap != nil && tip.Cmp(maxTipCap) > 0 { + return nil, nil, nil, fmt.Errorf("tip is too high: %v, cap:%v", tip, maxTipCap) + } + if minBaseFee != nil && baseFee.Cmp(minBaseFee) == -1 { m.l.Debug("Enforcing min base fee", "minBaseFee", minBaseFee, "origBaseFee", baseFee) baseFee = new(big.Int).Set(minBaseFee) } + if maxBaseFee != nil && baseFee.Cmp(maxBaseFee) > 0 { + return nil, nil, nil, fmt.Errorf("baseFee is too high: %v, cap:%v", baseFee, maxBaseFee) + } return tip, baseFee, blobFee, nil } diff --git a/op-service/txmgr/txmgr_test.go b/op-service/txmgr/txmgr_test.go index b79319689e3..3add4d5d6d6 100644 --- a/op-service/txmgr/txmgr_test.go +++ b/op-service/txmgr/txmgr_test.go @@ -133,11 +133,16 @@ type gasPricer struct { mu sync.Mutex } +const ( + baseGasTipFee = 5 + baseBaseFee = 7 +) + func newGasPricer(mineAtEpoch int64) *gasPricer { return &gasPricer{ mineAtEpoch: mineAtEpoch, - baseGasTipFee: big.NewInt(5), - baseBaseFee: big.NewInt(7), + baseGasTipFee: big.NewInt(baseGasTipFee), + baseBaseFee: big.NewInt(baseBaseFee), // Simulate 100 excess blobs, which results in a blobBaseFee of 50 wei. This default means // blob txs will be subject to the geth minimum blobgas fee of 1 gwei. excessBlobGas: 100 * (params.BlobTxBlobGasPerBlob), @@ -1448,7 +1453,7 @@ func TestMinFees(t *testing.T) { conf.MinTipCap.Store(tt.minTipCap) h := newTestHarnessWithConfig(t, conf) - tip, baseFee, _, err := h.mgr.SuggestGasPriceCaps(context.TODO()) + tip, baseFee, _, err := h.mgr.SuggestGasPriceCaps(context.Background()) require.NoError(err) if tt.expectMinBaseFee { @@ -1466,6 +1471,53 @@ func TestMinFees(t *testing.T) { } } +func TestMaxFees(t *testing.T) { + for _, tt := range []struct { + desc string + maxBaseFee *big.Int + maxTipCap *big.Int + expectMaxBaseFee bool + expectMaxTipCap bool + }{ + { + desc: "no-maxs", + }, + { + desc: "max-basefee", + maxBaseFee: big.NewInt(baseBaseFee - 1), + expectMaxBaseFee: true, + }, + { + desc: "max-tipcap", + maxTipCap: big.NewInt(baseGasTipFee - 1), + expectMaxTipCap: true, + }, + } { + t.Run(tt.desc, func(t *testing.T) { + require := require.New(t) + conf := configWithNumConfs(1) + conf.MaxBaseFee.Store(tt.maxBaseFee) + conf.MaxTipCap.Store(tt.maxTipCap) + h := newTestHarnessWithConfig(t, conf) + + tip, baseFee, _, err := h.mgr.SuggestGasPriceCaps(context.Background()) + if tt.expectMaxBaseFee { + require.Equal(err, fmt.Errorf("baseFee is too high: %v, cap:%v", h.gasPricer.baseBaseFee, tt.maxBaseFee), "expect baseFee is too high") + } + + if tt.expectMaxTipCap { + require.Equal(err, fmt.Errorf("tip is too high: %v, cap:%v", h.gasPricer.baseGasTipFee, tt.maxTipCap), "expect tip is too high") + } + + if !(tt.expectMaxBaseFee || tt.expectMaxTipCap) { + require.NoError(err) + require.Equal(h.gasPricer.baseBaseFee, baseFee, "expect suggested base fee to equal mock base fee") + require.Equal(h.gasPricer.baseGasTipFee, tip, "expect suggested tip to equal mock tip") + } + }) + } +} + // TestClose ensures that the tx manager will refuse new work and cancel any in progress func TestClose(t *testing.T) { testSendVariants(t, func(t *testing.T, send testSendVariantsFn) { From 8699a083d856b474f8fff5ed0ea40e82d944d441 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Wed, 5 Mar 2025 07:35:58 -0700 Subject: [PATCH 068/130] ctb: Add contracts book (#14643) --- op-deployer/README.md | 5 + packages/contracts-bedrock/README.md | 215 +- packages/contracts-bedrock/book/.gitignore | 1 + .../contracts-bedrock/{meta => book}/LICENSE | 0 packages/contracts-bedrock/book/book.toml | 18 + packages/contracts-bedrock/book/custom.css | 5 + .../contracts-bedrock/book/mermaid-init.js | 35 + .../contracts-bedrock/book/mermaid.min.js | 2186 +++++++++++++++++ .../contracts-bedrock/book/src/SUMMARY.md | 15 + .../book/src/contributing/code-freezes.md | 1 + .../src/contributing/interfaces.md} | 4 +- .../book/src/contributing/opcm.md | 98 + .../src/contributing/style-guide.md} | 2 +- .../book/src/introduction.md | 56 + .../src/policies/code-freezes.md} | 0 .../src/policies/solidity-upgrades.md} | 0 .../src/policies/versioning.md} | 4 +- packages/contracts-bedrock/meta/POLICY.md | 41 - 18 files changed, 2426 insertions(+), 260 deletions(-) create mode 100644 op-deployer/README.md create mode 100644 packages/contracts-bedrock/book/.gitignore rename packages/contracts-bedrock/{meta => book}/LICENSE (100%) create mode 100644 packages/contracts-bedrock/book/book.toml create mode 100644 packages/contracts-bedrock/book/custom.css create mode 100644 packages/contracts-bedrock/book/mermaid-init.js create mode 100644 packages/contracts-bedrock/book/mermaid.min.js create mode 100644 packages/contracts-bedrock/book/src/SUMMARY.md create mode 100644 packages/contracts-bedrock/book/src/contributing/code-freezes.md rename packages/contracts-bedrock/{meta/INTERFACES.md => book/src/contributing/interfaces.md} (99%) create mode 100644 packages/contracts-bedrock/book/src/contributing/opcm.md rename packages/contracts-bedrock/{meta/STYLE_GUIDE.md => book/src/contributing/style-guide.md} (99%) create mode 100644 packages/contracts-bedrock/book/src/introduction.md rename packages/contracts-bedrock/{meta/CODE_FREEZES.md => book/src/policies/code-freezes.md} (100%) rename packages/contracts-bedrock/{meta/SOLIDITY_UPGRADES.md => book/src/policies/solidity-upgrades.md} (100%) rename packages/contracts-bedrock/{meta/VERSIONING.md => book/src/policies/versioning.md} (97%) delete mode 100644 packages/contracts-bedrock/meta/POLICY.md diff --git a/op-deployer/README.md b/op-deployer/README.md new file mode 100644 index 00000000000..6020e352f37 --- /dev/null +++ b/op-deployer/README.md @@ -0,0 +1,5 @@ +# op-deployer + +OP Deployer automates deploying new OP Stack chains. For more information, check out the [book][book]. + +[book]: https://devdocs.optimism.io/op-deployer \ No newline at end of file diff --git a/packages/contracts-bedrock/README.md b/packages/contracts-bedrock/README.md index 20376524c1e..55cfa8b2bb9 100644 --- a/packages/contracts-bedrock/README.md +++ b/packages/contracts-bedrock/README.md @@ -1,216 +1,5 @@ # OP Stack Smart Contracts -This package contains the L1 and L2 smart contracts for the OP Stack. -Detailed specifications for the contracts contained within this package can be found at [specs.optimism.io](https://specs.optimism.io). -High-level information about these contracts can be found within this README and within the [Optimism Developer Docs](https://docs.optimism.io). +For more information, check out the [book][book]. - - -## Table of Contents - -- [Architecture Overview](#architecture-overview) -- [External Usage](#external-usage) - - [Using OP Stack Contracts in Solidity](#using-op-stack-contracts-in-solidity) - - [Using OP Stack Contracts in JavaScript](#using-op-stack-contracts-in-javascript) - - [Deployed Addresses](#deployed-addresses) -- [Contributing](#contributing) - - [Contributing Guide](#contributing-guide) - - [Style Guide](#style-guide) - - [Contract Interfaces](#contract-interfaces) - - [Solidity Versioning](#solidity-versioning) - - [Frozen Code](#frozen-code) -- [Deployment](#deployment) - - [Deploying Production Networks](#deploying-production-networks) -- [Generating L2 Genesis Allocs](#generating-l2-genesis-allocs) - - [Configuration](#configuration) - - [Custom Gas Token](#custom-gas-token) - - [Execution](#execution) - - [Deploying a single contract](#deploying-a-single-contract) -- [Testing](#testing) - - [Test Setup](#test-setup) - - [Static Analysis](#static-analysis) - - - -## Architecture Overview - -Refer to the [Optimism Overview page within the OP Stack Specs](https://specs.optimism.io/protocol/overview.html#architecture-overview) for a detailed architecture overview of core L1 contracts, core L2 contracts, and smart contract proxies. - -## External Usage - -### Using OP Stack Contracts in Solidity - -OP Stack smart contracts are published to NPM and can be installed via: - -```sh -npm install @eth-optimism/contracts-bedrock -``` - -Refer to the [Optimism Developer Docs](https://docs.optimism.io/builders/dapp-developers/contracts/system-contracts#using-system-contracts-in-solidity) for additional information about how to use this package. - -### Using OP Stack Contracts in JavaScript - -Contract ABIs and addresses are published to NPM in a separate package and can be installed via: - -```sh -npm install @eth-optimism/contracts-ts -``` - -Refer to the [Optimism Developer Docs](https://docs.optimism.io/builders/dapp-developers/contracts/system-contracts#using-system-contracts-in-javascript) for additional information about how to use this package. - -### Deployed Addresses - -See the [Optimism Developer Docs](https://docs.optimism.io/chain/addresses) for the deployed addresses of these smart contracts for OP Mainnet and OP Sepolia. - -## Contributing - -### Contributing Guide - -Contributions to the OP Stack are always welcome. -Please refer to the [CONTRIBUTING.md](../../CONTRIBUTING.md) for general information about how to contribute to the OP Stack monorepo. - -When contributing to the `contracts-bedrock` package there are some additional steps you should follow. These have been conveniently packaged into a just command which you should run before pushing your changes. - -```bash -just pre-pr -``` - -### Style Guide - -OP Stack smart contracts should be written according to the [STYLE_GUIDE.md](./meta/STYLE_GUIDE.md) found within this repository. -Maintaining a consistent code style makes code easier to review and maintain, ultimately making the development process safer. - -### Contract Interfaces - -OP Stack smart contracts use contract interfaces in a relatively unique way. Please refer to -[INTERFACES.md](./meta/INTERFACES.md) to read more about how the OP Stack uses contract interfaces. - -### Solidity Versioning - -OP Stack smart contracts are designed to utilize a single, consistent Solidity version. Please -refer to [SOLIDITY_UPGRADES.md](./meta/SOLIDITY_UPGRADES.md) to understand the process for updating to -newer Solidity versions. - -### Frozen Code - -From time to time we need to ensure that certain files remain frozen, as they may be under audit or -a large PR is in the works and we wish to avoid a large rebase. In order to enforce this, -a hardcoded list of contracts is stored in `./scripts/checks/check-frozen-files.sh`. Any change -which affects the resulting init or source code of a contract which is not allowed to be modified -will prevent merging to the `develop` branch. - -In order to remove a file from the freeze it must be removed from the check file. - -## Deployment - -The smart contracts are deployed using `foundry`. The `DEPLOYMENT_OUTFILE` env var will determine the filepath that the -deployment artifact is written to on disk after the deployment. It comes in the form of a JSON file where keys are -the names of the contracts and the values are the addresses the contract was deployed to. - -The `DEPLOY_CONFIG_PATH` is a filepath to a deploy config file, see the `deploy-config` directory for examples and the -[DeployConfig](https://github.com/ethereum-optimism/optimism/blob/develop/op-chain-ops/genesis/config.go) definition for -descriptions of the values. -If you are following the official deployment tutorial, please make sure to use the `getting-started.json` file. - -```bash -DEPLOYMENT_OUTFILE=deployments/artifact.json \ -DEPLOY_CONFIG_PATH= \ - forge script scripts/deploy/Deploy.s.sol:Deploy \ - --broadcast --private-key $PRIVATE_KEY \ - --rpc-url $ETH_RPC_URL -``` - -The `IMPL_SALT` env var can be used to set the `create2` salt for deploying the implementation -contracts. - -This will deploy an entire new system of L1 smart contracts including a new `SuperchainConfig`. -In the future there will be an easy way to deploy only proxies and use shared implementations -for each of the contracts as well as a shared `SuperchainConfig` contract. - -### Deploying Production Networks - -Production users should deploy their L1 contracts from a contracts release. -All contracts releases are on git tags with the following format: `op-contracts/vX.Y.Z`. -See the [release process](https://github.com/ethereum-optimism/optimism?tab=readme-ov-file#development-and-release-process) -for more information. - -## Generating L2 Genesis Allocs - -A foundry script is used to generate the L2 genesis allocs. This is a JSON file that represents the L2 genesis state. -The `CONTRACT_ADDRESSES_PATH` env var represents the deployment artifact that was generated during a contract deployment. -The same deploy config JSON file should be used for L1 contracts deployment as when generating the L2 genesis allocs. -The `STATE_DUMP_PATH` env var represents the filepath at which the allocs will be written to on disk. - -```bash -CONTRACT_ADDRESSES_PATH=deployments/artifact.json \ -DEPLOY_CONFIG_PATH= \ -STATE_DUMP_PATH= \ - forge script scripts/L2Genesis.s.sol:L2Genesis \ - --sig 'runWithStateDump()' -``` - -### Configuration - -Create or modify a file `.json` inside of the [`deploy-config`](./deploy-config/) folder. -Use the env var `DEPLOY_CONFIG_PATH` to use a particular deploy config file at runtime. - -The script will read the latest active fork from the deploy config and the L2 genesis allocs generated will be -compatible with this fork. The automatically detected fork can be overwritten by setting the environment variable `FORK` -either to the lower-case fork name (currently `delta`, `ecotone`, `fjord`, `granite`, `holocene` or `isthmus`) or to `latest`, -which will select the latest fork available (currently `isthmus`). - -By default, the script will dump the L2 genesis allocs of the detected or selected fork only, to the file at `STATE_DUMP_PATH`. -The optional environment variable `OUTPUT_MODE` allows to modify this behavior by setting it to one of the following values: -* `latest` (default) - only dump the selected fork's allocs. -* `all` - also dump all intermediary fork's allocs. This only works if `STATE_DUMP_PATH` is _not_ set. In this case, all allocs - will be written to files `/state-dump-.json`. Another path cannot currently be specified for this use case. -* `none` - won't dump any allocs. Only makes sense for internal test usage. - -#### Custom Gas Token - -The Custom Gas Token feature is a Beta feature of the MIT licensed OP Stack. -While it has received initial review from core contributors, it is still undergoing testing, and may have bugs or other issues. - -### Execution - -Before deploying the contracts, you can verify the state diff produced by the deploy script using the `runWithStateDiff()` function signature which produces the outputs inside [`snapshots/state-diff/`](./snapshots/state-diff). -Run the deployment with state diffs by executing: `forge script -vvv scripts/deploy/Deploy.s.sol:Deploy --sig 'runWithStateDiff()' --rpc-url $ETH_RPC_URL --broadcast --private-key $PRIVATE_KEY`. - -1. Set the env vars `ETH_RPC_URL`, `PRIVATE_KEY` and `ETHERSCAN_API_KEY` if contract verification is desired. -1. Set the `DEPLOY_CONFIG_PATH` env var to a path on the filesystem that points to a deploy config. -1. Deploy the contracts with `forge script -vvv scripts/deploy/Deploy.s.sol:Deploy --rpc-url $ETH_RPC_URL --broadcast --private-key $PRIVATE_KEY` - Pass the `--verify` flag to verify the deployments automatically with Etherscan. - -### Deploying a single contract - -All of the functions for deploying a single contract are `public` meaning that the `--sig` argument to `forge script` can be used to -target the deployment of a single contract. - -## Testing - -### Test Setup - -The Solidity unit tests use the same codepaths to set up state that are used in production. The same L1 deploy script is used to deploy the L1 contracts for the in memory tests -and the L2 state is set up using the same L2 genesis generation code that is used for production and then loaded into foundry via the `vm.loadAllocs` cheatcode. This helps -to reduce the overhead of maintaining multiple ways to set up the state as well as give additional coverage to the "actual" way that the contracts are deployed. - -The L1 contract addresses are held in `deployments/hardhat/.deploy` and the L2 test state is held in a `.testdata` directory. The L1 addresses are used to create the L2 state -and it is possible for stale addresses to be pulled into the L2 state, causing tests to fail. Stale addresses may happen if the order of the L1 deployments happen differently -since some contracts are deployed using `CREATE`. Run `just clean` and rerun the tests if they are failing for an unknown reason. - -### Static Analysis - -`contracts-bedrock` uses [slither](https://github.com/crytic/slither) as its primary static analysis tool. -Slither will be run against PRs as part of CI, and new findings will be reported as a comment on the PR. -CI will fail if there are any new findings of medium or higher severity, as configured in the repo's Settings > Code Security and Analysis > Code Scanning > Protection rules setting. - -There are two corresponding jobs in CI: one calls "Slither Analysis" and one called "Code scanning results / Slither". -The former will always pass if Slither runs successfully, and the latter will fail if there are any new findings of medium or higher severity. - -Existing findings can be found in the repo's Security tab > [Code Scanning](https://github.com/ethereum-optimism/optimism/security/code-scanning) section. -You can view findings for a specific PR using the `pr:{number}` filter, such [`pr:9405`](https://github.com/ethereum-optimism/optimism/security/code-scanning?query=is:open+pr:9405). - -For each finding, either fix it locally and push a new commit, or dismiss it through the PR comment's UI. - -Note that you can run slither locally by running `slither .`, but because it does not contain the triaged results from GitHub, it will be noisy. -Instead, you should run `slither ./path/to/contract.sol` to run it against a specific file. +[book]: https://devdocs.optimism.io/contracts-bedrock \ No newline at end of file diff --git a/packages/contracts-bedrock/book/.gitignore b/packages/contracts-bedrock/book/.gitignore new file mode 100644 index 00000000000..7585238efed --- /dev/null +++ b/packages/contracts-bedrock/book/.gitignore @@ -0,0 +1 @@ +book diff --git a/packages/contracts-bedrock/meta/LICENSE b/packages/contracts-bedrock/book/LICENSE similarity index 100% rename from packages/contracts-bedrock/meta/LICENSE rename to packages/contracts-bedrock/book/LICENSE diff --git a/packages/contracts-bedrock/book/book.toml b/packages/contracts-bedrock/book/book.toml new file mode 100644 index 00000000000..fcdd19ea5b1 --- /dev/null +++ b/packages/contracts-bedrock/book/book.toml @@ -0,0 +1,18 @@ +[book] +authors = ["Optimism Contributors"] +language = "en" +multilingual = false +src = "src" +title = "OP Stack Smart Contracts" + +[output.html] +site-url = "/contracts-bedrock/" +git-repository-url = "https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/book" +edit-url-template = "https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/book/{path}" +additional-css = ["custom.css"] +additional-js = ["mermaid.min.js", "mermaid-init.js"] + +[preprocessor] + +[preprocessor.mermaid] +command = "mdbook-mermaid" \ No newline at end of file diff --git a/packages/contracts-bedrock/book/custom.css b/packages/contracts-bedrock/book/custom.css new file mode 100644 index 00000000000..7c94143752a --- /dev/null +++ b/packages/contracts-bedrock/book/custom.css @@ -0,0 +1,5 @@ +.content main { + max-width: 85%; + margin-left: auto; + margin-right: auto; +} diff --git a/packages/contracts-bedrock/book/mermaid-init.js b/packages/contracts-bedrock/book/mermaid-init.js new file mode 100644 index 00000000000..15a7f4e57c6 --- /dev/null +++ b/packages/contracts-bedrock/book/mermaid-init.js @@ -0,0 +1,35 @@ +(() => { + const darkThemes = ['ayu', 'navy', 'coal']; + const lightThemes = ['light', 'rust']; + + const classList = document.getElementsByTagName('html')[0].classList; + + let lastThemeWasLight = true; + for (const cssClass of classList) { + if (darkThemes.includes(cssClass)) { + lastThemeWasLight = false; + break; + } + } + + const theme = lastThemeWasLight ? 'default' : 'dark'; + mermaid.initialize({ startOnLoad: true, theme }); + + // Simplest way to make mermaid re-render the diagrams in the new theme is via refreshing the page + + for (const darkTheme of darkThemes) { + document.getElementById(darkTheme).addEventListener('click', () => { + if (lastThemeWasLight) { + window.location.reload(); + } + }); + } + + for (const lightTheme of lightThemes) { + document.getElementById(lightTheme).addEventListener('click', () => { + if (!lastThemeWasLight) { + window.location.reload(); + } + }); + } +})(); diff --git a/packages/contracts-bedrock/book/mermaid.min.js b/packages/contracts-bedrock/book/mermaid.min.js new file mode 100644 index 00000000000..9f2882748ce --- /dev/null +++ b/packages/contracts-bedrock/book/mermaid.min.js @@ -0,0 +1,2186 @@ +"use strict";var __esbuild_esm_mermaid=(()=>{var Pve=Object.create;var G1=Object.defineProperty;var Bve=Object.getOwnPropertyDescriptor;var Fve=Object.getOwnPropertyNames;var zve=Object.getPrototypeOf,Gve=Object.prototype.hasOwnProperty;var o=(t,e)=>G1(t,"name",{value:e,configurable:!0});var R=(t,e)=>()=>(t&&(e=t(t=0)),e);var gi=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports),hr=(t,e)=>{for(var r in e)G1(t,r,{get:e[r],enumerable:!0})},Rb=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Fve(e))!Gve.call(t,i)&&i!==r&&G1(t,i,{get:()=>e[i],enumerable:!(n=Bve(e,i))||n.enumerable});return t},dr=(t,e,r)=>(Rb(t,e,"default"),r&&Rb(r,e,"default")),Xi=(t,e,r)=>(r=t!=null?Pve(zve(t)):{},Rb(e||!t||!t.__esModule?G1(r,"default",{value:t,enumerable:!0}):r,t)),$ve=t=>Rb(G1({},"__esModule",{value:!0}),t);var Nb=gi((AC,_C)=>{"use strict";(function(t,e){typeof AC=="object"&&typeof _C<"u"?_C.exports=e():typeof define=="function"&&define.amd?define(e):(t=typeof globalThis<"u"?globalThis:t||self).dayjs=e()})(AC,function(){"use strict";var t=1e3,e=6e4,r=36e5,n="millisecond",i="second",a="minute",s="hour",l="day",u="week",h="month",f="quarter",d="year",p="date",m="Invalid Date",g=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,y=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,v={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:o(function(k){var I=["th","st","nd","rd"],C=k%100;return"["+k+(I[(C-20)%10]||I[C]||I[0])+"]"},"ordinal")},x=o(function(k,I,C){var O=String(k);return!O||O.length>=I?k:""+Array(I+1-O.length).join(C)+k},"m"),b={s:x,z:o(function(k){var I=-k.utcOffset(),C=Math.abs(I),O=Math.floor(C/60),D=C%60;return(I<=0?"+":"-")+x(O,2,"0")+":"+x(D,2,"0")},"z"),m:o(function k(I,C){if(I.date()1)return k(F[0])}else{var B=I.name;S[B]=I,D=B}return!O&&D&&(w=D),D||!O&&w},"t"),A=o(function(k,I){if(E(k))return k.clone();var C=typeof I=="object"?I:{};return C.date=k,C.args=arguments,new M(C)},"O"),L=b;L.l=_,L.i=E,L.w=function(k,I){return A(k,{locale:I.$L,utc:I.$u,x:I.$x,$offset:I.$offset})};var M=function(){function k(C){this.$L=_(C.locale,null,!0),this.parse(C),this.$x=this.$x||C.x||{},this[T]=!0}o(k,"M");var I=k.prototype;return I.parse=function(C){this.$d=function(O){var D=O.date,P=O.utc;if(D===null)return new Date(NaN);if(L.u(D))return new Date;if(D instanceof Date)return new Date(D);if(typeof D=="string"&&!/Z$/i.test(D)){var F=D.match(g);if(F){var B=F[2]-1||0,$=(F[7]||"0").substring(0,3);return P?new Date(Date.UTC(F[1],B,F[3]||1,F[4]||0,F[5]||0,F[6]||0,$)):new Date(F[1],B,F[3]||1,F[4]||0,F[5]||0,F[6]||0,$)}}return new Date(D)}(C),this.init()},I.init=function(){var C=this.$d;this.$y=C.getFullYear(),this.$M=C.getMonth(),this.$D=C.getDate(),this.$W=C.getDay(),this.$H=C.getHours(),this.$m=C.getMinutes(),this.$s=C.getSeconds(),this.$ms=C.getMilliseconds()},I.$utils=function(){return L},I.isValid=function(){return this.$d.toString()!==m},I.isSame=function(C,O){var D=A(C);return this.startOf(O)<=D&&D<=this.endOf(O)},I.isAfter=function(C,O){return A(C){"use strict";LF=Xi(Nb(),1),Zc={trace:0,debug:1,info:2,warn:3,error:4,fatal:5},V={trace:o((...t)=>{},"trace"),debug:o((...t)=>{},"debug"),info:o((...t)=>{},"info"),warn:o((...t)=>{},"warn"),error:o((...t)=>{},"error"),fatal:o((...t)=>{},"fatal")},$1=o(function(t="fatal"){let e=Zc.fatal;typeof t=="string"?t.toLowerCase()in Zc&&(e=Zc[t]):typeof t=="number"&&(e=t),V.trace=()=>{},V.debug=()=>{},V.info=()=>{},V.warn=()=>{},V.error=()=>{},V.fatal=()=>{},e<=Zc.fatal&&(V.fatal=console.error?console.error.bind(console,Eo("FATAL"),"color: orange"):console.log.bind(console,"\x1B[35m",Eo("FATAL"))),e<=Zc.error&&(V.error=console.error?console.error.bind(console,Eo("ERROR"),"color: orange"):console.log.bind(console,"\x1B[31m",Eo("ERROR"))),e<=Zc.warn&&(V.warn=console.warn?console.warn.bind(console,Eo("WARN"),"color: orange"):console.log.bind(console,"\x1B[33m",Eo("WARN"))),e<=Zc.info&&(V.info=console.info?console.info.bind(console,Eo("INFO"),"color: lightblue"):console.log.bind(console,"\x1B[34m",Eo("INFO"))),e<=Zc.debug&&(V.debug=console.debug?console.debug.bind(console,Eo("DEBUG"),"color: lightgreen"):console.log.bind(console,"\x1B[32m",Eo("DEBUG"))),e<=Zc.trace&&(V.trace=console.debug?console.debug.bind(console,Eo("TRACE"),"color: lightgreen"):console.log.bind(console,"\x1B[32m",Eo("TRACE")))},"setLogLevel"),Eo=o(t=>`%c${(0,LF.default)().format("ss.SSS")} : ${t} : `,"format")});var Vve,np,LC,DF,Mb=R(()=>{"use strict";Vve=Object.freeze({left:0,top:0,width:16,height:16}),np=Object.freeze({rotate:0,vFlip:!1,hFlip:!1}),LC=Object.freeze({...Vve,...np}),DF=Object.freeze({...LC,body:"",hidden:!1})});var Uve,RF,NF=R(()=>{"use strict";Mb();Uve=Object.freeze({width:null,height:null}),RF=Object.freeze({...Uve,...np})});var Ib,DC,Ob,MF=R(()=>{"use strict";Ib=/^[a-z0-9]+(-[a-z0-9]+)*$/,DC=o((t,e,r,n="")=>{let i=t.split(":");if(t.slice(0,1)==="@"){if(i.length<2||i.length>3)return null;n=i.shift().slice(1)}if(i.length>3||!i.length)return null;if(i.length>1){let l=i.pop(),u=i.pop(),h={provider:i.length>0?i[0]:n,prefix:u,name:l};return e&&!Ob(h)?null:h}let a=i[0],s=a.split("-");if(s.length>1){let l={provider:n,prefix:s.shift(),name:s.join("-")};return e&&!Ob(l)?null:l}if(r&&n===""){let l={provider:n,prefix:"",name:a};return e&&!Ob(l,r)?null:l}return null},"stringToIcon"),Ob=o((t,e)=>t?!!((t.provider===""||t.provider.match(Ib))&&(e&&t.prefix===""||t.prefix.match(Ib))&&t.name.match(Ib)):!1,"validateIconName")});function IF(t,e){let r={};!t.hFlip!=!e.hFlip&&(r.hFlip=!0),!t.vFlip!=!e.vFlip&&(r.vFlip=!0);let n=((t.rotate||0)+(e.rotate||0))%4;return n&&(r.rotate=n),r}var OF=R(()=>{"use strict";o(IF,"mergeIconTransformations")});function RC(t,e){let r=IF(t,e);for(let n in DF)n in np?n in t&&!(n in r)&&(r[n]=np[n]):n in e?r[n]=e[n]:n in t&&(r[n]=t[n]);return r}var PF=R(()=>{"use strict";Mb();OF();o(RC,"mergeIconData")});function BF(t,e){let r=t.icons,n=t.aliases||Object.create(null),i=Object.create(null);function a(s){if(r[s])return i[s]=[];if(!(s in i)){i[s]=null;let l=n[s]&&n[s].parent,u=l&&a(l);u&&(i[s]=[l].concat(u))}return i[s]}return o(a,"resolve"),(e||Object.keys(r).concat(Object.keys(n))).forEach(a),i}var FF=R(()=>{"use strict";o(BF,"getIconsTree")});function zF(t,e,r){let n=t.icons,i=t.aliases||Object.create(null),a={};function s(l){a=RC(n[l]||i[l],a)}return o(s,"parse"),s(e),r.forEach(s),RC(t,a)}function NC(t,e){if(t.icons[e])return zF(t,e,[]);let r=BF(t,[e])[e];return r?zF(t,e,r):null}var GF=R(()=>{"use strict";PF();FF();o(zF,"internalGetIconData");o(NC,"getIconData")});function MC(t,e,r){if(e===1)return t;if(r=r||100,typeof t=="number")return Math.ceil(t*e*r)/r;if(typeof t!="string")return t;let n=t.split(Hve);if(n===null||!n.length)return t;let i=[],a=n.shift(),s=Yve.test(a);for(;;){if(s){let l=parseFloat(a);isNaN(l)?i.push(a):i.push(Math.ceil(l*e*r)/r)}else i.push(a);if(a=n.shift(),a===void 0)return i.join("");s=!s}}var Hve,Yve,$F=R(()=>{"use strict";Hve=/(-?[0-9.]*[0-9]+[0-9.]*)/g,Yve=/^-?[0-9.]*[0-9]+[0-9.]*$/g;o(MC,"calculateSize")});function Wve(t,e="defs"){let r="",n=t.indexOf("<"+e);for(;n>=0;){let i=t.indexOf(">",n),a=t.indexOf("",a);if(s===-1)break;r+=t.slice(i+1,a).trim(),t=t.slice(0,n).trim()+t.slice(s+1)}return{defs:r,content:t}}function qve(t,e){return t?""+t+""+e:e}function VF(t,e,r){let n=Wve(t);return qve(n.defs,e+n.content+r)}var UF=R(()=>{"use strict";o(Wve,"splitSVGDefs");o(qve,"mergeDefsAndContent");o(VF,"wrapSVGContent")});function IC(t,e){let r={...LC,...t},n={...RF,...e},i={left:r.left,top:r.top,width:r.width,height:r.height},a=r.body;[r,n].forEach(y=>{let v=[],x=y.hFlip,b=y.vFlip,w=y.rotate;x?b?w+=2:(v.push("translate("+(i.width+i.left).toString()+" "+(0-i.top).toString()+")"),v.push("scale(-1 1)"),i.top=i.left=0):b&&(v.push("translate("+(0-i.left).toString()+" "+(i.height+i.top).toString()+")"),v.push("scale(1 -1)"),i.top=i.left=0);let S;switch(w<0&&(w-=Math.floor(w/4)*4),w=w%4,w){case 1:S=i.height/2+i.top,v.unshift("rotate(90 "+S.toString()+" "+S.toString()+")");break;case 2:v.unshift("rotate(180 "+(i.width/2+i.left).toString()+" "+(i.height/2+i.top).toString()+")");break;case 3:S=i.width/2+i.left,v.unshift("rotate(-90 "+S.toString()+" "+S.toString()+")");break}w%2===1&&(i.left!==i.top&&(S=i.left,i.left=i.top,i.top=S),i.width!==i.height&&(S=i.width,i.width=i.height,i.height=S)),v.length&&(a=VF(a,'',""))});let s=n.width,l=n.height,u=i.width,h=i.height,f,d;s===null?(d=l===null?"1em":l==="auto"?h:l,f=MC(d,u/h)):(f=s==="auto"?u:s,d=l===null?MC(f,h/u):l==="auto"?h:l);let p={},m=o((y,v)=>{Xve(v)||(p[y]=v.toString())},"setAttr");m("width",f),m("height",d);let g=[i.left,i.top,u,h];return p.viewBox=g.join(" "),{attributes:p,viewBox:g,body:a}}var Xve,HF=R(()=>{"use strict";Mb();NF();$F();UF();Xve=o(t=>t==="unset"||t==="undefined"||t==="none","isUnsetKeyword");o(IC,"iconToSVG")});function OC(t,e=Kve){let r=[],n;for(;n=jve.exec(t);)r.push(n[1]);if(!r.length)return t;let i="suffix"+(Math.random()*16777216|Date.now()).toString(16);return r.forEach(a=>{let s=typeof e=="function"?e(a):e+(Qve++).toString(),l=a.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");t=t.replace(new RegExp('([#;"])('+l+')([")]|\\.[a-z])',"g"),"$1"+s+i+"$3")}),t=t.replace(new RegExp(i,"g"),""),t}var jve,Kve,Qve,YF=R(()=>{"use strict";jve=/\sid="(\S+)"/g,Kve="IconifyId"+Date.now().toString(16)+(Math.random()*16777216|0).toString(16),Qve=0;o(OC,"replaceIDs")});function PC(t,e){let r=t.indexOf("xlink:")===-1?"":' xmlns:xlink="http://www.w3.org/1999/xlink"';for(let n in e)r+=" "+n+'="'+e[n]+'"';return'"+t+""}var WF=R(()=>{"use strict";o(PC,"iconToHTML")});var XF=gi((ait,qF)=>{"use strict";var ip=1e3,ap=ip*60,sp=ap*60,$f=sp*24,Zve=$f*7,Jve=$f*365.25;qF.exports=function(t,e){e=e||{};var r=typeof t;if(r==="string"&&t.length>0)return e2e(t);if(r==="number"&&isFinite(t))return e.long?r2e(t):t2e(t);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(t))};function e2e(t){if(t=String(t),!(t.length>100)){var e=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(t);if(e){var r=parseFloat(e[1]),n=(e[2]||"ms").toLowerCase();switch(n){case"years":case"year":case"yrs":case"yr":case"y":return r*Jve;case"weeks":case"week":case"w":return r*Zve;case"days":case"day":case"d":return r*$f;case"hours":case"hour":case"hrs":case"hr":case"h":return r*sp;case"minutes":case"minute":case"mins":case"min":case"m":return r*ap;case"seconds":case"second":case"secs":case"sec":case"s":return r*ip;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return r;default:return}}}}o(e2e,"parse");function t2e(t){var e=Math.abs(t);return e>=$f?Math.round(t/$f)+"d":e>=sp?Math.round(t/sp)+"h":e>=ap?Math.round(t/ap)+"m":e>=ip?Math.round(t/ip)+"s":t+"ms"}o(t2e,"fmtShort");function r2e(t){var e=Math.abs(t);return e>=$f?Pb(t,e,$f,"day"):e>=sp?Pb(t,e,sp,"hour"):e>=ap?Pb(t,e,ap,"minute"):e>=ip?Pb(t,e,ip,"second"):t+" ms"}o(r2e,"fmtLong");function Pb(t,e,r,n){var i=e>=r*1.5;return Math.round(t/r)+" "+n+(i?"s":"")}o(Pb,"plural")});var KF=gi((oit,jF)=>{"use strict";function n2e(t){r.debug=r,r.default=r,r.coerce=u,r.disable=a,r.enable=i,r.enabled=s,r.humanize=XF(),r.destroy=h,Object.keys(t).forEach(f=>{r[f]=t[f]}),r.names=[],r.skips=[],r.formatters={};function e(f){let d=0;for(let p=0;p{if(E==="%%")return"%";S++;let A=r.formatters[_];if(typeof A=="function"){let L=v[S];E=A.call(x,L),v.splice(S,1),S--}return E}),r.formatArgs.call(x,v),(x.log||r.log).apply(x,v)}return o(y,"debug"),y.namespace=f,y.useColors=r.useColors(),y.color=r.selectColor(f),y.extend=n,y.destroy=r.destroy,Object.defineProperty(y,"enabled",{enumerable:!0,configurable:!1,get:o(()=>p!==null?p:(m!==r.namespaces&&(m=r.namespaces,g=r.enabled(f)),g),"get"),set:o(v=>{p=v},"set")}),typeof r.init=="function"&&r.init(y),y}o(r,"createDebug");function n(f,d){let p=r(this.namespace+(typeof d>"u"?":":d)+f);return p.log=this.log,p}o(n,"extend");function i(f){r.save(f),r.namespaces=f,r.names=[],r.skips=[];let d,p=(typeof f=="string"?f:"").split(/[\s,]+/),m=p.length;for(d=0;d"-"+d)].join(",");return r.enable(""),f}o(a,"disable");function s(f){if(f[f.length-1]==="*")return!0;let d,p;for(d=0,p=r.skips.length;d{"use strict";Ys.formatArgs=a2e;Ys.save=s2e;Ys.load=o2e;Ys.useColors=i2e;Ys.storage=l2e();Ys.destroy=(()=>{let t=!1;return()=>{t||(t=!0,console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."))}})();Ys.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"];function i2e(){if(typeof window<"u"&&window.process&&(window.process.type==="renderer"||window.process.__nwjs))return!0;if(typeof navigator<"u"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))return!1;let t;return typeof document<"u"&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||typeof window<"u"&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||typeof navigator<"u"&&navigator.userAgent&&(t=navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/))&&parseInt(t[1],10)>=31||typeof navigator<"u"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)}o(i2e,"useColors");function a2e(t){if(t[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+t[0]+(this.useColors?"%c ":" ")+"+"+Bb.exports.humanize(this.diff),!this.useColors)return;let e="color: "+this.color;t.splice(1,0,e,"color: inherit");let r=0,n=0;t[0].replace(/%[a-zA-Z%]/g,i=>{i!=="%%"&&(r++,i==="%c"&&(n=r))}),t.splice(n,0,e)}o(a2e,"formatArgs");Ys.log=console.debug||console.log||(()=>{});function s2e(t){try{t?Ys.storage.setItem("debug",t):Ys.storage.removeItem("debug")}catch{}}o(s2e,"save");function o2e(){let t;try{t=Ys.storage.getItem("debug")}catch{}return!t&&typeof process<"u"&&"env"in process&&(t=process.env.DEBUG),t}o(o2e,"load");function l2e(){try{return localStorage}catch{}}o(l2e,"localstorage");Bb.exports=KF()(Ys);var{formatters:c2e}=Bb.exports;c2e.j=function(t){try{return JSON.stringify(t)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}}});var uit,ZF=R(()=>{"use strict";MF();GF();HF();YF();WF();uit=Xi(QF(),1)});var FC,BC,JF,Fb,u2e,zb,V1=R(()=>{"use strict";ut();ZF();FC={body:'?',height:80,width:80},BC=new Map,JF=new Map,Fb=o(t=>{for(let e of t){if(!e.name)throw new Error('Invalid icon loader. Must have a "name" property with non-empty string value.');if(V.debug("Registering icon pack:",e.name),"loader"in e)JF.set(e.name,e.loader);else if("icons"in e)BC.set(e.name,e.icons);else throw V.error("Invalid icon loader:",e),new Error('Invalid icon loader. Must have either "icons" or "loader" property.')}},"registerIconPacks"),u2e=o(async(t,e)=>{let r=DC(t,!0,e!==void 0);if(!r)throw new Error(`Invalid icon name: ${t}`);let n=r.prefix||e;if(!n)throw new Error(`Icon name must contain a prefix: ${t}`);let i=BC.get(n);if(!i){let s=JF.get(n);if(!s)throw new Error(`Icon set not found: ${r.prefix}`);try{i={...await s(),prefix:n},BC.set(n,i)}catch(l){throw V.error(l),new Error(`Failed to load icon set: ${r.prefix}`)}}let a=NC(i,r.name);if(!a)throw new Error(`Icon not found: ${t}`);return a},"getRegisteredIconData"),zb=o(async(t,e)=>{let r;try{r=await u2e(t,e?.fallbackPrefix)}catch(a){V.error(a),r=FC}let n=IC(r,e);return PC(OC(n.body),n.attributes)},"getIconSVG")});function Gb(t){for(var e=[],r=1;r{"use strict";o(Gb,"dedent")});var $b,Vf,ez,Vb=R(()=>{"use strict";$b=/^-{3}\s*[\n\r](.*?)[\n\r]-{3}\s*[\n\r]+/s,Vf=/%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi,ez=/\s*%%.*\n/gm});var op,GC=R(()=>{"use strict";op=class extends Error{static{o(this,"UnknownDiagramError")}constructor(e){super(e),this.name="UnknownDiagramError"}}});var Uf,lp,Ub,$C,tz,Hf=R(()=>{"use strict";ut();Vb();GC();Uf={},lp=o(function(t,e){t=t.replace($b,"").replace(Vf,"").replace(ez,` +`);for(let[r,{detector:n}]of Object.entries(Uf))if(n(t,e))return r;throw new op(`No diagram type detected matching given configuration for text: ${t}`)},"detectType"),Ub=o((...t)=>{for(let{id:e,detector:r,loader:n}of t)$C(e,r,n)},"registerLazyLoadedDiagrams"),$C=o((t,e,r)=>{Uf[t]&&V.warn(`Detector with key ${t} already exists. Overwriting.`),Uf[t]={detector:e,loader:r},V.debug(`Detector with key ${t} added${r?" with loader":""}`)},"addDetector"),tz=o(t=>Uf[t].loader,"getDiagramLoader")});var U1,rz,VC=R(()=>{"use strict";U1=function(){var t=o(function(_e,me,W,fe){for(W=W||{},fe=_e.length;fe--;W[_e[fe]]=me);return W},"o"),e=[1,24],r=[1,25],n=[1,26],i=[1,27],a=[1,28],s=[1,63],l=[1,64],u=[1,65],h=[1,66],f=[1,67],d=[1,68],p=[1,69],m=[1,29],g=[1,30],y=[1,31],v=[1,32],x=[1,33],b=[1,34],w=[1,35],S=[1,36],T=[1,37],E=[1,38],_=[1,39],A=[1,40],L=[1,41],M=[1,42],N=[1,43],k=[1,44],I=[1,45],C=[1,46],O=[1,47],D=[1,48],P=[1,50],F=[1,51],B=[1,52],$=[1,53],z=[1,54],Y=[1,55],Q=[1,56],X=[1,57],ie=[1,58],j=[1,59],J=[1,60],Z=[14,42],H=[14,34,36,37,38,39,40,41,42,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74],q=[12,14,34,36,37,38,39,40,41,42,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74],K=[1,82],se=[1,83],ce=[1,84],ue=[1,85],te=[12,14,42],De=[12,14,33,42],oe=[12,14,33,42,76,77,79,80],ke=[12,33],Ie=[34,36,37,38,39,40,41,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74],Se={trace:o(function(){},"trace"),yy:{},symbols_:{error:2,start:3,mermaidDoc:4,direction:5,direction_tb:6,direction_bt:7,direction_rl:8,direction_lr:9,graphConfig:10,C4_CONTEXT:11,NEWLINE:12,statements:13,EOF:14,C4_CONTAINER:15,C4_COMPONENT:16,C4_DYNAMIC:17,C4_DEPLOYMENT:18,otherStatements:19,diagramStatements:20,otherStatement:21,title:22,accDescription:23,acc_title:24,acc_title_value:25,acc_descr:26,acc_descr_value:27,acc_descr_multiline_value:28,boundaryStatement:29,boundaryStartStatement:30,boundaryStopStatement:31,boundaryStart:32,LBRACE:33,ENTERPRISE_BOUNDARY:34,attributes:35,SYSTEM_BOUNDARY:36,BOUNDARY:37,CONTAINER_BOUNDARY:38,NODE:39,NODE_L:40,NODE_R:41,RBRACE:42,diagramStatement:43,PERSON:44,PERSON_EXT:45,SYSTEM:46,SYSTEM_DB:47,SYSTEM_QUEUE:48,SYSTEM_EXT:49,SYSTEM_EXT_DB:50,SYSTEM_EXT_QUEUE:51,CONTAINER:52,CONTAINER_DB:53,CONTAINER_QUEUE:54,CONTAINER_EXT:55,CONTAINER_EXT_DB:56,CONTAINER_EXT_QUEUE:57,COMPONENT:58,COMPONENT_DB:59,COMPONENT_QUEUE:60,COMPONENT_EXT:61,COMPONENT_EXT_DB:62,COMPONENT_EXT_QUEUE:63,REL:64,BIREL:65,REL_U:66,REL_D:67,REL_L:68,REL_R:69,REL_B:70,REL_INDEX:71,UPDATE_EL_STYLE:72,UPDATE_REL_STYLE:73,UPDATE_LAYOUT_CONFIG:74,attribute:75,STR:76,STR_KEY:77,STR_VALUE:78,ATTRIBUTE:79,ATTRIBUTE_EMPTY:80,$accept:0,$end:1},terminals_:{2:"error",6:"direction_tb",7:"direction_bt",8:"direction_rl",9:"direction_lr",11:"C4_CONTEXT",12:"NEWLINE",14:"EOF",15:"C4_CONTAINER",16:"C4_COMPONENT",17:"C4_DYNAMIC",18:"C4_DEPLOYMENT",22:"title",23:"accDescription",24:"acc_title",25:"acc_title_value",26:"acc_descr",27:"acc_descr_value",28:"acc_descr_multiline_value",33:"LBRACE",34:"ENTERPRISE_BOUNDARY",36:"SYSTEM_BOUNDARY",37:"BOUNDARY",38:"CONTAINER_BOUNDARY",39:"NODE",40:"NODE_L",41:"NODE_R",42:"RBRACE",44:"PERSON",45:"PERSON_EXT",46:"SYSTEM",47:"SYSTEM_DB",48:"SYSTEM_QUEUE",49:"SYSTEM_EXT",50:"SYSTEM_EXT_DB",51:"SYSTEM_EXT_QUEUE",52:"CONTAINER",53:"CONTAINER_DB",54:"CONTAINER_QUEUE",55:"CONTAINER_EXT",56:"CONTAINER_EXT_DB",57:"CONTAINER_EXT_QUEUE",58:"COMPONENT",59:"COMPONENT_DB",60:"COMPONENT_QUEUE",61:"COMPONENT_EXT",62:"COMPONENT_EXT_DB",63:"COMPONENT_EXT_QUEUE",64:"REL",65:"BIREL",66:"REL_U",67:"REL_D",68:"REL_L",69:"REL_R",70:"REL_B",71:"REL_INDEX",72:"UPDATE_EL_STYLE",73:"UPDATE_REL_STYLE",74:"UPDATE_LAYOUT_CONFIG",76:"STR",77:"STR_KEY",78:"STR_VALUE",79:"ATTRIBUTE",80:"ATTRIBUTE_EMPTY"},productions_:[0,[3,1],[3,1],[5,1],[5,1],[5,1],[5,1],[4,1],[10,4],[10,4],[10,4],[10,4],[10,4],[13,1],[13,1],[13,2],[19,1],[19,2],[19,3],[21,1],[21,1],[21,2],[21,2],[21,1],[29,3],[30,3],[30,3],[30,4],[32,2],[32,2],[32,2],[32,2],[32,2],[32,2],[32,2],[31,1],[20,1],[20,2],[20,3],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,1],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[43,2],[35,1],[35,2],[75,1],[75,2],[75,1],[75,1]],performAction:o(function(me,W,fe,ge,re,he,ne){var ae=he.length-1;switch(re){case 3:ge.setDirection("TB");break;case 4:ge.setDirection("BT");break;case 5:ge.setDirection("RL");break;case 6:ge.setDirection("LR");break;case 8:case 9:case 10:case 11:case 12:ge.setC4Type(he[ae-3]);break;case 19:ge.setTitle(he[ae].substring(6)),this.$=he[ae].substring(6);break;case 20:ge.setAccDescription(he[ae].substring(15)),this.$=he[ae].substring(15);break;case 21:this.$=he[ae].trim(),ge.setTitle(this.$);break;case 22:case 23:this.$=he[ae].trim(),ge.setAccDescription(this.$);break;case 28:he[ae].splice(2,0,"ENTERPRISE"),ge.addPersonOrSystemBoundary(...he[ae]),this.$=he[ae];break;case 29:he[ae].splice(2,0,"SYSTEM"),ge.addPersonOrSystemBoundary(...he[ae]),this.$=he[ae];break;case 30:ge.addPersonOrSystemBoundary(...he[ae]),this.$=he[ae];break;case 31:he[ae].splice(2,0,"CONTAINER"),ge.addContainerBoundary(...he[ae]),this.$=he[ae];break;case 32:ge.addDeploymentNode("node",...he[ae]),this.$=he[ae];break;case 33:ge.addDeploymentNode("nodeL",...he[ae]),this.$=he[ae];break;case 34:ge.addDeploymentNode("nodeR",...he[ae]),this.$=he[ae];break;case 35:ge.popBoundaryParseStack();break;case 39:ge.addPersonOrSystem("person",...he[ae]),this.$=he[ae];break;case 40:ge.addPersonOrSystem("external_person",...he[ae]),this.$=he[ae];break;case 41:ge.addPersonOrSystem("system",...he[ae]),this.$=he[ae];break;case 42:ge.addPersonOrSystem("system_db",...he[ae]),this.$=he[ae];break;case 43:ge.addPersonOrSystem("system_queue",...he[ae]),this.$=he[ae];break;case 44:ge.addPersonOrSystem("external_system",...he[ae]),this.$=he[ae];break;case 45:ge.addPersonOrSystem("external_system_db",...he[ae]),this.$=he[ae];break;case 46:ge.addPersonOrSystem("external_system_queue",...he[ae]),this.$=he[ae];break;case 47:ge.addContainer("container",...he[ae]),this.$=he[ae];break;case 48:ge.addContainer("container_db",...he[ae]),this.$=he[ae];break;case 49:ge.addContainer("container_queue",...he[ae]),this.$=he[ae];break;case 50:ge.addContainer("external_container",...he[ae]),this.$=he[ae];break;case 51:ge.addContainer("external_container_db",...he[ae]),this.$=he[ae];break;case 52:ge.addContainer("external_container_queue",...he[ae]),this.$=he[ae];break;case 53:ge.addComponent("component",...he[ae]),this.$=he[ae];break;case 54:ge.addComponent("component_db",...he[ae]),this.$=he[ae];break;case 55:ge.addComponent("component_queue",...he[ae]),this.$=he[ae];break;case 56:ge.addComponent("external_component",...he[ae]),this.$=he[ae];break;case 57:ge.addComponent("external_component_db",...he[ae]),this.$=he[ae];break;case 58:ge.addComponent("external_component_queue",...he[ae]),this.$=he[ae];break;case 60:ge.addRel("rel",...he[ae]),this.$=he[ae];break;case 61:ge.addRel("birel",...he[ae]),this.$=he[ae];break;case 62:ge.addRel("rel_u",...he[ae]),this.$=he[ae];break;case 63:ge.addRel("rel_d",...he[ae]),this.$=he[ae];break;case 64:ge.addRel("rel_l",...he[ae]),this.$=he[ae];break;case 65:ge.addRel("rel_r",...he[ae]),this.$=he[ae];break;case 66:ge.addRel("rel_b",...he[ae]),this.$=he[ae];break;case 67:he[ae].splice(0,1),ge.addRel("rel",...he[ae]),this.$=he[ae];break;case 68:ge.updateElStyle("update_el_style",...he[ae]),this.$=he[ae];break;case 69:ge.updateRelStyle("update_rel_style",...he[ae]),this.$=he[ae];break;case 70:ge.updateLayoutConfig("update_layout_config",...he[ae]),this.$=he[ae];break;case 71:this.$=[he[ae]];break;case 72:he[ae].unshift(he[ae-1]),this.$=he[ae];break;case 73:case 75:this.$=he[ae].trim();break;case 74:let we={};we[he[ae-1].trim()]=he[ae].trim(),this.$=we;break;case 76:this.$="";break}},"anonymous"),table:[{3:1,4:2,5:3,6:[1,5],7:[1,6],8:[1,7],9:[1,8],10:4,11:[1,9],15:[1,10],16:[1,11],17:[1,12],18:[1,13]},{1:[3]},{1:[2,1]},{1:[2,2]},{1:[2,7]},{1:[2,3]},{1:[2,4]},{1:[2,5]},{1:[2,6]},{12:[1,14]},{12:[1,15]},{12:[1,16]},{12:[1,17]},{12:[1,18]},{13:19,19:20,20:21,21:22,22:e,23:r,24:n,26:i,28:a,29:49,30:61,32:62,34:s,36:l,37:u,38:h,39:f,40:d,41:p,43:23,44:m,45:g,46:y,47:v,48:x,49:b,50:w,51:S,52:T,53:E,54:_,55:A,56:L,57:M,58:N,59:k,60:I,61:C,62:O,63:D,64:P,65:F,66:B,67:$,68:z,69:Y,70:Q,71:X,72:ie,73:j,74:J},{13:70,19:20,20:21,21:22,22:e,23:r,24:n,26:i,28:a,29:49,30:61,32:62,34:s,36:l,37:u,38:h,39:f,40:d,41:p,43:23,44:m,45:g,46:y,47:v,48:x,49:b,50:w,51:S,52:T,53:E,54:_,55:A,56:L,57:M,58:N,59:k,60:I,61:C,62:O,63:D,64:P,65:F,66:B,67:$,68:z,69:Y,70:Q,71:X,72:ie,73:j,74:J},{13:71,19:20,20:21,21:22,22:e,23:r,24:n,26:i,28:a,29:49,30:61,32:62,34:s,36:l,37:u,38:h,39:f,40:d,41:p,43:23,44:m,45:g,46:y,47:v,48:x,49:b,50:w,51:S,52:T,53:E,54:_,55:A,56:L,57:M,58:N,59:k,60:I,61:C,62:O,63:D,64:P,65:F,66:B,67:$,68:z,69:Y,70:Q,71:X,72:ie,73:j,74:J},{13:72,19:20,20:21,21:22,22:e,23:r,24:n,26:i,28:a,29:49,30:61,32:62,34:s,36:l,37:u,38:h,39:f,40:d,41:p,43:23,44:m,45:g,46:y,47:v,48:x,49:b,50:w,51:S,52:T,53:E,54:_,55:A,56:L,57:M,58:N,59:k,60:I,61:C,62:O,63:D,64:P,65:F,66:B,67:$,68:z,69:Y,70:Q,71:X,72:ie,73:j,74:J},{13:73,19:20,20:21,21:22,22:e,23:r,24:n,26:i,28:a,29:49,30:61,32:62,34:s,36:l,37:u,38:h,39:f,40:d,41:p,43:23,44:m,45:g,46:y,47:v,48:x,49:b,50:w,51:S,52:T,53:E,54:_,55:A,56:L,57:M,58:N,59:k,60:I,61:C,62:O,63:D,64:P,65:F,66:B,67:$,68:z,69:Y,70:Q,71:X,72:ie,73:j,74:J},{14:[1,74]},t(Z,[2,13],{43:23,29:49,30:61,32:62,20:75,34:s,36:l,37:u,38:h,39:f,40:d,41:p,44:m,45:g,46:y,47:v,48:x,49:b,50:w,51:S,52:T,53:E,54:_,55:A,56:L,57:M,58:N,59:k,60:I,61:C,62:O,63:D,64:P,65:F,66:B,67:$,68:z,69:Y,70:Q,71:X,72:ie,73:j,74:J}),t(Z,[2,14]),t(H,[2,16],{12:[1,76]}),t(Z,[2,36],{12:[1,77]}),t(q,[2,19]),t(q,[2,20]),{25:[1,78]},{27:[1,79]},t(q,[2,23]),{35:80,75:81,76:K,77:se,79:ce,80:ue},{35:86,75:81,76:K,77:se,79:ce,80:ue},{35:87,75:81,76:K,77:se,79:ce,80:ue},{35:88,75:81,76:K,77:se,79:ce,80:ue},{35:89,75:81,76:K,77:se,79:ce,80:ue},{35:90,75:81,76:K,77:se,79:ce,80:ue},{35:91,75:81,76:K,77:se,79:ce,80:ue},{35:92,75:81,76:K,77:se,79:ce,80:ue},{35:93,75:81,76:K,77:se,79:ce,80:ue},{35:94,75:81,76:K,77:se,79:ce,80:ue},{35:95,75:81,76:K,77:se,79:ce,80:ue},{35:96,75:81,76:K,77:se,79:ce,80:ue},{35:97,75:81,76:K,77:se,79:ce,80:ue},{35:98,75:81,76:K,77:se,79:ce,80:ue},{35:99,75:81,76:K,77:se,79:ce,80:ue},{35:100,75:81,76:K,77:se,79:ce,80:ue},{35:101,75:81,76:K,77:se,79:ce,80:ue},{35:102,75:81,76:K,77:se,79:ce,80:ue},{35:103,75:81,76:K,77:se,79:ce,80:ue},{35:104,75:81,76:K,77:se,79:ce,80:ue},t(te,[2,59]),{35:105,75:81,76:K,77:se,79:ce,80:ue},{35:106,75:81,76:K,77:se,79:ce,80:ue},{35:107,75:81,76:K,77:se,79:ce,80:ue},{35:108,75:81,76:K,77:se,79:ce,80:ue},{35:109,75:81,76:K,77:se,79:ce,80:ue},{35:110,75:81,76:K,77:se,79:ce,80:ue},{35:111,75:81,76:K,77:se,79:ce,80:ue},{35:112,75:81,76:K,77:se,79:ce,80:ue},{35:113,75:81,76:K,77:se,79:ce,80:ue},{35:114,75:81,76:K,77:se,79:ce,80:ue},{35:115,75:81,76:K,77:se,79:ce,80:ue},{20:116,29:49,30:61,32:62,34:s,36:l,37:u,38:h,39:f,40:d,41:p,43:23,44:m,45:g,46:y,47:v,48:x,49:b,50:w,51:S,52:T,53:E,54:_,55:A,56:L,57:M,58:N,59:k,60:I,61:C,62:O,63:D,64:P,65:F,66:B,67:$,68:z,69:Y,70:Q,71:X,72:ie,73:j,74:J},{12:[1,118],33:[1,117]},{35:119,75:81,76:K,77:se,79:ce,80:ue},{35:120,75:81,76:K,77:se,79:ce,80:ue},{35:121,75:81,76:K,77:se,79:ce,80:ue},{35:122,75:81,76:K,77:se,79:ce,80:ue},{35:123,75:81,76:K,77:se,79:ce,80:ue},{35:124,75:81,76:K,77:se,79:ce,80:ue},{35:125,75:81,76:K,77:se,79:ce,80:ue},{14:[1,126]},{14:[1,127]},{14:[1,128]},{14:[1,129]},{1:[2,8]},t(Z,[2,15]),t(H,[2,17],{21:22,19:130,22:e,23:r,24:n,26:i,28:a}),t(Z,[2,37],{19:20,20:21,21:22,43:23,29:49,30:61,32:62,13:131,22:e,23:r,24:n,26:i,28:a,34:s,36:l,37:u,38:h,39:f,40:d,41:p,44:m,45:g,46:y,47:v,48:x,49:b,50:w,51:S,52:T,53:E,54:_,55:A,56:L,57:M,58:N,59:k,60:I,61:C,62:O,63:D,64:P,65:F,66:B,67:$,68:z,69:Y,70:Q,71:X,72:ie,73:j,74:J}),t(q,[2,21]),t(q,[2,22]),t(te,[2,39]),t(De,[2,71],{75:81,35:132,76:K,77:se,79:ce,80:ue}),t(oe,[2,73]),{78:[1,133]},t(oe,[2,75]),t(oe,[2,76]),t(te,[2,40]),t(te,[2,41]),t(te,[2,42]),t(te,[2,43]),t(te,[2,44]),t(te,[2,45]),t(te,[2,46]),t(te,[2,47]),t(te,[2,48]),t(te,[2,49]),t(te,[2,50]),t(te,[2,51]),t(te,[2,52]),t(te,[2,53]),t(te,[2,54]),t(te,[2,55]),t(te,[2,56]),t(te,[2,57]),t(te,[2,58]),t(te,[2,60]),t(te,[2,61]),t(te,[2,62]),t(te,[2,63]),t(te,[2,64]),t(te,[2,65]),t(te,[2,66]),t(te,[2,67]),t(te,[2,68]),t(te,[2,69]),t(te,[2,70]),{31:134,42:[1,135]},{12:[1,136]},{33:[1,137]},t(ke,[2,28]),t(ke,[2,29]),t(ke,[2,30]),t(ke,[2,31]),t(ke,[2,32]),t(ke,[2,33]),t(ke,[2,34]),{1:[2,9]},{1:[2,10]},{1:[2,11]},{1:[2,12]},t(H,[2,18]),t(Z,[2,38]),t(De,[2,72]),t(oe,[2,74]),t(te,[2,24]),t(te,[2,35]),t(Ie,[2,25]),t(Ie,[2,26],{12:[1,138]}),t(Ie,[2,27])],defaultActions:{2:[2,1],3:[2,2],4:[2,7],5:[2,3],6:[2,4],7:[2,5],8:[2,6],74:[2,8],126:[2,9],127:[2,10],128:[2,11],129:[2,12]},parseError:o(function(me,W){if(W.recoverable)this.trace(me);else{var fe=new Error(me);throw fe.hash=W,fe}},"parseError"),parse:o(function(me){var W=this,fe=[0],ge=[],re=[null],he=[],ne=this.table,ae="",we=0,Te=0,Ce=0,Ae=2,Ge=1,Me=he.slice.call(arguments,1),ye=Object.create(this.lexer),He={yy:{}};for(var ze in this.yy)Object.prototype.hasOwnProperty.call(this.yy,ze)&&(He.yy[ze]=this.yy[ze]);ye.setInput(me,He.yy),He.yy.lexer=ye,He.yy.parser=this,typeof ye.yylloc>"u"&&(ye.yylloc={});var Ze=ye.yylloc;he.push(Ze);var gt=ye.options&&ye.options.ranges;typeof He.yy.parseError=="function"?this.parseError=He.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function yt(St){fe.length=fe.length-2*St,re.length=re.length-St,he.length=he.length-St}o(yt,"popStack");function tt(){var St;return St=ge.pop()||ye.lex()||Ge,typeof St!="number"&&(St instanceof Array&&(ge=St,St=ge.pop()),St=W.symbols_[St]||St),St}o(tt,"lex");for(var Ye,Je,Ve,je,kt,at,xt={},it,dt,lt,It;;){if(Ve=fe[fe.length-1],this.defaultActions[Ve]?je=this.defaultActions[Ve]:((Ye===null||typeof Ye>"u")&&(Ye=tt()),je=ne[Ve]&&ne[Ve][Ye]),typeof je>"u"||!je.length||!je[0]){var mt="";It=[];for(it in ne[Ve])this.terminals_[it]&&it>Ae&&It.push("'"+this.terminals_[it]+"'");ye.showPosition?mt="Parse error on line "+(we+1)+`: +`+ye.showPosition()+` +Expecting `+It.join(", ")+", got '"+(this.terminals_[Ye]||Ye)+"'":mt="Parse error on line "+(we+1)+": Unexpected "+(Ye==Ge?"end of input":"'"+(this.terminals_[Ye]||Ye)+"'"),this.parseError(mt,{text:ye.match,token:this.terminals_[Ye]||Ye,line:ye.yylineno,loc:Ze,expected:It})}if(je[0]instanceof Array&&je.length>1)throw new Error("Parse Error: multiple actions possible at state: "+Ve+", token: "+Ye);switch(je[0]){case 1:fe.push(Ye),re.push(ye.yytext),he.push(ye.yylloc),fe.push(je[1]),Ye=null,Je?(Ye=Je,Je=null):(Te=ye.yyleng,ae=ye.yytext,we=ye.yylineno,Ze=ye.yylloc,Ce>0&&Ce--);break;case 2:if(dt=this.productions_[je[1]][1],xt.$=re[re.length-dt],xt._$={first_line:he[he.length-(dt||1)].first_line,last_line:he[he.length-1].last_line,first_column:he[he.length-(dt||1)].first_column,last_column:he[he.length-1].last_column},gt&&(xt._$.range=[he[he.length-(dt||1)].range[0],he[he.length-1].range[1]]),at=this.performAction.apply(xt,[ae,Te,we,He.yy,je[1],re,he].concat(Me)),typeof at<"u")return at;dt&&(fe=fe.slice(0,-1*dt*2),re=re.slice(0,-1*dt),he=he.slice(0,-1*dt)),fe.push(this.productions_[je[1]][0]),re.push(xt.$),he.push(xt._$),lt=ne[fe[fe.length-2]][fe[fe.length-1]],fe.push(lt);break;case 3:return!0}}return!0},"parse")},Ue=function(){var _e={EOF:1,parseError:o(function(W,fe){if(this.yy.parser)this.yy.parser.parseError(W,fe);else throw new Error(W)},"parseError"),setInput:o(function(me,W){return this.yy=W||this.yy||{},this._input=me,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:o(function(){var me=this._input[0];this.yytext+=me,this.yyleng++,this.offset++,this.match+=me,this.matched+=me;var W=me.match(/(?:\r\n?|\n).*/g);return W?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),me},"input"),unput:o(function(me){var W=me.length,fe=me.split(/(?:\r\n?|\n)/g);this._input=me+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-W),this.offset-=W;var ge=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),fe.length-1&&(this.yylineno-=fe.length-1);var re=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:fe?(fe.length===ge.length?this.yylloc.first_column:0)+ge[ge.length-fe.length].length-fe[0].length:this.yylloc.first_column-W},this.options.ranges&&(this.yylloc.range=[re[0],re[0]+this.yyleng-W]),this.yyleng=this.yytext.length,this},"unput"),more:o(function(){return this._more=!0,this},"more"),reject:o(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:o(function(me){this.unput(this.match.slice(me))},"less"),pastInput:o(function(){var me=this.matched.substr(0,this.matched.length-this.match.length);return(me.length>20?"...":"")+me.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:o(function(){var me=this.match;return me.length<20&&(me+=this._input.substr(0,20-me.length)),(me.substr(0,20)+(me.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:o(function(){var me=this.pastInput(),W=new Array(me.length+1).join("-");return me+this.upcomingInput()+` +`+W+"^"},"showPosition"),test_match:o(function(me,W){var fe,ge,re;if(this.options.backtrack_lexer&&(re={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(re.yylloc.range=this.yylloc.range.slice(0))),ge=me[0].match(/(?:\r\n?|\n).*/g),ge&&(this.yylineno+=ge.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:ge?ge[ge.length-1].length-ge[ge.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+me[0].length},this.yytext+=me[0],this.match+=me[0],this.matches=me,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(me[0].length),this.matched+=me[0],fe=this.performAction.call(this,this.yy,this,W,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),fe)return fe;if(this._backtrack){for(var he in re)this[he]=re[he];return!1}return!1},"test_match"),next:o(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var me,W,fe,ge;this._more||(this.yytext="",this.match="");for(var re=this._currentRules(),he=0;heW[0].length)){if(W=fe,ge=he,this.options.backtrack_lexer){if(me=this.test_match(fe,re[he]),me!==!1)return me;if(this._backtrack){W=!1;continue}else return!1}else if(!this.options.flex)break}return W?(me=this.test_match(W,re[ge]),me!==!1?me:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:o(function(){var W=this.next();return W||this.lex()},"lex"),begin:o(function(W){this.conditionStack.push(W)},"begin"),popState:o(function(){var W=this.conditionStack.length-1;return W>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:o(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:o(function(W){return W=this.conditionStack.length-1-Math.abs(W||0),W>=0?this.conditionStack[W]:"INITIAL"},"topState"),pushState:o(function(W){this.begin(W)},"pushState"),stateStackSize:o(function(){return this.conditionStack.length},"stateStackSize"),options:{},performAction:o(function(W,fe,ge,re){var he=re;switch(ge){case 0:return 6;case 1:return 7;case 2:return 8;case 3:return 9;case 4:return 22;case 5:return 23;case 6:return this.begin("acc_title"),24;break;case 7:return this.popState(),"acc_title_value";break;case 8:return this.begin("acc_descr"),26;break;case 9:return this.popState(),"acc_descr_value";break;case 10:this.begin("acc_descr_multiline");break;case 11:this.popState();break;case 12:return"acc_descr_multiline_value";case 13:break;case 14:c;break;case 15:return 12;case 16:break;case 17:return 11;case 18:return 15;case 19:return 16;case 20:return 17;case 21:return 18;case 22:return this.begin("person_ext"),45;break;case 23:return this.begin("person"),44;break;case 24:return this.begin("system_ext_queue"),51;break;case 25:return this.begin("system_ext_db"),50;break;case 26:return this.begin("system_ext"),49;break;case 27:return this.begin("system_queue"),48;break;case 28:return this.begin("system_db"),47;break;case 29:return this.begin("system"),46;break;case 30:return this.begin("boundary"),37;break;case 31:return this.begin("enterprise_boundary"),34;break;case 32:return this.begin("system_boundary"),36;break;case 33:return this.begin("container_ext_queue"),57;break;case 34:return this.begin("container_ext_db"),56;break;case 35:return this.begin("container_ext"),55;break;case 36:return this.begin("container_queue"),54;break;case 37:return this.begin("container_db"),53;break;case 38:return this.begin("container"),52;break;case 39:return this.begin("container_boundary"),38;break;case 40:return this.begin("component_ext_queue"),63;break;case 41:return this.begin("component_ext_db"),62;break;case 42:return this.begin("component_ext"),61;break;case 43:return this.begin("component_queue"),60;break;case 44:return this.begin("component_db"),59;break;case 45:return this.begin("component"),58;break;case 46:return this.begin("node"),39;break;case 47:return this.begin("node"),39;break;case 48:return this.begin("node_l"),40;break;case 49:return this.begin("node_r"),41;break;case 50:return this.begin("rel"),64;break;case 51:return this.begin("birel"),65;break;case 52:return this.begin("rel_u"),66;break;case 53:return this.begin("rel_u"),66;break;case 54:return this.begin("rel_d"),67;break;case 55:return this.begin("rel_d"),67;break;case 56:return this.begin("rel_l"),68;break;case 57:return this.begin("rel_l"),68;break;case 58:return this.begin("rel_r"),69;break;case 59:return this.begin("rel_r"),69;break;case 60:return this.begin("rel_b"),70;break;case 61:return this.begin("rel_index"),71;break;case 62:return this.begin("update_el_style"),72;break;case 63:return this.begin("update_rel_style"),73;break;case 64:return this.begin("update_layout_config"),74;break;case 65:return"EOF_IN_STRUCT";case 66:return this.begin("attribute"),"ATTRIBUTE_EMPTY";break;case 67:this.begin("attribute");break;case 68:this.popState(),this.popState();break;case 69:return 80;case 70:break;case 71:return 80;case 72:this.begin("string");break;case 73:this.popState();break;case 74:return"STR";case 75:this.begin("string_kv");break;case 76:return this.begin("string_kv_key"),"STR_KEY";break;case 77:this.popState(),this.begin("string_kv_value");break;case 78:return"STR_VALUE";case 79:this.popState(),this.popState();break;case 80:return"STR";case 81:return"LBRACE";case 82:return"RBRACE";case 83:return"SPACE";case 84:return"EOL";case 85:return 14}},"anonymous"),rules:[/^(?:.*direction\s+TB[^\n]*)/,/^(?:.*direction\s+BT[^\n]*)/,/^(?:.*direction\s+RL[^\n]*)/,/^(?:.*direction\s+LR[^\n]*)/,/^(?:title\s[^#\n;]+)/,/^(?:accDescription\s[^#\n;]+)/,/^(?:accTitle\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*\{\s*)/,/^(?:[\}])/,/^(?:[^\}]*)/,/^(?:%%(?!\{)*[^\n]*(\r?\n?)+)/,/^(?:%%[^\n]*(\r?\n)*)/,/^(?:\s*(\r?\n)+)/,/^(?:\s+)/,/^(?:C4Context\b)/,/^(?:C4Container\b)/,/^(?:C4Component\b)/,/^(?:C4Dynamic\b)/,/^(?:C4Deployment\b)/,/^(?:Person_Ext\b)/,/^(?:Person\b)/,/^(?:SystemQueue_Ext\b)/,/^(?:SystemDb_Ext\b)/,/^(?:System_Ext\b)/,/^(?:SystemQueue\b)/,/^(?:SystemDb\b)/,/^(?:System\b)/,/^(?:Boundary\b)/,/^(?:Enterprise_Boundary\b)/,/^(?:System_Boundary\b)/,/^(?:ContainerQueue_Ext\b)/,/^(?:ContainerDb_Ext\b)/,/^(?:Container_Ext\b)/,/^(?:ContainerQueue\b)/,/^(?:ContainerDb\b)/,/^(?:Container\b)/,/^(?:Container_Boundary\b)/,/^(?:ComponentQueue_Ext\b)/,/^(?:ComponentDb_Ext\b)/,/^(?:Component_Ext\b)/,/^(?:ComponentQueue\b)/,/^(?:ComponentDb\b)/,/^(?:Component\b)/,/^(?:Deployment_Node\b)/,/^(?:Node\b)/,/^(?:Node_L\b)/,/^(?:Node_R\b)/,/^(?:Rel\b)/,/^(?:BiRel\b)/,/^(?:Rel_Up\b)/,/^(?:Rel_U\b)/,/^(?:Rel_Down\b)/,/^(?:Rel_D\b)/,/^(?:Rel_Left\b)/,/^(?:Rel_L\b)/,/^(?:Rel_Right\b)/,/^(?:Rel_R\b)/,/^(?:Rel_Back\b)/,/^(?:RelIndex\b)/,/^(?:UpdateElementStyle\b)/,/^(?:UpdateRelStyle\b)/,/^(?:UpdateLayoutConfig\b)/,/^(?:$)/,/^(?:[(][ ]*[,])/,/^(?:[(])/,/^(?:[)])/,/^(?:,,)/,/^(?:,)/,/^(?:[ ]*["]["])/,/^(?:[ ]*["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:[ ]*[\$])/,/^(?:[^=]*)/,/^(?:[=][ ]*["])/,/^(?:[^"]+)/,/^(?:["])/,/^(?:[^,]+)/,/^(?:\{)/,/^(?:\})/,/^(?:[\s]+)/,/^(?:[\n\r]+)/,/^(?:$)/],conditions:{acc_descr_multiline:{rules:[11,12],inclusive:!1},acc_descr:{rules:[9],inclusive:!1},acc_title:{rules:[7],inclusive:!1},string_kv_value:{rules:[78,79],inclusive:!1},string_kv_key:{rules:[77],inclusive:!1},string_kv:{rules:[76],inclusive:!1},string:{rules:[73,74],inclusive:!1},attribute:{rules:[68,69,70,71,72,75,80],inclusive:!1},update_layout_config:{rules:[65,66,67,68],inclusive:!1},update_rel_style:{rules:[65,66,67,68],inclusive:!1},update_el_style:{rules:[65,66,67,68],inclusive:!1},rel_b:{rules:[65,66,67,68],inclusive:!1},rel_r:{rules:[65,66,67,68],inclusive:!1},rel_l:{rules:[65,66,67,68],inclusive:!1},rel_d:{rules:[65,66,67,68],inclusive:!1},rel_u:{rules:[65,66,67,68],inclusive:!1},rel_bi:{rules:[],inclusive:!1},rel:{rules:[65,66,67,68],inclusive:!1},node_r:{rules:[65,66,67,68],inclusive:!1},node_l:{rules:[65,66,67,68],inclusive:!1},node:{rules:[65,66,67,68],inclusive:!1},index:{rules:[],inclusive:!1},rel_index:{rules:[65,66,67,68],inclusive:!1},component_ext_queue:{rules:[],inclusive:!1},component_ext_db:{rules:[65,66,67,68],inclusive:!1},component_ext:{rules:[65,66,67,68],inclusive:!1},component_queue:{rules:[65,66,67,68],inclusive:!1},component_db:{rules:[65,66,67,68],inclusive:!1},component:{rules:[65,66,67,68],inclusive:!1},container_boundary:{rules:[65,66,67,68],inclusive:!1},container_ext_queue:{rules:[65,66,67,68],inclusive:!1},container_ext_db:{rules:[65,66,67,68],inclusive:!1},container_ext:{rules:[65,66,67,68],inclusive:!1},container_queue:{rules:[65,66,67,68],inclusive:!1},container_db:{rules:[65,66,67,68],inclusive:!1},container:{rules:[65,66,67,68],inclusive:!1},birel:{rules:[65,66,67,68],inclusive:!1},system_boundary:{rules:[65,66,67,68],inclusive:!1},enterprise_boundary:{rules:[65,66,67,68],inclusive:!1},boundary:{rules:[65,66,67,68],inclusive:!1},system_ext_queue:{rules:[65,66,67,68],inclusive:!1},system_ext_db:{rules:[65,66,67,68],inclusive:!1},system_ext:{rules:[65,66,67,68],inclusive:!1},system_queue:{rules:[65,66,67,68],inclusive:!1},system_db:{rules:[65,66,67,68],inclusive:!1},system:{rules:[65,66,67,68],inclusive:!1},person_ext:{rules:[65,66,67,68],inclusive:!1},person:{rules:[65,66,67,68],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,8,10,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,81,82,83,84,85],inclusive:!0}}};return _e}();Se.lexer=Ue;function Pe(){this.yy={}}return o(Pe,"Parser"),Pe.prototype=Se,Se.Parser=Pe,new Pe}();U1.parser=U1;rz=U1});var UC,On,cp=R(()=>{"use strict";UC=o((t,e,{depth:r=2,clobber:n=!1}={})=>{let i={depth:r,clobber:n};return Array.isArray(e)&&!Array.isArray(t)?(e.forEach(a=>UC(t,a,i)),t):Array.isArray(e)&&Array.isArray(t)?(e.forEach(a=>{t.includes(a)||t.push(a)}),t):t===void 0||r<=0?t!=null&&typeof t=="object"&&typeof e=="object"?Object.assign(t,e):e:(e!==void 0&&typeof t=="object"&&typeof e=="object"&&Object.keys(e).forEach(a=>{typeof e[a]=="object"&&(t[a]===void 0||typeof t[a]=="object")?(t[a]===void 0&&(t[a]=Array.isArray(e[a])?[]:{}),t[a]=UC(t[a],e[a],{depth:r-1,clobber:n})):(n||typeof t[a]!="object"&&typeof e[a]!="object")&&(t[a]=e[a])}),t)},"assignWithDepth"),On=UC});var Hb,nz,iz=R(()=>{"use strict";Hb={min:{r:0,g:0,b:0,s:0,l:0,a:0},max:{r:255,g:255,b:255,h:360,s:100,l:100,a:1},clamp:{r:o(t=>t>=255?255:t<0?0:t,"r"),g:o(t=>t>=255?255:t<0?0:t,"g"),b:o(t=>t>=255?255:t<0?0:t,"b"),h:o(t=>t%360,"h"),s:o(t=>t>=100?100:t<0?0:t,"s"),l:o(t=>t>=100?100:t<0?0:t,"l"),a:o(t=>t>=1?1:t<0?0:t,"a")},toLinear:o(t=>{let e=t/255;return t>.03928?Math.pow((e+.055)/1.055,2.4):e/12.92},"toLinear"),hue2rgb:o((t,e,r)=>(r<0&&(r+=1),r>1&&(r-=1),r<.16666666666666666?t+(e-t)*6*r:r<.5?e:r<.6666666666666666?t+(e-t)*(.6666666666666666-r)*6:t),"hue2rgb"),hsl2rgb:o(({h:t,s:e,l:r},n)=>{if(!e)return r*2.55;t/=360,e/=100,r/=100;let i=r<.5?r*(1+e):r+e-r*e,a=2*r-i;switch(n){case"r":return Hb.hue2rgb(a,i,t+.3333333333333333)*255;case"g":return Hb.hue2rgb(a,i,t)*255;case"b":return Hb.hue2rgb(a,i,t-.3333333333333333)*255}},"hsl2rgb"),rgb2hsl:o(({r:t,g:e,b:r},n)=>{t/=255,e/=255,r/=255;let i=Math.max(t,e,r),a=Math.min(t,e,r),s=(i+a)/2;if(n==="l")return s*100;if(i===a)return 0;let l=i-a,u=s>.5?l/(2-i-a):l/(i+a);if(n==="s")return u*100;switch(i){case t:return((e-r)/l+(e{"use strict";h2e={clamp:o((t,e,r)=>e>r?Math.min(e,Math.max(r,t)):Math.min(r,Math.max(e,t)),"clamp"),round:o(t=>Math.round(t*1e10)/1e10,"round")},az=h2e});var f2e,oz,lz=R(()=>{"use strict";f2e={dec2hex:o(t=>{let e=Math.round(t).toString(16);return e.length>1?e:`0${e}`},"dec2hex")},oz=f2e});var d2e,Bt,jl=R(()=>{"use strict";iz();sz();lz();d2e={channel:nz,lang:az,unit:oz},Bt=d2e});var Jc,Li,H1=R(()=>{"use strict";jl();Jc={};for(let t=0;t<=255;t++)Jc[t]=Bt.unit.dec2hex(t);Li={ALL:0,RGB:1,HSL:2}});var HC,cz,uz=R(()=>{"use strict";H1();HC=class{static{o(this,"Type")}constructor(){this.type=Li.ALL}get(){return this.type}set(e){if(this.type&&this.type!==e)throw new Error("Cannot change both RGB and HSL channels at the same time");this.type=e}reset(){this.type=Li.ALL}is(e){return this.type===e}},cz=HC});var YC,hz,fz=R(()=>{"use strict";jl();uz();H1();YC=class{static{o(this,"Channels")}constructor(e,r){this.color=r,this.changed=!1,this.data=e,this.type=new cz}set(e,r){return this.color=r,this.changed=!1,this.data=e,this.type.type=Li.ALL,this}_ensureHSL(){let e=this.data,{h:r,s:n,l:i}=e;r===void 0&&(e.h=Bt.channel.rgb2hsl(e,"h")),n===void 0&&(e.s=Bt.channel.rgb2hsl(e,"s")),i===void 0&&(e.l=Bt.channel.rgb2hsl(e,"l"))}_ensureRGB(){let e=this.data,{r,g:n,b:i}=e;r===void 0&&(e.r=Bt.channel.hsl2rgb(e,"r")),n===void 0&&(e.g=Bt.channel.hsl2rgb(e,"g")),i===void 0&&(e.b=Bt.channel.hsl2rgb(e,"b"))}get r(){let e=this.data,r=e.r;return!this.type.is(Li.HSL)&&r!==void 0?r:(this._ensureHSL(),Bt.channel.hsl2rgb(e,"r"))}get g(){let e=this.data,r=e.g;return!this.type.is(Li.HSL)&&r!==void 0?r:(this._ensureHSL(),Bt.channel.hsl2rgb(e,"g"))}get b(){let e=this.data,r=e.b;return!this.type.is(Li.HSL)&&r!==void 0?r:(this._ensureHSL(),Bt.channel.hsl2rgb(e,"b"))}get h(){let e=this.data,r=e.h;return!this.type.is(Li.RGB)&&r!==void 0?r:(this._ensureRGB(),Bt.channel.rgb2hsl(e,"h"))}get s(){let e=this.data,r=e.s;return!this.type.is(Li.RGB)&&r!==void 0?r:(this._ensureRGB(),Bt.channel.rgb2hsl(e,"s"))}get l(){let e=this.data,r=e.l;return!this.type.is(Li.RGB)&&r!==void 0?r:(this._ensureRGB(),Bt.channel.rgb2hsl(e,"l"))}get a(){return this.data.a}set r(e){this.type.set(Li.RGB),this.changed=!0,this.data.r=e}set g(e){this.type.set(Li.RGB),this.changed=!0,this.data.g=e}set b(e){this.type.set(Li.RGB),this.changed=!0,this.data.b=e}set h(e){this.type.set(Li.HSL),this.changed=!0,this.data.h=e}set s(e){this.type.set(Li.HSL),this.changed=!0,this.data.s=e}set l(e){this.type.set(Li.HSL),this.changed=!0,this.data.l=e}set a(e){this.changed=!0,this.data.a=e}},hz=YC});var p2e,oh,Y1=R(()=>{"use strict";fz();p2e=new hz({r:0,g:0,b:0,a:0},"transparent"),oh=p2e});var dz,Yf,WC=R(()=>{"use strict";Y1();H1();dz={re:/^#((?:[a-f0-9]{2}){2,4}|[a-f0-9]{3})$/i,parse:o(t=>{if(t.charCodeAt(0)!==35)return;let e=t.match(dz.re);if(!e)return;let r=e[1],n=parseInt(r,16),i=r.length,a=i%4===0,s=i>4,l=s?1:17,u=s?8:4,h=a?0:-1,f=s?255:15;return oh.set({r:(n>>u*(h+3)&f)*l,g:(n>>u*(h+2)&f)*l,b:(n>>u*(h+1)&f)*l,a:a?(n&f)*l/255:1},t)},"parse"),stringify:o(t=>{let{r:e,g:r,b:n,a:i}=t;return i<1?`#${Jc[Math.round(e)]}${Jc[Math.round(r)]}${Jc[Math.round(n)]}${Jc[Math.round(i*255)]}`:`#${Jc[Math.round(e)]}${Jc[Math.round(r)]}${Jc[Math.round(n)]}`},"stringify")},Yf=dz});var Yb,W1,pz=R(()=>{"use strict";jl();Y1();Yb={re:/^hsla?\(\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?(?:deg|grad|rad|turn)?)\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?%)\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?%)(?:\s*?(?:,|\/)\s*?\+?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?(%)?))?\s*?\)$/i,hueRe:/^(.+?)(deg|grad|rad|turn)$/i,_hue2deg:o(t=>{let e=t.match(Yb.hueRe);if(e){let[,r,n]=e;switch(n){case"grad":return Bt.channel.clamp.h(parseFloat(r)*.9);case"rad":return Bt.channel.clamp.h(parseFloat(r)*180/Math.PI);case"turn":return Bt.channel.clamp.h(parseFloat(r)*360)}}return Bt.channel.clamp.h(parseFloat(t))},"_hue2deg"),parse:o(t=>{let e=t.charCodeAt(0);if(e!==104&&e!==72)return;let r=t.match(Yb.re);if(!r)return;let[,n,i,a,s,l]=r;return oh.set({h:Yb._hue2deg(n),s:Bt.channel.clamp.s(parseFloat(i)),l:Bt.channel.clamp.l(parseFloat(a)),a:s?Bt.channel.clamp.a(l?parseFloat(s)/100:parseFloat(s)):1},t)},"parse"),stringify:o(t=>{let{h:e,s:r,l:n,a:i}=t;return i<1?`hsla(${Bt.lang.round(e)}, ${Bt.lang.round(r)}%, ${Bt.lang.round(n)}%, ${i})`:`hsl(${Bt.lang.round(e)}, ${Bt.lang.round(r)}%, ${Bt.lang.round(n)}%)`},"stringify")},W1=Yb});var Wb,qC,mz=R(()=>{"use strict";WC();Wb={colors:{aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyanaqua:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",rebeccapurple:"#663399",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",transparent:"#00000000",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"},parse:o(t=>{t=t.toLowerCase();let e=Wb.colors[t];if(e)return Yf.parse(e)},"parse"),stringify:o(t=>{let e=Yf.stringify(t);for(let r in Wb.colors)if(Wb.colors[r]===e)return r},"stringify")},qC=Wb});var gz,q1,yz=R(()=>{"use strict";jl();Y1();gz={re:/^rgba?\(\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))(?:\s*?(?:,|\/)\s*?\+?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?)))?\s*?\)$/i,parse:o(t=>{let e=t.charCodeAt(0);if(e!==114&&e!==82)return;let r=t.match(gz.re);if(!r)return;let[,n,i,a,s,l,u,h,f]=r;return oh.set({r:Bt.channel.clamp.r(i?parseFloat(n)*2.55:parseFloat(n)),g:Bt.channel.clamp.g(s?parseFloat(a)*2.55:parseFloat(a)),b:Bt.channel.clamp.b(u?parseFloat(l)*2.55:parseFloat(l)),a:h?Bt.channel.clamp.a(f?parseFloat(h)/100:parseFloat(h)):1},t)},"parse"),stringify:o(t=>{let{r:e,g:r,b:n,a:i}=t;return i<1?`rgba(${Bt.lang.round(e)}, ${Bt.lang.round(r)}, ${Bt.lang.round(n)}, ${Bt.lang.round(i)})`:`rgb(${Bt.lang.round(e)}, ${Bt.lang.round(r)}, ${Bt.lang.round(n)})`},"stringify")},q1=gz});var m2e,Di,eu=R(()=>{"use strict";WC();pz();mz();yz();H1();m2e={format:{keyword:qC,hex:Yf,rgb:q1,rgba:q1,hsl:W1,hsla:W1},parse:o(t=>{if(typeof t!="string")return t;let e=Yf.parse(t)||q1.parse(t)||W1.parse(t)||qC.parse(t);if(e)return e;throw new Error(`Unsupported color format: "${t}"`)},"parse"),stringify:o(t=>!t.changed&&t.color?t.color:t.type.is(Li.HSL)||t.data.r===void 0?W1.stringify(t):t.a<1||!Number.isInteger(t.r)||!Number.isInteger(t.g)||!Number.isInteger(t.b)?q1.stringify(t):Yf.stringify(t),"stringify")},Di=m2e});var g2e,qb,XC=R(()=>{"use strict";jl();eu();g2e=o((t,e)=>{let r=Di.parse(t);for(let n in e)r[n]=Bt.channel.clamp[n](e[n]);return Di.stringify(r)},"change"),qb=g2e});var y2e,Ws,jC=R(()=>{"use strict";jl();Y1();eu();XC();y2e=o((t,e,r=0,n=1)=>{if(typeof t!="number")return qb(t,{a:e});let i=oh.set({r:Bt.channel.clamp.r(t),g:Bt.channel.clamp.g(e),b:Bt.channel.clamp.b(r),a:Bt.channel.clamp.a(n)});return Di.stringify(i)},"rgba"),Ws=y2e});var v2e,X1,vz=R(()=>{"use strict";jl();eu();v2e=o((t,e)=>Bt.lang.round(Di.parse(t)[e]),"channel"),X1=v2e});var x2e,xz,bz=R(()=>{"use strict";jl();eu();x2e=o(t=>{let{r:e,g:r,b:n}=Di.parse(t),i=.2126*Bt.channel.toLinear(e)+.7152*Bt.channel.toLinear(r)+.0722*Bt.channel.toLinear(n);return Bt.lang.round(i)},"luminance"),xz=x2e});var b2e,wz,Tz=R(()=>{"use strict";bz();b2e=o(t=>xz(t)>=.5,"isLight"),wz=b2e});var w2e,Wa,kz=R(()=>{"use strict";Tz();w2e=o(t=>!wz(t),"isDark"),Wa=w2e});var T2e,Xb,KC=R(()=>{"use strict";jl();eu();T2e=o((t,e,r)=>{let n=Di.parse(t),i=n[e],a=Bt.channel.clamp[e](i+r);return i!==a&&(n[e]=a),Di.stringify(n)},"adjustChannel"),Xb=T2e});var k2e,Et,Ez=R(()=>{"use strict";KC();k2e=o((t,e)=>Xb(t,"l",e),"lighten"),Et=k2e});var E2e,Dt,Cz=R(()=>{"use strict";KC();E2e=o((t,e)=>Xb(t,"l",-e),"darken"),Dt=E2e});var C2e,Oe,Sz=R(()=>{"use strict";eu();XC();C2e=o((t,e)=>{let r=Di.parse(t),n={};for(let i in e)e[i]&&(n[i]=r[i]+e[i]);return qb(t,n)},"adjust"),Oe=C2e});var S2e,Az,_z=R(()=>{"use strict";eu();jC();S2e=o((t,e,r=50)=>{let{r:n,g:i,b:a,a:s}=Di.parse(t),{r:l,g:u,b:h,a:f}=Di.parse(e),d=r/100,p=d*2-1,m=s-f,y=((p*m===-1?p:(p+m)/(1+p*m))+1)/2,v=1-y,x=n*y+l*v,b=i*y+u*v,w=a*y+h*v,S=s*d+f*(1-d);return Ws(x,b,w,S)},"mix"),Az=S2e});var A2e,ot,Lz=R(()=>{"use strict";eu();_z();A2e=o((t,e=100)=>{let r=Di.parse(t);return r.r=255-r.r,r.g=255-r.g,r.b=255-r.b,Az(r,t,e)},"invert"),ot=A2e});var Dz=R(()=>{"use strict";jC();vz();kz();Ez();Cz();Sz();Lz()});var al=R(()=>{"use strict";Dz()});var lh,ch,j1=R(()=>{"use strict";lh="#ffffff",ch="#f2f2f2"});var yi,up=R(()=>{"use strict";al();yi=o((t,e)=>e?Oe(t,{s:-40,l:10}):Oe(t,{s:-40,l:-10}),"mkBorder")});var QC,Nz,Mz=R(()=>{"use strict";al();j1();up();QC=class{static{o(this,"Theme")}constructor(){this.background="#f4f4f4",this.primaryColor="#fff4dd",this.noteBkgColor="#fff5ad",this.noteTextColor="#333",this.THEME_COLOR_LIMIT=12,this.fontFamily='"trebuchet ms", verdana, arial, sans-serif',this.fontSize="16px"}updateColors(){if(this.primaryTextColor=this.primaryTextColor||(this.darkMode?"#eee":"#333"),this.secondaryColor=this.secondaryColor||Oe(this.primaryColor,{h:-120}),this.tertiaryColor=this.tertiaryColor||Oe(this.primaryColor,{h:180,l:5}),this.primaryBorderColor=this.primaryBorderColor||yi(this.primaryColor,this.darkMode),this.secondaryBorderColor=this.secondaryBorderColor||yi(this.secondaryColor,this.darkMode),this.tertiaryBorderColor=this.tertiaryBorderColor||yi(this.tertiaryColor,this.darkMode),this.noteBorderColor=this.noteBorderColor||yi(this.noteBkgColor,this.darkMode),this.noteBkgColor=this.noteBkgColor||"#fff5ad",this.noteTextColor=this.noteTextColor||"#333",this.secondaryTextColor=this.secondaryTextColor||ot(this.secondaryColor),this.tertiaryTextColor=this.tertiaryTextColor||ot(this.tertiaryColor),this.lineColor=this.lineColor||ot(this.background),this.arrowheadColor=this.arrowheadColor||ot(this.background),this.textColor=this.textColor||this.primaryTextColor,this.border2=this.border2||this.tertiaryBorderColor,this.nodeBkg=this.nodeBkg||this.primaryColor,this.mainBkg=this.mainBkg||this.primaryColor,this.nodeBorder=this.nodeBorder||this.primaryBorderColor,this.clusterBkg=this.clusterBkg||this.tertiaryColor,this.clusterBorder=this.clusterBorder||this.tertiaryBorderColor,this.defaultLinkColor=this.defaultLinkColor||this.lineColor,this.titleColor=this.titleColor||this.tertiaryTextColor,this.edgeLabelBackground=this.edgeLabelBackground||(this.darkMode?Dt(this.secondaryColor,30):this.secondaryColor),this.nodeTextColor=this.nodeTextColor||this.primaryTextColor,this.actorBorder=this.actorBorder||this.primaryBorderColor,this.actorBkg=this.actorBkg||this.mainBkg,this.actorTextColor=this.actorTextColor||this.primaryTextColor,this.actorLineColor=this.actorLineColor||this.actorBorder,this.labelBoxBkgColor=this.labelBoxBkgColor||this.actorBkg,this.signalColor=this.signalColor||this.textColor,this.signalTextColor=this.signalTextColor||this.textColor,this.labelBoxBorderColor=this.labelBoxBorderColor||this.actorBorder,this.labelTextColor=this.labelTextColor||this.actorTextColor,this.loopTextColor=this.loopTextColor||this.actorTextColor,this.activationBorderColor=this.activationBorderColor||Dt(this.secondaryColor,10),this.activationBkgColor=this.activationBkgColor||this.secondaryColor,this.sequenceNumberColor=this.sequenceNumberColor||ot(this.lineColor),this.sectionBkgColor=this.sectionBkgColor||this.tertiaryColor,this.altSectionBkgColor=this.altSectionBkgColor||"white",this.sectionBkgColor=this.sectionBkgColor||this.secondaryColor,this.sectionBkgColor2=this.sectionBkgColor2||this.primaryColor,this.excludeBkgColor=this.excludeBkgColor||"#eeeeee",this.taskBorderColor=this.taskBorderColor||this.primaryBorderColor,this.taskBkgColor=this.taskBkgColor||this.primaryColor,this.activeTaskBorderColor=this.activeTaskBorderColor||this.primaryColor,this.activeTaskBkgColor=this.activeTaskBkgColor||Et(this.primaryColor,23),this.gridColor=this.gridColor||"lightgrey",this.doneTaskBkgColor=this.doneTaskBkgColor||"lightgrey",this.doneTaskBorderColor=this.doneTaskBorderColor||"grey",this.critBorderColor=this.critBorderColor||"#ff8888",this.critBkgColor=this.critBkgColor||"red",this.todayLineColor=this.todayLineColor||"red",this.taskTextColor=this.taskTextColor||this.textColor,this.taskTextOutsideColor=this.taskTextOutsideColor||this.textColor,this.taskTextLightColor=this.taskTextLightColor||this.textColor,this.taskTextColor=this.taskTextColor||this.primaryTextColor,this.taskTextDarkColor=this.taskTextDarkColor||this.textColor,this.taskTextClickableColor=this.taskTextClickableColor||"#003163",this.personBorder=this.personBorder||this.primaryBorderColor,this.personBkg=this.personBkg||this.mainBkg,this.transitionColor=this.transitionColor||this.lineColor,this.transitionLabelColor=this.transitionLabelColor||this.textColor,this.stateLabelColor=this.stateLabelColor||this.stateBkg||this.primaryTextColor,this.stateBkg=this.stateBkg||this.mainBkg,this.labelBackgroundColor=this.labelBackgroundColor||this.stateBkg,this.compositeBackground=this.compositeBackground||this.background||this.tertiaryColor,this.altBackground=this.altBackground||this.tertiaryColor,this.compositeTitleBackground=this.compositeTitleBackground||this.mainBkg,this.compositeBorder=this.compositeBorder||this.nodeBorder,this.innerEndBackground=this.nodeBorder,this.errorBkgColor=this.errorBkgColor||this.tertiaryColor,this.errorTextColor=this.errorTextColor||this.tertiaryTextColor,this.transitionColor=this.transitionColor||this.lineColor,this.specialStateColor=this.lineColor,this.cScale0=this.cScale0||this.primaryColor,this.cScale1=this.cScale1||this.secondaryColor,this.cScale2=this.cScale2||this.tertiaryColor,this.cScale3=this.cScale3||Oe(this.primaryColor,{h:30}),this.cScale4=this.cScale4||Oe(this.primaryColor,{h:60}),this.cScale5=this.cScale5||Oe(this.primaryColor,{h:90}),this.cScale6=this.cScale6||Oe(this.primaryColor,{h:120}),this.cScale7=this.cScale7||Oe(this.primaryColor,{h:150}),this.cScale8=this.cScale8||Oe(this.primaryColor,{h:210,l:150}),this.cScale9=this.cScale9||Oe(this.primaryColor,{h:270}),this.cScale10=this.cScale10||Oe(this.primaryColor,{h:300}),this.cScale11=this.cScale11||Oe(this.primaryColor,{h:330}),this.darkMode)for(let r=0;r{this[n]=e[n]}),this.updateColors(),r.forEach(n=>{this[n]=e[n]})}},Nz=o(t=>{let e=new QC;return e.calculate(t),e},"getThemeVariables")});var ZC,Iz,Oz=R(()=>{"use strict";al();up();ZC=class{static{o(this,"Theme")}constructor(){this.background="#333",this.primaryColor="#1f2020",this.secondaryColor=Et(this.primaryColor,16),this.tertiaryColor=Oe(this.primaryColor,{h:-160}),this.primaryBorderColor=ot(this.background),this.secondaryBorderColor=yi(this.secondaryColor,this.darkMode),this.tertiaryBorderColor=yi(this.tertiaryColor,this.darkMode),this.primaryTextColor=ot(this.primaryColor),this.secondaryTextColor=ot(this.secondaryColor),this.tertiaryTextColor=ot(this.tertiaryColor),this.lineColor=ot(this.background),this.textColor=ot(this.background),this.mainBkg="#1f2020",this.secondBkg="calculated",this.mainContrastColor="lightgrey",this.darkTextColor=Et(ot("#323D47"),10),this.lineColor="calculated",this.border1="#ccc",this.border2=Ws(255,255,255,.25),this.arrowheadColor="calculated",this.fontFamily='"trebuchet ms", verdana, arial, sans-serif',this.fontSize="16px",this.labelBackground="#181818",this.textColor="#ccc",this.THEME_COLOR_LIMIT=12,this.nodeBkg="calculated",this.nodeBorder="calculated",this.clusterBkg="calculated",this.clusterBorder="calculated",this.defaultLinkColor="calculated",this.titleColor="#F9FFFE",this.edgeLabelBackground="calculated",this.actorBorder="calculated",this.actorBkg="calculated",this.actorTextColor="calculated",this.actorLineColor="calculated",this.signalColor="calculated",this.signalTextColor="calculated",this.labelBoxBkgColor="calculated",this.labelBoxBorderColor="calculated",this.labelTextColor="calculated",this.loopTextColor="calculated",this.noteBorderColor="calculated",this.noteBkgColor="#fff5ad",this.noteTextColor="calculated",this.activationBorderColor="calculated",this.activationBkgColor="calculated",this.sequenceNumberColor="black",this.sectionBkgColor=Dt("#EAE8D9",30),this.altSectionBkgColor="calculated",this.sectionBkgColor2="#EAE8D9",this.excludeBkgColor=Dt(this.sectionBkgColor,10),this.taskBorderColor=Ws(255,255,255,70),this.taskBkgColor="calculated",this.taskTextColor="calculated",this.taskTextLightColor="calculated",this.taskTextOutsideColor="calculated",this.taskTextClickableColor="#003163",this.activeTaskBorderColor=Ws(255,255,255,50),this.activeTaskBkgColor="#81B1DB",this.gridColor="calculated",this.doneTaskBkgColor="calculated",this.doneTaskBorderColor="grey",this.critBorderColor="#E83737",this.critBkgColor="#E83737",this.taskTextDarkColor="calculated",this.todayLineColor="#DB5757",this.personBorder=this.primaryBorderColor,this.personBkg=this.mainBkg,this.archEdgeColor="calculated",this.archEdgeArrowColor="calculated",this.archEdgeWidth="3",this.archGroupBorderColor=this.primaryBorderColor,this.archGroupBorderWidth="2px",this.labelColor="calculated",this.errorBkgColor="#a44141",this.errorTextColor="#ddd"}updateColors(){this.secondBkg=Et(this.mainBkg,16),this.lineColor=this.mainContrastColor,this.arrowheadColor=this.mainContrastColor,this.nodeBkg=this.mainBkg,this.nodeBorder=this.border1,this.clusterBkg=this.secondBkg,this.clusterBorder=this.border2,this.defaultLinkColor=this.lineColor,this.edgeLabelBackground=Et(this.labelBackground,25),this.actorBorder=this.border1,this.actorBkg=this.mainBkg,this.actorTextColor=this.mainContrastColor,this.actorLineColor=this.actorBorder,this.signalColor=this.mainContrastColor,this.signalTextColor=this.mainContrastColor,this.labelBoxBkgColor=this.actorBkg,this.labelBoxBorderColor=this.actorBorder,this.labelTextColor=this.mainContrastColor,this.loopTextColor=this.mainContrastColor,this.noteBorderColor=this.secondaryBorderColor,this.noteBkgColor=this.secondBkg,this.noteTextColor=this.secondaryTextColor,this.activationBorderColor=this.border1,this.activationBkgColor=this.secondBkg,this.altSectionBkgColor=this.background,this.taskBkgColor=Et(this.mainBkg,23),this.taskTextColor=this.darkTextColor,this.taskTextLightColor=this.mainContrastColor,this.taskTextOutsideColor=this.taskTextLightColor,this.gridColor=this.mainContrastColor,this.doneTaskBkgColor=this.mainContrastColor,this.taskTextDarkColor=this.darkTextColor,this.archEdgeColor=this.lineColor,this.archEdgeArrowColor=this.lineColor,this.transitionColor=this.transitionColor||this.lineColor,this.transitionLabelColor=this.transitionLabelColor||this.textColor,this.stateLabelColor=this.stateLabelColor||this.stateBkg||this.primaryTextColor,this.stateBkg=this.stateBkg||this.mainBkg,this.labelBackgroundColor=this.labelBackgroundColor||this.stateBkg,this.compositeBackground=this.compositeBackground||this.background||this.tertiaryColor,this.altBackground=this.altBackground||"#555",this.compositeTitleBackground=this.compositeTitleBackground||this.mainBkg,this.compositeBorder=this.compositeBorder||this.nodeBorder,this.innerEndBackground=this.primaryBorderColor,this.specialStateColor="#f4f4f4",this.errorBkgColor=this.errorBkgColor||this.tertiaryColor,this.errorTextColor=this.errorTextColor||this.tertiaryTextColor,this.fillType0=this.primaryColor,this.fillType1=this.secondaryColor,this.fillType2=Oe(this.primaryColor,{h:64}),this.fillType3=Oe(this.secondaryColor,{h:64}),this.fillType4=Oe(this.primaryColor,{h:-64}),this.fillType5=Oe(this.secondaryColor,{h:-64}),this.fillType6=Oe(this.primaryColor,{h:128}),this.fillType7=Oe(this.secondaryColor,{h:128}),this.cScale1=this.cScale1||"#0b0000",this.cScale2=this.cScale2||"#4d1037",this.cScale3=this.cScale3||"#3f5258",this.cScale4=this.cScale4||"#4f2f1b",this.cScale5=this.cScale5||"#6e0a0a",this.cScale6=this.cScale6||"#3b0048",this.cScale7=this.cScale7||"#995a01",this.cScale8=this.cScale8||"#154706",this.cScale9=this.cScale9||"#161722",this.cScale10=this.cScale10||"#00296f",this.cScale11=this.cScale11||"#01629c",this.cScale12=this.cScale12||"#010029",this.cScale0=this.cScale0||this.primaryColor,this.cScale1=this.cScale1||this.secondaryColor,this.cScale2=this.cScale2||this.tertiaryColor,this.cScale3=this.cScale3||Oe(this.primaryColor,{h:30}),this.cScale4=this.cScale4||Oe(this.primaryColor,{h:60}),this.cScale5=this.cScale5||Oe(this.primaryColor,{h:90}),this.cScale6=this.cScale6||Oe(this.primaryColor,{h:120}),this.cScale7=this.cScale7||Oe(this.primaryColor,{h:150}),this.cScale8=this.cScale8||Oe(this.primaryColor,{h:210}),this.cScale9=this.cScale9||Oe(this.primaryColor,{h:270}),this.cScale10=this.cScale10||Oe(this.primaryColor,{h:300}),this.cScale11=this.cScale11||Oe(this.primaryColor,{h:330});for(let e=0;e{this[n]=e[n]}),this.updateColors(),r.forEach(n=>{this[n]=e[n]})}},Iz=o(t=>{let e=new ZC;return e.calculate(t),e},"getThemeVariables")});var JC,hp,jb=R(()=>{"use strict";al();up();j1();JC=class{static{o(this,"Theme")}constructor(){this.background="#f4f4f4",this.primaryColor="#ECECFF",this.secondaryColor=Oe(this.primaryColor,{h:120}),this.secondaryColor="#ffffde",this.tertiaryColor=Oe(this.primaryColor,{h:-160}),this.primaryBorderColor=yi(this.primaryColor,this.darkMode),this.secondaryBorderColor=yi(this.secondaryColor,this.darkMode),this.tertiaryBorderColor=yi(this.tertiaryColor,this.darkMode),this.primaryTextColor=ot(this.primaryColor),this.secondaryTextColor=ot(this.secondaryColor),this.tertiaryTextColor=ot(this.tertiaryColor),this.lineColor=ot(this.background),this.textColor=ot(this.background),this.background="white",this.mainBkg="#ECECFF",this.secondBkg="#ffffde",this.lineColor="#333333",this.border1="#9370DB",this.border2="#aaaa33",this.arrowheadColor="#333333",this.fontFamily='"trebuchet ms", verdana, arial, sans-serif',this.fontSize="16px",this.labelBackground="rgba(232,232,232, 0.8)",this.textColor="#333",this.THEME_COLOR_LIMIT=12,this.nodeBkg="calculated",this.nodeBorder="calculated",this.clusterBkg="calculated",this.clusterBorder="calculated",this.defaultLinkColor="calculated",this.titleColor="calculated",this.edgeLabelBackground="calculated",this.actorBorder="calculated",this.actorBkg="calculated",this.actorTextColor="black",this.actorLineColor="calculated",this.signalColor="calculated",this.signalTextColor="calculated",this.labelBoxBkgColor="calculated",this.labelBoxBorderColor="calculated",this.labelTextColor="calculated",this.loopTextColor="calculated",this.noteBorderColor="calculated",this.noteBkgColor="#fff5ad",this.noteTextColor="calculated",this.activationBorderColor="#666",this.activationBkgColor="#f4f4f4",this.sequenceNumberColor="white",this.sectionBkgColor="calculated",this.altSectionBkgColor="calculated",this.sectionBkgColor2="calculated",this.excludeBkgColor="#eeeeee",this.taskBorderColor="calculated",this.taskBkgColor="calculated",this.taskTextLightColor="calculated",this.taskTextColor=this.taskTextLightColor,this.taskTextDarkColor="calculated",this.taskTextOutsideColor=this.taskTextDarkColor,this.taskTextClickableColor="calculated",this.activeTaskBorderColor="calculated",this.activeTaskBkgColor="calculated",this.gridColor="calculated",this.doneTaskBkgColor="calculated",this.doneTaskBorderColor="calculated",this.critBorderColor="calculated",this.critBkgColor="calculated",this.todayLineColor="calculated",this.sectionBkgColor=Ws(102,102,255,.49),this.altSectionBkgColor="white",this.sectionBkgColor2="#fff400",this.taskBorderColor="#534fbc",this.taskBkgColor="#8a90dd",this.taskTextLightColor="white",this.taskTextColor="calculated",this.taskTextDarkColor="black",this.taskTextOutsideColor="calculated",this.taskTextClickableColor="#003163",this.activeTaskBorderColor="#534fbc",this.activeTaskBkgColor="#bfc7ff",this.gridColor="lightgrey",this.doneTaskBkgColor="lightgrey",this.doneTaskBorderColor="grey",this.critBorderColor="#ff8888",this.critBkgColor="red",this.todayLineColor="red",this.personBorder=this.primaryBorderColor,this.personBkg=this.mainBkg,this.archEdgeColor="calculated",this.archEdgeArrowColor="calculated",this.archEdgeWidth="3",this.archGroupBorderColor=this.primaryBorderColor,this.archGroupBorderWidth="2px",this.labelColor="black",this.errorBkgColor="#552222",this.errorTextColor="#552222",this.updateColors()}updateColors(){this.cScale0=this.cScale0||this.primaryColor,this.cScale1=this.cScale1||this.secondaryColor,this.cScale2=this.cScale2||this.tertiaryColor,this.cScale3=this.cScale3||Oe(this.primaryColor,{h:30}),this.cScale4=this.cScale4||Oe(this.primaryColor,{h:60}),this.cScale5=this.cScale5||Oe(this.primaryColor,{h:90}),this.cScale6=this.cScale6||Oe(this.primaryColor,{h:120}),this.cScale7=this.cScale7||Oe(this.primaryColor,{h:150}),this.cScale8=this.cScale8||Oe(this.primaryColor,{h:210}),this.cScale9=this.cScale9||Oe(this.primaryColor,{h:270}),this.cScale10=this.cScale10||Oe(this.primaryColor,{h:300}),this.cScale11=this.cScale11||Oe(this.primaryColor,{h:330}),this.cScalePeer1=this.cScalePeer1||Dt(this.secondaryColor,45),this.cScalePeer2=this.cScalePeer2||Dt(this.tertiaryColor,40);for(let e=0;e{this[n]=e[n]}),this.updateColors(),r.forEach(n=>{this[n]=e[n]})}},hp=o(t=>{let e=new JC;return e.calculate(t),e},"getThemeVariables")});var e7,Pz,Bz=R(()=>{"use strict";al();j1();up();e7=class{static{o(this,"Theme")}constructor(){this.background="#f4f4f4",this.primaryColor="#cde498",this.secondaryColor="#cdffb2",this.background="white",this.mainBkg="#cde498",this.secondBkg="#cdffb2",this.lineColor="green",this.border1="#13540c",this.border2="#6eaa49",this.arrowheadColor="green",this.fontFamily='"trebuchet ms", verdana, arial, sans-serif',this.fontSize="16px",this.tertiaryColor=Et("#cde498",10),this.primaryBorderColor=yi(this.primaryColor,this.darkMode),this.secondaryBorderColor=yi(this.secondaryColor,this.darkMode),this.tertiaryBorderColor=yi(this.tertiaryColor,this.darkMode),this.primaryTextColor=ot(this.primaryColor),this.secondaryTextColor=ot(this.secondaryColor),this.tertiaryTextColor=ot(this.primaryColor),this.lineColor=ot(this.background),this.textColor=ot(this.background),this.THEME_COLOR_LIMIT=12,this.nodeBkg="calculated",this.nodeBorder="calculated",this.clusterBkg="calculated",this.clusterBorder="calculated",this.defaultLinkColor="calculated",this.titleColor="#333",this.edgeLabelBackground="#e8e8e8",this.actorBorder="calculated",this.actorBkg="calculated",this.actorTextColor="black",this.actorLineColor="calculated",this.signalColor="#333",this.signalTextColor="#333",this.labelBoxBkgColor="calculated",this.labelBoxBorderColor="#326932",this.labelTextColor="calculated",this.loopTextColor="calculated",this.noteBorderColor="calculated",this.noteBkgColor="#fff5ad",this.noteTextColor="calculated",this.activationBorderColor="#666",this.activationBkgColor="#f4f4f4",this.sequenceNumberColor="white",this.sectionBkgColor="#6eaa49",this.altSectionBkgColor="white",this.sectionBkgColor2="#6eaa49",this.excludeBkgColor="#eeeeee",this.taskBorderColor="calculated",this.taskBkgColor="#487e3a",this.taskTextLightColor="white",this.taskTextColor="calculated",this.taskTextDarkColor="black",this.taskTextOutsideColor="calculated",this.taskTextClickableColor="#003163",this.activeTaskBorderColor="calculated",this.activeTaskBkgColor="calculated",this.gridColor="lightgrey",this.doneTaskBkgColor="lightgrey",this.doneTaskBorderColor="grey",this.critBorderColor="#ff8888",this.critBkgColor="red",this.todayLineColor="red",this.personBorder=this.primaryBorderColor,this.personBkg=this.mainBkg,this.archEdgeColor="calculated",this.archEdgeArrowColor="calculated",this.archEdgeWidth="3",this.archGroupBorderColor=this.primaryBorderColor,this.archGroupBorderWidth="2px",this.labelColor="black",this.errorBkgColor="#552222",this.errorTextColor="#552222"}updateColors(){this.actorBorder=Dt(this.mainBkg,20),this.actorBkg=this.mainBkg,this.labelBoxBkgColor=this.actorBkg,this.labelTextColor=this.actorTextColor,this.loopTextColor=this.actorTextColor,this.noteBorderColor=this.border2,this.noteTextColor=this.actorTextColor,this.actorLineColor=this.actorBorder,this.cScale0=this.cScale0||this.primaryColor,this.cScale1=this.cScale1||this.secondaryColor,this.cScale2=this.cScale2||this.tertiaryColor,this.cScale3=this.cScale3||Oe(this.primaryColor,{h:30}),this.cScale4=this.cScale4||Oe(this.primaryColor,{h:60}),this.cScale5=this.cScale5||Oe(this.primaryColor,{h:90}),this.cScale6=this.cScale6||Oe(this.primaryColor,{h:120}),this.cScale7=this.cScale7||Oe(this.primaryColor,{h:150}),this.cScale8=this.cScale8||Oe(this.primaryColor,{h:210}),this.cScale9=this.cScale9||Oe(this.primaryColor,{h:270}),this.cScale10=this.cScale10||Oe(this.primaryColor,{h:300}),this.cScale11=this.cScale11||Oe(this.primaryColor,{h:330}),this.cScalePeer1=this.cScalePeer1||Dt(this.secondaryColor,45),this.cScalePeer2=this.cScalePeer2||Dt(this.tertiaryColor,40);for(let e=0;e{this[n]=e[n]}),this.updateColors(),r.forEach(n=>{this[n]=e[n]})}},Pz=o(t=>{let e=new e7;return e.calculate(t),e},"getThemeVariables")});var t7,Fz,zz=R(()=>{"use strict";al();up();j1();t7=class{static{o(this,"Theme")}constructor(){this.primaryColor="#eee",this.contrast="#707070",this.secondaryColor=Et(this.contrast,55),this.background="#ffffff",this.tertiaryColor=Oe(this.primaryColor,{h:-160}),this.primaryBorderColor=yi(this.primaryColor,this.darkMode),this.secondaryBorderColor=yi(this.secondaryColor,this.darkMode),this.tertiaryBorderColor=yi(this.tertiaryColor,this.darkMode),this.primaryTextColor=ot(this.primaryColor),this.secondaryTextColor=ot(this.secondaryColor),this.tertiaryTextColor=ot(this.tertiaryColor),this.lineColor=ot(this.background),this.textColor=ot(this.background),this.mainBkg="#eee",this.secondBkg="calculated",this.lineColor="#666",this.border1="#999",this.border2="calculated",this.note="#ffa",this.text="#333",this.critical="#d42",this.done="#bbb",this.arrowheadColor="#333333",this.fontFamily='"trebuchet ms", verdana, arial, sans-serif',this.fontSize="16px",this.THEME_COLOR_LIMIT=12,this.nodeBkg="calculated",this.nodeBorder="calculated",this.clusterBkg="calculated",this.clusterBorder="calculated",this.defaultLinkColor="calculated",this.titleColor="calculated",this.edgeLabelBackground="white",this.actorBorder="calculated",this.actorBkg="calculated",this.actorTextColor="calculated",this.actorLineColor=this.actorBorder,this.signalColor="calculated",this.signalTextColor="calculated",this.labelBoxBkgColor="calculated",this.labelBoxBorderColor="calculated",this.labelTextColor="calculated",this.loopTextColor="calculated",this.noteBorderColor="calculated",this.noteBkgColor="calculated",this.noteTextColor="calculated",this.activationBorderColor="#666",this.activationBkgColor="#f4f4f4",this.sequenceNumberColor="white",this.sectionBkgColor="calculated",this.altSectionBkgColor="white",this.sectionBkgColor2="calculated",this.excludeBkgColor="#eeeeee",this.taskBorderColor="calculated",this.taskBkgColor="calculated",this.taskTextLightColor="white",this.taskTextColor="calculated",this.taskTextDarkColor="calculated",this.taskTextOutsideColor="calculated",this.taskTextClickableColor="#003163",this.activeTaskBorderColor="calculated",this.activeTaskBkgColor="calculated",this.gridColor="calculated",this.doneTaskBkgColor="calculated",this.doneTaskBorderColor="calculated",this.critBkgColor="calculated",this.critBorderColor="calculated",this.todayLineColor="calculated",this.personBorder=this.primaryBorderColor,this.personBkg=this.mainBkg,this.archEdgeColor="calculated",this.archEdgeArrowColor="calculated",this.archEdgeWidth="3",this.archGroupBorderColor=this.primaryBorderColor,this.archGroupBorderWidth="2px",this.labelColor="black",this.errorBkgColor="#552222",this.errorTextColor="#552222"}updateColors(){this.secondBkg=Et(this.contrast,55),this.border2=this.contrast,this.actorBorder=Et(this.border1,23),this.actorBkg=this.mainBkg,this.actorTextColor=this.text,this.actorLineColor=this.actorBorder,this.signalColor=this.text,this.signalTextColor=this.text,this.labelBoxBkgColor=this.actorBkg,this.labelBoxBorderColor=this.actorBorder,this.labelTextColor=this.text,this.loopTextColor=this.text,this.noteBorderColor="#999",this.noteBkgColor="#666",this.noteTextColor="#fff",this.cScale0=this.cScale0||"#555",this.cScale1=this.cScale1||"#F4F4F4",this.cScale2=this.cScale2||"#555",this.cScale3=this.cScale3||"#BBB",this.cScale4=this.cScale4||"#777",this.cScale5=this.cScale5||"#999",this.cScale6=this.cScale6||"#DDD",this.cScale7=this.cScale7||"#FFF",this.cScale8=this.cScale8||"#DDD",this.cScale9=this.cScale9||"#BBB",this.cScale10=this.cScale10||"#999",this.cScale11=this.cScale11||"#777";for(let e=0;e{this[n]=e[n]}),this.updateColors(),r.forEach(n=>{this[n]=e[n]})}},Fz=o(t=>{let e=new t7;return e.calculate(t),e},"getThemeVariables")});var Co,Kb=R(()=>{"use strict";Mz();Oz();jb();Bz();zz();Co={base:{getThemeVariables:Nz},dark:{getThemeVariables:Iz},default:{getThemeVariables:hp},forest:{getThemeVariables:Pz},neutral:{getThemeVariables:Fz}}});var tu,Gz=R(()=>{"use strict";tu={flowchart:{useMaxWidth:!0,titleTopMargin:25,subGraphTitleMargin:{top:0,bottom:0},diagramPadding:8,htmlLabels:!0,nodeSpacing:50,rankSpacing:50,curve:"basis",padding:15,defaultRenderer:"dagre-wrapper",wrappingWidth:200},sequence:{useMaxWidth:!0,hideUnusedParticipants:!1,activationWidth:10,diagramMarginX:50,diagramMarginY:10,actorMargin:50,width:150,height:65,boxMargin:10,boxTextMargin:5,noteMargin:10,messageMargin:35,messageAlign:"center",mirrorActors:!0,forceMenus:!1,bottomMarginAdj:1,rightAngles:!1,showSequenceNumbers:!1,actorFontSize:14,actorFontFamily:'"Open Sans", sans-serif',actorFontWeight:400,noteFontSize:14,noteFontFamily:'"trebuchet ms", verdana, arial, sans-serif',noteFontWeight:400,noteAlign:"center",messageFontSize:16,messageFontFamily:'"trebuchet ms", verdana, arial, sans-serif',messageFontWeight:400,wrap:!1,wrapPadding:10,labelBoxWidth:50,labelBoxHeight:20},gantt:{useMaxWidth:!0,titleTopMargin:25,barHeight:20,barGap:4,topPadding:50,rightPadding:75,leftPadding:75,gridLineStartPadding:35,fontSize:11,sectionFontSize:11,numberSectionStyles:4,axisFormat:"%Y-%m-%d",topAxis:!1,displayMode:"",weekday:"sunday"},journey:{useMaxWidth:!0,diagramMarginX:50,diagramMarginY:10,leftMargin:150,width:150,height:50,boxMargin:10,boxTextMargin:5,noteMargin:10,messageMargin:35,messageAlign:"center",bottomMarginAdj:1,rightAngles:!1,taskFontSize:14,taskFontFamily:'"Open Sans", sans-serif',taskMargin:50,activationWidth:10,textPlacement:"fo",actorColours:["#8FBC8F","#7CFC00","#00FFFF","#20B2AA","#B0E0E6","#FFFFE0"],sectionFills:["#191970","#8B008B","#4B0082","#2F4F4F","#800000","#8B4513","#00008B"],sectionColours:["#fff"]},class:{useMaxWidth:!0,titleTopMargin:25,arrowMarkerAbsolute:!1,dividerMargin:10,padding:5,textHeight:10,defaultRenderer:"dagre-wrapper",htmlLabels:!1},state:{useMaxWidth:!0,titleTopMargin:25,dividerMargin:10,sizeUnit:5,padding:8,textHeight:10,titleShift:-15,noteMargin:10,forkWidth:70,forkHeight:7,miniPadding:2,fontSizeFactor:5.02,fontSize:24,labelHeight:16,edgeLengthFactor:"20",compositTitleSize:35,radius:5,defaultRenderer:"dagre-wrapper"},er:{useMaxWidth:!0,titleTopMargin:25,diagramPadding:20,layoutDirection:"TB",minEntityWidth:100,minEntityHeight:75,entityPadding:15,stroke:"gray",fill:"honeydew",fontSize:12},pie:{useMaxWidth:!0,textPosition:.75},quadrantChart:{useMaxWidth:!0,chartWidth:500,chartHeight:500,titleFontSize:20,titlePadding:10,quadrantPadding:5,xAxisLabelPadding:5,yAxisLabelPadding:5,xAxisLabelFontSize:16,yAxisLabelFontSize:16,quadrantLabelFontSize:16,quadrantTextTopPadding:5,pointTextPadding:5,pointLabelFontSize:12,pointRadius:5,xAxisPosition:"top",yAxisPosition:"left",quadrantInternalBorderStrokeWidth:1,quadrantExternalBorderStrokeWidth:2},xyChart:{useMaxWidth:!0,width:700,height:500,titleFontSize:20,titlePadding:10,showTitle:!0,xAxis:{$ref:"#/$defs/XYChartAxisConfig",showLabel:!0,labelFontSize:14,labelPadding:5,showTitle:!0,titleFontSize:16,titlePadding:5,showTick:!0,tickLength:5,tickWidth:2,showAxisLine:!0,axisLineWidth:2},yAxis:{$ref:"#/$defs/XYChartAxisConfig",showLabel:!0,labelFontSize:14,labelPadding:5,showTitle:!0,titleFontSize:16,titlePadding:5,showTick:!0,tickLength:5,tickWidth:2,showAxisLine:!0,axisLineWidth:2},chartOrientation:"vertical",plotReservedSpacePercent:50},requirement:{useMaxWidth:!0,rect_fill:"#f9f9f9",text_color:"#333",rect_border_size:"0.5px",rect_border_color:"#bbb",rect_min_width:200,rect_min_height:200,fontSize:14,rect_padding:10,line_height:20},mindmap:{useMaxWidth:!0,padding:10,maxNodeWidth:200},timeline:{useMaxWidth:!0,diagramMarginX:50,diagramMarginY:10,leftMargin:150,width:150,height:50,boxMargin:10,boxTextMargin:5,noteMargin:10,messageMargin:35,messageAlign:"center",bottomMarginAdj:1,rightAngles:!1,taskFontSize:14,taskFontFamily:'"Open Sans", sans-serif',taskMargin:50,activationWidth:10,textPlacement:"fo",actorColours:["#8FBC8F","#7CFC00","#00FFFF","#20B2AA","#B0E0E6","#FFFFE0"],sectionFills:["#191970","#8B008B","#4B0082","#2F4F4F","#800000","#8B4513","#00008B"],sectionColours:["#fff"],disableMulticolor:!1},gitGraph:{useMaxWidth:!0,titleTopMargin:25,diagramPadding:8,nodeLabel:{width:75,height:100,x:-25,y:0},mainBranchName:"main",mainBranchOrder:0,showCommitLabel:!0,showBranches:!0,rotateCommitLabel:!0,parallelCommits:!1,arrowMarkerAbsolute:!1},c4:{useMaxWidth:!0,diagramMarginX:50,diagramMarginY:10,c4ShapeMargin:50,c4ShapePadding:20,width:216,height:60,boxMargin:10,c4ShapeInRow:4,nextLinePaddingX:0,c4BoundaryInRow:2,personFontSize:14,personFontFamily:'"Open Sans", sans-serif',personFontWeight:"normal",external_personFontSize:14,external_personFontFamily:'"Open Sans", sans-serif',external_personFontWeight:"normal",systemFontSize:14,systemFontFamily:'"Open Sans", sans-serif',systemFontWeight:"normal",external_systemFontSize:14,external_systemFontFamily:'"Open Sans", sans-serif',external_systemFontWeight:"normal",system_dbFontSize:14,system_dbFontFamily:'"Open Sans", sans-serif',system_dbFontWeight:"normal",external_system_dbFontSize:14,external_system_dbFontFamily:'"Open Sans", sans-serif',external_system_dbFontWeight:"normal",system_queueFontSize:14,system_queueFontFamily:'"Open Sans", sans-serif',system_queueFontWeight:"normal",external_system_queueFontSize:14,external_system_queueFontFamily:'"Open Sans", sans-serif',external_system_queueFontWeight:"normal",boundaryFontSize:14,boundaryFontFamily:'"Open Sans", sans-serif',boundaryFontWeight:"normal",messageFontSize:12,messageFontFamily:'"Open Sans", sans-serif',messageFontWeight:"normal",containerFontSize:14,containerFontFamily:'"Open Sans", sans-serif',containerFontWeight:"normal",external_containerFontSize:14,external_containerFontFamily:'"Open Sans", sans-serif',external_containerFontWeight:"normal",container_dbFontSize:14,container_dbFontFamily:'"Open Sans", sans-serif',container_dbFontWeight:"normal",external_container_dbFontSize:14,external_container_dbFontFamily:'"Open Sans", sans-serif',external_container_dbFontWeight:"normal",container_queueFontSize:14,container_queueFontFamily:'"Open Sans", sans-serif',container_queueFontWeight:"normal",external_container_queueFontSize:14,external_container_queueFontFamily:'"Open Sans", sans-serif',external_container_queueFontWeight:"normal",componentFontSize:14,componentFontFamily:'"Open Sans", sans-serif',componentFontWeight:"normal",external_componentFontSize:14,external_componentFontFamily:'"Open Sans", sans-serif',external_componentFontWeight:"normal",component_dbFontSize:14,component_dbFontFamily:'"Open Sans", sans-serif',component_dbFontWeight:"normal",external_component_dbFontSize:14,external_component_dbFontFamily:'"Open Sans", sans-serif',external_component_dbFontWeight:"normal",component_queueFontSize:14,component_queueFontFamily:'"Open Sans", sans-serif',component_queueFontWeight:"normal",external_component_queueFontSize:14,external_component_queueFontFamily:'"Open Sans", sans-serif',external_component_queueFontWeight:"normal",wrap:!0,wrapPadding:10,person_bg_color:"#08427B",person_border_color:"#073B6F",external_person_bg_color:"#686868",external_person_border_color:"#8A8A8A",system_bg_color:"#1168BD",system_border_color:"#3C7FC0",system_db_bg_color:"#1168BD",system_db_border_color:"#3C7FC0",system_queue_bg_color:"#1168BD",system_queue_border_color:"#3C7FC0",external_system_bg_color:"#999999",external_system_border_color:"#8A8A8A",external_system_db_bg_color:"#999999",external_system_db_border_color:"#8A8A8A",external_system_queue_bg_color:"#999999",external_system_queue_border_color:"#8A8A8A",container_bg_color:"#438DD5",container_border_color:"#3C7FC0",container_db_bg_color:"#438DD5",container_db_border_color:"#3C7FC0",container_queue_bg_color:"#438DD5",container_queue_border_color:"#3C7FC0",external_container_bg_color:"#B3B3B3",external_container_border_color:"#A6A6A6",external_container_db_bg_color:"#B3B3B3",external_container_db_border_color:"#A6A6A6",external_container_queue_bg_color:"#B3B3B3",external_container_queue_border_color:"#A6A6A6",component_bg_color:"#85BBF0",component_border_color:"#78A8D8",component_db_bg_color:"#85BBF0",component_db_border_color:"#78A8D8",component_queue_bg_color:"#85BBF0",component_queue_border_color:"#78A8D8",external_component_bg_color:"#CCCCCC",external_component_border_color:"#BFBFBF",external_component_db_bg_color:"#CCCCCC",external_component_db_border_color:"#BFBFBF",external_component_queue_bg_color:"#CCCCCC",external_component_queue_border_color:"#BFBFBF"},sankey:{useMaxWidth:!0,width:600,height:400,linkColor:"gradient",nodeAlignment:"justify",showValues:!0,prefix:"",suffix:""},block:{useMaxWidth:!0,padding:8},packet:{useMaxWidth:!0,rowHeight:32,bitWidth:32,bitsPerRow:32,showBits:!0,paddingX:5,paddingY:5},architecture:{useMaxWidth:!0,padding:40,iconSize:80,fontSize:16},theme:"default",look:"classic",handDrawnSeed:0,layout:"dagre",maxTextSize:5e4,maxEdges:500,darkMode:!1,fontFamily:'"trebuchet ms", verdana, arial, sans-serif;',logLevel:5,securityLevel:"strict",startOnLoad:!0,arrowMarkerAbsolute:!1,secure:["secure","securityLevel","startOnLoad","maxTextSize","suppressErrorRendering","maxEdges"],legacyMathML:!1,forceLegacyMathML:!1,deterministicIds:!1,fontSize:16,markdownAutoWrap:!0,suppressErrorRendering:!1}});var $z,Vz,Uz,mr,sl=R(()=>{"use strict";Kb();Gz();$z={...tu,deterministicIDSeed:void 0,elk:{mergeEdges:!1,nodePlacementStrategy:"SIMPLE"},themeCSS:void 0,themeVariables:Co.default.getThemeVariables(),sequence:{...tu.sequence,messageFont:o(function(){return{fontFamily:this.messageFontFamily,fontSize:this.messageFontSize,fontWeight:this.messageFontWeight}},"messageFont"),noteFont:o(function(){return{fontFamily:this.noteFontFamily,fontSize:this.noteFontSize,fontWeight:this.noteFontWeight}},"noteFont"),actorFont:o(function(){return{fontFamily:this.actorFontFamily,fontSize:this.actorFontSize,fontWeight:this.actorFontWeight}},"actorFont")},gantt:{...tu.gantt,tickInterval:void 0,useWidth:void 0},c4:{...tu.c4,useWidth:void 0,personFont:o(function(){return{fontFamily:this.personFontFamily,fontSize:this.personFontSize,fontWeight:this.personFontWeight}},"personFont"),external_personFont:o(function(){return{fontFamily:this.external_personFontFamily,fontSize:this.external_personFontSize,fontWeight:this.external_personFontWeight}},"external_personFont"),systemFont:o(function(){return{fontFamily:this.systemFontFamily,fontSize:this.systemFontSize,fontWeight:this.systemFontWeight}},"systemFont"),external_systemFont:o(function(){return{fontFamily:this.external_systemFontFamily,fontSize:this.external_systemFontSize,fontWeight:this.external_systemFontWeight}},"external_systemFont"),system_dbFont:o(function(){return{fontFamily:this.system_dbFontFamily,fontSize:this.system_dbFontSize,fontWeight:this.system_dbFontWeight}},"system_dbFont"),external_system_dbFont:o(function(){return{fontFamily:this.external_system_dbFontFamily,fontSize:this.external_system_dbFontSize,fontWeight:this.external_system_dbFontWeight}},"external_system_dbFont"),system_queueFont:o(function(){return{fontFamily:this.system_queueFontFamily,fontSize:this.system_queueFontSize,fontWeight:this.system_queueFontWeight}},"system_queueFont"),external_system_queueFont:o(function(){return{fontFamily:this.external_system_queueFontFamily,fontSize:this.external_system_queueFontSize,fontWeight:this.external_system_queueFontWeight}},"external_system_queueFont"),containerFont:o(function(){return{fontFamily:this.containerFontFamily,fontSize:this.containerFontSize,fontWeight:this.containerFontWeight}},"containerFont"),external_containerFont:o(function(){return{fontFamily:this.external_containerFontFamily,fontSize:this.external_containerFontSize,fontWeight:this.external_containerFontWeight}},"external_containerFont"),container_dbFont:o(function(){return{fontFamily:this.container_dbFontFamily,fontSize:this.container_dbFontSize,fontWeight:this.container_dbFontWeight}},"container_dbFont"),external_container_dbFont:o(function(){return{fontFamily:this.external_container_dbFontFamily,fontSize:this.external_container_dbFontSize,fontWeight:this.external_container_dbFontWeight}},"external_container_dbFont"),container_queueFont:o(function(){return{fontFamily:this.container_queueFontFamily,fontSize:this.container_queueFontSize,fontWeight:this.container_queueFontWeight}},"container_queueFont"),external_container_queueFont:o(function(){return{fontFamily:this.external_container_queueFontFamily,fontSize:this.external_container_queueFontSize,fontWeight:this.external_container_queueFontWeight}},"external_container_queueFont"),componentFont:o(function(){return{fontFamily:this.componentFontFamily,fontSize:this.componentFontSize,fontWeight:this.componentFontWeight}},"componentFont"),external_componentFont:o(function(){return{fontFamily:this.external_componentFontFamily,fontSize:this.external_componentFontSize,fontWeight:this.external_componentFontWeight}},"external_componentFont"),component_dbFont:o(function(){return{fontFamily:this.component_dbFontFamily,fontSize:this.component_dbFontSize,fontWeight:this.component_dbFontWeight}},"component_dbFont"),external_component_dbFont:o(function(){return{fontFamily:this.external_component_dbFontFamily,fontSize:this.external_component_dbFontSize,fontWeight:this.external_component_dbFontWeight}},"external_component_dbFont"),component_queueFont:o(function(){return{fontFamily:this.component_queueFontFamily,fontSize:this.component_queueFontSize,fontWeight:this.component_queueFontWeight}},"component_queueFont"),external_component_queueFont:o(function(){return{fontFamily:this.external_component_queueFontFamily,fontSize:this.external_component_queueFontSize,fontWeight:this.external_component_queueFontWeight}},"external_component_queueFont"),boundaryFont:o(function(){return{fontFamily:this.boundaryFontFamily,fontSize:this.boundaryFontSize,fontWeight:this.boundaryFontWeight}},"boundaryFont"),messageFont:o(function(){return{fontFamily:this.messageFontFamily,fontSize:this.messageFontSize,fontWeight:this.messageFontWeight}},"messageFont")},pie:{...tu.pie,useWidth:984},xyChart:{...tu.xyChart,useWidth:void 0},requirement:{...tu.requirement,useWidth:void 0},packet:{...tu.packet}},Vz=o((t,e="")=>Object.keys(t).reduce((r,n)=>Array.isArray(t[n])?r:typeof t[n]=="object"&&t[n]!==null?[...r,e+n,...Vz(t[n],"")]:[...r,e+n],[]),"keyify"),Uz=new Set(Vz($z,"")),mr=$z});var fp,_2e,r7=R(()=>{"use strict";sl();ut();fp=o(t=>{if(V.debug("sanitizeDirective called with",t),!(typeof t!="object"||t==null)){if(Array.isArray(t)){t.forEach(e=>fp(e));return}for(let e of Object.keys(t)){if(V.debug("Checking key",e),e.startsWith("__")||e.includes("proto")||e.includes("constr")||!Uz.has(e)||t[e]==null){V.debug("sanitize deleting key: ",e),delete t[e];continue}if(typeof t[e]=="object"){V.debug("sanitizing object",e),fp(t[e]);continue}let r=["themeCSS","fontFamily","altFontFamily"];for(let n of r)e.includes(n)&&(V.debug("sanitizing css option",e),t[e]=_2e(t[e]))}if(t.themeVariables)for(let e of Object.keys(t.themeVariables)){let r=t.themeVariables[e];r?.match&&!r.match(/^[\d "#%(),.;A-Za-z]+$/)&&(t.themeVariables[e]="")}V.debug("After sanitization",t)}},"sanitizeDirective"),_2e=o(t=>{let e=0,r=0;for(let n of t){if(e{"use strict";cp();ut();Kb();sl();r7();uh=Object.freeze(mr),fs=On({},uh),dp=[],K1=On({},uh),Qb=o((t,e)=>{let r=On({},t),n={};for(let i of e)Xz(i),n=On(n,i);if(r=On(r,n),n.theme&&n.theme in Co){let i=On({},Yz),a=On(i.themeVariables||{},n.themeVariables);r.theme&&r.theme in Co&&(r.themeVariables=Co[r.theme].getThemeVariables(a))}return K1=r,Kz(K1),K1},"updateCurrentConfig"),n7=o(t=>(fs=On({},uh),fs=On(fs,t),t.theme&&Co[t.theme]&&(fs.themeVariables=Co[t.theme].getThemeVariables(t.themeVariables)),Qb(fs,dp),fs),"setSiteConfig"),Wz=o(t=>{Yz=On({},t)},"saveConfigFromInitialize"),qz=o(t=>(fs=On(fs,t),Qb(fs,dp),fs),"updateSiteConfig"),i7=o(()=>On({},fs),"getSiteConfig"),Zb=o(t=>(Kz(t),On(K1,t),Or()),"setConfig"),Or=o(()=>On({},K1),"getConfig"),Xz=o(t=>{t&&(["secure",...fs.secure??[]].forEach(e=>{Object.hasOwn(t,e)&&(V.debug(`Denied attempt to modify a secure key ${e}`,t[e]),delete t[e])}),Object.keys(t).forEach(e=>{e.startsWith("__")&&delete t[e]}),Object.keys(t).forEach(e=>{typeof t[e]=="string"&&(t[e].includes("<")||t[e].includes(">")||t[e].includes("url(data:"))&&delete t[e],typeof t[e]=="object"&&Xz(t[e])}))},"sanitize"),jz=o(t=>{fp(t),t.fontFamily&&!t.themeVariables?.fontFamily&&(t.themeVariables={...t.themeVariables,fontFamily:t.fontFamily}),dp.push(t),Qb(fs,dp)},"addDirective"),Q1=o((t=fs)=>{dp=[],Qb(t,dp)},"reset"),L2e={LAZY_LOAD_DEPRECATED:"The configuration options lazyLoadedDiagrams and loadExternalDiagramsAtStartup are deprecated. Please use registerExternalDiagrams instead."},Hz={},D2e=o(t=>{Hz[t]||(V.warn(L2e[t]),Hz[t]=!0)},"issueWarning"),Kz=o(t=>{t&&(t.lazyLoadedDiagrams||t.loadExternalDiagramsAtStartup)&&D2e("LAZY_LOAD_DEPRECATED")},"checkConfig")});var o7=gi((a7,s7)=>{"use strict";(function(t,e){typeof a7=="object"&&typeof s7<"u"?s7.exports=e():typeof define=="function"&&define.amd?define(e):(t=typeof globalThis<"u"?globalThis:t||self,t.DOMPurify=e())})(a7,function(){"use strict";let{entries:t,setPrototypeOf:e,isFrozen:r,getPrototypeOf:n,getOwnPropertyDescriptor:i}=Object,{freeze:a,seal:s,create:l}=Object,{apply:u,construct:h}=typeof Reflect<"u"&&Reflect;a||(a=o(function(Se){return Se},"freeze")),s||(s=o(function(Se){return Se},"seal")),u||(u=o(function(Se,Ue,Pe){return Se.apply(Ue,Pe)},"apply")),h||(h=o(function(Se,Ue){return new Se(...Ue)},"construct"));let f=E(Array.prototype.forEach),d=E(Array.prototype.pop),p=E(Array.prototype.push),m=E(String.prototype.toLowerCase),g=E(String.prototype.toString),y=E(String.prototype.match),v=E(String.prototype.replace),x=E(String.prototype.indexOf),b=E(String.prototype.trim),w=E(Object.prototype.hasOwnProperty),S=E(RegExp.prototype.test),T=_(TypeError);function E(Ie){return function(Se){for(var Ue=arguments.length,Pe=new Array(Ue>1?Ue-1:0),_e=1;_e2&&arguments[2]!==void 0?arguments[2]:m;e&&e(Ie,null);let Pe=Se.length;for(;Pe--;){let _e=Se[Pe];if(typeof _e=="string"){let me=Ue(_e);me!==_e&&(r(Se)||(Se[Pe]=me),_e=me)}Ie[_e]=!0}return Ie}o(A,"addToSet");function L(Ie){for(let Se=0;Se/gm),ie=s(/\${[\w\W]*}/gm),j=s(/^data-[\-\w.\u00B7-\uFFFF]/),J=s(/^aria-[\-\w]+$/),Z=s(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),H=s(/^(?:\w+script|data):/i),q=s(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),K=s(/^html$/i),se=s(/^[a-z][.\w]*(-[.\w]+)+$/i);var ce=Object.freeze({__proto__:null,MUSTACHE_EXPR:Q,ERB_EXPR:X,TMPLIT_EXPR:ie,DATA_ATTR:j,ARIA_ATTR:J,IS_ALLOWED_URI:Z,IS_SCRIPT_OR_DATA:H,ATTR_WHITESPACE:q,DOCTYPE_NAME:K,CUSTOM_ELEMENT:se});let ue={element:1,attribute:2,text:3,cdataSection:4,entityReference:5,entityNode:6,progressingInstruction:7,comment:8,document:9,documentType:10,documentFragment:11,notation:12},te=o(function(){return typeof window>"u"?null:window},"getGlobal"),De=o(function(Se,Ue){if(typeof Se!="object"||typeof Se.createPolicy!="function")return null;let Pe=null,_e="data-tt-policy-suffix";Ue&&Ue.hasAttribute(_e)&&(Pe=Ue.getAttribute(_e));let me="dompurify"+(Pe?"#"+Pe:"");try{return Se.createPolicy(me,{createHTML(W){return W},createScriptURL(W){return W}})}catch{return console.warn("TrustedTypes policy "+me+" could not be created."),null}},"_createTrustedTypesPolicy");function oe(){let Ie=arguments.length>0&&arguments[0]!==void 0?arguments[0]:te(),Se=o(Ft=>oe(Ft),"DOMPurify");if(Se.version="3.1.6",Se.removed=[],!Ie||!Ie.document||Ie.document.nodeType!==ue.document)return Se.isSupported=!1,Se;let{document:Ue}=Ie,Pe=Ue,_e=Pe.currentScript,{DocumentFragment:me,HTMLTemplateElement:W,Node:fe,Element:ge,NodeFilter:re,NamedNodeMap:he=Ie.NamedNodeMap||Ie.MozNamedAttrMap,HTMLFormElement:ne,DOMParser:ae,trustedTypes:we}=Ie,Te=ge.prototype,Ce=N(Te,"cloneNode"),Ae=N(Te,"remove"),Ge=N(Te,"nextSibling"),Me=N(Te,"childNodes"),ye=N(Te,"parentNode");if(typeof W=="function"){let Ft=Ue.createElement("template");Ft.content&&Ft.content.ownerDocument&&(Ue=Ft.content.ownerDocument)}let He,ze="",{implementation:Ze,createNodeIterator:gt,createDocumentFragment:yt,getElementsByTagName:tt}=Ue,{importNode:Ye}=Pe,Je={};Se.isSupported=typeof t=="function"&&typeof ye=="function"&&Ze&&Ze.createHTMLDocument!==void 0;let{MUSTACHE_EXPR:Ve,ERB_EXPR:je,TMPLIT_EXPR:kt,DATA_ATTR:at,ARIA_ATTR:xt,IS_SCRIPT_OR_DATA:it,ATTR_WHITESPACE:dt,CUSTOM_ELEMENT:lt}=ce,{IS_ALLOWED_URI:It}=ce,mt=null,St=A({},[...k,...I,...C,...D,...F]),gr=null,xn=A({},[...B,...$,...z,...Y]),jt=Object.seal(l(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),rn=null,Er=null,Kn=!0,hn=!0,Qn=!1,on=!0,Rn=!1,Ha=!0,_a=!1,To=!1,qi=!1,ht=!1,At=!1,$t=!1,rt=!0,Ot=!1,pe="user-content-",ur=!0,be=!1,Ir={},Xc=null,M1=A({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),_b=null,I1=A({},["audio","video","img","source","image","track"]),O1=null,ci=A({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),ko="http://www.w3.org/1998/Math/MathML",ih="http://www.w3.org/2000/svg",Us="http://www.w3.org/1999/xhtml",ah=Us,Lb=!1,P1=null,sa=A({},[ko,ih,Us],g),jc=null,Kc=["application/xhtml+xml","text/html"],us="text/html",_i=null,Wl=null,sh=Ue.createElement("form"),zf=o(function(Re){return Re instanceof RegExp||Re instanceof Function},"isRegexOrFunction"),Hs=o(function(){let Re=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};if(!(Wl&&Wl===Re)){if((!Re||typeof Re!="object")&&(Re={}),Re=M(Re),jc=Kc.indexOf(Re.PARSER_MEDIA_TYPE)===-1?us:Re.PARSER_MEDIA_TYPE,_i=jc==="application/xhtml+xml"?g:m,mt=w(Re,"ALLOWED_TAGS")?A({},Re.ALLOWED_TAGS,_i):St,gr=w(Re,"ALLOWED_ATTR")?A({},Re.ALLOWED_ATTR,_i):xn,P1=w(Re,"ALLOWED_NAMESPACES")?A({},Re.ALLOWED_NAMESPACES,g):sa,O1=w(Re,"ADD_URI_SAFE_ATTR")?A(M(ci),Re.ADD_URI_SAFE_ATTR,_i):ci,_b=w(Re,"ADD_DATA_URI_TAGS")?A(M(I1),Re.ADD_DATA_URI_TAGS,_i):I1,Xc=w(Re,"FORBID_CONTENTS")?A({},Re.FORBID_CONTENTS,_i):M1,rn=w(Re,"FORBID_TAGS")?A({},Re.FORBID_TAGS,_i):{},Er=w(Re,"FORBID_ATTR")?A({},Re.FORBID_ATTR,_i):{},Ir=w(Re,"USE_PROFILES")?Re.USE_PROFILES:!1,Kn=Re.ALLOW_ARIA_ATTR!==!1,hn=Re.ALLOW_DATA_ATTR!==!1,Qn=Re.ALLOW_UNKNOWN_PROTOCOLS||!1,on=Re.ALLOW_SELF_CLOSE_IN_ATTR!==!1,Rn=Re.SAFE_FOR_TEMPLATES||!1,Ha=Re.SAFE_FOR_XML!==!1,_a=Re.WHOLE_DOCUMENT||!1,ht=Re.RETURN_DOM||!1,At=Re.RETURN_DOM_FRAGMENT||!1,$t=Re.RETURN_TRUSTED_TYPE||!1,qi=Re.FORCE_BODY||!1,rt=Re.SANITIZE_DOM!==!1,Ot=Re.SANITIZE_NAMED_PROPS||!1,ur=Re.KEEP_CONTENT!==!1,be=Re.IN_PLACE||!1,It=Re.ALLOWED_URI_REGEXP||Z,ah=Re.NAMESPACE||Us,jt=Re.CUSTOM_ELEMENT_HANDLING||{},Re.CUSTOM_ELEMENT_HANDLING&&zf(Re.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(jt.tagNameCheck=Re.CUSTOM_ELEMENT_HANDLING.tagNameCheck),Re.CUSTOM_ELEMENT_HANDLING&&zf(Re.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(jt.attributeNameCheck=Re.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),Re.CUSTOM_ELEMENT_HANDLING&&typeof Re.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements=="boolean"&&(jt.allowCustomizedBuiltInElements=Re.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Rn&&(hn=!1),At&&(ht=!0),Ir&&(mt=A({},F),gr=[],Ir.html===!0&&(A(mt,k),A(gr,B)),Ir.svg===!0&&(A(mt,I),A(gr,$),A(gr,Y)),Ir.svgFilters===!0&&(A(mt,C),A(gr,$),A(gr,Y)),Ir.mathMl===!0&&(A(mt,D),A(gr,z),A(gr,Y))),Re.ADD_TAGS&&(mt===St&&(mt=M(mt)),A(mt,Re.ADD_TAGS,_i)),Re.ADD_ATTR&&(gr===xn&&(gr=M(gr)),A(gr,Re.ADD_ATTR,_i)),Re.ADD_URI_SAFE_ATTR&&A(O1,Re.ADD_URI_SAFE_ATTR,_i),Re.FORBID_CONTENTS&&(Xc===M1&&(Xc=M(Xc)),A(Xc,Re.FORBID_CONTENTS,_i)),ur&&(mt["#text"]=!0),_a&&A(mt,["html","head","body"]),mt.table&&(A(mt,["tbody"]),delete rn.tbody),Re.TRUSTED_TYPES_POLICY){if(typeof Re.TRUSTED_TYPES_POLICY.createHTML!="function")throw T('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if(typeof Re.TRUSTED_TYPES_POLICY.createScriptURL!="function")throw T('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');He=Re.TRUSTED_TYPES_POLICY,ze=He.createHTML("")}else He===void 0&&(He=De(we,_e)),He!==null&&typeof ze=="string"&&(ze=He.createHTML(""));a&&a(Re),Wl=Re}},"_parseConfig"),B1=A({},["mi","mo","mn","ms","mtext"]),Gf=A({},["foreignobject","annotation-xml"]),F1=A({},["title","style","font","a","script"]),La=A({},[...I,...C,...O]),vF=A({},[...D,...P]),Ive=o(function(Re){let st=ye(Re);(!st||!st.tagName)&&(st={namespaceURI:ah,tagName:"template"});let Rt=m(Re.tagName),bn=m(st.tagName);return P1[Re.namespaceURI]?Re.namespaceURI===ih?st.namespaceURI===Us?Rt==="svg":st.namespaceURI===ko?Rt==="svg"&&(bn==="annotation-xml"||B1[bn]):!!La[Rt]:Re.namespaceURI===ko?st.namespaceURI===Us?Rt==="math":st.namespaceURI===ih?Rt==="math"&&Gf[bn]:!!vF[Rt]:Re.namespaceURI===Us?st.namespaceURI===ih&&!Gf[bn]||st.namespaceURI===ko&&!B1[bn]?!1:!vF[Rt]&&(F1[Rt]||!La[Rt]):!!(jc==="application/xhtml+xml"&&P1[Re.namespaceURI]):!1},"_checkValidNamespace"),ql=o(function(Re){p(Se.removed,{element:Re});try{ye(Re).removeChild(Re)}catch{Ae(Re)}},"_forceRemove"),Db=o(function(Re,st){try{p(Se.removed,{attribute:st.getAttributeNode(Re),from:st})}catch{p(Se.removed,{attribute:null,from:st})}if(st.removeAttribute(Re),Re==="is"&&!gr[Re])if(ht||At)try{ql(st)}catch{}else try{st.setAttribute(Re,"")}catch{}},"_removeAttribute"),xF=o(function(Re){let st=null,Rt=null;if(qi)Re=""+Re;else{let oa=y(Re,/^[\r\n\t ]+/);Rt=oa&&oa[0]}jc==="application/xhtml+xml"&&ah===Us&&(Re=''+Re+"");let bn=He?He.createHTML(Re):Re;if(ah===Us)try{st=new ae().parseFromString(bn,jc)}catch{}if(!st||!st.documentElement){st=Ze.createDocument(ah,"template",null);try{st.documentElement.innerHTML=Lb?ze:bn}catch{}}let Da=st.body||st.documentElement;return Re&&Rt&&Da.insertBefore(Ue.createTextNode(Rt),Da.childNodes[0]||null),ah===Us?tt.call(st,_a?"html":"body")[0]:_a?st.documentElement:Da},"_initDocument"),bF=o(function(Re){return gt.call(Re.ownerDocument||Re,Re,re.SHOW_ELEMENT|re.SHOW_COMMENT|re.SHOW_TEXT|re.SHOW_PROCESSING_INSTRUCTION|re.SHOW_CDATA_SECTION,null)},"_createNodeIterator"),wF=o(function(Re){return Re instanceof ne&&(typeof Re.nodeName!="string"||typeof Re.textContent!="string"||typeof Re.removeChild!="function"||!(Re.attributes instanceof he)||typeof Re.removeAttribute!="function"||typeof Re.setAttribute!="function"||typeof Re.namespaceURI!="string"||typeof Re.insertBefore!="function"||typeof Re.hasChildNodes!="function")},"_isClobbered"),TF=o(function(Re){return typeof fe=="function"&&Re instanceof fe},"_isNode"),Qc=o(function(Re,st,Rt){Je[Re]&&f(Je[Re],bn=>{bn.call(Se,st,Rt,Wl)})},"_executeHook"),kF=o(function(Re){let st=null;if(Qc("beforeSanitizeElements",Re,null),wF(Re))return ql(Re),!0;let Rt=_i(Re.nodeName);if(Qc("uponSanitizeElement",Re,{tagName:Rt,allowedTags:mt}),Re.hasChildNodes()&&!TF(Re.firstElementChild)&&S(/<[/\w]/g,Re.innerHTML)&&S(/<[/\w]/g,Re.textContent)||Re.nodeType===ue.progressingInstruction||Ha&&Re.nodeType===ue.comment&&S(/<[/\w]/g,Re.data))return ql(Re),!0;if(!mt[Rt]||rn[Rt]){if(!rn[Rt]&&CF(Rt)&&(jt.tagNameCheck instanceof RegExp&&S(jt.tagNameCheck,Rt)||jt.tagNameCheck instanceof Function&&jt.tagNameCheck(Rt)))return!1;if(ur&&!Xc[Rt]){let bn=ye(Re)||Re.parentNode,Da=Me(Re)||Re.childNodes;if(Da&&bn){let oa=Da.length;for(let hs=oa-1;hs>=0;--hs){let Xl=Ce(Da[hs],!0);Xl.__removalCount=(Re.__removalCount||0)+1,bn.insertBefore(Xl,Ge(Re))}}}return ql(Re),!0}return Re instanceof ge&&!Ive(Re)||(Rt==="noscript"||Rt==="noembed"||Rt==="noframes")&&S(/<\/no(script|embed|frames)/i,Re.innerHTML)?(ql(Re),!0):(Rn&&Re.nodeType===ue.text&&(st=Re.textContent,f([Ve,je,kt],bn=>{st=v(st,bn," ")}),Re.textContent!==st&&(p(Se.removed,{element:Re.cloneNode()}),Re.textContent=st)),Qc("afterSanitizeElements",Re,null),!1)},"_sanitizeElements"),EF=o(function(Re,st,Rt){if(rt&&(st==="id"||st==="name")&&(Rt in Ue||Rt in sh))return!1;if(!(hn&&!Er[st]&&S(at,st))){if(!(Kn&&S(xt,st))){if(!gr[st]||Er[st]){if(!(CF(Re)&&(jt.tagNameCheck instanceof RegExp&&S(jt.tagNameCheck,Re)||jt.tagNameCheck instanceof Function&&jt.tagNameCheck(Re))&&(jt.attributeNameCheck instanceof RegExp&&S(jt.attributeNameCheck,st)||jt.attributeNameCheck instanceof Function&&jt.attributeNameCheck(st))||st==="is"&&jt.allowCustomizedBuiltInElements&&(jt.tagNameCheck instanceof RegExp&&S(jt.tagNameCheck,Rt)||jt.tagNameCheck instanceof Function&&jt.tagNameCheck(Rt))))return!1}else if(!O1[st]){if(!S(It,v(Rt,dt,""))){if(!((st==="src"||st==="xlink:href"||st==="href")&&Re!=="script"&&x(Rt,"data:")===0&&_b[Re])){if(!(Qn&&!S(it,v(Rt,dt,"")))){if(Rt)return!1}}}}}}return!0},"_isValidAttribute"),CF=o(function(Re){return Re!=="annotation-xml"&&y(Re,lt)},"_isBasicCustomElement"),SF=o(function(Re){Qc("beforeSanitizeAttributes",Re,null);let{attributes:st}=Re;if(!st)return;let Rt={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:gr},bn=st.length;for(;bn--;){let Da=st[bn],{name:oa,namespaceURI:hs,value:Xl}=Da,z1=_i(oa),Ya=oa==="value"?Xl:b(Xl);if(Rt.attrName=z1,Rt.attrValue=Ya,Rt.keepAttr=!0,Rt.forceKeepAttr=void 0,Qc("uponSanitizeAttribute",Re,Rt),Ya=Rt.attrValue,Ha&&S(/((--!?|])>)|<\/(style|title)/i,Ya)){Db(oa,Re);continue}if(Rt.forceKeepAttr||(Db(oa,Re),!Rt.keepAttr))continue;if(!on&&S(/\/>/i,Ya)){Db(oa,Re);continue}Rn&&f([Ve,je,kt],_F=>{Ya=v(Ya,_F," ")});let AF=_i(Re.nodeName);if(EF(AF,z1,Ya)){if(Ot&&(z1==="id"||z1==="name")&&(Db(oa,Re),Ya=pe+Ya),He&&typeof we=="object"&&typeof we.getAttributeType=="function"&&!hs)switch(we.getAttributeType(AF,z1)){case"TrustedHTML":{Ya=He.createHTML(Ya);break}case"TrustedScriptURL":{Ya=He.createScriptURL(Ya);break}}try{hs?Re.setAttributeNS(hs,oa,Ya):Re.setAttribute(oa,Ya),wF(Re)?ql(Re):d(Se.removed)}catch{}}}Qc("afterSanitizeAttributes",Re,null)},"_sanitizeAttributes"),Ove=o(function Ft(Re){let st=null,Rt=bF(Re);for(Qc("beforeSanitizeShadowDOM",Re,null);st=Rt.nextNode();)Qc("uponSanitizeShadowNode",st,null),!kF(st)&&(st.content instanceof me&&Ft(st.content),SF(st));Qc("afterSanitizeShadowDOM",Re,null)},"_sanitizeShadowDOM");return Se.sanitize=function(Ft){let Re=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},st=null,Rt=null,bn=null,Da=null;if(Lb=!Ft,Lb&&(Ft=""),typeof Ft!="string"&&!TF(Ft))if(typeof Ft.toString=="function"){if(Ft=Ft.toString(),typeof Ft!="string")throw T("dirty is not a string, aborting")}else throw T("toString is not a function");if(!Se.isSupported)return Ft;if(To||Hs(Re),Se.removed=[],typeof Ft=="string"&&(be=!1),be){if(Ft.nodeName){let Xl=_i(Ft.nodeName);if(!mt[Xl]||rn[Xl])throw T("root node is forbidden and cannot be sanitized in-place")}}else if(Ft instanceof fe)st=xF(""),Rt=st.ownerDocument.importNode(Ft,!0),Rt.nodeType===ue.element&&Rt.nodeName==="BODY"||Rt.nodeName==="HTML"?st=Rt:st.appendChild(Rt);else{if(!ht&&!Rn&&!_a&&Ft.indexOf("<")===-1)return He&&$t?He.createHTML(Ft):Ft;if(st=xF(Ft),!st)return ht?null:$t?ze:""}st&&qi&&ql(st.firstChild);let oa=bF(be?Ft:st);for(;bn=oa.nextNode();)kF(bn)||(bn.content instanceof me&&Ove(bn.content),SF(bn));if(be)return Ft;if(ht){if(At)for(Da=yt.call(st.ownerDocument);st.firstChild;)Da.appendChild(st.firstChild);else Da=st;return(gr.shadowroot||gr.shadowrootmode)&&(Da=Ye.call(Pe,Da,!0)),Da}let hs=_a?st.outerHTML:st.innerHTML;return _a&&mt["!doctype"]&&st.ownerDocument&&st.ownerDocument.doctype&&st.ownerDocument.doctype.name&&S(K,st.ownerDocument.doctype.name)&&(hs=" +`+hs),Rn&&f([Ve,je,kt],Xl=>{hs=v(hs,Xl," ")}),He&&$t?He.createHTML(hs):hs},Se.setConfig=function(){let Ft=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};Hs(Ft),To=!0},Se.clearConfig=function(){Wl=null,To=!1},Se.isValidAttribute=function(Ft,Re,st){Wl||Hs({});let Rt=_i(Ft),bn=_i(Re);return EF(Rt,bn,st)},Se.addHook=function(Ft,Re){typeof Re=="function"&&(Je[Ft]=Je[Ft]||[],p(Je[Ft],Re))},Se.removeHook=function(Ft){if(Je[Ft])return d(Je[Ft])},Se.removeHooks=function(Ft){Je[Ft]&&(Je[Ft]=[])},Se.removeAllHooks=function(){Je={}},Se}o(oe,"createDOMPurify");var ke=oe();return ke})});var k$={};hr(k$,{default:()=>Ebe});function B2e(t){return String(t).replace(P2e,e=>O2e[e])}function $2e(t){if(t.default)return t.default;var e=t.type,r=Array.isArray(e)?e[0]:e;if(typeof r!="string")return r.enum[0];switch(r){case"boolean":return!1;case"string":return"";case"number":return 0;case"object":return{}}}function X2e(t){for(var e=0;e=i[0]&&t<=i[1])return r.name}return null}function LG(t){for(var e=0;e=h4[e]&&t<=h4[e+1])return!0;return!1}function axe(t,e){Zl[t]=e}function M7(t,e,r){if(!Zl[e])throw new Error("Font metrics not found for font: "+e+".");var n=t.charCodeAt(0),i=Zl[e][n];if(!i&&t[0]in Zz&&(n=Zz[t[0]].charCodeAt(0),i=Zl[e][n]),!i&&r==="text"&&LG(n)&&(i=Zl[e][77]),i)return{depth:i[0],height:i[1],italic:i[2],skew:i[3],width:i[4]}}function sxe(t){var e;if(t>=5?e=0:t>=3?e=1:e=2,!l7[e]){var r=l7[e]={cssEmPerMu:Jb.quad[e]/18};for(var n in Jb)Jb.hasOwnProperty(n)&&(r[n]=Jb[n][e])}return l7[e]}function tG(t){if(t instanceof ms)return t;throw new Error("Expected symbolNode but got "+String(t)+".")}function uxe(t){if(t instanceof jf)return t;throw new Error("Expected span but got "+String(t)+".")}function G(t,e,r,n,i,a){wn[t][i]={font:e,group:r,replace:n},a&&n&&(wn[t][n]=wn[t][i])}function vt(t){for(var{type:e,names:r,props:n,handler:i,htmlBuilder:a,mathmlBuilder:s}=t,l={type:e,numArgs:n.numArgs,argTypes:n.argTypes,allowedInArgument:!!n.allowedInArgument,allowedInText:!!n.allowedInText,allowedInMath:n.allowedInMath===void 0?!0:n.allowedInMath,numOptionalArgs:n.numOptionalArgs||0,infix:!!n.infix,primitive:!!n.primitive,handler:i},u=0;u0&&(a.push(s4(s,e)),s=[]),a.push(n[l]));s.length>0&&a.push(s4(s,e));var h;r?(h=s4(Ri(r,e,!0)),h.classes=["tag"],a.push(h)):i&&a.push(i);var f=su(["katex-html"],a);if(f.setAttribute("aria-hidden","true"),h){var d=h.children[0];d.style.height=ct(f.height+f.depth),f.depth&&(d.style.verticalAlign=ct(-f.depth))}return f}function $G(t){return new Xf(t)}function sG(t,e,r,n,i){var a=gs(t,r),s;a.length===1&&a[0]instanceof ps&&Vt.contains(["mrow","mtable"],a[0].type)?s=a[0]:s=new et.MathNode("mrow",a);var l=new et.MathNode("annotation",[new et.TextNode(e)]);l.setAttribute("encoding","application/x-tex");var u=new et.MathNode("semantics",[s,l]),h=new et.MathNode("math",[u]);h.setAttribute("xmlns","http://www.w3.org/1998/Math/MathML"),n&&h.setAttribute("display","block");var f=i?"katex":"katex-mathml";return Be.makeSpan([f],[h])}function ir(t,e){if(!t||t.type!==e)throw new Error("Expected node of type "+e+", but got "+(t?"node of type "+t.type:String(t)));return t}function B7(t){var e=T4(t);if(!e)throw new Error("Expected node of symbol group type, but got "+(t?"node of type "+t.type:String(t)));return e}function T4(t){return t&&(t.type==="atom"||fxe.hasOwnProperty(t.type))?t:null}function YG(t,e){var r=Ri(t.body,e,!0);return Vxe([t.mclass],r,e)}function WG(t,e){var r,n=gs(t.body,e);return t.mclass==="minner"?r=new et.MathNode("mpadded",n):t.mclass==="mord"?t.isCharacterBox?(r=n[0],r.type="mi"):r=new et.MathNode("mi",n):(t.isCharacterBox?(r=n[0],r.type="mo"):r=new et.MathNode("mo",n),t.mclass==="mbin"?(r.attributes.lspace="0.22em",r.attributes.rspace="0.22em"):t.mclass==="mpunct"?(r.attributes.lspace="0em",r.attributes.rspace="0.17em"):t.mclass==="mopen"||t.mclass==="mclose"?(r.attributes.lspace="0em",r.attributes.rspace="0em"):t.mclass==="minner"&&(r.attributes.lspace="0.0556em",r.attributes.width="+0.1111em")),r}function Yxe(t,e,r){var n=Uxe[t];switch(n){case"\\\\cdrightarrow":case"\\\\cdleftarrow":return r.callFunction(n,[e[0]],[e[1]]);case"\\uparrow":case"\\downarrow":{var i=r.callFunction("\\\\cdleft",[e[0]],[]),a={type:"atom",text:n,mode:"math",family:"rel"},s=r.callFunction("\\Big",[a],[]),l=r.callFunction("\\\\cdright",[e[1]],[]),u={type:"ordgroup",mode:"math",body:[i,s,l]};return r.callFunction("\\\\cdparent",[u],[])}case"\\\\cdlongequal":return r.callFunction("\\\\cdlongequal",[],[]);case"\\Vert":{var h={type:"textord",text:"\\Vert",mode:"math"};return r.callFunction("\\Big",[h],[])}default:return{type:"textord",text:" ",mode:"math"}}}function Wxe(t){var e=[];for(t.gullet.beginGroup(),t.gullet.macros.set("\\cr","\\\\\\relax"),t.gullet.beginGroup();;){e.push(t.parseExpression(!1,"\\\\")),t.gullet.endGroup(),t.gullet.beginGroup();var r=t.fetch().text;if(r==="&"||r==="\\\\")t.consume();else if(r==="\\end"){e[e.length-1].length===0&&e.pop();break}else throw new nt("Expected \\\\ or \\cr or \\end",t.nextToken)}for(var n=[],i=[n],a=0;a-1))if("<>AV".indexOf(h)>-1)for(var d=0;d<2;d++){for(var p=!0,m=u+1;mAV=|." after @',s[u]);var g=Yxe(h,f,t),y={type:"styling",body:[g],mode:"math",style:"display"};n.push(y),l=oG()}a%2===0?n.push(l):n.shift(),n=[],i.push(n)}t.gullet.endGroup(),t.gullet.endGroup();var v=new Array(i[0].length).fill({type:"align",align:"c",pregap:.25,postgap:.25});return{type:"array",mode:"math",body:i,arraystretch:1,addJot:!0,rowGaps:[null],cols:v,colSeparationType:"CD",hLinesBeforeRow:new Array(i.length+1).fill([])}}function E4(t,e){var r=T4(t);if(r&&Vt.contains(abe,r.text))return r;throw r?new nt("Invalid delimiter '"+r.text+"' after '"+e.funcName+"'",t):new nt("Invalid delimiter type '"+t.type+"'",t)}function uG(t){if(!t.body)throw new Error("Bug: The leftright ParseNode wasn't fully parsed.")}function ec(t){for(var{type:e,names:r,props:n,handler:i,htmlBuilder:a,mathmlBuilder:s}=t,l={type:e,numArgs:n.numArgs||0,allowedInText:!1,numOptionalArgs:0,handler:i},u=0;u1||!f)&&y.pop(),x.length{"use strict";Xs=class t{static{o(this,"SourceLocation")}constructor(e,r,n){this.lexer=void 0,this.start=void 0,this.end=void 0,this.lexer=e,this.start=r,this.end=n}static range(e,r){return r?!e||!e.loc||!r.loc||e.loc.lexer!==r.loc.lexer?null:new t(e.loc.lexer,e.loc.start,r.loc.end):e&&e.loc}},Ao=class t{static{o(this,"Token")}constructor(e,r){this.text=void 0,this.loc=void 0,this.noexpand=void 0,this.treatAsRelax=void 0,this.text=e,this.loc=r}range(e,r){return new t(r,Xs.range(this,e))}},nt=class t{static{o(this,"ParseError")}constructor(e,r){this.name=void 0,this.position=void 0,this.length=void 0,this.rawMessage=void 0;var n="KaTeX parse error: "+e,i,a,s=r&&r.loc;if(s&&s.start<=s.end){var l=s.lexer.input;i=s.start,a=s.end,i===l.length?n+=" at end of input: ":n+=" at position "+(i+1)+": ";var u=l.slice(i,a).replace(/[^]/g,"$&\u0332"),h;i>15?h="\u2026"+l.slice(i-15,i):h=l.slice(0,i);var f;a+15":">","<":"<",'"':""","'":"'"},P2e=/[&><"']/g;o(B2e,"escape");_G=o(function t(e){return e.type==="ordgroup"||e.type==="color"?e.body.length===1?t(e.body[0]):e:e.type==="font"?t(e.body):e},"getBaseElem"),F2e=o(function(e){var r=_G(e);return r.type==="mathord"||r.type==="textord"||r.type==="atom"},"isCharacterBox"),z2e=o(function(e){if(!e)throw new Error("Expected non-null, but got "+String(e));return e},"assert"),G2e=o(function(e){var r=/^[\x00-\x20]*([^\\/#?]*?)(:|�*58|�*3a|&colon)/i.exec(e);return r?r[2]!==":"||!/^[a-zA-Z][a-zA-Z0-9+\-.]*$/.test(r[1])?null:r[1].toLowerCase():"_relative"},"protocolFromUrl"),Vt={contains:R2e,deflt:N2e,escape:B2e,hyphenate:I2e,getBaseElem:_G,isCharacterBox:F2e,protocolFromUrl:G2e},u4={displayMode:{type:"boolean",description:"Render math in display mode, which puts the math in display style (so \\int and \\sum are large, for example), and centers the math on the page on its own line.",cli:"-d, --display-mode"},output:{type:{enum:["htmlAndMathml","html","mathml"]},description:"Determines the markup language of the output.",cli:"-F, --format "},leqno:{type:"boolean",description:"Render display math in leqno style (left-justified tags)."},fleqn:{type:"boolean",description:"Render display math flush left."},throwOnError:{type:"boolean",default:!0,cli:"-t, --no-throw-on-error",cliDescription:"Render errors (in the color given by --error-color) instead of throwing a ParseError exception when encountering an error."},errorColor:{type:"string",default:"#cc0000",cli:"-c, --error-color ",cliDescription:"A color string given in the format 'rgb' or 'rrggbb' (no #). This option determines the color of errors rendered by the -t option.",cliProcessor:o(t=>"#"+t,"cliProcessor")},macros:{type:"object",cli:"-m, --macro ",cliDescription:"Define custom macro of the form '\\foo:expansion' (use multiple -m arguments for multiple macros).",cliDefault:[],cliProcessor:o((t,e)=>(e.push(t),e),"cliProcessor")},minRuleThickness:{type:"number",description:"Specifies a minimum thickness, in ems, for fraction lines, `\\sqrt` top lines, `{array}` vertical lines, `\\hline`, `\\hdashline`, `\\underline`, `\\overline`, and the borders of `\\fbox`, `\\boxed`, and `\\fcolorbox`.",processor:o(t=>Math.max(0,t),"processor"),cli:"--min-rule-thickness ",cliProcessor:parseFloat},colorIsTextColor:{type:"boolean",description:"Makes \\color behave like LaTeX's 2-argument \\textcolor, instead of LaTeX's one-argument \\color mode change.",cli:"-b, --color-is-text-color"},strict:{type:[{enum:["warn","ignore","error"]},"boolean","function"],description:"Turn on strict / LaTeX faithfulness mode, which throws an error if the input uses features that are not supported by LaTeX.",cli:"-S, --strict",cliDefault:!1},trust:{type:["boolean","function"],description:"Trust the input, enabling all HTML features such as \\url.",cli:"-T, --trust"},maxSize:{type:"number",default:1/0,description:"If non-zero, all user-specified sizes, e.g. in \\rule{500em}{500em}, will be capped to maxSize ems. Otherwise, elements and spaces can be arbitrarily large",processor:o(t=>Math.max(0,t),"processor"),cli:"-s, --max-size ",cliProcessor:parseInt},maxExpand:{type:"number",default:1e3,description:"Limit the number of macro expansions to the specified number, to prevent e.g. infinite macro loops. If set to Infinity, the macro expander will try to fully expand as in LaTeX.",processor:o(t=>Math.max(0,t),"processor"),cli:"-e, --max-expand ",cliProcessor:o(t=>t==="Infinity"?1/0:parseInt(t),"cliProcessor")},globalGroup:{type:"boolean",cli:!1}};o($2e,"getDefaultValue");ry=class{static{o(this,"Settings")}constructor(e){this.displayMode=void 0,this.output=void 0,this.leqno=void 0,this.fleqn=void 0,this.throwOnError=void 0,this.errorColor=void 0,this.macros=void 0,this.minRuleThickness=void 0,this.colorIsTextColor=void 0,this.strict=void 0,this.trust=void 0,this.maxSize=void 0,this.maxExpand=void 0,this.globalGroup=void 0,e=e||{};for(var r in u4)if(u4.hasOwnProperty(r)){var n=u4[r];this[r]=e[r]!==void 0?n.processor?n.processor(e[r]):e[r]:$2e(n)}}reportNonstrict(e,r,n){var i=this.strict;if(typeof i=="function"&&(i=i(e,r,n)),!(!i||i==="ignore")){if(i===!0||i==="error")throw new nt("LaTeX-incompatible input and strict mode is set to 'error': "+(r+" ["+e+"]"),n);i==="warn"?typeof console<"u"&&console.warn("LaTeX-incompatible input and strict mode is set to 'warn': "+(r+" ["+e+"]")):typeof console<"u"&&console.warn("LaTeX-incompatible input and strict mode is set to "+("unrecognized '"+i+"': "+r+" ["+e+"]"))}}useStrictBehavior(e,r,n){var i=this.strict;if(typeof i=="function")try{i=i(e,r,n)}catch{i="error"}return!i||i==="ignore"?!1:i===!0||i==="error"?!0:i==="warn"?(typeof console<"u"&&console.warn("LaTeX-incompatible input and strict mode is set to 'warn': "+(r+" ["+e+"]")),!1):(typeof console<"u"&&console.warn("LaTeX-incompatible input and strict mode is set to "+("unrecognized '"+i+"': "+r+" ["+e+"]")),!1)}isTrusted(e){if(e.url&&!e.protocol){var r=Vt.protocolFromUrl(e.url);if(r==null)return!1;e.protocol=r}var n=typeof this.trust=="function"?this.trust(e):this.trust;return!!n}},Kl=class{static{o(this,"Style")}constructor(e,r,n){this.id=void 0,this.size=void 0,this.cramped=void 0,this.id=e,this.size=r,this.cramped=n}sup(){return Ql[V2e[this.id]]}sub(){return Ql[U2e[this.id]]}fracNum(){return Ql[H2e[this.id]]}fracDen(){return Ql[Y2e[this.id]]}cramp(){return Ql[W2e[this.id]]}text(){return Ql[q2e[this.id]]}isTight(){return this.size>=2}},N7=0,f4=1,gp=2,iu=3,ny=4,So=5,yp=6,qa=7,Ql=[new Kl(N7,0,!1),new Kl(f4,0,!0),new Kl(gp,1,!1),new Kl(iu,1,!0),new Kl(ny,2,!1),new Kl(So,2,!0),new Kl(yp,3,!1),new Kl(qa,3,!0)],V2e=[ny,So,ny,So,yp,qa,yp,qa],U2e=[So,So,So,So,qa,qa,qa,qa],H2e=[gp,iu,ny,So,yp,qa,yp,qa],Y2e=[iu,iu,So,So,qa,qa,qa,qa],W2e=[f4,f4,iu,iu,So,So,qa,qa],q2e=[N7,f4,gp,iu,gp,iu,gp,iu],Ht={DISPLAY:Ql[N7],TEXT:Ql[gp],SCRIPT:Ql[ny],SCRIPTSCRIPT:Ql[yp]},b7=[{name:"latin",blocks:[[256,591],[768,879]]},{name:"cyrillic",blocks:[[1024,1279]]},{name:"armenian",blocks:[[1328,1423]]},{name:"brahmic",blocks:[[2304,4255]]},{name:"georgian",blocks:[[4256,4351]]},{name:"cjk",blocks:[[12288,12543],[19968,40879],[65280,65376]]},{name:"hangul",blocks:[[44032,55215]]}];o(X2e,"scriptFromCodepoint");h4=[];b7.forEach(t=>t.blocks.forEach(e=>h4.push(...e)));o(LG,"supportedCodepoint");mp=80,j2e=o(function(e,r){return"M95,"+(622+e+r)+` +c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14 +c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54 +c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10 +s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429 +c69,-144,104.5,-217.7,106.5,-221 +l`+e/2.075+" -"+e+` +c5.3,-9.3,12,-14,20,-14 +H400000v`+(40+e)+`H845.2724 +s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7 +c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47z +M`+(834+e)+" "+r+"h400000v"+(40+e)+"h-400000z"},"sqrtMain"),K2e=o(function(e,r){return"M263,"+(601+e+r)+`c0.7,0,18,39.7,52,119 +c34,79.3,68.167,158.7,102.5,238c34.3,79.3,51.8,119.3,52.5,120 +c340,-704.7,510.7,-1060.3,512,-1067 +l`+e/2.084+" -"+e+` +c4.7,-7.3,11,-11,19,-11 +H40000v`+(40+e)+`H1012.3 +s-271.3,567,-271.3,567c-38.7,80.7,-84,175,-136,283c-52,108,-89.167,185.3,-111.5,232 +c-22.3,46.7,-33.8,70.3,-34.5,71c-4.7,4.7,-12.3,7,-23,7s-12,-1,-12,-1 +s-109,-253,-109,-253c-72.7,-168,-109.3,-252,-110,-252c-10.7,8,-22,16.7,-34,26 +c-22,17.3,-33.3,26,-34,26s-26,-26,-26,-26s76,-59,76,-59s76,-60,76,-60z +M`+(1001+e)+" "+r+"h400000v"+(40+e)+"h-400000z"},"sqrtSize1"),Q2e=o(function(e,r){return"M983 "+(10+e+r)+` +l`+e/3.13+" -"+e+` +c4,-6.7,10,-10,18,-10 H400000v`+(40+e)+` +H1013.1s-83.4,268,-264.1,840c-180.7,572,-277,876.3,-289,913c-4.7,4.7,-12.7,7,-24,7 +s-12,0,-12,0c-1.3,-3.3,-3.7,-11.7,-7,-25c-35.3,-125.3,-106.7,-373.3,-214,-744 +c-10,12,-21,25,-33,39s-32,39,-32,39c-6,-5.3,-15,-14,-27,-26s25,-30,25,-30 +c26.7,-32.7,52,-63,76,-91s52,-60,52,-60s208,722,208,722 +c56,-175.3,126.3,-397.3,211,-666c84.7,-268.7,153.8,-488.2,207.5,-658.5 +c53.7,-170.3,84.5,-266.8,92.5,-289.5z +M`+(1001+e)+" "+r+"h400000v"+(40+e)+"h-400000z"},"sqrtSize2"),Z2e=o(function(e,r){return"M424,"+(2398+e+r)+` +c-1.3,-0.7,-38.5,-172,-111.5,-514c-73,-342,-109.8,-513.3,-110.5,-514 +c0,-2,-10.7,14.3,-32,49c-4.7,7.3,-9.8,15.7,-15.5,25c-5.7,9.3,-9.8,16,-12.5,20 +s-5,7,-5,7c-4,-3.3,-8.3,-7.7,-13,-13s-13,-13,-13,-13s76,-122,76,-122s77,-121,77,-121 +s209,968,209,968c0,-2,84.7,-361.7,254,-1079c169.3,-717.3,254.7,-1077.7,256,-1081 +l`+e/4.223+" -"+e+`c4,-6.7,10,-10,18,-10 H400000 +v`+(40+e)+`H1014.6 +s-87.3,378.7,-272.6,1166c-185.3,787.3,-279.3,1182.3,-282,1185 +c-2,6,-10,9,-24,9 +c-8,0,-12,-0.7,-12,-2z M`+(1001+e)+" "+r+` +h400000v`+(40+e)+"h-400000z"},"sqrtSize3"),J2e=o(function(e,r){return"M473,"+(2713+e+r)+` +c339.3,-1799.3,509.3,-2700,510,-2702 l`+e/5.298+" -"+e+` +c3.3,-7.3,9.3,-11,18,-11 H400000v`+(40+e)+`H1017.7 +s-90.5,478,-276.2,1466c-185.7,988,-279.5,1483,-281.5,1485c-2,6,-10,9,-24,9 +c-8,0,-12,-0.7,-12,-2c0,-1.3,-5.3,-32,-16,-92c-50.7,-293.3,-119.7,-693.3,-207,-1200 +c0,-1.3,-5.3,8.7,-16,30c-10.7,21.3,-21.3,42.7,-32,64s-16,33,-16,33s-26,-26,-26,-26 +s76,-153,76,-153s77,-151,77,-151c0.7,0.7,35.7,202,105,604c67.3,400.7,102,602.7,104, +606zM`+(1001+e)+" "+r+"h400000v"+(40+e)+"H1017.7z"},"sqrtSize4"),exe=o(function(e){var r=e/2;return"M400000 "+e+" H0 L"+r+" 0 l65 45 L145 "+(e-80)+" H400000z"},"phasePath"),txe=o(function(e,r,n){var i=n-54-r-e;return"M702 "+(e+r)+"H400000"+(40+e)+` +H742v`+i+`l-4 4-4 4c-.667.7 -2 1.5-4 2.5s-4.167 1.833-6.5 2.5-5.5 1-9.5 1 +h-12l-28-84c-16.667-52-96.667 -294.333-240-727l-212 -643 -85 170 +c-4-3.333-8.333-7.667-13 -13l-13-13l77-155 77-156c66 199.333 139 419.667 +219 661 l218 661zM702 `+r+"H400000v"+(40+e)+"H742z"},"sqrtTall"),rxe=o(function(e,r,n){r=1e3*r;var i="";switch(e){case"sqrtMain":i=j2e(r,mp);break;case"sqrtSize1":i=K2e(r,mp);break;case"sqrtSize2":i=Q2e(r,mp);break;case"sqrtSize3":i=Z2e(r,mp);break;case"sqrtSize4":i=J2e(r,mp);break;case"sqrtTall":i=txe(r,mp,n)}return i},"sqrtPath"),nxe=o(function(e,r){switch(e){case"\u239C":return"M291 0 H417 V"+r+" H291z M291 0 H417 V"+r+" H291z";case"\u2223":return"M145 0 H188 V"+r+" H145z M145 0 H188 V"+r+" H145z";case"\u2225":return"M145 0 H188 V"+r+" H145z M145 0 H188 V"+r+" H145z"+("M367 0 H410 V"+r+" H367z M367 0 H410 V"+r+" H367z");case"\u239F":return"M457 0 H583 V"+r+" H457z M457 0 H583 V"+r+" H457z";case"\u23A2":return"M319 0 H403 V"+r+" H319z M319 0 H403 V"+r+" H319z";case"\u23A5":return"M263 0 H347 V"+r+" H263z M263 0 H347 V"+r+" H263z";case"\u23AA":return"M384 0 H504 V"+r+" H384z M384 0 H504 V"+r+" H384z";case"\u23D0":return"M312 0 H355 V"+r+" H312z M312 0 H355 V"+r+" H312z";case"\u2016":return"M257 0 H300 V"+r+" H257z M257 0 H300 V"+r+" H257z"+("M478 0 H521 V"+r+" H478z M478 0 H521 V"+r+" H478z");default:return""}},"innerPath"),Qz={doubleleftarrow:`M262 157 +l10-10c34-36 62.7-77 86-123 3.3-8 5-13.3 5-16 0-5.3-6.7-8-20-8-7.3 + 0-12.2.5-14.5 1.5-2.3 1-4.8 4.5-7.5 10.5-49.3 97.3-121.7 169.3-217 216-28 + 14-57.3 25-88 33-6.7 2-11 3.8-13 5.5-2 1.7-3 4.2-3 7.5s1 5.8 3 7.5 +c2 1.7 6.3 3.5 13 5.5 68 17.3 128.2 47.8 180.5 91.5 52.3 43.7 93.8 96.2 124.5 + 157.5 9.3 8 15.3 12.3 18 13h6c12-.7 18-4 18-10 0-2-1.7-7-5-15-23.3-46-52-87 +-86-123l-10-10h399738v-40H218c328 0 0 0 0 0l-10-8c-26.7-20-65.7-43-117-69 2.7 +-2 6-3.7 10-5 36.7-16 72.3-37.3 107-64l10-8h399782v-40z +m8 0v40h399730v-40zm0 194v40h399730v-40z`,doublerightarrow:`M399738 392l +-10 10c-34 36-62.7 77-86 123-3.3 8-5 13.3-5 16 0 5.3 6.7 8 20 8 7.3 0 12.2-.5 + 14.5-1.5 2.3-1 4.8-4.5 7.5-10.5 49.3-97.3 121.7-169.3 217-216 28-14 57.3-25 88 +-33 6.7-2 11-3.8 13-5.5 2-1.7 3-4.2 3-7.5s-1-5.8-3-7.5c-2-1.7-6.3-3.5-13-5.5-68 +-17.3-128.2-47.8-180.5-91.5-52.3-43.7-93.8-96.2-124.5-157.5-9.3-8-15.3-12.3-18 +-13h-6c-12 .7-18 4-18 10 0 2 1.7 7 5 15 23.3 46 52 87 86 123l10 10H0v40h399782 +c-328 0 0 0 0 0l10 8c26.7 20 65.7 43 117 69-2.7 2-6 3.7-10 5-36.7 16-72.3 37.3 +-107 64l-10 8H0v40zM0 157v40h399730v-40zm0 194v40h399730v-40z`,leftarrow:`M400000 241H110l3-3c68.7-52.7 113.7-120 + 135-202 4-14.7 6-23 6-25 0-7.3-7-11-21-11-8 0-13.2.8-15.5 2.5-2.3 1.7-4.2 5.8 +-5.5 12.5-1.3 4.7-2.7 10.3-4 17-12 48.7-34.8 92-68.5 130S65.3 228.3 18 247 +c-10 4-16 7.7-18 11 0 8.7 6 14.3 18 17 47.3 18.7 87.8 47 121.5 85S196 441.3 208 + 490c.7 2 1.3 5 2 9s1.2 6.7 1.5 8c.3 1.3 1 3.3 2 6s2.2 4.5 3.5 5.5c1.3 1 3.3 + 1.8 6 2.5s6 1 10 1c14 0 21-3.7 21-11 0-2-2-10.3-6-25-20-79.3-65-146.7-135-202 + l-3-3h399890zM100 241v40h399900v-40z`,leftbrace:`M6 548l-6-6v-35l6-11c56-104 135.3-181.3 238-232 57.3-28.7 117 +-45 179-50h399577v120H403c-43.3 7-81 15-113 26-100.7 33-179.7 91-237 174-2.7 + 5-6 9-10 13-.7 1-7.3 1-20 1H6z`,leftbraceunder:`M0 6l6-6h17c12.688 0 19.313.3 20 1 4 4 7.313 8.3 10 13 + 35.313 51.3 80.813 93.8 136.5 127.5 55.688 33.7 117.188 55.8 184.5 66.5.688 + 0 2 .3 4 1 18.688 2.7 76 4.3 172 5h399450v120H429l-6-1c-124.688-8-235-61.7 +-331-161C60.687 138.7 32.312 99.3 7 54L0 41V6z`,leftgroup:`M400000 80 +H435C64 80 168.3 229.4 21 260c-5.9 1.2-18 0-18 0-2 0-3-1-3-3v-38C76 61 257 0 + 435 0h399565z`,leftgroupunder:`M400000 262 +H435C64 262 168.3 112.6 21 82c-5.9-1.2-18 0-18 0-2 0-3 1-3 3v38c76 158 257 219 + 435 219h399565z`,leftharpoon:`M0 267c.7 5.3 3 10 7 14h399993v-40H93c3.3 +-3.3 10.2-9.5 20.5-18.5s17.8-15.8 22.5-20.5c50.7-52 88-110.3 112-175 4-11.3 5 +-18.3 3-21-1.3-4-7.3-6-18-6-8 0-13 .7-15 2s-4.7 6.7-8 16c-42 98.7-107.3 174.7 +-196 228-6.7 4.7-10.7 8-12 10-1.3 2-2 5.7-2 11zm100-26v40h399900v-40z`,leftharpoonplus:`M0 267c.7 5.3 3 10 7 14h399993v-40H93c3.3-3.3 10.2-9.5 + 20.5-18.5s17.8-15.8 22.5-20.5c50.7-52 88-110.3 112-175 4-11.3 5-18.3 3-21-1.3 +-4-7.3-6-18-6-8 0-13 .7-15 2s-4.7 6.7-8 16c-42 98.7-107.3 174.7-196 228-6.7 4.7 +-10.7 8-12 10-1.3 2-2 5.7-2 11zm100-26v40h399900v-40zM0 435v40h400000v-40z +m0 0v40h400000v-40z`,leftharpoondown:`M7 241c-4 4-6.333 8.667-7 14 0 5.333.667 9 2 11s5.333 + 5.333 12 10c90.667 54 156 130 196 228 3.333 10.667 6.333 16.333 9 17 2 .667 5 + 1 9 1h5c10.667 0 16.667-2 18-6 2-2.667 1-9.667-3-21-32-87.333-82.667-157.667 +-152-211l-3-3h399907v-40zM93 281 H400000 v-40L7 241z`,leftharpoondownplus:`M7 435c-4 4-6.3 8.7-7 14 0 5.3.7 9 2 11s5.3 5.3 12 + 10c90.7 54 156 130 196 228 3.3 10.7 6.3 16.3 9 17 2 .7 5 1 9 1h5c10.7 0 16.7 +-2 18-6 2-2.7 1-9.7-3-21-32-87.3-82.7-157.7-152-211l-3-3h399907v-40H7zm93 0 +v40h399900v-40zM0 241v40h399900v-40zm0 0v40h399900v-40z`,lefthook:`M400000 281 H103s-33-11.2-61-33.5S0 197.3 0 164s14.2-61.2 42.5 +-83.5C70.8 58.2 104 47 142 47 c16.7 0 25 6.7 25 20 0 12-8.7 18.7-26 20-40 3.3 +-68.7 15.7-86 37-10 12-15 25.3-15 40 0 22.7 9.8 40.7 29.5 54 19.7 13.3 43.5 21 + 71.5 23h399859zM103 281v-40h399897v40z`,leftlinesegment:`M40 281 V428 H0 V94 H40 V241 H400000 v40z +M40 281 V428 H0 V94 H40 V241 H400000 v40z`,leftmapsto:`M40 281 V448H0V74H40V241H400000v40z +M40 281 V448H0V74H40V241H400000v40z`,leftToFrom:`M0 147h400000v40H0zm0 214c68 40 115.7 95.7 143 167h22c15.3 0 23 +-.3 23-1 0-1.3-5.3-13.7-16-37-18-35.3-41.3-69-70-101l-7-8h399905v-40H95l7-8 +c28.7-32 52-65.7 70-101 10.7-23.3 16-35.7 16-37 0-.7-7.7-1-23-1h-22C115.7 265.3 + 68 321 0 361zm0-174v-40h399900v40zm100 154v40h399900v-40z`,longequal:`M0 50 h400000 v40H0z m0 194h40000v40H0z +M0 50 h400000 v40H0z m0 194h40000v40H0z`,midbrace:`M200428 334 +c-100.7-8.3-195.3-44-280-108-55.3-42-101.7-93-139-153l-9-14c-2.7 4-5.7 8.7-9 14 +-53.3 86.7-123.7 153-211 199-66.7 36-137.3 56.3-212 62H0V214h199568c178.3-11.7 + 311.7-78.3 403-201 6-8 9.7-12 11-12 .7-.7 6.7-1 18-1s17.3.3 18 1c1.3 0 5 4 11 + 12 44.7 59.3 101.3 106.3 170 141s145.3 54.3 229 60h199572v120z`,midbraceunder:`M199572 214 +c100.7 8.3 195.3 44 280 108 55.3 42 101.7 93 139 153l9 14c2.7-4 5.7-8.7 9-14 + 53.3-86.7 123.7-153 211-199 66.7-36 137.3-56.3 212-62h199568v120H200432c-178.3 + 11.7-311.7 78.3-403 201-6 8-9.7 12-11 12-.7.7-6.7 1-18 1s-17.3-.3-18-1c-1.3 0 +-5-4-11-12-44.7-59.3-101.3-106.3-170-141s-145.3-54.3-229-60H0V214z`,oiintSize1:`M512.6 71.6c272.6 0 320.3 106.8 320.3 178.2 0 70.8-47.7 177.6 +-320.3 177.6S193.1 320.6 193.1 249.8c0-71.4 46.9-178.2 319.5-178.2z +m368.1 178.2c0-86.4-60.9-215.4-368.1-215.4-306.4 0-367.3 129-367.3 215.4 0 85.8 +60.9 214.8 367.3 214.8 307.2 0 368.1-129 368.1-214.8z`,oiintSize2:`M757.8 100.1c384.7 0 451.1 137.6 451.1 230 0 91.3-66.4 228.8 +-451.1 228.8-386.3 0-452.7-137.5-452.7-228.8 0-92.4 66.4-230 452.7-230z +m502.4 230c0-111.2-82.4-277.2-502.4-277.2s-504 166-504 277.2 +c0 110 84 276 504 276s502.4-166 502.4-276z`,oiiintSize1:`M681.4 71.6c408.9 0 480.5 106.8 480.5 178.2 0 70.8-71.6 177.6 +-480.5 177.6S202.1 320.6 202.1 249.8c0-71.4 70.5-178.2 479.3-178.2z +m525.8 178.2c0-86.4-86.8-215.4-525.7-215.4-437.9 0-524.7 129-524.7 215.4 0 +85.8 86.8 214.8 524.7 214.8 438.9 0 525.7-129 525.7-214.8z`,oiiintSize2:`M1021.2 53c603.6 0 707.8 165.8 707.8 277.2 0 110-104.2 275.8 +-707.8 275.8-606 0-710.2-165.8-710.2-275.8C311 218.8 415.2 53 1021.2 53z +m770.4 277.1c0-131.2-126.4-327.6-770.5-327.6S248.4 198.9 248.4 330.1 +c0 130 128.8 326.4 772.7 326.4s770.5-196.4 770.5-326.4z`,rightarrow:`M0 241v40h399891c-47.3 35.3-84 78-110 128 +-16.7 32-27.7 63.7-33 95 0 1.3-.2 2.7-.5 4-.3 1.3-.5 2.3-.5 3 0 7.3 6.7 11 20 + 11 8 0 13.2-.8 15.5-2.5 2.3-1.7 4.2-5.5 5.5-11.5 2-13.3 5.7-27 11-41 14.7-44.7 + 39-84.5 73-119.5s73.7-60.2 119-75.5c6-2 9-5.7 9-11s-3-9-9-11c-45.3-15.3-85 +-40.5-119-75.5s-58.3-74.8-73-119.5c-4.7-14-8.3-27.3-11-40-1.3-6.7-3.2-10.8-5.5 +-12.5-2.3-1.7-7.5-2.5-15.5-2.5-14 0-21 3.7-21 11 0 2 2 10.3 6 25 20.7 83.3 67 + 151.7 139 205zm0 0v40h399900v-40z`,rightbrace:`M400000 542l +-6 6h-17c-12.7 0-19.3-.3-20-1-4-4-7.3-8.3-10-13-35.3-51.3-80.8-93.8-136.5-127.5 +s-117.2-55.8-184.5-66.5c-.7 0-2-.3-4-1-18.7-2.7-76-4.3-172-5H0V214h399571l6 1 +c124.7 8 235 61.7 331 161 31.3 33.3 59.7 72.7 85 118l7 13v35z`,rightbraceunder:`M399994 0l6 6v35l-6 11c-56 104-135.3 181.3-238 232-57.3 + 28.7-117 45-179 50H-300V214h399897c43.3-7 81-15 113-26 100.7-33 179.7-91 237 +-174 2.7-5 6-9 10-13 .7-1 7.3-1 20-1h17z`,rightgroup:`M0 80h399565c371 0 266.7 149.4 414 180 5.9 1.2 18 0 18 0 2 0 + 3-1 3-3v-38c-76-158-257-219-435-219H0z`,rightgroupunder:`M0 262h399565c371 0 266.7-149.4 414-180 5.9-1.2 18 0 18 + 0 2 0 3 1 3 3v38c-76 158-257 219-435 219H0z`,rightharpoon:`M0 241v40h399993c4.7-4.7 7-9.3 7-14 0-9.3 +-3.7-15.3-11-18-92.7-56.7-159-133.7-199-231-3.3-9.3-6-14.7-8-16-2-1.3-7-2-15-2 +-10.7 0-16.7 2-18 6-2 2.7-1 9.7 3 21 15.3 42 36.7 81.8 64 119.5 27.3 37.7 58 + 69.2 92 94.5zm0 0v40h399900v-40z`,rightharpoonplus:`M0 241v40h399993c4.7-4.7 7-9.3 7-14 0-9.3-3.7-15.3-11 +-18-92.7-56.7-159-133.7-199-231-3.3-9.3-6-14.7-8-16-2-1.3-7-2-15-2-10.7 0-16.7 + 2-18 6-2 2.7-1 9.7 3 21 15.3 42 36.7 81.8 64 119.5 27.3 37.7 58 69.2 92 94.5z +m0 0v40h399900v-40z m100 194v40h399900v-40zm0 0v40h399900v-40z`,rightharpoondown:`M399747 511c0 7.3 6.7 11 20 11 8 0 13-.8 15-2.5s4.7-6.8 + 8-15.5c40-94 99.3-166.3 178-217 13.3-8 20.3-12.3 21-13 5.3-3.3 8.5-5.8 9.5 +-7.5 1-1.7 1.5-5.2 1.5-10.5s-2.3-10.3-7-15H0v40h399908c-34 25.3-64.7 57-92 95 +-27.3 38-48.7 77.7-64 119-3.3 8.7-5 14-5 16zM0 241v40h399900v-40z`,rightharpoondownplus:`M399747 705c0 7.3 6.7 11 20 11 8 0 13-.8 + 15-2.5s4.7-6.8 8-15.5c40-94 99.3-166.3 178-217 13.3-8 20.3-12.3 21-13 5.3-3.3 + 8.5-5.8 9.5-7.5 1-1.7 1.5-5.2 1.5-10.5s-2.3-10.3-7-15H0v40h399908c-34 25.3 +-64.7 57-92 95-27.3 38-48.7 77.7-64 119-3.3 8.7-5 14-5 16zM0 435v40h399900v-40z +m0-194v40h400000v-40zm0 0v40h400000v-40z`,righthook:`M399859 241c-764 0 0 0 0 0 40-3.3 68.7-15.7 86-37 10-12 15-25.3 + 15-40 0-22.7-9.8-40.7-29.5-54-19.7-13.3-43.5-21-71.5-23-17.3-1.3-26-8-26-20 0 +-13.3 8.7-20 26-20 38 0 71 11.2 99 33.5 0 0 7 5.6 21 16.7 14 11.2 21 33.5 21 + 66.8s-14 61.2-42 83.5c-28 22.3-61 33.5-99 33.5L0 241z M0 281v-40h399859v40z`,rightlinesegment:`M399960 241 V94 h40 V428 h-40 V281 H0 v-40z +M399960 241 V94 h40 V428 h-40 V281 H0 v-40z`,rightToFrom:`M400000 167c-70.7-42-118-97.7-142-167h-23c-15.3 0-23 .3-23 + 1 0 1.3 5.3 13.7 16 37 18 35.3 41.3 69 70 101l7 8H0v40h399905l-7 8c-28.7 32 +-52 65.7-70 101-10.7 23.3-16 35.7-16 37 0 .7 7.7 1 23 1h23c24-69.3 71.3-125 142 +-167z M100 147v40h399900v-40zM0 341v40h399900v-40z`,twoheadleftarrow:`M0 167c68 40 + 115.7 95.7 143 167h22c15.3 0 23-.3 23-1 0-1.3-5.3-13.7-16-37-18-35.3-41.3-69 +-70-101l-7-8h125l9 7c50.7 39.3 85 86 103 140h46c0-4.7-6.3-18.7-19-42-18-35.3 +-40-67.3-66-96l-9-9h399716v-40H284l9-9c26-28.7 48-60.7 66-96 12.7-23.333 19 +-37.333 19-42h-46c-18 54-52.3 100.7-103 140l-9 7H95l7-8c28.7-32 52-65.7 70-101 + 10.7-23.333 16-35.7 16-37 0-.7-7.7-1-23-1h-22C115.7 71.3 68 127 0 167z`,twoheadrightarrow:`M400000 167 +c-68-40-115.7-95.7-143-167h-22c-15.3 0-23 .3-23 1 0 1.3 5.3 13.7 16 37 18 35.3 + 41.3 69 70 101l7 8h-125l-9-7c-50.7-39.3-85-86-103-140h-46c0 4.7 6.3 18.7 19 42 + 18 35.3 40 67.3 66 96l9 9H0v40h399716l-9 9c-26 28.7-48 60.7-66 96-12.7 23.333 +-19 37.333-19 42h46c18-54 52.3-100.7 103-140l9-7h125l-7 8c-28.7 32-52 65.7-70 + 101-10.7 23.333-16 35.7-16 37 0 .7 7.7 1 23 1h22c27.3-71.3 75-127 143-167z`,tilde1:`M200 55.538c-77 0-168 73.953-177 73.953-3 0-7 +-2.175-9-5.437L2 97c-1-2-2-4-2-6 0-4 2-7 5-9l20-12C116 12 171 0 207 0c86 0 + 114 68 191 68 78 0 168-68 177-68 4 0 7 2 9 5l12 19c1 2.175 2 4.35 2 6.525 0 + 4.35-2 7.613-5 9.788l-19 13.05c-92 63.077-116.937 75.308-183 76.128 +-68.267.847-113-73.952-191-73.952z`,tilde2:`M344 55.266c-142 0-300.638 81.316-311.5 86.418 +-8.01 3.762-22.5 10.91-23.5 5.562L1 120c-1-2-1-3-1-4 0-5 3-9 8-10l18.4-9C160.9 + 31.9 283 0 358 0c148 0 188 122 331 122s314-97 326-97c4 0 8 2 10 7l7 21.114 +c1 2.14 1 3.21 1 4.28 0 5.347-3 9.626-7 10.696l-22.3 12.622C852.6 158.372 751 + 181.476 676 181.476c-149 0-189-126.21-332-126.21z`,tilde3:`M786 59C457 59 32 175.242 13 175.242c-6 0-10-3.457 +-11-10.37L.15 138c-1-7 3-12 10-13l19.2-6.4C378.4 40.7 634.3 0 804.3 0c337 0 + 411.8 157 746.8 157 328 0 754-112 773-112 5 0 10 3 11 9l1 14.075c1 8.066-.697 + 16.595-6.697 17.492l-21.052 7.31c-367.9 98.146-609.15 122.696-778.15 122.696 + -338 0-409-156.573-744-156.573z`,tilde4:`M786 58C457 58 32 177.487 13 177.487c-6 0-10-3.345 +-11-10.035L.15 143c-1-7 3-12 10-13l22-6.7C381.2 35 637.15 0 807.15 0c337 0 409 + 177 744 177 328 0 754-127 773-127 5 0 10 3 11 9l1 14.794c1 7.805-3 13.38-9 + 14.495l-20.7 5.574c-366.85 99.79-607.3 139.372-776.3 139.372-338 0-409 + -175.236-744-175.236z`,vec:`M377 20c0-5.333 1.833-10 5.5-14S391 0 397 0c4.667 0 8.667 1.667 12 5 +3.333 2.667 6.667 9 10 19 6.667 24.667 20.333 43.667 41 57 7.333 4.667 11 +10.667 11 18 0 6-1 10-3 12s-6.667 5-14 9c-28.667 14.667-53.667 35.667-75 63 +-1.333 1.333-3.167 3.5-5.5 6.5s-4 4.833-5 5.5c-1 .667-2.5 1.333-4.5 2s-4.333 1 +-7 1c-4.667 0-9.167-1.833-13.5-5.5S337 184 337 178c0-12.667 15.667-32.333 47-59 +H213l-171-1c-8.667-6-13-12.333-13-19 0-4.667 4.333-11.333 13-20h359 +c-16-25.333-24-45-24-59z`,widehat1:`M529 0h5l519 115c5 1 9 5 9 10 0 1-1 2-1 3l-4 22 +c-1 5-5 9-11 9h-2L532 67 19 159h-2c-5 0-9-4-11-9l-5-22c-1-6 2-12 8-13z`,widehat2:`M1181 0h2l1171 176c6 0 10 5 10 11l-2 23c-1 6-5 10 +-11 10h-1L1182 67 15 220h-1c-6 0-10-4-11-10l-2-23c-1-6 4-11 10-11z`,widehat3:`M1181 0h2l1171 236c6 0 10 5 10 11l-2 23c-1 6-5 10 +-11 10h-1L1182 67 15 280h-1c-6 0-10-4-11-10l-2-23c-1-6 4-11 10-11z`,widehat4:`M1181 0h2l1171 296c6 0 10 5 10 11l-2 23c-1 6-5 10 +-11 10h-1L1182 67 15 340h-1c-6 0-10-4-11-10l-2-23c-1-6 4-11 10-11z`,widecheck1:`M529,159h5l519,-115c5,-1,9,-5,9,-10c0,-1,-1,-2,-1,-3l-4,-22c-1, +-5,-5,-9,-11,-9h-2l-512,92l-513,-92h-2c-5,0,-9,4,-11,9l-5,22c-1,6,2,12,8,13z`,widecheck2:`M1181,220h2l1171,-176c6,0,10,-5,10,-11l-2,-23c-1,-6,-5,-10, +-11,-10h-1l-1168,153l-1167,-153h-1c-6,0,-10,4,-11,10l-2,23c-1,6,4,11,10,11z`,widecheck3:`M1181,280h2l1171,-236c6,0,10,-5,10,-11l-2,-23c-1,-6,-5,-10, +-11,-10h-1l-1168,213l-1167,-213h-1c-6,0,-10,4,-11,10l-2,23c-1,6,4,11,10,11z`,widecheck4:`M1181,340h2l1171,-296c6,0,10,-5,10,-11l-2,-23c-1,-6,-5,-10, +-11,-10h-1l-1168,273l-1167,-273h-1c-6,0,-10,4,-11,10l-2,23c-1,6,4,11,10,11z`,baraboveleftarrow:`M400000 620h-399890l3 -3c68.7 -52.7 113.7 -120 135 -202 +c4 -14.7 6 -23 6 -25c0 -7.3 -7 -11 -21 -11c-8 0 -13.2 0.8 -15.5 2.5 +c-2.3 1.7 -4.2 5.8 -5.5 12.5c-1.3 4.7 -2.7 10.3 -4 17c-12 48.7 -34.8 92 -68.5 130 +s-74.2 66.3 -121.5 85c-10 4 -16 7.7 -18 11c0 8.7 6 14.3 18 17c47.3 18.7 87.8 47 +121.5 85s56.5 81.3 68.5 130c0.7 2 1.3 5 2 9s1.2 6.7 1.5 8c0.3 1.3 1 3.3 2 6 +s2.2 4.5 3.5 5.5c1.3 1 3.3 1.8 6 2.5s6 1 10 1c14 0 21 -3.7 21 -11 +c0 -2 -2 -10.3 -6 -25c-20 -79.3 -65 -146.7 -135 -202l-3 -3h399890z +M100 620v40h399900v-40z M0 241v40h399900v-40zM0 241v40h399900v-40z`,rightarrowabovebar:`M0 241v40h399891c-47.3 35.3-84 78-110 128-16.7 32 +-27.7 63.7-33 95 0 1.3-.2 2.7-.5 4-.3 1.3-.5 2.3-.5 3 0 7.3 6.7 11 20 11 8 0 +13.2-.8 15.5-2.5 2.3-1.7 4.2-5.5 5.5-11.5 2-13.3 5.7-27 11-41 14.7-44.7 39 +-84.5 73-119.5s73.7-60.2 119-75.5c6-2 9-5.7 9-11s-3-9-9-11c-45.3-15.3-85-40.5 +-119-75.5s-58.3-74.8-73-119.5c-4.7-14-8.3-27.3-11-40-1.3-6.7-3.2-10.8-5.5 +-12.5-2.3-1.7-7.5-2.5-15.5-2.5-14 0-21 3.7-21 11 0 2 2 10.3 6 25 20.7 83.3 67 +151.7 139 205zm96 379h399894v40H0zm0 0h399904v40H0z`,baraboveshortleftharpoon:`M507,435c-4,4,-6.3,8.7,-7,14c0,5.3,0.7,9,2,11 +c1.3,2,5.3,5.3,12,10c90.7,54,156,130,196,228c3.3,10.7,6.3,16.3,9,17 +c2,0.7,5,1,9,1c0,0,5,0,5,0c10.7,0,16.7,-2,18,-6c2,-2.7,1,-9.7,-3,-21 +c-32,-87.3,-82.7,-157.7,-152,-211c0,0,-3,-3,-3,-3l399351,0l0,-40 +c-398570,0,-399437,0,-399437,0z M593 435 v40 H399500 v-40z +M0 281 v-40 H399908 v40z M0 281 v-40 H399908 v40z`,rightharpoonaboveshortbar:`M0,241 l0,40c399126,0,399993,0,399993,0 +c4.7,-4.7,7,-9.3,7,-14c0,-9.3,-3.7,-15.3,-11,-18c-92.7,-56.7,-159,-133.7,-199, +-231c-3.3,-9.3,-6,-14.7,-8,-16c-2,-1.3,-7,-2,-15,-2c-10.7,0,-16.7,2,-18,6 +c-2,2.7,-1,9.7,3,21c15.3,42,36.7,81.8,64,119.5c27.3,37.7,58,69.2,92,94.5z +M0 241 v40 H399908 v-40z M0 475 v-40 H399500 v40z M0 475 v-40 H399500 v40z`,shortbaraboveleftharpoon:`M7,435c-4,4,-6.3,8.7,-7,14c0,5.3,0.7,9,2,11 +c1.3,2,5.3,5.3,12,10c90.7,54,156,130,196,228c3.3,10.7,6.3,16.3,9,17c2,0.7,5,1,9, +1c0,0,5,0,5,0c10.7,0,16.7,-2,18,-6c2,-2.7,1,-9.7,-3,-21c-32,-87.3,-82.7,-157.7, +-152,-211c0,0,-3,-3,-3,-3l399907,0l0,-40c-399126,0,-399993,0,-399993,0z +M93 435 v40 H400000 v-40z M500 241 v40 H400000 v-40z M500 241 v40 H400000 v-40z`,shortrightharpoonabovebar:`M53,241l0,40c398570,0,399437,0,399437,0 +c4.7,-4.7,7,-9.3,7,-14c0,-9.3,-3.7,-15.3,-11,-18c-92.7,-56.7,-159,-133.7,-199, +-231c-3.3,-9.3,-6,-14.7,-8,-16c-2,-1.3,-7,-2,-15,-2c-10.7,0,-16.7,2,-18,6 +c-2,2.7,-1,9.7,3,21c15.3,42,36.7,81.8,64,119.5c27.3,37.7,58,69.2,92,94.5z +M500 241 v40 H399408 v-40z M500 435 v40 H400000 v-40z`},ixe=o(function(e,r){switch(e){case"lbrack":return"M403 1759 V84 H666 V0 H319 V1759 v"+r+` v1759 h347 v-84 +H403z M403 1759 V0 H319 V1759 v`+r+" v1759 h84z";case"rbrack":return"M347 1759 V0 H0 V84 H263 V1759 v"+r+` v1759 H0 v84 H347z +M347 1759 V0 H263 V1759 v`+r+" v1759 h84z";case"vert":return"M145 15 v585 v"+r+` v585 c2.667,10,9.667,15,21,15 +c10,0,16.667,-5,20,-15 v-585 v`+-r+` v-585 c-2.667,-10,-9.667,-15,-21,-15 +c-10,0,-16.667,5,-20,15z M188 15 H145 v585 v`+r+" v585 h43z";case"doublevert":return"M145 15 v585 v"+r+` v585 c2.667,10,9.667,15,21,15 +c10,0,16.667,-5,20,-15 v-585 v`+-r+` v-585 c-2.667,-10,-9.667,-15,-21,-15 +c-10,0,-16.667,5,-20,15z M188 15 H145 v585 v`+r+` v585 h43z +M367 15 v585 v`+r+` v585 c2.667,10,9.667,15,21,15 +c10,0,16.667,-5,20,-15 v-585 v`+-r+` v-585 c-2.667,-10,-9.667,-15,-21,-15 +c-10,0,-16.667,5,-20,15z M410 15 H367 v585 v`+r+" v585 h43z";case"lfloor":return"M319 602 V0 H403 V602 v"+r+` v1715 h263 v84 H319z +MM319 602 V0 H403 V602 v`+r+" v1715 H319z";case"rfloor":return"M319 602 V0 H403 V602 v"+r+` v1799 H0 v-84 H319z +MM319 602 V0 H403 V602 v`+r+" v1715 H319z";case"lceil":return"M403 1759 V84 H666 V0 H319 V1759 v"+r+` v602 h84z +M403 1759 V0 H319 V1759 v`+r+" v602 h84z";case"rceil":return"M347 1759 V0 H0 V84 H263 V1759 v"+r+` v602 h84z +M347 1759 V0 h-84 V1759 v`+r+" v602 h84z";case"lparen":return`M863,9c0,-2,-2,-5,-6,-9c0,0,-17,0,-17,0c-12.7,0,-19.3,0.3,-20,1 +c-5.3,5.3,-10.3,11,-15,17c-242.7,294.7,-395.3,682,-458,1162c-21.3,163.3,-33.3,349, +-36,557 l0,`+(r+84)+`c0.2,6,0,26,0,60c2,159.3,10,310.7,24,454c53.3,528,210, +949.7,470,1265c4.7,6,9.7,11.7,15,17c0.7,0.7,7,1,19,1c0,0,18,0,18,0c4,-4,6,-7,6,-9 +c0,-2.7,-3.3,-8.7,-10,-18c-135.3,-192.7,-235.5,-414.3,-300.5,-665c-65,-250.7,-102.5, +-544.7,-112.5,-882c-2,-104,-3,-167,-3,-189 +l0,-`+(r+92)+`c0,-162.7,5.7,-314,17,-454c20.7,-272,63.7,-513,129,-723c65.3, +-210,155.3,-396.3,270,-559c6.7,-9.3,10,-15.3,10,-18z`;case"rparen":return`M76,0c-16.7,0,-25,3,-25,9c0,2,2,6.3,6,13c21.3,28.7,42.3,60.3, +63,95c96.7,156.7,172.8,332.5,228.5,527.5c55.7,195,92.8,416.5,111.5,664.5 +c11.3,139.3,17,290.7,17,454c0,28,1.7,43,3.3,45l0,`+(r+9)+` +c-3,4,-3.3,16.7,-3.3,38c0,162,-5.7,313.7,-17,455c-18.7,248,-55.8,469.3,-111.5,664 +c-55.7,194.7,-131.8,370.3,-228.5,527c-20.7,34.7,-41.7,66.3,-63,95c-2,3.3,-4,7,-6,11 +c0,7.3,5.7,11,17,11c0,0,11,0,11,0c9.3,0,14.3,-0.3,15,-1c5.3,-5.3,10.3,-11,15,-17 +c242.7,-294.7,395.3,-681.7,458,-1161c21.3,-164.7,33.3,-350.7,36,-558 +l0,-`+(r+144)+`c-2,-159.3,-10,-310.7,-24,-454c-53.3,-528,-210,-949.7, +-470,-1265c-4.7,-6,-9.7,-11.7,-15,-17c-0.7,-0.7,-6.7,-1,-18,-1z`;default:throw new Error("Unknown stretchy delimiter.")}},"tallDelim"),Xf=class{static{o(this,"DocumentFragment")}constructor(e){this.children=void 0,this.classes=void 0,this.height=void 0,this.depth=void 0,this.maxFontSize=void 0,this.style=void 0,this.children=e,this.classes=[],this.height=0,this.depth=0,this.maxFontSize=0,this.style={}}hasClass(e){return Vt.contains(this.classes,e)}toNode(){for(var e=document.createDocumentFragment(),r=0;rr.toText(),"toText");return this.children.map(e).join("")}},Zl={"AMS-Regular":{32:[0,0,0,0,.25],65:[0,.68889,0,0,.72222],66:[0,.68889,0,0,.66667],67:[0,.68889,0,0,.72222],68:[0,.68889,0,0,.72222],69:[0,.68889,0,0,.66667],70:[0,.68889,0,0,.61111],71:[0,.68889,0,0,.77778],72:[0,.68889,0,0,.77778],73:[0,.68889,0,0,.38889],74:[.16667,.68889,0,0,.5],75:[0,.68889,0,0,.77778],76:[0,.68889,0,0,.66667],77:[0,.68889,0,0,.94445],78:[0,.68889,0,0,.72222],79:[.16667,.68889,0,0,.77778],80:[0,.68889,0,0,.61111],81:[.16667,.68889,0,0,.77778],82:[0,.68889,0,0,.72222],83:[0,.68889,0,0,.55556],84:[0,.68889,0,0,.66667],85:[0,.68889,0,0,.72222],86:[0,.68889,0,0,.72222],87:[0,.68889,0,0,1],88:[0,.68889,0,0,.72222],89:[0,.68889,0,0,.72222],90:[0,.68889,0,0,.66667],107:[0,.68889,0,0,.55556],160:[0,0,0,0,.25],165:[0,.675,.025,0,.75],174:[.15559,.69224,0,0,.94666],240:[0,.68889,0,0,.55556],295:[0,.68889,0,0,.54028],710:[0,.825,0,0,2.33334],732:[0,.9,0,0,2.33334],770:[0,.825,0,0,2.33334],771:[0,.9,0,0,2.33334],989:[.08167,.58167,0,0,.77778],1008:[0,.43056,.04028,0,.66667],8245:[0,.54986,0,0,.275],8463:[0,.68889,0,0,.54028],8487:[0,.68889,0,0,.72222],8498:[0,.68889,0,0,.55556],8502:[0,.68889,0,0,.66667],8503:[0,.68889,0,0,.44445],8504:[0,.68889,0,0,.66667],8513:[0,.68889,0,0,.63889],8592:[-.03598,.46402,0,0,.5],8594:[-.03598,.46402,0,0,.5],8602:[-.13313,.36687,0,0,1],8603:[-.13313,.36687,0,0,1],8606:[.01354,.52239,0,0,1],8608:[.01354,.52239,0,0,1],8610:[.01354,.52239,0,0,1.11111],8611:[.01354,.52239,0,0,1.11111],8619:[0,.54986,0,0,1],8620:[0,.54986,0,0,1],8621:[-.13313,.37788,0,0,1.38889],8622:[-.13313,.36687,0,0,1],8624:[0,.69224,0,0,.5],8625:[0,.69224,0,0,.5],8630:[0,.43056,0,0,1],8631:[0,.43056,0,0,1],8634:[.08198,.58198,0,0,.77778],8635:[.08198,.58198,0,0,.77778],8638:[.19444,.69224,0,0,.41667],8639:[.19444,.69224,0,0,.41667],8642:[.19444,.69224,0,0,.41667],8643:[.19444,.69224,0,0,.41667],8644:[.1808,.675,0,0,1],8646:[.1808,.675,0,0,1],8647:[.1808,.675,0,0,1],8648:[.19444,.69224,0,0,.83334],8649:[.1808,.675,0,0,1],8650:[.19444,.69224,0,0,.83334],8651:[.01354,.52239,0,0,1],8652:[.01354,.52239,0,0,1],8653:[-.13313,.36687,0,0,1],8654:[-.13313,.36687,0,0,1],8655:[-.13313,.36687,0,0,1],8666:[.13667,.63667,0,0,1],8667:[.13667,.63667,0,0,1],8669:[-.13313,.37788,0,0,1],8672:[-.064,.437,0,0,1.334],8674:[-.064,.437,0,0,1.334],8705:[0,.825,0,0,.5],8708:[0,.68889,0,0,.55556],8709:[.08167,.58167,0,0,.77778],8717:[0,.43056,0,0,.42917],8722:[-.03598,.46402,0,0,.5],8724:[.08198,.69224,0,0,.77778],8726:[.08167,.58167,0,0,.77778],8733:[0,.69224,0,0,.77778],8736:[0,.69224,0,0,.72222],8737:[0,.69224,0,0,.72222],8738:[.03517,.52239,0,0,.72222],8739:[.08167,.58167,0,0,.22222],8740:[.25142,.74111,0,0,.27778],8741:[.08167,.58167,0,0,.38889],8742:[.25142,.74111,0,0,.5],8756:[0,.69224,0,0,.66667],8757:[0,.69224,0,0,.66667],8764:[-.13313,.36687,0,0,.77778],8765:[-.13313,.37788,0,0,.77778],8769:[-.13313,.36687,0,0,.77778],8770:[-.03625,.46375,0,0,.77778],8774:[.30274,.79383,0,0,.77778],8776:[-.01688,.48312,0,0,.77778],8778:[.08167,.58167,0,0,.77778],8782:[.06062,.54986,0,0,.77778],8783:[.06062,.54986,0,0,.77778],8785:[.08198,.58198,0,0,.77778],8786:[.08198,.58198,0,0,.77778],8787:[.08198,.58198,0,0,.77778],8790:[0,.69224,0,0,.77778],8791:[.22958,.72958,0,0,.77778],8796:[.08198,.91667,0,0,.77778],8806:[.25583,.75583,0,0,.77778],8807:[.25583,.75583,0,0,.77778],8808:[.25142,.75726,0,0,.77778],8809:[.25142,.75726,0,0,.77778],8812:[.25583,.75583,0,0,.5],8814:[.20576,.70576,0,0,.77778],8815:[.20576,.70576,0,0,.77778],8816:[.30274,.79383,0,0,.77778],8817:[.30274,.79383,0,0,.77778],8818:[.22958,.72958,0,0,.77778],8819:[.22958,.72958,0,0,.77778],8822:[.1808,.675,0,0,.77778],8823:[.1808,.675,0,0,.77778],8828:[.13667,.63667,0,0,.77778],8829:[.13667,.63667,0,0,.77778],8830:[.22958,.72958,0,0,.77778],8831:[.22958,.72958,0,0,.77778],8832:[.20576,.70576,0,0,.77778],8833:[.20576,.70576,0,0,.77778],8840:[.30274,.79383,0,0,.77778],8841:[.30274,.79383,0,0,.77778],8842:[.13597,.63597,0,0,.77778],8843:[.13597,.63597,0,0,.77778],8847:[.03517,.54986,0,0,.77778],8848:[.03517,.54986,0,0,.77778],8858:[.08198,.58198,0,0,.77778],8859:[.08198,.58198,0,0,.77778],8861:[.08198,.58198,0,0,.77778],8862:[0,.675,0,0,.77778],8863:[0,.675,0,0,.77778],8864:[0,.675,0,0,.77778],8865:[0,.675,0,0,.77778],8872:[0,.69224,0,0,.61111],8873:[0,.69224,0,0,.72222],8874:[0,.69224,0,0,.88889],8876:[0,.68889,0,0,.61111],8877:[0,.68889,0,0,.61111],8878:[0,.68889,0,0,.72222],8879:[0,.68889,0,0,.72222],8882:[.03517,.54986,0,0,.77778],8883:[.03517,.54986,0,0,.77778],8884:[.13667,.63667,0,0,.77778],8885:[.13667,.63667,0,0,.77778],8888:[0,.54986,0,0,1.11111],8890:[.19444,.43056,0,0,.55556],8891:[.19444,.69224,0,0,.61111],8892:[.19444,.69224,0,0,.61111],8901:[0,.54986,0,0,.27778],8903:[.08167,.58167,0,0,.77778],8905:[.08167,.58167,0,0,.77778],8906:[.08167,.58167,0,0,.77778],8907:[0,.69224,0,0,.77778],8908:[0,.69224,0,0,.77778],8909:[-.03598,.46402,0,0,.77778],8910:[0,.54986,0,0,.76042],8911:[0,.54986,0,0,.76042],8912:[.03517,.54986,0,0,.77778],8913:[.03517,.54986,0,0,.77778],8914:[0,.54986,0,0,.66667],8915:[0,.54986,0,0,.66667],8916:[0,.69224,0,0,.66667],8918:[.0391,.5391,0,0,.77778],8919:[.0391,.5391,0,0,.77778],8920:[.03517,.54986,0,0,1.33334],8921:[.03517,.54986,0,0,1.33334],8922:[.38569,.88569,0,0,.77778],8923:[.38569,.88569,0,0,.77778],8926:[.13667,.63667,0,0,.77778],8927:[.13667,.63667,0,0,.77778],8928:[.30274,.79383,0,0,.77778],8929:[.30274,.79383,0,0,.77778],8934:[.23222,.74111,0,0,.77778],8935:[.23222,.74111,0,0,.77778],8936:[.23222,.74111,0,0,.77778],8937:[.23222,.74111,0,0,.77778],8938:[.20576,.70576,0,0,.77778],8939:[.20576,.70576,0,0,.77778],8940:[.30274,.79383,0,0,.77778],8941:[.30274,.79383,0,0,.77778],8994:[.19444,.69224,0,0,.77778],8995:[.19444,.69224,0,0,.77778],9416:[.15559,.69224,0,0,.90222],9484:[0,.69224,0,0,.5],9488:[0,.69224,0,0,.5],9492:[0,.37788,0,0,.5],9496:[0,.37788,0,0,.5],9585:[.19444,.68889,0,0,.88889],9586:[.19444,.74111,0,0,.88889],9632:[0,.675,0,0,.77778],9633:[0,.675,0,0,.77778],9650:[0,.54986,0,0,.72222],9651:[0,.54986,0,0,.72222],9654:[.03517,.54986,0,0,.77778],9660:[0,.54986,0,0,.72222],9661:[0,.54986,0,0,.72222],9664:[.03517,.54986,0,0,.77778],9674:[.11111,.69224,0,0,.66667],9733:[.19444,.69224,0,0,.94445],10003:[0,.69224,0,0,.83334],10016:[0,.69224,0,0,.83334],10731:[.11111,.69224,0,0,.66667],10846:[.19444,.75583,0,0,.61111],10877:[.13667,.63667,0,0,.77778],10878:[.13667,.63667,0,0,.77778],10885:[.25583,.75583,0,0,.77778],10886:[.25583,.75583,0,0,.77778],10887:[.13597,.63597,0,0,.77778],10888:[.13597,.63597,0,0,.77778],10889:[.26167,.75726,0,0,.77778],10890:[.26167,.75726,0,0,.77778],10891:[.48256,.98256,0,0,.77778],10892:[.48256,.98256,0,0,.77778],10901:[.13667,.63667,0,0,.77778],10902:[.13667,.63667,0,0,.77778],10933:[.25142,.75726,0,0,.77778],10934:[.25142,.75726,0,0,.77778],10935:[.26167,.75726,0,0,.77778],10936:[.26167,.75726,0,0,.77778],10937:[.26167,.75726,0,0,.77778],10938:[.26167,.75726,0,0,.77778],10949:[.25583,.75583,0,0,.77778],10950:[.25583,.75583,0,0,.77778],10955:[.28481,.79383,0,0,.77778],10956:[.28481,.79383,0,0,.77778],57350:[.08167,.58167,0,0,.22222],57351:[.08167,.58167,0,0,.38889],57352:[.08167,.58167,0,0,.77778],57353:[0,.43056,.04028,0,.66667],57356:[.25142,.75726,0,0,.77778],57357:[.25142,.75726,0,0,.77778],57358:[.41951,.91951,0,0,.77778],57359:[.30274,.79383,0,0,.77778],57360:[.30274,.79383,0,0,.77778],57361:[.41951,.91951,0,0,.77778],57366:[.25142,.75726,0,0,.77778],57367:[.25142,.75726,0,0,.77778],57368:[.25142,.75726,0,0,.77778],57369:[.25142,.75726,0,0,.77778],57370:[.13597,.63597,0,0,.77778],57371:[.13597,.63597,0,0,.77778]},"Caligraphic-Regular":{32:[0,0,0,0,.25],65:[0,.68333,0,.19445,.79847],66:[0,.68333,.03041,.13889,.65681],67:[0,.68333,.05834,.13889,.52653],68:[0,.68333,.02778,.08334,.77139],69:[0,.68333,.08944,.11111,.52778],70:[0,.68333,.09931,.11111,.71875],71:[.09722,.68333,.0593,.11111,.59487],72:[0,.68333,.00965,.11111,.84452],73:[0,.68333,.07382,0,.54452],74:[.09722,.68333,.18472,.16667,.67778],75:[0,.68333,.01445,.05556,.76195],76:[0,.68333,0,.13889,.68972],77:[0,.68333,0,.13889,1.2009],78:[0,.68333,.14736,.08334,.82049],79:[0,.68333,.02778,.11111,.79611],80:[0,.68333,.08222,.08334,.69556],81:[.09722,.68333,0,.11111,.81667],82:[0,.68333,0,.08334,.8475],83:[0,.68333,.075,.13889,.60556],84:[0,.68333,.25417,0,.54464],85:[0,.68333,.09931,.08334,.62583],86:[0,.68333,.08222,0,.61278],87:[0,.68333,.08222,.08334,.98778],88:[0,.68333,.14643,.13889,.7133],89:[.09722,.68333,.08222,.08334,.66834],90:[0,.68333,.07944,.13889,.72473],160:[0,0,0,0,.25]},"Fraktur-Regular":{32:[0,0,0,0,.25],33:[0,.69141,0,0,.29574],34:[0,.69141,0,0,.21471],38:[0,.69141,0,0,.73786],39:[0,.69141,0,0,.21201],40:[.24982,.74947,0,0,.38865],41:[.24982,.74947,0,0,.38865],42:[0,.62119,0,0,.27764],43:[.08319,.58283,0,0,.75623],44:[0,.10803,0,0,.27764],45:[.08319,.58283,0,0,.75623],46:[0,.10803,0,0,.27764],47:[.24982,.74947,0,0,.50181],48:[0,.47534,0,0,.50181],49:[0,.47534,0,0,.50181],50:[0,.47534,0,0,.50181],51:[.18906,.47534,0,0,.50181],52:[.18906,.47534,0,0,.50181],53:[.18906,.47534,0,0,.50181],54:[0,.69141,0,0,.50181],55:[.18906,.47534,0,0,.50181],56:[0,.69141,0,0,.50181],57:[.18906,.47534,0,0,.50181],58:[0,.47534,0,0,.21606],59:[.12604,.47534,0,0,.21606],61:[-.13099,.36866,0,0,.75623],63:[0,.69141,0,0,.36245],65:[0,.69141,0,0,.7176],66:[0,.69141,0,0,.88397],67:[0,.69141,0,0,.61254],68:[0,.69141,0,0,.83158],69:[0,.69141,0,0,.66278],70:[.12604,.69141,0,0,.61119],71:[0,.69141,0,0,.78539],72:[.06302,.69141,0,0,.7203],73:[0,.69141,0,0,.55448],74:[.12604,.69141,0,0,.55231],75:[0,.69141,0,0,.66845],76:[0,.69141,0,0,.66602],77:[0,.69141,0,0,1.04953],78:[0,.69141,0,0,.83212],79:[0,.69141,0,0,.82699],80:[.18906,.69141,0,0,.82753],81:[.03781,.69141,0,0,.82699],82:[0,.69141,0,0,.82807],83:[0,.69141,0,0,.82861],84:[0,.69141,0,0,.66899],85:[0,.69141,0,0,.64576],86:[0,.69141,0,0,.83131],87:[0,.69141,0,0,1.04602],88:[0,.69141,0,0,.71922],89:[.18906,.69141,0,0,.83293],90:[.12604,.69141,0,0,.60201],91:[.24982,.74947,0,0,.27764],93:[.24982,.74947,0,0,.27764],94:[0,.69141,0,0,.49965],97:[0,.47534,0,0,.50046],98:[0,.69141,0,0,.51315],99:[0,.47534,0,0,.38946],100:[0,.62119,0,0,.49857],101:[0,.47534,0,0,.40053],102:[.18906,.69141,0,0,.32626],103:[.18906,.47534,0,0,.5037],104:[.18906,.69141,0,0,.52126],105:[0,.69141,0,0,.27899],106:[0,.69141,0,0,.28088],107:[0,.69141,0,0,.38946],108:[0,.69141,0,0,.27953],109:[0,.47534,0,0,.76676],110:[0,.47534,0,0,.52666],111:[0,.47534,0,0,.48885],112:[.18906,.52396,0,0,.50046],113:[.18906,.47534,0,0,.48912],114:[0,.47534,0,0,.38919],115:[0,.47534,0,0,.44266],116:[0,.62119,0,0,.33301],117:[0,.47534,0,0,.5172],118:[0,.52396,0,0,.5118],119:[0,.52396,0,0,.77351],120:[.18906,.47534,0,0,.38865],121:[.18906,.47534,0,0,.49884],122:[.18906,.47534,0,0,.39054],160:[0,0,0,0,.25],8216:[0,.69141,0,0,.21471],8217:[0,.69141,0,0,.21471],58112:[0,.62119,0,0,.49749],58113:[0,.62119,0,0,.4983],58114:[.18906,.69141,0,0,.33328],58115:[.18906,.69141,0,0,.32923],58116:[.18906,.47534,0,0,.50343],58117:[0,.69141,0,0,.33301],58118:[0,.62119,0,0,.33409],58119:[0,.47534,0,0,.50073]},"Main-Bold":{32:[0,0,0,0,.25],33:[0,.69444,0,0,.35],34:[0,.69444,0,0,.60278],35:[.19444,.69444,0,0,.95833],36:[.05556,.75,0,0,.575],37:[.05556,.75,0,0,.95833],38:[0,.69444,0,0,.89444],39:[0,.69444,0,0,.31944],40:[.25,.75,0,0,.44722],41:[.25,.75,0,0,.44722],42:[0,.75,0,0,.575],43:[.13333,.63333,0,0,.89444],44:[.19444,.15556,0,0,.31944],45:[0,.44444,0,0,.38333],46:[0,.15556,0,0,.31944],47:[.25,.75,0,0,.575],48:[0,.64444,0,0,.575],49:[0,.64444,0,0,.575],50:[0,.64444,0,0,.575],51:[0,.64444,0,0,.575],52:[0,.64444,0,0,.575],53:[0,.64444,0,0,.575],54:[0,.64444,0,0,.575],55:[0,.64444,0,0,.575],56:[0,.64444,0,0,.575],57:[0,.64444,0,0,.575],58:[0,.44444,0,0,.31944],59:[.19444,.44444,0,0,.31944],60:[.08556,.58556,0,0,.89444],61:[-.10889,.39111,0,0,.89444],62:[.08556,.58556,0,0,.89444],63:[0,.69444,0,0,.54305],64:[0,.69444,0,0,.89444],65:[0,.68611,0,0,.86944],66:[0,.68611,0,0,.81805],67:[0,.68611,0,0,.83055],68:[0,.68611,0,0,.88194],69:[0,.68611,0,0,.75555],70:[0,.68611,0,0,.72361],71:[0,.68611,0,0,.90416],72:[0,.68611,0,0,.9],73:[0,.68611,0,0,.43611],74:[0,.68611,0,0,.59444],75:[0,.68611,0,0,.90138],76:[0,.68611,0,0,.69166],77:[0,.68611,0,0,1.09166],78:[0,.68611,0,0,.9],79:[0,.68611,0,0,.86388],80:[0,.68611,0,0,.78611],81:[.19444,.68611,0,0,.86388],82:[0,.68611,0,0,.8625],83:[0,.68611,0,0,.63889],84:[0,.68611,0,0,.8],85:[0,.68611,0,0,.88472],86:[0,.68611,.01597,0,.86944],87:[0,.68611,.01597,0,1.18888],88:[0,.68611,0,0,.86944],89:[0,.68611,.02875,0,.86944],90:[0,.68611,0,0,.70277],91:[.25,.75,0,0,.31944],92:[.25,.75,0,0,.575],93:[.25,.75,0,0,.31944],94:[0,.69444,0,0,.575],95:[.31,.13444,.03194,0,.575],97:[0,.44444,0,0,.55902],98:[0,.69444,0,0,.63889],99:[0,.44444,0,0,.51111],100:[0,.69444,0,0,.63889],101:[0,.44444,0,0,.52708],102:[0,.69444,.10903,0,.35139],103:[.19444,.44444,.01597,0,.575],104:[0,.69444,0,0,.63889],105:[0,.69444,0,0,.31944],106:[.19444,.69444,0,0,.35139],107:[0,.69444,0,0,.60694],108:[0,.69444,0,0,.31944],109:[0,.44444,0,0,.95833],110:[0,.44444,0,0,.63889],111:[0,.44444,0,0,.575],112:[.19444,.44444,0,0,.63889],113:[.19444,.44444,0,0,.60694],114:[0,.44444,0,0,.47361],115:[0,.44444,0,0,.45361],116:[0,.63492,0,0,.44722],117:[0,.44444,0,0,.63889],118:[0,.44444,.01597,0,.60694],119:[0,.44444,.01597,0,.83055],120:[0,.44444,0,0,.60694],121:[.19444,.44444,.01597,0,.60694],122:[0,.44444,0,0,.51111],123:[.25,.75,0,0,.575],124:[.25,.75,0,0,.31944],125:[.25,.75,0,0,.575],126:[.35,.34444,0,0,.575],160:[0,0,0,0,.25],163:[0,.69444,0,0,.86853],168:[0,.69444,0,0,.575],172:[0,.44444,0,0,.76666],176:[0,.69444,0,0,.86944],177:[.13333,.63333,0,0,.89444],184:[.17014,0,0,0,.51111],198:[0,.68611,0,0,1.04166],215:[.13333,.63333,0,0,.89444],216:[.04861,.73472,0,0,.89444],223:[0,.69444,0,0,.59722],230:[0,.44444,0,0,.83055],247:[.13333,.63333,0,0,.89444],248:[.09722,.54167,0,0,.575],305:[0,.44444,0,0,.31944],338:[0,.68611,0,0,1.16944],339:[0,.44444,0,0,.89444],567:[.19444,.44444,0,0,.35139],710:[0,.69444,0,0,.575],711:[0,.63194,0,0,.575],713:[0,.59611,0,0,.575],714:[0,.69444,0,0,.575],715:[0,.69444,0,0,.575],728:[0,.69444,0,0,.575],729:[0,.69444,0,0,.31944],730:[0,.69444,0,0,.86944],732:[0,.69444,0,0,.575],733:[0,.69444,0,0,.575],915:[0,.68611,0,0,.69166],916:[0,.68611,0,0,.95833],920:[0,.68611,0,0,.89444],923:[0,.68611,0,0,.80555],926:[0,.68611,0,0,.76666],928:[0,.68611,0,0,.9],931:[0,.68611,0,0,.83055],933:[0,.68611,0,0,.89444],934:[0,.68611,0,0,.83055],936:[0,.68611,0,0,.89444],937:[0,.68611,0,0,.83055],8211:[0,.44444,.03194,0,.575],8212:[0,.44444,.03194,0,1.14999],8216:[0,.69444,0,0,.31944],8217:[0,.69444,0,0,.31944],8220:[0,.69444,0,0,.60278],8221:[0,.69444,0,0,.60278],8224:[.19444,.69444,0,0,.51111],8225:[.19444,.69444,0,0,.51111],8242:[0,.55556,0,0,.34444],8407:[0,.72444,.15486,0,.575],8463:[0,.69444,0,0,.66759],8465:[0,.69444,0,0,.83055],8467:[0,.69444,0,0,.47361],8472:[.19444,.44444,0,0,.74027],8476:[0,.69444,0,0,.83055],8501:[0,.69444,0,0,.70277],8592:[-.10889,.39111,0,0,1.14999],8593:[.19444,.69444,0,0,.575],8594:[-.10889,.39111,0,0,1.14999],8595:[.19444,.69444,0,0,.575],8596:[-.10889,.39111,0,0,1.14999],8597:[.25,.75,0,0,.575],8598:[.19444,.69444,0,0,1.14999],8599:[.19444,.69444,0,0,1.14999],8600:[.19444,.69444,0,0,1.14999],8601:[.19444,.69444,0,0,1.14999],8636:[-.10889,.39111,0,0,1.14999],8637:[-.10889,.39111,0,0,1.14999],8640:[-.10889,.39111,0,0,1.14999],8641:[-.10889,.39111,0,0,1.14999],8656:[-.10889,.39111,0,0,1.14999],8657:[.19444,.69444,0,0,.70277],8658:[-.10889,.39111,0,0,1.14999],8659:[.19444,.69444,0,0,.70277],8660:[-.10889,.39111,0,0,1.14999],8661:[.25,.75,0,0,.70277],8704:[0,.69444,0,0,.63889],8706:[0,.69444,.06389,0,.62847],8707:[0,.69444,0,0,.63889],8709:[.05556,.75,0,0,.575],8711:[0,.68611,0,0,.95833],8712:[.08556,.58556,0,0,.76666],8715:[.08556,.58556,0,0,.76666],8722:[.13333,.63333,0,0,.89444],8723:[.13333,.63333,0,0,.89444],8725:[.25,.75,0,0,.575],8726:[.25,.75,0,0,.575],8727:[-.02778,.47222,0,0,.575],8728:[-.02639,.47361,0,0,.575],8729:[-.02639,.47361,0,0,.575],8730:[.18,.82,0,0,.95833],8733:[0,.44444,0,0,.89444],8734:[0,.44444,0,0,1.14999],8736:[0,.69224,0,0,.72222],8739:[.25,.75,0,0,.31944],8741:[.25,.75,0,0,.575],8743:[0,.55556,0,0,.76666],8744:[0,.55556,0,0,.76666],8745:[0,.55556,0,0,.76666],8746:[0,.55556,0,0,.76666],8747:[.19444,.69444,.12778,0,.56875],8764:[-.10889,.39111,0,0,.89444],8768:[.19444,.69444,0,0,.31944],8771:[.00222,.50222,0,0,.89444],8773:[.027,.638,0,0,.894],8776:[.02444,.52444,0,0,.89444],8781:[.00222,.50222,0,0,.89444],8801:[.00222,.50222,0,0,.89444],8804:[.19667,.69667,0,0,.89444],8805:[.19667,.69667,0,0,.89444],8810:[.08556,.58556,0,0,1.14999],8811:[.08556,.58556,0,0,1.14999],8826:[.08556,.58556,0,0,.89444],8827:[.08556,.58556,0,0,.89444],8834:[.08556,.58556,0,0,.89444],8835:[.08556,.58556,0,0,.89444],8838:[.19667,.69667,0,0,.89444],8839:[.19667,.69667,0,0,.89444],8846:[0,.55556,0,0,.76666],8849:[.19667,.69667,0,0,.89444],8850:[.19667,.69667,0,0,.89444],8851:[0,.55556,0,0,.76666],8852:[0,.55556,0,0,.76666],8853:[.13333,.63333,0,0,.89444],8854:[.13333,.63333,0,0,.89444],8855:[.13333,.63333,0,0,.89444],8856:[.13333,.63333,0,0,.89444],8857:[.13333,.63333,0,0,.89444],8866:[0,.69444,0,0,.70277],8867:[0,.69444,0,0,.70277],8868:[0,.69444,0,0,.89444],8869:[0,.69444,0,0,.89444],8900:[-.02639,.47361,0,0,.575],8901:[-.02639,.47361,0,0,.31944],8902:[-.02778,.47222,0,0,.575],8968:[.25,.75,0,0,.51111],8969:[.25,.75,0,0,.51111],8970:[.25,.75,0,0,.51111],8971:[.25,.75,0,0,.51111],8994:[-.13889,.36111,0,0,1.14999],8995:[-.13889,.36111,0,0,1.14999],9651:[.19444,.69444,0,0,1.02222],9657:[-.02778,.47222,0,0,.575],9661:[.19444,.69444,0,0,1.02222],9667:[-.02778,.47222,0,0,.575],9711:[.19444,.69444,0,0,1.14999],9824:[.12963,.69444,0,0,.89444],9825:[.12963,.69444,0,0,.89444],9826:[.12963,.69444,0,0,.89444],9827:[.12963,.69444,0,0,.89444],9837:[0,.75,0,0,.44722],9838:[.19444,.69444,0,0,.44722],9839:[.19444,.69444,0,0,.44722],10216:[.25,.75,0,0,.44722],10217:[.25,.75,0,0,.44722],10815:[0,.68611,0,0,.9],10927:[.19667,.69667,0,0,.89444],10928:[.19667,.69667,0,0,.89444],57376:[.19444,.69444,0,0,0]},"Main-BoldItalic":{32:[0,0,0,0,.25],33:[0,.69444,.11417,0,.38611],34:[0,.69444,.07939,0,.62055],35:[.19444,.69444,.06833,0,.94444],37:[.05556,.75,.12861,0,.94444],38:[0,.69444,.08528,0,.88555],39:[0,.69444,.12945,0,.35555],40:[.25,.75,.15806,0,.47333],41:[.25,.75,.03306,0,.47333],42:[0,.75,.14333,0,.59111],43:[.10333,.60333,.03306,0,.88555],44:[.19444,.14722,0,0,.35555],45:[0,.44444,.02611,0,.41444],46:[0,.14722,0,0,.35555],47:[.25,.75,.15806,0,.59111],48:[0,.64444,.13167,0,.59111],49:[0,.64444,.13167,0,.59111],50:[0,.64444,.13167,0,.59111],51:[0,.64444,.13167,0,.59111],52:[.19444,.64444,.13167,0,.59111],53:[0,.64444,.13167,0,.59111],54:[0,.64444,.13167,0,.59111],55:[.19444,.64444,.13167,0,.59111],56:[0,.64444,.13167,0,.59111],57:[0,.64444,.13167,0,.59111],58:[0,.44444,.06695,0,.35555],59:[.19444,.44444,.06695,0,.35555],61:[-.10889,.39111,.06833,0,.88555],63:[0,.69444,.11472,0,.59111],64:[0,.69444,.09208,0,.88555],65:[0,.68611,0,0,.86555],66:[0,.68611,.0992,0,.81666],67:[0,.68611,.14208,0,.82666],68:[0,.68611,.09062,0,.87555],69:[0,.68611,.11431,0,.75666],70:[0,.68611,.12903,0,.72722],71:[0,.68611,.07347,0,.89527],72:[0,.68611,.17208,0,.8961],73:[0,.68611,.15681,0,.47166],74:[0,.68611,.145,0,.61055],75:[0,.68611,.14208,0,.89499],76:[0,.68611,0,0,.69777],77:[0,.68611,.17208,0,1.07277],78:[0,.68611,.17208,0,.8961],79:[0,.68611,.09062,0,.85499],80:[0,.68611,.0992,0,.78721],81:[.19444,.68611,.09062,0,.85499],82:[0,.68611,.02559,0,.85944],83:[0,.68611,.11264,0,.64999],84:[0,.68611,.12903,0,.7961],85:[0,.68611,.17208,0,.88083],86:[0,.68611,.18625,0,.86555],87:[0,.68611,.18625,0,1.15999],88:[0,.68611,.15681,0,.86555],89:[0,.68611,.19803,0,.86555],90:[0,.68611,.14208,0,.70888],91:[.25,.75,.1875,0,.35611],93:[.25,.75,.09972,0,.35611],94:[0,.69444,.06709,0,.59111],95:[.31,.13444,.09811,0,.59111],97:[0,.44444,.09426,0,.59111],98:[0,.69444,.07861,0,.53222],99:[0,.44444,.05222,0,.53222],100:[0,.69444,.10861,0,.59111],101:[0,.44444,.085,0,.53222],102:[.19444,.69444,.21778,0,.4],103:[.19444,.44444,.105,0,.53222],104:[0,.69444,.09426,0,.59111],105:[0,.69326,.11387,0,.35555],106:[.19444,.69326,.1672,0,.35555],107:[0,.69444,.11111,0,.53222],108:[0,.69444,.10861,0,.29666],109:[0,.44444,.09426,0,.94444],110:[0,.44444,.09426,0,.64999],111:[0,.44444,.07861,0,.59111],112:[.19444,.44444,.07861,0,.59111],113:[.19444,.44444,.105,0,.53222],114:[0,.44444,.11111,0,.50167],115:[0,.44444,.08167,0,.48694],116:[0,.63492,.09639,0,.385],117:[0,.44444,.09426,0,.62055],118:[0,.44444,.11111,0,.53222],119:[0,.44444,.11111,0,.76777],120:[0,.44444,.12583,0,.56055],121:[.19444,.44444,.105,0,.56166],122:[0,.44444,.13889,0,.49055],126:[.35,.34444,.11472,0,.59111],160:[0,0,0,0,.25],168:[0,.69444,.11473,0,.59111],176:[0,.69444,0,0,.94888],184:[.17014,0,0,0,.53222],198:[0,.68611,.11431,0,1.02277],216:[.04861,.73472,.09062,0,.88555],223:[.19444,.69444,.09736,0,.665],230:[0,.44444,.085,0,.82666],248:[.09722,.54167,.09458,0,.59111],305:[0,.44444,.09426,0,.35555],338:[0,.68611,.11431,0,1.14054],339:[0,.44444,.085,0,.82666],567:[.19444,.44444,.04611,0,.385],710:[0,.69444,.06709,0,.59111],711:[0,.63194,.08271,0,.59111],713:[0,.59444,.10444,0,.59111],714:[0,.69444,.08528,0,.59111],715:[0,.69444,0,0,.59111],728:[0,.69444,.10333,0,.59111],729:[0,.69444,.12945,0,.35555],730:[0,.69444,0,0,.94888],732:[0,.69444,.11472,0,.59111],733:[0,.69444,.11472,0,.59111],915:[0,.68611,.12903,0,.69777],916:[0,.68611,0,0,.94444],920:[0,.68611,.09062,0,.88555],923:[0,.68611,0,0,.80666],926:[0,.68611,.15092,0,.76777],928:[0,.68611,.17208,0,.8961],931:[0,.68611,.11431,0,.82666],933:[0,.68611,.10778,0,.88555],934:[0,.68611,.05632,0,.82666],936:[0,.68611,.10778,0,.88555],937:[0,.68611,.0992,0,.82666],8211:[0,.44444,.09811,0,.59111],8212:[0,.44444,.09811,0,1.18221],8216:[0,.69444,.12945,0,.35555],8217:[0,.69444,.12945,0,.35555],8220:[0,.69444,.16772,0,.62055],8221:[0,.69444,.07939,0,.62055]},"Main-Italic":{32:[0,0,0,0,.25],33:[0,.69444,.12417,0,.30667],34:[0,.69444,.06961,0,.51444],35:[.19444,.69444,.06616,0,.81777],37:[.05556,.75,.13639,0,.81777],38:[0,.69444,.09694,0,.76666],39:[0,.69444,.12417,0,.30667],40:[.25,.75,.16194,0,.40889],41:[.25,.75,.03694,0,.40889],42:[0,.75,.14917,0,.51111],43:[.05667,.56167,.03694,0,.76666],44:[.19444,.10556,0,0,.30667],45:[0,.43056,.02826,0,.35778],46:[0,.10556,0,0,.30667],47:[.25,.75,.16194,0,.51111],48:[0,.64444,.13556,0,.51111],49:[0,.64444,.13556,0,.51111],50:[0,.64444,.13556,0,.51111],51:[0,.64444,.13556,0,.51111],52:[.19444,.64444,.13556,0,.51111],53:[0,.64444,.13556,0,.51111],54:[0,.64444,.13556,0,.51111],55:[.19444,.64444,.13556,0,.51111],56:[0,.64444,.13556,0,.51111],57:[0,.64444,.13556,0,.51111],58:[0,.43056,.0582,0,.30667],59:[.19444,.43056,.0582,0,.30667],61:[-.13313,.36687,.06616,0,.76666],63:[0,.69444,.1225,0,.51111],64:[0,.69444,.09597,0,.76666],65:[0,.68333,0,0,.74333],66:[0,.68333,.10257,0,.70389],67:[0,.68333,.14528,0,.71555],68:[0,.68333,.09403,0,.755],69:[0,.68333,.12028,0,.67833],70:[0,.68333,.13305,0,.65277],71:[0,.68333,.08722,0,.77361],72:[0,.68333,.16389,0,.74333],73:[0,.68333,.15806,0,.38555],74:[0,.68333,.14028,0,.525],75:[0,.68333,.14528,0,.76888],76:[0,.68333,0,0,.62722],77:[0,.68333,.16389,0,.89666],78:[0,.68333,.16389,0,.74333],79:[0,.68333,.09403,0,.76666],80:[0,.68333,.10257,0,.67833],81:[.19444,.68333,.09403,0,.76666],82:[0,.68333,.03868,0,.72944],83:[0,.68333,.11972,0,.56222],84:[0,.68333,.13305,0,.71555],85:[0,.68333,.16389,0,.74333],86:[0,.68333,.18361,0,.74333],87:[0,.68333,.18361,0,.99888],88:[0,.68333,.15806,0,.74333],89:[0,.68333,.19383,0,.74333],90:[0,.68333,.14528,0,.61333],91:[.25,.75,.1875,0,.30667],93:[.25,.75,.10528,0,.30667],94:[0,.69444,.06646,0,.51111],95:[.31,.12056,.09208,0,.51111],97:[0,.43056,.07671,0,.51111],98:[0,.69444,.06312,0,.46],99:[0,.43056,.05653,0,.46],100:[0,.69444,.10333,0,.51111],101:[0,.43056,.07514,0,.46],102:[.19444,.69444,.21194,0,.30667],103:[.19444,.43056,.08847,0,.46],104:[0,.69444,.07671,0,.51111],105:[0,.65536,.1019,0,.30667],106:[.19444,.65536,.14467,0,.30667],107:[0,.69444,.10764,0,.46],108:[0,.69444,.10333,0,.25555],109:[0,.43056,.07671,0,.81777],110:[0,.43056,.07671,0,.56222],111:[0,.43056,.06312,0,.51111],112:[.19444,.43056,.06312,0,.51111],113:[.19444,.43056,.08847,0,.46],114:[0,.43056,.10764,0,.42166],115:[0,.43056,.08208,0,.40889],116:[0,.61508,.09486,0,.33222],117:[0,.43056,.07671,0,.53666],118:[0,.43056,.10764,0,.46],119:[0,.43056,.10764,0,.66444],120:[0,.43056,.12042,0,.46389],121:[.19444,.43056,.08847,0,.48555],122:[0,.43056,.12292,0,.40889],126:[.35,.31786,.11585,0,.51111],160:[0,0,0,0,.25],168:[0,.66786,.10474,0,.51111],176:[0,.69444,0,0,.83129],184:[.17014,0,0,0,.46],198:[0,.68333,.12028,0,.88277],216:[.04861,.73194,.09403,0,.76666],223:[.19444,.69444,.10514,0,.53666],230:[0,.43056,.07514,0,.71555],248:[.09722,.52778,.09194,0,.51111],338:[0,.68333,.12028,0,.98499],339:[0,.43056,.07514,0,.71555],710:[0,.69444,.06646,0,.51111],711:[0,.62847,.08295,0,.51111],713:[0,.56167,.10333,0,.51111],714:[0,.69444,.09694,0,.51111],715:[0,.69444,0,0,.51111],728:[0,.69444,.10806,0,.51111],729:[0,.66786,.11752,0,.30667],730:[0,.69444,0,0,.83129],732:[0,.66786,.11585,0,.51111],733:[0,.69444,.1225,0,.51111],915:[0,.68333,.13305,0,.62722],916:[0,.68333,0,0,.81777],920:[0,.68333,.09403,0,.76666],923:[0,.68333,0,0,.69222],926:[0,.68333,.15294,0,.66444],928:[0,.68333,.16389,0,.74333],931:[0,.68333,.12028,0,.71555],933:[0,.68333,.11111,0,.76666],934:[0,.68333,.05986,0,.71555],936:[0,.68333,.11111,0,.76666],937:[0,.68333,.10257,0,.71555],8211:[0,.43056,.09208,0,.51111],8212:[0,.43056,.09208,0,1.02222],8216:[0,.69444,.12417,0,.30667],8217:[0,.69444,.12417,0,.30667],8220:[0,.69444,.1685,0,.51444],8221:[0,.69444,.06961,0,.51444],8463:[0,.68889,0,0,.54028]},"Main-Regular":{32:[0,0,0,0,.25],33:[0,.69444,0,0,.27778],34:[0,.69444,0,0,.5],35:[.19444,.69444,0,0,.83334],36:[.05556,.75,0,0,.5],37:[.05556,.75,0,0,.83334],38:[0,.69444,0,0,.77778],39:[0,.69444,0,0,.27778],40:[.25,.75,0,0,.38889],41:[.25,.75,0,0,.38889],42:[0,.75,0,0,.5],43:[.08333,.58333,0,0,.77778],44:[.19444,.10556,0,0,.27778],45:[0,.43056,0,0,.33333],46:[0,.10556,0,0,.27778],47:[.25,.75,0,0,.5],48:[0,.64444,0,0,.5],49:[0,.64444,0,0,.5],50:[0,.64444,0,0,.5],51:[0,.64444,0,0,.5],52:[0,.64444,0,0,.5],53:[0,.64444,0,0,.5],54:[0,.64444,0,0,.5],55:[0,.64444,0,0,.5],56:[0,.64444,0,0,.5],57:[0,.64444,0,0,.5],58:[0,.43056,0,0,.27778],59:[.19444,.43056,0,0,.27778],60:[.0391,.5391,0,0,.77778],61:[-.13313,.36687,0,0,.77778],62:[.0391,.5391,0,0,.77778],63:[0,.69444,0,0,.47222],64:[0,.69444,0,0,.77778],65:[0,.68333,0,0,.75],66:[0,.68333,0,0,.70834],67:[0,.68333,0,0,.72222],68:[0,.68333,0,0,.76389],69:[0,.68333,0,0,.68056],70:[0,.68333,0,0,.65278],71:[0,.68333,0,0,.78472],72:[0,.68333,0,0,.75],73:[0,.68333,0,0,.36111],74:[0,.68333,0,0,.51389],75:[0,.68333,0,0,.77778],76:[0,.68333,0,0,.625],77:[0,.68333,0,0,.91667],78:[0,.68333,0,0,.75],79:[0,.68333,0,0,.77778],80:[0,.68333,0,0,.68056],81:[.19444,.68333,0,0,.77778],82:[0,.68333,0,0,.73611],83:[0,.68333,0,0,.55556],84:[0,.68333,0,0,.72222],85:[0,.68333,0,0,.75],86:[0,.68333,.01389,0,.75],87:[0,.68333,.01389,0,1.02778],88:[0,.68333,0,0,.75],89:[0,.68333,.025,0,.75],90:[0,.68333,0,0,.61111],91:[.25,.75,0,0,.27778],92:[.25,.75,0,0,.5],93:[.25,.75,0,0,.27778],94:[0,.69444,0,0,.5],95:[.31,.12056,.02778,0,.5],97:[0,.43056,0,0,.5],98:[0,.69444,0,0,.55556],99:[0,.43056,0,0,.44445],100:[0,.69444,0,0,.55556],101:[0,.43056,0,0,.44445],102:[0,.69444,.07778,0,.30556],103:[.19444,.43056,.01389,0,.5],104:[0,.69444,0,0,.55556],105:[0,.66786,0,0,.27778],106:[.19444,.66786,0,0,.30556],107:[0,.69444,0,0,.52778],108:[0,.69444,0,0,.27778],109:[0,.43056,0,0,.83334],110:[0,.43056,0,0,.55556],111:[0,.43056,0,0,.5],112:[.19444,.43056,0,0,.55556],113:[.19444,.43056,0,0,.52778],114:[0,.43056,0,0,.39167],115:[0,.43056,0,0,.39445],116:[0,.61508,0,0,.38889],117:[0,.43056,0,0,.55556],118:[0,.43056,.01389,0,.52778],119:[0,.43056,.01389,0,.72222],120:[0,.43056,0,0,.52778],121:[.19444,.43056,.01389,0,.52778],122:[0,.43056,0,0,.44445],123:[.25,.75,0,0,.5],124:[.25,.75,0,0,.27778],125:[.25,.75,0,0,.5],126:[.35,.31786,0,0,.5],160:[0,0,0,0,.25],163:[0,.69444,0,0,.76909],167:[.19444,.69444,0,0,.44445],168:[0,.66786,0,0,.5],172:[0,.43056,0,0,.66667],176:[0,.69444,0,0,.75],177:[.08333,.58333,0,0,.77778],182:[.19444,.69444,0,0,.61111],184:[.17014,0,0,0,.44445],198:[0,.68333,0,0,.90278],215:[.08333,.58333,0,0,.77778],216:[.04861,.73194,0,0,.77778],223:[0,.69444,0,0,.5],230:[0,.43056,0,0,.72222],247:[.08333,.58333,0,0,.77778],248:[.09722,.52778,0,0,.5],305:[0,.43056,0,0,.27778],338:[0,.68333,0,0,1.01389],339:[0,.43056,0,0,.77778],567:[.19444,.43056,0,0,.30556],710:[0,.69444,0,0,.5],711:[0,.62847,0,0,.5],713:[0,.56778,0,0,.5],714:[0,.69444,0,0,.5],715:[0,.69444,0,0,.5],728:[0,.69444,0,0,.5],729:[0,.66786,0,0,.27778],730:[0,.69444,0,0,.75],732:[0,.66786,0,0,.5],733:[0,.69444,0,0,.5],915:[0,.68333,0,0,.625],916:[0,.68333,0,0,.83334],920:[0,.68333,0,0,.77778],923:[0,.68333,0,0,.69445],926:[0,.68333,0,0,.66667],928:[0,.68333,0,0,.75],931:[0,.68333,0,0,.72222],933:[0,.68333,0,0,.77778],934:[0,.68333,0,0,.72222],936:[0,.68333,0,0,.77778],937:[0,.68333,0,0,.72222],8211:[0,.43056,.02778,0,.5],8212:[0,.43056,.02778,0,1],8216:[0,.69444,0,0,.27778],8217:[0,.69444,0,0,.27778],8220:[0,.69444,0,0,.5],8221:[0,.69444,0,0,.5],8224:[.19444,.69444,0,0,.44445],8225:[.19444,.69444,0,0,.44445],8230:[0,.123,0,0,1.172],8242:[0,.55556,0,0,.275],8407:[0,.71444,.15382,0,.5],8463:[0,.68889,0,0,.54028],8465:[0,.69444,0,0,.72222],8467:[0,.69444,0,.11111,.41667],8472:[.19444,.43056,0,.11111,.63646],8476:[0,.69444,0,0,.72222],8501:[0,.69444,0,0,.61111],8592:[-.13313,.36687,0,0,1],8593:[.19444,.69444,0,0,.5],8594:[-.13313,.36687,0,0,1],8595:[.19444,.69444,0,0,.5],8596:[-.13313,.36687,0,0,1],8597:[.25,.75,0,0,.5],8598:[.19444,.69444,0,0,1],8599:[.19444,.69444,0,0,1],8600:[.19444,.69444,0,0,1],8601:[.19444,.69444,0,0,1],8614:[.011,.511,0,0,1],8617:[.011,.511,0,0,1.126],8618:[.011,.511,0,0,1.126],8636:[-.13313,.36687,0,0,1],8637:[-.13313,.36687,0,0,1],8640:[-.13313,.36687,0,0,1],8641:[-.13313,.36687,0,0,1],8652:[.011,.671,0,0,1],8656:[-.13313,.36687,0,0,1],8657:[.19444,.69444,0,0,.61111],8658:[-.13313,.36687,0,0,1],8659:[.19444,.69444,0,0,.61111],8660:[-.13313,.36687,0,0,1],8661:[.25,.75,0,0,.61111],8704:[0,.69444,0,0,.55556],8706:[0,.69444,.05556,.08334,.5309],8707:[0,.69444,0,0,.55556],8709:[.05556,.75,0,0,.5],8711:[0,.68333,0,0,.83334],8712:[.0391,.5391,0,0,.66667],8715:[.0391,.5391,0,0,.66667],8722:[.08333,.58333,0,0,.77778],8723:[.08333,.58333,0,0,.77778],8725:[.25,.75,0,0,.5],8726:[.25,.75,0,0,.5],8727:[-.03472,.46528,0,0,.5],8728:[-.05555,.44445,0,0,.5],8729:[-.05555,.44445,0,0,.5],8730:[.2,.8,0,0,.83334],8733:[0,.43056,0,0,.77778],8734:[0,.43056,0,0,1],8736:[0,.69224,0,0,.72222],8739:[.25,.75,0,0,.27778],8741:[.25,.75,0,0,.5],8743:[0,.55556,0,0,.66667],8744:[0,.55556,0,0,.66667],8745:[0,.55556,0,0,.66667],8746:[0,.55556,0,0,.66667],8747:[.19444,.69444,.11111,0,.41667],8764:[-.13313,.36687,0,0,.77778],8768:[.19444,.69444,0,0,.27778],8771:[-.03625,.46375,0,0,.77778],8773:[-.022,.589,0,0,.778],8776:[-.01688,.48312,0,0,.77778],8781:[-.03625,.46375,0,0,.77778],8784:[-.133,.673,0,0,.778],8801:[-.03625,.46375,0,0,.77778],8804:[.13597,.63597,0,0,.77778],8805:[.13597,.63597,0,0,.77778],8810:[.0391,.5391,0,0,1],8811:[.0391,.5391,0,0,1],8826:[.0391,.5391,0,0,.77778],8827:[.0391,.5391,0,0,.77778],8834:[.0391,.5391,0,0,.77778],8835:[.0391,.5391,0,0,.77778],8838:[.13597,.63597,0,0,.77778],8839:[.13597,.63597,0,0,.77778],8846:[0,.55556,0,0,.66667],8849:[.13597,.63597,0,0,.77778],8850:[.13597,.63597,0,0,.77778],8851:[0,.55556,0,0,.66667],8852:[0,.55556,0,0,.66667],8853:[.08333,.58333,0,0,.77778],8854:[.08333,.58333,0,0,.77778],8855:[.08333,.58333,0,0,.77778],8856:[.08333,.58333,0,0,.77778],8857:[.08333,.58333,0,0,.77778],8866:[0,.69444,0,0,.61111],8867:[0,.69444,0,0,.61111],8868:[0,.69444,0,0,.77778],8869:[0,.69444,0,0,.77778],8872:[.249,.75,0,0,.867],8900:[-.05555,.44445,0,0,.5],8901:[-.05555,.44445,0,0,.27778],8902:[-.03472,.46528,0,0,.5],8904:[.005,.505,0,0,.9],8942:[.03,.903,0,0,.278],8943:[-.19,.313,0,0,1.172],8945:[-.1,.823,0,0,1.282],8968:[.25,.75,0,0,.44445],8969:[.25,.75,0,0,.44445],8970:[.25,.75,0,0,.44445],8971:[.25,.75,0,0,.44445],8994:[-.14236,.35764,0,0,1],8995:[-.14236,.35764,0,0,1],9136:[.244,.744,0,0,.412],9137:[.244,.745,0,0,.412],9651:[.19444,.69444,0,0,.88889],9657:[-.03472,.46528,0,0,.5],9661:[.19444,.69444,0,0,.88889],9667:[-.03472,.46528,0,0,.5],9711:[.19444,.69444,0,0,1],9824:[.12963,.69444,0,0,.77778],9825:[.12963,.69444,0,0,.77778],9826:[.12963,.69444,0,0,.77778],9827:[.12963,.69444,0,0,.77778],9837:[0,.75,0,0,.38889],9838:[.19444,.69444,0,0,.38889],9839:[.19444,.69444,0,0,.38889],10216:[.25,.75,0,0,.38889],10217:[.25,.75,0,0,.38889],10222:[.244,.744,0,0,.412],10223:[.244,.745,0,0,.412],10229:[.011,.511,0,0,1.609],10230:[.011,.511,0,0,1.638],10231:[.011,.511,0,0,1.859],10232:[.024,.525,0,0,1.609],10233:[.024,.525,0,0,1.638],10234:[.024,.525,0,0,1.858],10236:[.011,.511,0,0,1.638],10815:[0,.68333,0,0,.75],10927:[.13597,.63597,0,0,.77778],10928:[.13597,.63597,0,0,.77778],57376:[.19444,.69444,0,0,0]},"Math-BoldItalic":{32:[0,0,0,0,.25],48:[0,.44444,0,0,.575],49:[0,.44444,0,0,.575],50:[0,.44444,0,0,.575],51:[.19444,.44444,0,0,.575],52:[.19444,.44444,0,0,.575],53:[.19444,.44444,0,0,.575],54:[0,.64444,0,0,.575],55:[.19444,.44444,0,0,.575],56:[0,.64444,0,0,.575],57:[.19444,.44444,0,0,.575],65:[0,.68611,0,0,.86944],66:[0,.68611,.04835,0,.8664],67:[0,.68611,.06979,0,.81694],68:[0,.68611,.03194,0,.93812],69:[0,.68611,.05451,0,.81007],70:[0,.68611,.15972,0,.68889],71:[0,.68611,0,0,.88673],72:[0,.68611,.08229,0,.98229],73:[0,.68611,.07778,0,.51111],74:[0,.68611,.10069,0,.63125],75:[0,.68611,.06979,0,.97118],76:[0,.68611,0,0,.75555],77:[0,.68611,.11424,0,1.14201],78:[0,.68611,.11424,0,.95034],79:[0,.68611,.03194,0,.83666],80:[0,.68611,.15972,0,.72309],81:[.19444,.68611,0,0,.86861],82:[0,.68611,.00421,0,.87235],83:[0,.68611,.05382,0,.69271],84:[0,.68611,.15972,0,.63663],85:[0,.68611,.11424,0,.80027],86:[0,.68611,.25555,0,.67778],87:[0,.68611,.15972,0,1.09305],88:[0,.68611,.07778,0,.94722],89:[0,.68611,.25555,0,.67458],90:[0,.68611,.06979,0,.77257],97:[0,.44444,0,0,.63287],98:[0,.69444,0,0,.52083],99:[0,.44444,0,0,.51342],100:[0,.69444,0,0,.60972],101:[0,.44444,0,0,.55361],102:[.19444,.69444,.11042,0,.56806],103:[.19444,.44444,.03704,0,.5449],104:[0,.69444,0,0,.66759],105:[0,.69326,0,0,.4048],106:[.19444,.69326,.0622,0,.47083],107:[0,.69444,.01852,0,.6037],108:[0,.69444,.0088,0,.34815],109:[0,.44444,0,0,1.0324],110:[0,.44444,0,0,.71296],111:[0,.44444,0,0,.58472],112:[.19444,.44444,0,0,.60092],113:[.19444,.44444,.03704,0,.54213],114:[0,.44444,.03194,0,.5287],115:[0,.44444,0,0,.53125],116:[0,.63492,0,0,.41528],117:[0,.44444,0,0,.68102],118:[0,.44444,.03704,0,.56666],119:[0,.44444,.02778,0,.83148],120:[0,.44444,0,0,.65903],121:[.19444,.44444,.03704,0,.59028],122:[0,.44444,.04213,0,.55509],160:[0,0,0,0,.25],915:[0,.68611,.15972,0,.65694],916:[0,.68611,0,0,.95833],920:[0,.68611,.03194,0,.86722],923:[0,.68611,0,0,.80555],926:[0,.68611,.07458,0,.84125],928:[0,.68611,.08229,0,.98229],931:[0,.68611,.05451,0,.88507],933:[0,.68611,.15972,0,.67083],934:[0,.68611,0,0,.76666],936:[0,.68611,.11653,0,.71402],937:[0,.68611,.04835,0,.8789],945:[0,.44444,0,0,.76064],946:[.19444,.69444,.03403,0,.65972],947:[.19444,.44444,.06389,0,.59003],948:[0,.69444,.03819,0,.52222],949:[0,.44444,0,0,.52882],950:[.19444,.69444,.06215,0,.50833],951:[.19444,.44444,.03704,0,.6],952:[0,.69444,.03194,0,.5618],953:[0,.44444,0,0,.41204],954:[0,.44444,0,0,.66759],955:[0,.69444,0,0,.67083],956:[.19444,.44444,0,0,.70787],957:[0,.44444,.06898,0,.57685],958:[.19444,.69444,.03021,0,.50833],959:[0,.44444,0,0,.58472],960:[0,.44444,.03704,0,.68241],961:[.19444,.44444,0,0,.6118],962:[.09722,.44444,.07917,0,.42361],963:[0,.44444,.03704,0,.68588],964:[0,.44444,.13472,0,.52083],965:[0,.44444,.03704,0,.63055],966:[.19444,.44444,0,0,.74722],967:[.19444,.44444,0,0,.71805],968:[.19444,.69444,.03704,0,.75833],969:[0,.44444,.03704,0,.71782],977:[0,.69444,0,0,.69155],981:[.19444,.69444,0,0,.7125],982:[0,.44444,.03194,0,.975],1009:[.19444,.44444,0,0,.6118],1013:[0,.44444,0,0,.48333],57649:[0,.44444,0,0,.39352],57911:[.19444,.44444,0,0,.43889]},"Math-Italic":{32:[0,0,0,0,.25],48:[0,.43056,0,0,.5],49:[0,.43056,0,0,.5],50:[0,.43056,0,0,.5],51:[.19444,.43056,0,0,.5],52:[.19444,.43056,0,0,.5],53:[.19444,.43056,0,0,.5],54:[0,.64444,0,0,.5],55:[.19444,.43056,0,0,.5],56:[0,.64444,0,0,.5],57:[.19444,.43056,0,0,.5],65:[0,.68333,0,.13889,.75],66:[0,.68333,.05017,.08334,.75851],67:[0,.68333,.07153,.08334,.71472],68:[0,.68333,.02778,.05556,.82792],69:[0,.68333,.05764,.08334,.7382],70:[0,.68333,.13889,.08334,.64306],71:[0,.68333,0,.08334,.78625],72:[0,.68333,.08125,.05556,.83125],73:[0,.68333,.07847,.11111,.43958],74:[0,.68333,.09618,.16667,.55451],75:[0,.68333,.07153,.05556,.84931],76:[0,.68333,0,.02778,.68056],77:[0,.68333,.10903,.08334,.97014],78:[0,.68333,.10903,.08334,.80347],79:[0,.68333,.02778,.08334,.76278],80:[0,.68333,.13889,.08334,.64201],81:[.19444,.68333,0,.08334,.79056],82:[0,.68333,.00773,.08334,.75929],83:[0,.68333,.05764,.08334,.6132],84:[0,.68333,.13889,.08334,.58438],85:[0,.68333,.10903,.02778,.68278],86:[0,.68333,.22222,0,.58333],87:[0,.68333,.13889,0,.94445],88:[0,.68333,.07847,.08334,.82847],89:[0,.68333,.22222,0,.58056],90:[0,.68333,.07153,.08334,.68264],97:[0,.43056,0,0,.52859],98:[0,.69444,0,0,.42917],99:[0,.43056,0,.05556,.43276],100:[0,.69444,0,.16667,.52049],101:[0,.43056,0,.05556,.46563],102:[.19444,.69444,.10764,.16667,.48959],103:[.19444,.43056,.03588,.02778,.47697],104:[0,.69444,0,0,.57616],105:[0,.65952,0,0,.34451],106:[.19444,.65952,.05724,0,.41181],107:[0,.69444,.03148,0,.5206],108:[0,.69444,.01968,.08334,.29838],109:[0,.43056,0,0,.87801],110:[0,.43056,0,0,.60023],111:[0,.43056,0,.05556,.48472],112:[.19444,.43056,0,.08334,.50313],113:[.19444,.43056,.03588,.08334,.44641],114:[0,.43056,.02778,.05556,.45116],115:[0,.43056,0,.05556,.46875],116:[0,.61508,0,.08334,.36111],117:[0,.43056,0,.02778,.57246],118:[0,.43056,.03588,.02778,.48472],119:[0,.43056,.02691,.08334,.71592],120:[0,.43056,0,.02778,.57153],121:[.19444,.43056,.03588,.05556,.49028],122:[0,.43056,.04398,.05556,.46505],160:[0,0,0,0,.25],915:[0,.68333,.13889,.08334,.61528],916:[0,.68333,0,.16667,.83334],920:[0,.68333,.02778,.08334,.76278],923:[0,.68333,0,.16667,.69445],926:[0,.68333,.07569,.08334,.74236],928:[0,.68333,.08125,.05556,.83125],931:[0,.68333,.05764,.08334,.77986],933:[0,.68333,.13889,.05556,.58333],934:[0,.68333,0,.08334,.66667],936:[0,.68333,.11,.05556,.61222],937:[0,.68333,.05017,.08334,.7724],945:[0,.43056,.0037,.02778,.6397],946:[.19444,.69444,.05278,.08334,.56563],947:[.19444,.43056,.05556,0,.51773],948:[0,.69444,.03785,.05556,.44444],949:[0,.43056,0,.08334,.46632],950:[.19444,.69444,.07378,.08334,.4375],951:[.19444,.43056,.03588,.05556,.49653],952:[0,.69444,.02778,.08334,.46944],953:[0,.43056,0,.05556,.35394],954:[0,.43056,0,0,.57616],955:[0,.69444,0,0,.58334],956:[.19444,.43056,0,.02778,.60255],957:[0,.43056,.06366,.02778,.49398],958:[.19444,.69444,.04601,.11111,.4375],959:[0,.43056,0,.05556,.48472],960:[0,.43056,.03588,0,.57003],961:[.19444,.43056,0,.08334,.51702],962:[.09722,.43056,.07986,.08334,.36285],963:[0,.43056,.03588,0,.57141],964:[0,.43056,.1132,.02778,.43715],965:[0,.43056,.03588,.02778,.54028],966:[.19444,.43056,0,.08334,.65417],967:[.19444,.43056,0,.05556,.62569],968:[.19444,.69444,.03588,.11111,.65139],969:[0,.43056,.03588,0,.62245],977:[0,.69444,0,.08334,.59144],981:[.19444,.69444,0,.08334,.59583],982:[0,.43056,.02778,0,.82813],1009:[.19444,.43056,0,.08334,.51702],1013:[0,.43056,0,.05556,.4059],57649:[0,.43056,0,.02778,.32246],57911:[.19444,.43056,0,.08334,.38403]},"SansSerif-Bold":{32:[0,0,0,0,.25],33:[0,.69444,0,0,.36667],34:[0,.69444,0,0,.55834],35:[.19444,.69444,0,0,.91667],36:[.05556,.75,0,0,.55],37:[.05556,.75,0,0,1.02912],38:[0,.69444,0,0,.83056],39:[0,.69444,0,0,.30556],40:[.25,.75,0,0,.42778],41:[.25,.75,0,0,.42778],42:[0,.75,0,0,.55],43:[.11667,.61667,0,0,.85556],44:[.10556,.13056,0,0,.30556],45:[0,.45833,0,0,.36667],46:[0,.13056,0,0,.30556],47:[.25,.75,0,0,.55],48:[0,.69444,0,0,.55],49:[0,.69444,0,0,.55],50:[0,.69444,0,0,.55],51:[0,.69444,0,0,.55],52:[0,.69444,0,0,.55],53:[0,.69444,0,0,.55],54:[0,.69444,0,0,.55],55:[0,.69444,0,0,.55],56:[0,.69444,0,0,.55],57:[0,.69444,0,0,.55],58:[0,.45833,0,0,.30556],59:[.10556,.45833,0,0,.30556],61:[-.09375,.40625,0,0,.85556],63:[0,.69444,0,0,.51945],64:[0,.69444,0,0,.73334],65:[0,.69444,0,0,.73334],66:[0,.69444,0,0,.73334],67:[0,.69444,0,0,.70278],68:[0,.69444,0,0,.79445],69:[0,.69444,0,0,.64167],70:[0,.69444,0,0,.61111],71:[0,.69444,0,0,.73334],72:[0,.69444,0,0,.79445],73:[0,.69444,0,0,.33056],74:[0,.69444,0,0,.51945],75:[0,.69444,0,0,.76389],76:[0,.69444,0,0,.58056],77:[0,.69444,0,0,.97778],78:[0,.69444,0,0,.79445],79:[0,.69444,0,0,.79445],80:[0,.69444,0,0,.70278],81:[.10556,.69444,0,0,.79445],82:[0,.69444,0,0,.70278],83:[0,.69444,0,0,.61111],84:[0,.69444,0,0,.73334],85:[0,.69444,0,0,.76389],86:[0,.69444,.01528,0,.73334],87:[0,.69444,.01528,0,1.03889],88:[0,.69444,0,0,.73334],89:[0,.69444,.0275,0,.73334],90:[0,.69444,0,0,.67223],91:[.25,.75,0,0,.34306],93:[.25,.75,0,0,.34306],94:[0,.69444,0,0,.55],95:[.35,.10833,.03056,0,.55],97:[0,.45833,0,0,.525],98:[0,.69444,0,0,.56111],99:[0,.45833,0,0,.48889],100:[0,.69444,0,0,.56111],101:[0,.45833,0,0,.51111],102:[0,.69444,.07639,0,.33611],103:[.19444,.45833,.01528,0,.55],104:[0,.69444,0,0,.56111],105:[0,.69444,0,0,.25556],106:[.19444,.69444,0,0,.28611],107:[0,.69444,0,0,.53056],108:[0,.69444,0,0,.25556],109:[0,.45833,0,0,.86667],110:[0,.45833,0,0,.56111],111:[0,.45833,0,0,.55],112:[.19444,.45833,0,0,.56111],113:[.19444,.45833,0,0,.56111],114:[0,.45833,.01528,0,.37222],115:[0,.45833,0,0,.42167],116:[0,.58929,0,0,.40417],117:[0,.45833,0,0,.56111],118:[0,.45833,.01528,0,.5],119:[0,.45833,.01528,0,.74445],120:[0,.45833,0,0,.5],121:[.19444,.45833,.01528,0,.5],122:[0,.45833,0,0,.47639],126:[.35,.34444,0,0,.55],160:[0,0,0,0,.25],168:[0,.69444,0,0,.55],176:[0,.69444,0,0,.73334],180:[0,.69444,0,0,.55],184:[.17014,0,0,0,.48889],305:[0,.45833,0,0,.25556],567:[.19444,.45833,0,0,.28611],710:[0,.69444,0,0,.55],711:[0,.63542,0,0,.55],713:[0,.63778,0,0,.55],728:[0,.69444,0,0,.55],729:[0,.69444,0,0,.30556],730:[0,.69444,0,0,.73334],732:[0,.69444,0,0,.55],733:[0,.69444,0,0,.55],915:[0,.69444,0,0,.58056],916:[0,.69444,0,0,.91667],920:[0,.69444,0,0,.85556],923:[0,.69444,0,0,.67223],926:[0,.69444,0,0,.73334],928:[0,.69444,0,0,.79445],931:[0,.69444,0,0,.79445],933:[0,.69444,0,0,.85556],934:[0,.69444,0,0,.79445],936:[0,.69444,0,0,.85556],937:[0,.69444,0,0,.79445],8211:[0,.45833,.03056,0,.55],8212:[0,.45833,.03056,0,1.10001],8216:[0,.69444,0,0,.30556],8217:[0,.69444,0,0,.30556],8220:[0,.69444,0,0,.55834],8221:[0,.69444,0,0,.55834]},"SansSerif-Italic":{32:[0,0,0,0,.25],33:[0,.69444,.05733,0,.31945],34:[0,.69444,.00316,0,.5],35:[.19444,.69444,.05087,0,.83334],36:[.05556,.75,.11156,0,.5],37:[.05556,.75,.03126,0,.83334],38:[0,.69444,.03058,0,.75834],39:[0,.69444,.07816,0,.27778],40:[.25,.75,.13164,0,.38889],41:[.25,.75,.02536,0,.38889],42:[0,.75,.11775,0,.5],43:[.08333,.58333,.02536,0,.77778],44:[.125,.08333,0,0,.27778],45:[0,.44444,.01946,0,.33333],46:[0,.08333,0,0,.27778],47:[.25,.75,.13164,0,.5],48:[0,.65556,.11156,0,.5],49:[0,.65556,.11156,0,.5],50:[0,.65556,.11156,0,.5],51:[0,.65556,.11156,0,.5],52:[0,.65556,.11156,0,.5],53:[0,.65556,.11156,0,.5],54:[0,.65556,.11156,0,.5],55:[0,.65556,.11156,0,.5],56:[0,.65556,.11156,0,.5],57:[0,.65556,.11156,0,.5],58:[0,.44444,.02502,0,.27778],59:[.125,.44444,.02502,0,.27778],61:[-.13,.37,.05087,0,.77778],63:[0,.69444,.11809,0,.47222],64:[0,.69444,.07555,0,.66667],65:[0,.69444,0,0,.66667],66:[0,.69444,.08293,0,.66667],67:[0,.69444,.11983,0,.63889],68:[0,.69444,.07555,0,.72223],69:[0,.69444,.11983,0,.59722],70:[0,.69444,.13372,0,.56945],71:[0,.69444,.11983,0,.66667],72:[0,.69444,.08094,0,.70834],73:[0,.69444,.13372,0,.27778],74:[0,.69444,.08094,0,.47222],75:[0,.69444,.11983,0,.69445],76:[0,.69444,0,0,.54167],77:[0,.69444,.08094,0,.875],78:[0,.69444,.08094,0,.70834],79:[0,.69444,.07555,0,.73611],80:[0,.69444,.08293,0,.63889],81:[.125,.69444,.07555,0,.73611],82:[0,.69444,.08293,0,.64584],83:[0,.69444,.09205,0,.55556],84:[0,.69444,.13372,0,.68056],85:[0,.69444,.08094,0,.6875],86:[0,.69444,.1615,0,.66667],87:[0,.69444,.1615,0,.94445],88:[0,.69444,.13372,0,.66667],89:[0,.69444,.17261,0,.66667],90:[0,.69444,.11983,0,.61111],91:[.25,.75,.15942,0,.28889],93:[.25,.75,.08719,0,.28889],94:[0,.69444,.0799,0,.5],95:[.35,.09444,.08616,0,.5],97:[0,.44444,.00981,0,.48056],98:[0,.69444,.03057,0,.51667],99:[0,.44444,.08336,0,.44445],100:[0,.69444,.09483,0,.51667],101:[0,.44444,.06778,0,.44445],102:[0,.69444,.21705,0,.30556],103:[.19444,.44444,.10836,0,.5],104:[0,.69444,.01778,0,.51667],105:[0,.67937,.09718,0,.23889],106:[.19444,.67937,.09162,0,.26667],107:[0,.69444,.08336,0,.48889],108:[0,.69444,.09483,0,.23889],109:[0,.44444,.01778,0,.79445],110:[0,.44444,.01778,0,.51667],111:[0,.44444,.06613,0,.5],112:[.19444,.44444,.0389,0,.51667],113:[.19444,.44444,.04169,0,.51667],114:[0,.44444,.10836,0,.34167],115:[0,.44444,.0778,0,.38333],116:[0,.57143,.07225,0,.36111],117:[0,.44444,.04169,0,.51667],118:[0,.44444,.10836,0,.46111],119:[0,.44444,.10836,0,.68334],120:[0,.44444,.09169,0,.46111],121:[.19444,.44444,.10836,0,.46111],122:[0,.44444,.08752,0,.43472],126:[.35,.32659,.08826,0,.5],160:[0,0,0,0,.25],168:[0,.67937,.06385,0,.5],176:[0,.69444,0,0,.73752],184:[.17014,0,0,0,.44445],305:[0,.44444,.04169,0,.23889],567:[.19444,.44444,.04169,0,.26667],710:[0,.69444,.0799,0,.5],711:[0,.63194,.08432,0,.5],713:[0,.60889,.08776,0,.5],714:[0,.69444,.09205,0,.5],715:[0,.69444,0,0,.5],728:[0,.69444,.09483,0,.5],729:[0,.67937,.07774,0,.27778],730:[0,.69444,0,0,.73752],732:[0,.67659,.08826,0,.5],733:[0,.69444,.09205,0,.5],915:[0,.69444,.13372,0,.54167],916:[0,.69444,0,0,.83334],920:[0,.69444,.07555,0,.77778],923:[0,.69444,0,0,.61111],926:[0,.69444,.12816,0,.66667],928:[0,.69444,.08094,0,.70834],931:[0,.69444,.11983,0,.72222],933:[0,.69444,.09031,0,.77778],934:[0,.69444,.04603,0,.72222],936:[0,.69444,.09031,0,.77778],937:[0,.69444,.08293,0,.72222],8211:[0,.44444,.08616,0,.5],8212:[0,.44444,.08616,0,1],8216:[0,.69444,.07816,0,.27778],8217:[0,.69444,.07816,0,.27778],8220:[0,.69444,.14205,0,.5],8221:[0,.69444,.00316,0,.5]},"SansSerif-Regular":{32:[0,0,0,0,.25],33:[0,.69444,0,0,.31945],34:[0,.69444,0,0,.5],35:[.19444,.69444,0,0,.83334],36:[.05556,.75,0,0,.5],37:[.05556,.75,0,0,.83334],38:[0,.69444,0,0,.75834],39:[0,.69444,0,0,.27778],40:[.25,.75,0,0,.38889],41:[.25,.75,0,0,.38889],42:[0,.75,0,0,.5],43:[.08333,.58333,0,0,.77778],44:[.125,.08333,0,0,.27778],45:[0,.44444,0,0,.33333],46:[0,.08333,0,0,.27778],47:[.25,.75,0,0,.5],48:[0,.65556,0,0,.5],49:[0,.65556,0,0,.5],50:[0,.65556,0,0,.5],51:[0,.65556,0,0,.5],52:[0,.65556,0,0,.5],53:[0,.65556,0,0,.5],54:[0,.65556,0,0,.5],55:[0,.65556,0,0,.5],56:[0,.65556,0,0,.5],57:[0,.65556,0,0,.5],58:[0,.44444,0,0,.27778],59:[.125,.44444,0,0,.27778],61:[-.13,.37,0,0,.77778],63:[0,.69444,0,0,.47222],64:[0,.69444,0,0,.66667],65:[0,.69444,0,0,.66667],66:[0,.69444,0,0,.66667],67:[0,.69444,0,0,.63889],68:[0,.69444,0,0,.72223],69:[0,.69444,0,0,.59722],70:[0,.69444,0,0,.56945],71:[0,.69444,0,0,.66667],72:[0,.69444,0,0,.70834],73:[0,.69444,0,0,.27778],74:[0,.69444,0,0,.47222],75:[0,.69444,0,0,.69445],76:[0,.69444,0,0,.54167],77:[0,.69444,0,0,.875],78:[0,.69444,0,0,.70834],79:[0,.69444,0,0,.73611],80:[0,.69444,0,0,.63889],81:[.125,.69444,0,0,.73611],82:[0,.69444,0,0,.64584],83:[0,.69444,0,0,.55556],84:[0,.69444,0,0,.68056],85:[0,.69444,0,0,.6875],86:[0,.69444,.01389,0,.66667],87:[0,.69444,.01389,0,.94445],88:[0,.69444,0,0,.66667],89:[0,.69444,.025,0,.66667],90:[0,.69444,0,0,.61111],91:[.25,.75,0,0,.28889],93:[.25,.75,0,0,.28889],94:[0,.69444,0,0,.5],95:[.35,.09444,.02778,0,.5],97:[0,.44444,0,0,.48056],98:[0,.69444,0,0,.51667],99:[0,.44444,0,0,.44445],100:[0,.69444,0,0,.51667],101:[0,.44444,0,0,.44445],102:[0,.69444,.06944,0,.30556],103:[.19444,.44444,.01389,0,.5],104:[0,.69444,0,0,.51667],105:[0,.67937,0,0,.23889],106:[.19444,.67937,0,0,.26667],107:[0,.69444,0,0,.48889],108:[0,.69444,0,0,.23889],109:[0,.44444,0,0,.79445],110:[0,.44444,0,0,.51667],111:[0,.44444,0,0,.5],112:[.19444,.44444,0,0,.51667],113:[.19444,.44444,0,0,.51667],114:[0,.44444,.01389,0,.34167],115:[0,.44444,0,0,.38333],116:[0,.57143,0,0,.36111],117:[0,.44444,0,0,.51667],118:[0,.44444,.01389,0,.46111],119:[0,.44444,.01389,0,.68334],120:[0,.44444,0,0,.46111],121:[.19444,.44444,.01389,0,.46111],122:[0,.44444,0,0,.43472],126:[.35,.32659,0,0,.5],160:[0,0,0,0,.25],168:[0,.67937,0,0,.5],176:[0,.69444,0,0,.66667],184:[.17014,0,0,0,.44445],305:[0,.44444,0,0,.23889],567:[.19444,.44444,0,0,.26667],710:[0,.69444,0,0,.5],711:[0,.63194,0,0,.5],713:[0,.60889,0,0,.5],714:[0,.69444,0,0,.5],715:[0,.69444,0,0,.5],728:[0,.69444,0,0,.5],729:[0,.67937,0,0,.27778],730:[0,.69444,0,0,.66667],732:[0,.67659,0,0,.5],733:[0,.69444,0,0,.5],915:[0,.69444,0,0,.54167],916:[0,.69444,0,0,.83334],920:[0,.69444,0,0,.77778],923:[0,.69444,0,0,.61111],926:[0,.69444,0,0,.66667],928:[0,.69444,0,0,.70834],931:[0,.69444,0,0,.72222],933:[0,.69444,0,0,.77778],934:[0,.69444,0,0,.72222],936:[0,.69444,0,0,.77778],937:[0,.69444,0,0,.72222],8211:[0,.44444,.02778,0,.5],8212:[0,.44444,.02778,0,1],8216:[0,.69444,0,0,.27778],8217:[0,.69444,0,0,.27778],8220:[0,.69444,0,0,.5],8221:[0,.69444,0,0,.5]},"Script-Regular":{32:[0,0,0,0,.25],65:[0,.7,.22925,0,.80253],66:[0,.7,.04087,0,.90757],67:[0,.7,.1689,0,.66619],68:[0,.7,.09371,0,.77443],69:[0,.7,.18583,0,.56162],70:[0,.7,.13634,0,.89544],71:[0,.7,.17322,0,.60961],72:[0,.7,.29694,0,.96919],73:[0,.7,.19189,0,.80907],74:[.27778,.7,.19189,0,1.05159],75:[0,.7,.31259,0,.91364],76:[0,.7,.19189,0,.87373],77:[0,.7,.15981,0,1.08031],78:[0,.7,.3525,0,.9015],79:[0,.7,.08078,0,.73787],80:[0,.7,.08078,0,1.01262],81:[0,.7,.03305,0,.88282],82:[0,.7,.06259,0,.85],83:[0,.7,.19189,0,.86767],84:[0,.7,.29087,0,.74697],85:[0,.7,.25815,0,.79996],86:[0,.7,.27523,0,.62204],87:[0,.7,.27523,0,.80532],88:[0,.7,.26006,0,.94445],89:[0,.7,.2939,0,.70961],90:[0,.7,.24037,0,.8212],160:[0,0,0,0,.25]},"Size1-Regular":{32:[0,0,0,0,.25],40:[.35001,.85,0,0,.45834],41:[.35001,.85,0,0,.45834],47:[.35001,.85,0,0,.57778],91:[.35001,.85,0,0,.41667],92:[.35001,.85,0,0,.57778],93:[.35001,.85,0,0,.41667],123:[.35001,.85,0,0,.58334],125:[.35001,.85,0,0,.58334],160:[0,0,0,0,.25],710:[0,.72222,0,0,.55556],732:[0,.72222,0,0,.55556],770:[0,.72222,0,0,.55556],771:[0,.72222,0,0,.55556],8214:[-99e-5,.601,0,0,.77778],8593:[1e-5,.6,0,0,.66667],8595:[1e-5,.6,0,0,.66667],8657:[1e-5,.6,0,0,.77778],8659:[1e-5,.6,0,0,.77778],8719:[.25001,.75,0,0,.94445],8720:[.25001,.75,0,0,.94445],8721:[.25001,.75,0,0,1.05556],8730:[.35001,.85,0,0,1],8739:[-.00599,.606,0,0,.33333],8741:[-.00599,.606,0,0,.55556],8747:[.30612,.805,.19445,0,.47222],8748:[.306,.805,.19445,0,.47222],8749:[.306,.805,.19445,0,.47222],8750:[.30612,.805,.19445,0,.47222],8896:[.25001,.75,0,0,.83334],8897:[.25001,.75,0,0,.83334],8898:[.25001,.75,0,0,.83334],8899:[.25001,.75,0,0,.83334],8968:[.35001,.85,0,0,.47222],8969:[.35001,.85,0,0,.47222],8970:[.35001,.85,0,0,.47222],8971:[.35001,.85,0,0,.47222],9168:[-99e-5,.601,0,0,.66667],10216:[.35001,.85,0,0,.47222],10217:[.35001,.85,0,0,.47222],10752:[.25001,.75,0,0,1.11111],10753:[.25001,.75,0,0,1.11111],10754:[.25001,.75,0,0,1.11111],10756:[.25001,.75,0,0,.83334],10758:[.25001,.75,0,0,.83334]},"Size2-Regular":{32:[0,0,0,0,.25],40:[.65002,1.15,0,0,.59722],41:[.65002,1.15,0,0,.59722],47:[.65002,1.15,0,0,.81111],91:[.65002,1.15,0,0,.47222],92:[.65002,1.15,0,0,.81111],93:[.65002,1.15,0,0,.47222],123:[.65002,1.15,0,0,.66667],125:[.65002,1.15,0,0,.66667],160:[0,0,0,0,.25],710:[0,.75,0,0,1],732:[0,.75,0,0,1],770:[0,.75,0,0,1],771:[0,.75,0,0,1],8719:[.55001,1.05,0,0,1.27778],8720:[.55001,1.05,0,0,1.27778],8721:[.55001,1.05,0,0,1.44445],8730:[.65002,1.15,0,0,1],8747:[.86225,1.36,.44445,0,.55556],8748:[.862,1.36,.44445,0,.55556],8749:[.862,1.36,.44445,0,.55556],8750:[.86225,1.36,.44445,0,.55556],8896:[.55001,1.05,0,0,1.11111],8897:[.55001,1.05,0,0,1.11111],8898:[.55001,1.05,0,0,1.11111],8899:[.55001,1.05,0,0,1.11111],8968:[.65002,1.15,0,0,.52778],8969:[.65002,1.15,0,0,.52778],8970:[.65002,1.15,0,0,.52778],8971:[.65002,1.15,0,0,.52778],10216:[.65002,1.15,0,0,.61111],10217:[.65002,1.15,0,0,.61111],10752:[.55001,1.05,0,0,1.51112],10753:[.55001,1.05,0,0,1.51112],10754:[.55001,1.05,0,0,1.51112],10756:[.55001,1.05,0,0,1.11111],10758:[.55001,1.05,0,0,1.11111]},"Size3-Regular":{32:[0,0,0,0,.25],40:[.95003,1.45,0,0,.73611],41:[.95003,1.45,0,0,.73611],47:[.95003,1.45,0,0,1.04445],91:[.95003,1.45,0,0,.52778],92:[.95003,1.45,0,0,1.04445],93:[.95003,1.45,0,0,.52778],123:[.95003,1.45,0,0,.75],125:[.95003,1.45,0,0,.75],160:[0,0,0,0,.25],710:[0,.75,0,0,1.44445],732:[0,.75,0,0,1.44445],770:[0,.75,0,0,1.44445],771:[0,.75,0,0,1.44445],8730:[.95003,1.45,0,0,1],8968:[.95003,1.45,0,0,.58334],8969:[.95003,1.45,0,0,.58334],8970:[.95003,1.45,0,0,.58334],8971:[.95003,1.45,0,0,.58334],10216:[.95003,1.45,0,0,.75],10217:[.95003,1.45,0,0,.75]},"Size4-Regular":{32:[0,0,0,0,.25],40:[1.25003,1.75,0,0,.79167],41:[1.25003,1.75,0,0,.79167],47:[1.25003,1.75,0,0,1.27778],91:[1.25003,1.75,0,0,.58334],92:[1.25003,1.75,0,0,1.27778],93:[1.25003,1.75,0,0,.58334],123:[1.25003,1.75,0,0,.80556],125:[1.25003,1.75,0,0,.80556],160:[0,0,0,0,.25],710:[0,.825,0,0,1.8889],732:[0,.825,0,0,1.8889],770:[0,.825,0,0,1.8889],771:[0,.825,0,0,1.8889],8730:[1.25003,1.75,0,0,1],8968:[1.25003,1.75,0,0,.63889],8969:[1.25003,1.75,0,0,.63889],8970:[1.25003,1.75,0,0,.63889],8971:[1.25003,1.75,0,0,.63889],9115:[.64502,1.155,0,0,.875],9116:[1e-5,.6,0,0,.875],9117:[.64502,1.155,0,0,.875],9118:[.64502,1.155,0,0,.875],9119:[1e-5,.6,0,0,.875],9120:[.64502,1.155,0,0,.875],9121:[.64502,1.155,0,0,.66667],9122:[-99e-5,.601,0,0,.66667],9123:[.64502,1.155,0,0,.66667],9124:[.64502,1.155,0,0,.66667],9125:[-99e-5,.601,0,0,.66667],9126:[.64502,1.155,0,0,.66667],9127:[1e-5,.9,0,0,.88889],9128:[.65002,1.15,0,0,.88889],9129:[.90001,0,0,0,.88889],9130:[0,.3,0,0,.88889],9131:[1e-5,.9,0,0,.88889],9132:[.65002,1.15,0,0,.88889],9133:[.90001,0,0,0,.88889],9143:[.88502,.915,0,0,1.05556],10216:[1.25003,1.75,0,0,.80556],10217:[1.25003,1.75,0,0,.80556],57344:[-.00499,.605,0,0,1.05556],57345:[-.00499,.605,0,0,1.05556],57680:[0,.12,0,0,.45],57681:[0,.12,0,0,.45],57682:[0,.12,0,0,.45],57683:[0,.12,0,0,.45]},"Typewriter-Regular":{32:[0,0,0,0,.525],33:[0,.61111,0,0,.525],34:[0,.61111,0,0,.525],35:[0,.61111,0,0,.525],36:[.08333,.69444,0,0,.525],37:[.08333,.69444,0,0,.525],38:[0,.61111,0,0,.525],39:[0,.61111,0,0,.525],40:[.08333,.69444,0,0,.525],41:[.08333,.69444,0,0,.525],42:[0,.52083,0,0,.525],43:[-.08056,.53055,0,0,.525],44:[.13889,.125,0,0,.525],45:[-.08056,.53055,0,0,.525],46:[0,.125,0,0,.525],47:[.08333,.69444,0,0,.525],48:[0,.61111,0,0,.525],49:[0,.61111,0,0,.525],50:[0,.61111,0,0,.525],51:[0,.61111,0,0,.525],52:[0,.61111,0,0,.525],53:[0,.61111,0,0,.525],54:[0,.61111,0,0,.525],55:[0,.61111,0,0,.525],56:[0,.61111,0,0,.525],57:[0,.61111,0,0,.525],58:[0,.43056,0,0,.525],59:[.13889,.43056,0,0,.525],60:[-.05556,.55556,0,0,.525],61:[-.19549,.41562,0,0,.525],62:[-.05556,.55556,0,0,.525],63:[0,.61111,0,0,.525],64:[0,.61111,0,0,.525],65:[0,.61111,0,0,.525],66:[0,.61111,0,0,.525],67:[0,.61111,0,0,.525],68:[0,.61111,0,0,.525],69:[0,.61111,0,0,.525],70:[0,.61111,0,0,.525],71:[0,.61111,0,0,.525],72:[0,.61111,0,0,.525],73:[0,.61111,0,0,.525],74:[0,.61111,0,0,.525],75:[0,.61111,0,0,.525],76:[0,.61111,0,0,.525],77:[0,.61111,0,0,.525],78:[0,.61111,0,0,.525],79:[0,.61111,0,0,.525],80:[0,.61111,0,0,.525],81:[.13889,.61111,0,0,.525],82:[0,.61111,0,0,.525],83:[0,.61111,0,0,.525],84:[0,.61111,0,0,.525],85:[0,.61111,0,0,.525],86:[0,.61111,0,0,.525],87:[0,.61111,0,0,.525],88:[0,.61111,0,0,.525],89:[0,.61111,0,0,.525],90:[0,.61111,0,0,.525],91:[.08333,.69444,0,0,.525],92:[.08333,.69444,0,0,.525],93:[.08333,.69444,0,0,.525],94:[0,.61111,0,0,.525],95:[.09514,0,0,0,.525],96:[0,.61111,0,0,.525],97:[0,.43056,0,0,.525],98:[0,.61111,0,0,.525],99:[0,.43056,0,0,.525],100:[0,.61111,0,0,.525],101:[0,.43056,0,0,.525],102:[0,.61111,0,0,.525],103:[.22222,.43056,0,0,.525],104:[0,.61111,0,0,.525],105:[0,.61111,0,0,.525],106:[.22222,.61111,0,0,.525],107:[0,.61111,0,0,.525],108:[0,.61111,0,0,.525],109:[0,.43056,0,0,.525],110:[0,.43056,0,0,.525],111:[0,.43056,0,0,.525],112:[.22222,.43056,0,0,.525],113:[.22222,.43056,0,0,.525],114:[0,.43056,0,0,.525],115:[0,.43056,0,0,.525],116:[0,.55358,0,0,.525],117:[0,.43056,0,0,.525],118:[0,.43056,0,0,.525],119:[0,.43056,0,0,.525],120:[0,.43056,0,0,.525],121:[.22222,.43056,0,0,.525],122:[0,.43056,0,0,.525],123:[.08333,.69444,0,0,.525],124:[.08333,.69444,0,0,.525],125:[.08333,.69444,0,0,.525],126:[0,.61111,0,0,.525],127:[0,.61111,0,0,.525],160:[0,0,0,0,.525],176:[0,.61111,0,0,.525],184:[.19445,0,0,0,.525],305:[0,.43056,0,0,.525],567:[.22222,.43056,0,0,.525],711:[0,.56597,0,0,.525],713:[0,.56555,0,0,.525],714:[0,.61111,0,0,.525],715:[0,.61111,0,0,.525],728:[0,.61111,0,0,.525],730:[0,.61111,0,0,.525],770:[0,.61111,0,0,.525],771:[0,.61111,0,0,.525],776:[0,.61111,0,0,.525],915:[0,.61111,0,0,.525],916:[0,.61111,0,0,.525],920:[0,.61111,0,0,.525],923:[0,.61111,0,0,.525],926:[0,.61111,0,0,.525],928:[0,.61111,0,0,.525],931:[0,.61111,0,0,.525],933:[0,.61111,0,0,.525],934:[0,.61111,0,0,.525],936:[0,.61111,0,0,.525],937:[0,.61111,0,0,.525],8216:[0,.61111,0,0,.525],8217:[0,.61111,0,0,.525],8242:[0,.61111,0,0,.525],9251:[.11111,.21944,0,0,.525]}},Jb={slant:[.25,.25,.25],space:[0,0,0],stretch:[0,0,0],shrink:[0,0,0],xHeight:[.431,.431,.431],quad:[1,1.171,1.472],extraSpace:[0,0,0],num1:[.677,.732,.925],num2:[.394,.384,.387],num3:[.444,.471,.504],denom1:[.686,.752,1.025],denom2:[.345,.344,.532],sup1:[.413,.503,.504],sup2:[.363,.431,.404],sup3:[.289,.286,.294],sub1:[.15,.143,.2],sub2:[.247,.286,.4],supDrop:[.386,.353,.494],subDrop:[.05,.071,.1],delim1:[2.39,1.7,1.98],delim2:[1.01,1.157,1.42],axisHeight:[.25,.25,.25],defaultRuleThickness:[.04,.049,.049],bigOpSpacing1:[.111,.111,.111],bigOpSpacing2:[.166,.166,.166],bigOpSpacing3:[.2,.2,.2],bigOpSpacing4:[.6,.611,.611],bigOpSpacing5:[.1,.143,.143],sqrtRuleThickness:[.04,.04,.04],ptPerEm:[10,10,10],doubleRuleSep:[.2,.2,.2],arrayRuleWidth:[.04,.04,.04],fboxsep:[.3,.3,.3],fboxrule:[.04,.04,.04]},Zz={\u00C5:"A",\u00D0:"D",\u00DE:"o",\u00E5:"a",\u00F0:"d",\u00FE:"o",\u0410:"A",\u0411:"B",\u0412:"B",\u0413:"F",\u0414:"A",\u0415:"E",\u0416:"K",\u0417:"3",\u0418:"N",\u0419:"N",\u041A:"K",\u041B:"N",\u041C:"M",\u041D:"H",\u041E:"O",\u041F:"N",\u0420:"P",\u0421:"C",\u0422:"T",\u0423:"y",\u0424:"O",\u0425:"X",\u0426:"U",\u0427:"h",\u0428:"W",\u0429:"W",\u042A:"B",\u042B:"X",\u042C:"B",\u042D:"3",\u042E:"X",\u042F:"R",\u0430:"a",\u0431:"b",\u0432:"a",\u0433:"r",\u0434:"y",\u0435:"e",\u0436:"m",\u0437:"e",\u0438:"n",\u0439:"n",\u043A:"n",\u043B:"n",\u043C:"m",\u043D:"n",\u043E:"o",\u043F:"n",\u0440:"p",\u0441:"c",\u0442:"o",\u0443:"y",\u0444:"b",\u0445:"x",\u0446:"n",\u0447:"n",\u0448:"w",\u0449:"w",\u044A:"a",\u044B:"m",\u044C:"a",\u044D:"e",\u044E:"m",\u044F:"r"};o(axe,"setFontMetrics");o(M7,"getCharacterMetrics");l7={};o(sxe,"getGlobalMetrics");oxe=[[1,1,1],[2,1,1],[3,1,1],[4,2,1],[5,2,1],[6,3,1],[7,4,2],[8,6,3],[9,7,6],[10,8,7],[11,10,9]],Jz=[.5,.6,.7,.8,.9,1,1.2,1.44,1.728,2.074,2.488],eG=o(function(e,r){return r.size<2?e:oxe[e-1][r.size-1]},"sizeAtStyle"),d4=class t{static{o(this,"Options")}constructor(e){this.style=void 0,this.color=void 0,this.size=void 0,this.textSize=void 0,this.phantom=void 0,this.font=void 0,this.fontFamily=void 0,this.fontWeight=void 0,this.fontShape=void 0,this.sizeMultiplier=void 0,this.maxSize=void 0,this.minRuleThickness=void 0,this._fontMetrics=void 0,this.style=e.style,this.color=e.color,this.size=e.size||t.BASESIZE,this.textSize=e.textSize||this.size,this.phantom=!!e.phantom,this.font=e.font||"",this.fontFamily=e.fontFamily||"",this.fontWeight=e.fontWeight||"",this.fontShape=e.fontShape||"",this.sizeMultiplier=Jz[this.size-1],this.maxSize=e.maxSize,this.minRuleThickness=e.minRuleThickness,this._fontMetrics=void 0}extend(e){var r={style:this.style,size:this.size,textSize:this.textSize,color:this.color,phantom:this.phantom,font:this.font,fontFamily:this.fontFamily,fontWeight:this.fontWeight,fontShape:this.fontShape,maxSize:this.maxSize,minRuleThickness:this.minRuleThickness};for(var n in e)e.hasOwnProperty(n)&&(r[n]=e[n]);return new t(r)}havingStyle(e){return this.style===e?this:this.extend({style:e,size:eG(this.textSize,e)})}havingCrampedStyle(){return this.havingStyle(this.style.cramp())}havingSize(e){return this.size===e&&this.textSize===e?this:this.extend({style:this.style.text(),size:e,textSize:e,sizeMultiplier:Jz[e-1]})}havingBaseStyle(e){e=e||this.style.text();var r=eG(t.BASESIZE,e);return this.size===r&&this.textSize===t.BASESIZE&&this.style===e?this:this.extend({style:e,size:r})}havingBaseSizing(){var e;switch(this.style.id){case 4:case 5:e=3;break;case 6:case 7:e=1;break;default:e=6}return this.extend({style:this.style.text(),size:e})}withColor(e){return this.extend({color:e})}withPhantom(){return this.extend({phantom:!0})}withFont(e){return this.extend({font:e})}withTextFontFamily(e){return this.extend({fontFamily:e,font:""})}withTextFontWeight(e){return this.extend({fontWeight:e,font:""})}withTextFontShape(e){return this.extend({fontShape:e,font:""})}sizingClasses(e){return e.size!==this.size?["sizing","reset-size"+e.size,"size"+this.size]:[]}baseSizingClasses(){return this.size!==t.BASESIZE?["sizing","reset-size"+this.size,"size"+t.BASESIZE]:[]}fontMetrics(){return this._fontMetrics||(this._fontMetrics=sxe(this.size)),this._fontMetrics}getColor(){return this.phantom?"transparent":this.color}};d4.BASESIZE=6;w7={pt:1,mm:7227/2540,cm:7227/254,in:72.27,bp:803/800,pc:12,dd:1238/1157,cc:14856/1157,nd:685/642,nc:1370/107,sp:1/65536,px:803/800},lxe={ex:!0,em:!0,mu:!0},DG=o(function(e){return typeof e!="string"&&(e=e.unit),e in w7||e in lxe||e==="ex"},"validUnit"),Hn=o(function(e,r){var n;if(e.unit in w7)n=w7[e.unit]/r.fontMetrics().ptPerEm/r.sizeMultiplier;else if(e.unit==="mu")n=r.fontMetrics().cssEmPerMu;else{var i;if(r.style.isTight()?i=r.havingStyle(r.style.text()):i=r,e.unit==="ex")n=i.fontMetrics().xHeight;else if(e.unit==="em")n=i.fontMetrics().quad;else throw new nt("Invalid unit: '"+e.unit+"'");i!==r&&(n*=i.sizeMultiplier/r.sizeMultiplier)}return Math.min(e.number*n,r.maxSize)},"calculateSize"),ct=o(function(e){return+e.toFixed(4)+"em"},"makeEm"),dh=o(function(e){return e.filter(r=>r).join(" ")},"createClass"),RG=o(function(e,r,n){if(this.classes=e||[],this.attributes={},this.height=0,this.depth=0,this.maxFontSize=0,this.style=n||{},r){r.style.isTight()&&this.classes.push("mtight");var i=r.getColor();i&&(this.style.color=i)}},"initNode"),NG=o(function(e){var r=document.createElement(e);r.className=dh(this.classes);for(var n in this.style)this.style.hasOwnProperty(n)&&(r.style[n]=this.style[n]);for(var i in this.attributes)this.attributes.hasOwnProperty(i)&&r.setAttribute(i,this.attributes[i]);for(var a=0;a",r},"toMarkup"),jf=class{static{o(this,"Span")}constructor(e,r,n,i){this.children=void 0,this.attributes=void 0,this.classes=void 0,this.height=void 0,this.depth=void 0,this.width=void 0,this.maxFontSize=void 0,this.style=void 0,RG.call(this,e,n,i),this.children=r||[]}setAttribute(e,r){this.attributes[e]=r}hasClass(e){return Vt.contains(this.classes,e)}toNode(){return NG.call(this,"span")}toMarkup(){return MG.call(this,"span")}},iy=class{static{o(this,"Anchor")}constructor(e,r,n,i){this.children=void 0,this.attributes=void 0,this.classes=void 0,this.height=void 0,this.depth=void 0,this.maxFontSize=void 0,this.style=void 0,RG.call(this,r,i),this.children=n||[],this.setAttribute("href",e)}setAttribute(e,r){this.attributes[e]=r}hasClass(e){return Vt.contains(this.classes,e)}toNode(){return NG.call(this,"a")}toMarkup(){return MG.call(this,"a")}},T7=class{static{o(this,"Img")}constructor(e,r,n){this.src=void 0,this.alt=void 0,this.classes=void 0,this.height=void 0,this.depth=void 0,this.maxFontSize=void 0,this.style=void 0,this.alt=r,this.src=e,this.classes=["mord"],this.style=n}hasClass(e){return Vt.contains(this.classes,e)}toNode(){var e=document.createElement("img");e.src=this.src,e.alt=this.alt,e.className="mord";for(var r in this.style)this.style.hasOwnProperty(r)&&(e.style[r]=this.style[r]);return e}toMarkup(){var e=''+Vt.escape(this.alt)+'0&&(r=document.createElement("span"),r.style.marginRight=ct(this.italic)),this.classes.length>0&&(r=r||document.createElement("span"),r.className=dh(this.classes));for(var n in this.style)this.style.hasOwnProperty(n)&&(r=r||document.createElement("span"),r.style[n]=this.style[n]);return r?(r.appendChild(e),r):e}toMarkup(){var e=!1,r="0&&(n+="margin-right:"+this.italic+"em;");for(var i in this.style)this.style.hasOwnProperty(i)&&(n+=Vt.hyphenate(i)+":"+this.style[i]+";");n&&(e=!0,r+=' style="'+Vt.escape(n)+'"');var a=Vt.escape(this.text);return e?(r+=">",r+=a,r+="",r):a}},ll=class{static{o(this,"SvgNode")}constructor(e,r){this.children=void 0,this.attributes=void 0,this.children=e||[],this.attributes=r||{}}toNode(){var e="http://www.w3.org/2000/svg",r=document.createElementNS(e,"svg");for(var n in this.attributes)Object.prototype.hasOwnProperty.call(this.attributes,n)&&r.setAttribute(n,this.attributes[n]);for(var i=0;i':''}},ay=class{static{o(this,"LineNode")}constructor(e){this.attributes=void 0,this.attributes=e||{}}toNode(){var e="http://www.w3.org/2000/svg",r=document.createElementNS(e,"line");for(var n in this.attributes)Object.prototype.hasOwnProperty.call(this.attributes,n)&&r.setAttribute(n,this.attributes[n]);return r}toMarkup(){var e="","\\gt",!0);G(U,ee,xe,"\u2208","\\in",!0);G(U,ee,xe,"\uE020","\\@not");G(U,ee,xe,"\u2282","\\subset",!0);G(U,ee,xe,"\u2283","\\supset",!0);G(U,ee,xe,"\u2286","\\subseteq",!0);G(U,ee,xe,"\u2287","\\supseteq",!0);G(U,ve,xe,"\u2288","\\nsubseteq",!0);G(U,ve,xe,"\u2289","\\nsupseteq",!0);G(U,ee,xe,"\u22A8","\\models");G(U,ee,xe,"\u2190","\\leftarrow",!0);G(U,ee,xe,"\u2264","\\le");G(U,ee,xe,"\u2264","\\leq",!0);G(U,ee,xe,"<","\\lt",!0);G(U,ee,xe,"\u2192","\\rightarrow",!0);G(U,ee,xe,"\u2192","\\to");G(U,ve,xe,"\u2271","\\ngeq",!0);G(U,ve,xe,"\u2270","\\nleq",!0);G(U,ee,lu,"\xA0","\\ ");G(U,ee,lu,"\xA0","\\space");G(U,ee,lu,"\xA0","\\nobreakspace");G(Qe,ee,lu,"\xA0","\\ ");G(Qe,ee,lu,"\xA0"," ");G(Qe,ee,lu,"\xA0","\\space");G(Qe,ee,lu,"\xA0","\\nobreakspace");G(U,ee,lu,null,"\\nobreak");G(U,ee,lu,null,"\\allowbreak");G(U,ee,b4,",",",");G(U,ee,b4,";",";");G(U,ve,bt,"\u22BC","\\barwedge",!0);G(U,ve,bt,"\u22BB","\\veebar",!0);G(U,ee,bt,"\u2299","\\odot",!0);G(U,ee,bt,"\u2295","\\oplus",!0);G(U,ee,bt,"\u2297","\\otimes",!0);G(U,ee,Le,"\u2202","\\partial",!0);G(U,ee,bt,"\u2298","\\oslash",!0);G(U,ve,bt,"\u229A","\\circledcirc",!0);G(U,ve,bt,"\u22A1","\\boxdot",!0);G(U,ee,bt,"\u25B3","\\bigtriangleup");G(U,ee,bt,"\u25BD","\\bigtriangledown");G(U,ee,bt,"\u2020","\\dagger");G(U,ee,bt,"\u22C4","\\diamond");G(U,ee,bt,"\u22C6","\\star");G(U,ee,bt,"\u25C3","\\triangleleft");G(U,ee,bt,"\u25B9","\\triangleright");G(U,ee,js,"{","\\{");G(Qe,ee,Le,"{","\\{");G(Qe,ee,Le,"{","\\textbraceleft");G(U,ee,Xa,"}","\\}");G(Qe,ee,Le,"}","\\}");G(Qe,ee,Le,"}","\\textbraceright");G(U,ee,js,"{","\\lbrace");G(U,ee,Xa,"}","\\rbrace");G(U,ee,js,"[","\\lbrack",!0);G(Qe,ee,Le,"[","\\lbrack",!0);G(U,ee,Xa,"]","\\rbrack",!0);G(Qe,ee,Le,"]","\\rbrack",!0);G(U,ee,js,"(","\\lparen",!0);G(U,ee,Xa,")","\\rparen",!0);G(Qe,ee,Le,"<","\\textless",!0);G(Qe,ee,Le,">","\\textgreater",!0);G(U,ee,js,"\u230A","\\lfloor",!0);G(U,ee,Xa,"\u230B","\\rfloor",!0);G(U,ee,js,"\u2308","\\lceil",!0);G(U,ee,Xa,"\u2309","\\rceil",!0);G(U,ee,Le,"\\","\\backslash");G(U,ee,Le,"\u2223","|");G(U,ee,Le,"\u2223","\\vert");G(Qe,ee,Le,"|","\\textbar",!0);G(U,ee,Le,"\u2225","\\|");G(U,ee,Le,"\u2225","\\Vert");G(Qe,ee,Le,"\u2225","\\textbardbl");G(Qe,ee,Le,"~","\\textasciitilde");G(Qe,ee,Le,"\\","\\textbackslash");G(Qe,ee,Le,"^","\\textasciicircum");G(U,ee,xe,"\u2191","\\uparrow",!0);G(U,ee,xe,"\u21D1","\\Uparrow",!0);G(U,ee,xe,"\u2193","\\downarrow",!0);G(U,ee,xe,"\u21D3","\\Downarrow",!0);G(U,ee,xe,"\u2195","\\updownarrow",!0);G(U,ee,xe,"\u21D5","\\Updownarrow",!0);G(U,ee,xi,"\u2210","\\coprod");G(U,ee,xi,"\u22C1","\\bigvee");G(U,ee,xi,"\u22C0","\\bigwedge");G(U,ee,xi,"\u2A04","\\biguplus");G(U,ee,xi,"\u22C2","\\bigcap");G(U,ee,xi,"\u22C3","\\bigcup");G(U,ee,xi,"\u222B","\\int");G(U,ee,xi,"\u222B","\\intop");G(U,ee,xi,"\u222C","\\iint");G(U,ee,xi,"\u222D","\\iiint");G(U,ee,xi,"\u220F","\\prod");G(U,ee,xi,"\u2211","\\sum");G(U,ee,xi,"\u2A02","\\bigotimes");G(U,ee,xi,"\u2A01","\\bigoplus");G(U,ee,xi,"\u2A00","\\bigodot");G(U,ee,xi,"\u222E","\\oint");G(U,ee,xi,"\u222F","\\oiint");G(U,ee,xi,"\u2230","\\oiiint");G(U,ee,xi,"\u2A06","\\bigsqcup");G(U,ee,xi,"\u222B","\\smallint");G(Qe,ee,vp,"\u2026","\\textellipsis");G(U,ee,vp,"\u2026","\\mathellipsis");G(Qe,ee,vp,"\u2026","\\ldots",!0);G(U,ee,vp,"\u2026","\\ldots",!0);G(U,ee,vp,"\u22EF","\\@cdots",!0);G(U,ee,vp,"\u22F1","\\ddots",!0);G(U,ee,Le,"\u22EE","\\varvdots");G(U,ee,Pn,"\u02CA","\\acute");G(U,ee,Pn,"\u02CB","\\grave");G(U,ee,Pn,"\xA8","\\ddot");G(U,ee,Pn,"~","\\tilde");G(U,ee,Pn,"\u02C9","\\bar");G(U,ee,Pn,"\u02D8","\\breve");G(U,ee,Pn,"\u02C7","\\check");G(U,ee,Pn,"^","\\hat");G(U,ee,Pn,"\u20D7","\\vec");G(U,ee,Pn,"\u02D9","\\dot");G(U,ee,Pn,"\u02DA","\\mathring");G(U,ee,Ut,"\uE131","\\@imath");G(U,ee,Ut,"\uE237","\\@jmath");G(U,ee,Le,"\u0131","\u0131");G(U,ee,Le,"\u0237","\u0237");G(Qe,ee,Le,"\u0131","\\i",!0);G(Qe,ee,Le,"\u0237","\\j",!0);G(Qe,ee,Le,"\xDF","\\ss",!0);G(Qe,ee,Le,"\xE6","\\ae",!0);G(Qe,ee,Le,"\u0153","\\oe",!0);G(Qe,ee,Le,"\xF8","\\o",!0);G(Qe,ee,Le,"\xC6","\\AE",!0);G(Qe,ee,Le,"\u0152","\\OE",!0);G(Qe,ee,Le,"\xD8","\\O",!0);G(Qe,ee,Pn,"\u02CA","\\'");G(Qe,ee,Pn,"\u02CB","\\`");G(Qe,ee,Pn,"\u02C6","\\^");G(Qe,ee,Pn,"\u02DC","\\~");G(Qe,ee,Pn,"\u02C9","\\=");G(Qe,ee,Pn,"\u02D8","\\u");G(Qe,ee,Pn,"\u02D9","\\.");G(Qe,ee,Pn,"\xB8","\\c");G(Qe,ee,Pn,"\u02DA","\\r");G(Qe,ee,Pn,"\u02C7","\\v");G(Qe,ee,Pn,"\xA8",'\\"');G(Qe,ee,Pn,"\u02DD","\\H");G(Qe,ee,Pn,"\u25EF","\\textcircled");IG={"--":!0,"---":!0,"``":!0,"''":!0};G(Qe,ee,Le,"\u2013","--",!0);G(Qe,ee,Le,"\u2013","\\textendash");G(Qe,ee,Le,"\u2014","---",!0);G(Qe,ee,Le,"\u2014","\\textemdash");G(Qe,ee,Le,"\u2018","`",!0);G(Qe,ee,Le,"\u2018","\\textquoteleft");G(Qe,ee,Le,"\u2019","'",!0);G(Qe,ee,Le,"\u2019","\\textquoteright");G(Qe,ee,Le,"\u201C","``",!0);G(Qe,ee,Le,"\u201C","\\textquotedblleft");G(Qe,ee,Le,"\u201D","''",!0);G(Qe,ee,Le,"\u201D","\\textquotedblright");G(U,ee,Le,"\xB0","\\degree",!0);G(Qe,ee,Le,"\xB0","\\degree");G(Qe,ee,Le,"\xB0","\\textdegree",!0);G(U,ee,Le,"\xA3","\\pounds");G(U,ee,Le,"\xA3","\\mathsterling",!0);G(Qe,ee,Le,"\xA3","\\pounds");G(Qe,ee,Le,"\xA3","\\textsterling",!0);G(U,ve,Le,"\u2720","\\maltese");G(Qe,ve,Le,"\u2720","\\maltese");rG='0123456789/@."';for(e4=0;e40)return ol(a,h,i,r,s.concat(f));if(u){var d,p;if(u==="boldsymbol"){var m=mxe(a,i,r,s,n);d=m.fontName,p=[m.fontClass]}else l?(d=BG[u].fontName,p=[u]):(d=a4(u,r.fontWeight,r.fontShape),p=[u,r.fontWeight,r.fontShape]);if(w4(a,d,i).metrics)return ol(a,d,i,r,s.concat(p));if(IG.hasOwnProperty(a)&&d.slice(0,10)==="Typewriter"){for(var g=[],y=0;y{if(dh(t.classes)!==dh(e.classes)||t.skew!==e.skew||t.maxFontSize!==e.maxFontSize)return!1;if(t.classes.length===1){var r=t.classes[0];if(r==="mbin"||r==="mord")return!1}for(var n in t.style)if(t.style.hasOwnProperty(n)&&t.style[n]!==e.style[n])return!1;for(var i in e.style)if(e.style.hasOwnProperty(i)&&t.style[i]!==e.style[i])return!1;return!0},"canCombine"),vxe=o(t=>{for(var e=0;er&&(r=s.height),s.depth>n&&(n=s.depth),s.maxFontSize>i&&(i=s.maxFontSize)}e.height=r,e.depth=n,e.maxFontSize=i},"sizeElementFromChildren"),ds=o(function(e,r,n,i){var a=new jf(e,r,n,i);return I7(a),a},"makeSpan"),OG=o((t,e,r,n)=>new jf(t,e,r,n),"makeSvgSpan"),xxe=o(function(e,r,n){var i=ds([e],[],r);return i.height=Math.max(n||r.fontMetrics().defaultRuleThickness,r.minRuleThickness),i.style.borderBottomWidth=ct(i.height),i.maxFontSize=1,i},"makeLineSpan"),bxe=o(function(e,r,n,i){var a=new iy(e,r,n,i);return I7(a),a},"makeAnchor"),PG=o(function(e){var r=new Xf(e);return I7(r),r},"makeFragment"),wxe=o(function(e,r){return e instanceof Xf?ds([],[e],r):e},"wrapFragment"),Txe=o(function(e){if(e.positionType==="individualShift"){for(var r=e.children,n=[r[0]],i=-r[0].shift-r[0].elem.depth,a=i,s=1;s{var r=ds(["mspace"],[],e),n=Hn(t,e);return r.style.marginRight=ct(n),r},"makeGlue"),a4=o(function(e,r,n){var i="";switch(e){case"amsrm":i="AMS";break;case"textrm":i="Main";break;case"textsf":i="SansSerif";break;case"texttt":i="Typewriter";break;default:i=e}var a;return r==="textbf"&&n==="textit"?a="BoldItalic":r==="textbf"?a="Bold":r==="textit"?a="Italic":a="Regular",i+"-"+a},"retrieveTextFontName"),BG={mathbf:{variant:"bold",fontName:"Main-Bold"},mathrm:{variant:"normal",fontName:"Main-Regular"},textit:{variant:"italic",fontName:"Main-Italic"},mathit:{variant:"italic",fontName:"Main-Italic"},mathnormal:{variant:"italic",fontName:"Math-Italic"},mathbb:{variant:"double-struck",fontName:"AMS-Regular"},mathcal:{variant:"script",fontName:"Caligraphic-Regular"},mathfrak:{variant:"fraktur",fontName:"Fraktur-Regular"},mathscr:{variant:"script",fontName:"Script-Regular"},mathsf:{variant:"sans-serif",fontName:"SansSerif-Regular"},mathtt:{variant:"monospace",fontName:"Typewriter-Regular"}},FG={vec:["vec",.471,.714],oiintSize1:["oiintSize1",.957,.499],oiintSize2:["oiintSize2",1.472,.659],oiiintSize1:["oiiintSize1",1.304,.499],oiiintSize2:["oiiintSize2",1.98,.659]},Cxe=o(function(e,r){var[n,i,a]=FG[e],s=new Jl(n),l=new ll([s],{width:ct(i),height:ct(a),style:"width:"+ct(i),viewBox:"0 0 "+1e3*i+" "+1e3*a,preserveAspectRatio:"xMinYMin"}),u=OG(["overlay"],[l],r);return u.height=a,u.style.height=ct(a),u.style.width=ct(i),u},"staticSvg"),Be={fontMap:BG,makeSymbol:ol,mathsym:pxe,makeSpan:ds,makeSvgSpan:OG,makeLineSpan:xxe,makeAnchor:bxe,makeFragment:PG,wrapFragment:wxe,makeVList:kxe,makeOrd:gxe,makeGlue:Exe,staticSvg:Cxe,svgData:FG,tryCombineChars:vxe},Un={number:3,unit:"mu"},Wf={number:4,unit:"mu"},nu={number:5,unit:"mu"},Sxe={mord:{mop:Un,mbin:Wf,mrel:nu,minner:Un},mop:{mord:Un,mop:Un,mrel:nu,minner:Un},mbin:{mord:Wf,mop:Wf,mopen:Wf,minner:Wf},mrel:{mord:nu,mop:nu,mopen:nu,minner:nu},mopen:{},mclose:{mop:Un,mbin:Wf,mrel:nu,minner:Un},mpunct:{mord:Un,mop:Un,mrel:nu,mopen:Un,mclose:Un,mpunct:Un,minner:Un},minner:{mord:Un,mop:Un,mbin:Wf,mrel:nu,mopen:Un,mpunct:Un,minner:Un}},Axe={mord:{mop:Un},mop:{mord:Un,mop:Un},mbin:{},mrel:{},mopen:{},mclose:{mop:Un},mpunct:{},minner:{mop:Un}},zG={},m4={},g4={};o(vt,"defineFunction");o(Kf,"defineFunctionBuilders");y4=o(function(e){return e.type==="ordgroup"&&e.body.length===1?e.body[0]:e},"normalizeArgument"),ui=o(function(e){return e.type==="ordgroup"?e.body:[e]},"ordargument"),su=Be.makeSpan,_xe=["leftmost","mbin","mopen","mrel","mop","mpunct"],Lxe=["rightmost","mrel","mclose","mpunct"],Dxe={display:Ht.DISPLAY,text:Ht.TEXT,script:Ht.SCRIPT,scriptscript:Ht.SCRIPTSCRIPT},Rxe={mord:"mord",mop:"mop",mbin:"mbin",mrel:"mrel",mopen:"mopen",mclose:"mclose",mpunct:"mpunct",minner:"minner"},Ri=o(function(e,r,n,i){i===void 0&&(i=[null,null]);for(var a=[],s=0;s{var v=y.classes[0],x=g.classes[0];v==="mbin"&&Vt.contains(Lxe,x)?y.classes[0]="mord":x==="mbin"&&Vt.contains(_xe,v)&&(g.classes[0]="mord")},{node:d},p,m),aG(a,(g,y)=>{var v=E7(y),x=E7(g),b=v&&x?g.hasClass("mtight")?Axe[v][x]:Sxe[v][x]:null;if(b)return Be.makeGlue(b,h)},{node:d},p,m),a},"buildExpression"),aG=o(function t(e,r,n,i,a){i&&e.push(i);for(var s=0;sp=>{e.splice(d+1,0,p),s++})(s)}i&&e.pop()},"traverseNonSpaceNodes"),GG=o(function(e){return e instanceof Xf||e instanceof iy||e instanceof jf&&e.hasClass("enclosing")?e:null},"checkPartialGroup"),Nxe=o(function t(e,r){var n=GG(e);if(n){var i=n.children;if(i.length){if(r==="right")return t(i[i.length-1],"right");if(r==="left")return t(i[0],"left")}}return e},"getOutermostNode"),E7=o(function(e,r){return e?(r&&(e=Nxe(e,r)),Rxe[e.classes[0]]||null):null},"getTypeOfDomTree"),sy=o(function(e,r){var n=["nulldelimiter"].concat(e.baseSizingClasses());return su(r.concat(n))},"makeNullDelimiter"),Cr=o(function(e,r,n){if(!e)return su();if(m4[e.type]){var i=m4[e.type](e,r);if(n&&r.size!==n.size){i=su(r.sizingClasses(n),[i],r);var a=r.sizeMultiplier/n.sizeMultiplier;i.height*=a,i.depth*=a}return i}else throw new nt("Got group of unknown type: '"+e.type+"'")},"buildGroup");o(s4,"buildHTMLUnbreakable");o(C7,"buildHTML");o($G,"newDocumentFragment");ps=class{static{o(this,"MathNode")}constructor(e,r,n){this.type=void 0,this.attributes=void 0,this.children=void 0,this.classes=void 0,this.type=e,this.attributes={},this.children=r||[],this.classes=n||[]}setAttribute(e,r){this.attributes[e]=r}getAttribute(e){return this.attributes[e]}toNode(){var e=document.createElementNS("http://www.w3.org/1998/Math/MathML",this.type);for(var r in this.attributes)Object.prototype.hasOwnProperty.call(this.attributes,r)&&e.setAttribute(r,this.attributes[r]);this.classes.length>0&&(e.className=dh(this.classes));for(var n=0;n0&&(e+=' class ="'+Vt.escape(dh(this.classes))+'"'),e+=">";for(var n=0;n",e}toText(){return this.children.map(e=>e.toText()).join("")}},qf=class{static{o(this,"TextNode")}constructor(e){this.text=void 0,this.text=e}toNode(){return document.createTextNode(this.text)}toMarkup(){return Vt.escape(this.toText())}toText(){return this.text}},S7=class{static{o(this,"SpaceNode")}constructor(e){this.width=void 0,this.character=void 0,this.width=e,e>=.05555&&e<=.05556?this.character="\u200A":e>=.1666&&e<=.1667?this.character="\u2009":e>=.2222&&e<=.2223?this.character="\u2005":e>=.2777&&e<=.2778?this.character="\u2005\u200A":e>=-.05556&&e<=-.05555?this.character="\u200A\u2063":e>=-.1667&&e<=-.1666?this.character="\u2009\u2063":e>=-.2223&&e<=-.2222?this.character="\u205F\u2063":e>=-.2778&&e<=-.2777?this.character="\u2005\u2063":this.character=null}toNode(){if(this.character)return document.createTextNode(this.character);var e=document.createElementNS("http://www.w3.org/1998/Math/MathML","mspace");return e.setAttribute("width",ct(this.width)),e}toMarkup(){return this.character?""+this.character+"":''}toText(){return this.character?this.character:" "}},et={MathNode:ps,TextNode:qf,SpaceNode:S7,newDocumentFragment:$G},_o=o(function(e,r,n){return wn[r][e]&&wn[r][e].replace&&e.charCodeAt(0)!==55349&&!(IG.hasOwnProperty(e)&&n&&(n.fontFamily&&n.fontFamily.slice(4,6)==="tt"||n.font&&n.font.slice(4,6)==="tt"))&&(e=wn[r][e].replace),new et.TextNode(e)},"makeText"),O7=o(function(e){return e.length===1?e[0]:new et.MathNode("mrow",e)},"makeRow"),P7=o(function(e,r){if(r.fontFamily==="texttt")return"monospace";if(r.fontFamily==="textsf")return r.fontShape==="textit"&&r.fontWeight==="textbf"?"sans-serif-bold-italic":r.fontShape==="textit"?"sans-serif-italic":r.fontWeight==="textbf"?"bold-sans-serif":"sans-serif";if(r.fontShape==="textit"&&r.fontWeight==="textbf")return"bold-italic";if(r.fontShape==="textit")return"italic";if(r.fontWeight==="textbf")return"bold";var n=r.font;if(!n||n==="mathnormal")return null;var i=e.mode;if(n==="mathit")return"italic";if(n==="boldsymbol")return e.type==="textord"?"bold":"bold-italic";if(n==="mathbf")return"bold";if(n==="mathbb")return"double-struck";if(n==="mathfrak")return"fraktur";if(n==="mathscr"||n==="mathcal")return"script";if(n==="mathsf")return"sans-serif";if(n==="mathtt")return"monospace";var a=e.text;if(Vt.contains(["\\imath","\\jmath"],a))return null;wn[i][a]&&wn[i][a].replace&&(a=wn[i][a].replace);var s=Be.fontMap[n].fontName;return M7(a,s,i)?Be.fontMap[n].variant:null},"getVariant"),gs=o(function(e,r,n){if(e.length===1){var i=fn(e[0],r);return n&&i instanceof ps&&i.type==="mo"&&(i.setAttribute("lspace","0em"),i.setAttribute("rspace","0em")),[i]}for(var a=[],s,l=0;l0&&(d.text=d.text.slice(0,1)+"\u0338"+d.text.slice(1),a.pop())}}}a.push(u),s=u}return a},"buildExpression"),ph=o(function(e,r,n){return O7(gs(e,r,n))},"buildExpressionRow"),fn=o(function(e,r){if(!e)return new et.MathNode("mrow");if(g4[e.type]){var n=g4[e.type](e,r);return n}else throw new nt("Got group of unknown type: '"+e.type+"'")},"buildGroup");o(sG,"buildMathML");VG=o(function(e){return new d4({style:e.displayMode?Ht.DISPLAY:Ht.TEXT,maxSize:e.maxSize,minRuleThickness:e.minRuleThickness})},"optionsFromSettings"),UG=o(function(e,r){if(r.displayMode){var n=["katex-display"];r.leqno&&n.push("leqno"),r.fleqn&&n.push("fleqn"),e=Be.makeSpan(n,[e])}return e},"displayWrap"),Mxe=o(function(e,r,n){var i=VG(n),a;if(n.output==="mathml")return sG(e,r,i,n.displayMode,!0);if(n.output==="html"){var s=C7(e,i);a=Be.makeSpan(["katex"],[s])}else{var l=sG(e,r,i,n.displayMode,!1),u=C7(e,i);a=Be.makeSpan(["katex"],[l,u])}return UG(a,n)},"buildTree"),Ixe=o(function(e,r,n){var i=VG(n),a=C7(e,i),s=Be.makeSpan(["katex"],[a]);return UG(s,n)},"buildHTMLTree"),Oxe={widehat:"^",widecheck:"\u02C7",widetilde:"~",utilde:"~",overleftarrow:"\u2190",underleftarrow:"\u2190",xleftarrow:"\u2190",overrightarrow:"\u2192",underrightarrow:"\u2192",xrightarrow:"\u2192",underbrace:"\u23DF",overbrace:"\u23DE",overgroup:"\u23E0",undergroup:"\u23E1",overleftrightarrow:"\u2194",underleftrightarrow:"\u2194",xleftrightarrow:"\u2194",Overrightarrow:"\u21D2",xRightarrow:"\u21D2",overleftharpoon:"\u21BC",xleftharpoonup:"\u21BC",overrightharpoon:"\u21C0",xrightharpoonup:"\u21C0",xLeftarrow:"\u21D0",xLeftrightarrow:"\u21D4",xhookleftarrow:"\u21A9",xhookrightarrow:"\u21AA",xmapsto:"\u21A6",xrightharpoondown:"\u21C1",xleftharpoondown:"\u21BD",xrightleftharpoons:"\u21CC",xleftrightharpoons:"\u21CB",xtwoheadleftarrow:"\u219E",xtwoheadrightarrow:"\u21A0",xlongequal:"=",xtofrom:"\u21C4",xrightleftarrows:"\u21C4",xrightequilibrium:"\u21CC",xleftequilibrium:"\u21CB","\\cdrightarrow":"\u2192","\\cdleftarrow":"\u2190","\\cdlongequal":"="},Pxe=o(function(e){var r=new et.MathNode("mo",[new et.TextNode(Oxe[e.replace(/^\\/,"")])]);return r.setAttribute("stretchy","true"),r},"mathMLnode"),Bxe={overrightarrow:[["rightarrow"],.888,522,"xMaxYMin"],overleftarrow:[["leftarrow"],.888,522,"xMinYMin"],underrightarrow:[["rightarrow"],.888,522,"xMaxYMin"],underleftarrow:[["leftarrow"],.888,522,"xMinYMin"],xrightarrow:[["rightarrow"],1.469,522,"xMaxYMin"],"\\cdrightarrow":[["rightarrow"],3,522,"xMaxYMin"],xleftarrow:[["leftarrow"],1.469,522,"xMinYMin"],"\\cdleftarrow":[["leftarrow"],3,522,"xMinYMin"],Overrightarrow:[["doublerightarrow"],.888,560,"xMaxYMin"],xRightarrow:[["doublerightarrow"],1.526,560,"xMaxYMin"],xLeftarrow:[["doubleleftarrow"],1.526,560,"xMinYMin"],overleftharpoon:[["leftharpoon"],.888,522,"xMinYMin"],xleftharpoonup:[["leftharpoon"],.888,522,"xMinYMin"],xleftharpoondown:[["leftharpoondown"],.888,522,"xMinYMin"],overrightharpoon:[["rightharpoon"],.888,522,"xMaxYMin"],xrightharpoonup:[["rightharpoon"],.888,522,"xMaxYMin"],xrightharpoondown:[["rightharpoondown"],.888,522,"xMaxYMin"],xlongequal:[["longequal"],.888,334,"xMinYMin"],"\\cdlongequal":[["longequal"],3,334,"xMinYMin"],xtwoheadleftarrow:[["twoheadleftarrow"],.888,334,"xMinYMin"],xtwoheadrightarrow:[["twoheadrightarrow"],.888,334,"xMaxYMin"],overleftrightarrow:[["leftarrow","rightarrow"],.888,522],overbrace:[["leftbrace","midbrace","rightbrace"],1.6,548],underbrace:[["leftbraceunder","midbraceunder","rightbraceunder"],1.6,548],underleftrightarrow:[["leftarrow","rightarrow"],.888,522],xleftrightarrow:[["leftarrow","rightarrow"],1.75,522],xLeftrightarrow:[["doubleleftarrow","doublerightarrow"],1.75,560],xrightleftharpoons:[["leftharpoondownplus","rightharpoonplus"],1.75,716],xleftrightharpoons:[["leftharpoonplus","rightharpoondownplus"],1.75,716],xhookleftarrow:[["leftarrow","righthook"],1.08,522],xhookrightarrow:[["lefthook","rightarrow"],1.08,522],overlinesegment:[["leftlinesegment","rightlinesegment"],.888,522],underlinesegment:[["leftlinesegment","rightlinesegment"],.888,522],overgroup:[["leftgroup","rightgroup"],.888,342],undergroup:[["leftgroupunder","rightgroupunder"],.888,342],xmapsto:[["leftmapsto","rightarrow"],1.5,522],xtofrom:[["leftToFrom","rightToFrom"],1.75,528],xrightleftarrows:[["baraboveleftarrow","rightarrowabovebar"],1.75,901],xrightequilibrium:[["baraboveshortleftharpoon","rightharpoonaboveshortbar"],1.75,716],xleftequilibrium:[["shortbaraboveleftharpoon","shortrightharpoonabovebar"],1.75,716]},Fxe=o(function(e){return e.type==="ordgroup"?e.body.length:1},"groupLength"),zxe=o(function(e,r){function n(){var l=4e5,u=e.label.slice(1);if(Vt.contains(["widehat","widecheck","widetilde","utilde"],u)){var h=e,f=Fxe(h.base),d,p,m;if(f>5)u==="widehat"||u==="widecheck"?(d=420,l=2364,m=.42,p=u+"4"):(d=312,l=2340,m=.34,p="tilde4");else{var g=[1,1,2,2,3,3][f];u==="widehat"||u==="widecheck"?(l=[0,1062,2364,2364,2364][g],d=[0,239,300,360,420][g],m=[0,.24,.3,.3,.36,.42][g],p=u+g):(l=[0,600,1033,2339,2340][g],d=[0,260,286,306,312][g],m=[0,.26,.286,.3,.306,.34][g],p="tilde"+g)}var y=new Jl(p),v=new ll([y],{width:"100%",height:ct(m),viewBox:"0 0 "+l+" "+d,preserveAspectRatio:"none"});return{span:Be.makeSvgSpan([],[v],r),minWidth:0,height:m}}else{var x=[],b=Bxe[u],[w,S,T]=b,E=T/1e3,_=w.length,A,L;if(_===1){var M=b[3];A=["hide-tail"],L=[M]}else if(_===2)A=["halfarrow-left","halfarrow-right"],L=["xMinYMin","xMaxYMin"];else if(_===3)A=["brace-left","brace-center","brace-right"],L=["xMinYMin","xMidYMin","xMaxYMin"];else throw new Error(`Correct katexImagesData or update code here to support + `+_+" children.");for(var N=0;N<_;N++){var k=new Jl(w[N]),I=new ll([k],{width:"400em",height:ct(E),viewBox:"0 0 "+l+" "+T,preserveAspectRatio:L[N]+" slice"}),C=Be.makeSvgSpan([A[N]],[I],r);if(_===1)return{span:C,minWidth:S,height:E};C.style.height=ct(E),x.push(C)}return{span:Be.makeSpan(["stretchy"],x,r),minWidth:S,height:E}}}o(n,"buildSvgSpan_");var{span:i,minWidth:a,height:s}=n();return i.height=s,i.style.height=ct(s),a>0&&(i.style.minWidth=ct(a)),i},"svgSpan"),Gxe=o(function(e,r,n,i,a){var s,l=e.height+e.depth+n+i;if(/fbox|color|angl/.test(r)){if(s=Be.makeSpan(["stretchy",r],[],a),r==="fbox"){var u=a.color&&a.getColor();u&&(s.style.borderColor=u)}}else{var h=[];/^[bx]cancel$/.test(r)&&h.push(new ay({x1:"0",y1:"0",x2:"100%",y2:"100%","stroke-width":"0.046em"})),/^x?cancel$/.test(r)&&h.push(new ay({x1:"0",y1:"100%",x2:"100%",y2:"0","stroke-width":"0.046em"}));var f=new ll(h,{width:"100%",height:ct(l)});s=Be.makeSvgSpan([],[f],a)}return s.height=l,s.style.height=ct(l),s},"encloseSpan"),ou={encloseSpan:Gxe,mathMLnode:Pxe,svgSpan:zxe};o(ir,"assertNodeType");o(B7,"assertSymbolNodeType");o(T4,"checkSymbolNodeType");F7=o((t,e)=>{var r,n,i;t&&t.type==="supsub"?(n=ir(t.base,"accent"),r=n.base,t.base=r,i=uxe(Cr(t,e)),t.base=n):(n=ir(t,"accent"),r=n.base);var a=Cr(r,e.havingCrampedStyle()),s=n.isShifty&&Vt.isCharacterBox(r),l=0;if(s){var u=Vt.getBaseElem(r),h=Cr(u,e.havingCrampedStyle());l=tG(h).skew}var f=n.label==="\\c",d=f?a.height+a.depth:Math.min(a.height,e.fontMetrics().xHeight),p;if(n.isStretchy)p=ou.svgSpan(n,e),p=Be.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:a},{type:"elem",elem:p,wrapperClasses:["svg-align"],wrapperStyle:l>0?{width:"calc(100% - "+ct(2*l)+")",marginLeft:ct(2*l)}:void 0}]},e);else{var m,g;n.label==="\\vec"?(m=Be.staticSvg("vec",e),g=Be.svgData.vec[1]):(m=Be.makeOrd({mode:n.mode,text:n.label},e,"textord"),m=tG(m),m.italic=0,g=m.width,f&&(d+=m.depth)),p=Be.makeSpan(["accent-body"],[m]);var y=n.label==="\\textcircled";y&&(p.classes.push("accent-full"),d=a.height);var v=l;y||(v-=g/2),p.style.left=ct(v),n.label==="\\textcircled"&&(p.style.top=".2em"),p=Be.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:a},{type:"kern",size:-d},{type:"elem",elem:p}]},e)}var x=Be.makeSpan(["mord","accent"],[p],e);return i?(i.children[0]=x,i.height=Math.max(x.height,i.height),i.classes[0]="mord",i):x},"htmlBuilder$a"),HG=o((t,e)=>{var r=t.isStretchy?ou.mathMLnode(t.label):new et.MathNode("mo",[_o(t.label,t.mode)]),n=new et.MathNode("mover",[fn(t.base,e),r]);return n.setAttribute("accent","true"),n},"mathmlBuilder$9"),$xe=new RegExp(["\\acute","\\grave","\\ddot","\\tilde","\\bar","\\breve","\\check","\\hat","\\vec","\\dot","\\mathring"].map(t=>"\\"+t).join("|"));vt({type:"accent",names:["\\acute","\\grave","\\ddot","\\tilde","\\bar","\\breve","\\check","\\hat","\\vec","\\dot","\\mathring","\\widecheck","\\widehat","\\widetilde","\\overrightarrow","\\overleftarrow","\\Overrightarrow","\\overleftrightarrow","\\overgroup","\\overlinesegment","\\overleftharpoon","\\overrightharpoon"],props:{numArgs:1},handler:o((t,e)=>{var r=y4(e[0]),n=!$xe.test(t.funcName),i=!n||t.funcName==="\\widehat"||t.funcName==="\\widetilde"||t.funcName==="\\widecheck";return{type:"accent",mode:t.parser.mode,label:t.funcName,isStretchy:n,isShifty:i,base:r}},"handler"),htmlBuilder:F7,mathmlBuilder:HG});vt({type:"accent",names:["\\'","\\`","\\^","\\~","\\=","\\u","\\.",'\\"',"\\c","\\r","\\H","\\v","\\textcircled"],props:{numArgs:1,allowedInText:!0,allowedInMath:!0,argTypes:["primitive"]},handler:o((t,e)=>{var r=e[0],n=t.parser.mode;return n==="math"&&(t.parser.settings.reportNonstrict("mathVsTextAccents","LaTeX's accent "+t.funcName+" works only in text mode"),n="text"),{type:"accent",mode:n,label:t.funcName,isStretchy:!1,isShifty:!0,base:r}},"handler"),htmlBuilder:F7,mathmlBuilder:HG});vt({type:"accentUnder",names:["\\underleftarrow","\\underrightarrow","\\underleftrightarrow","\\undergroup","\\underlinesegment","\\utilde"],props:{numArgs:1},handler:o((t,e)=>{var{parser:r,funcName:n}=t,i=e[0];return{type:"accentUnder",mode:r.mode,label:n,base:i}},"handler"),htmlBuilder:o((t,e)=>{var r=Cr(t.base,e),n=ou.svgSpan(t,e),i=t.label==="\\utilde"?.12:0,a=Be.makeVList({positionType:"top",positionData:r.height,children:[{type:"elem",elem:n,wrapperClasses:["svg-align"]},{type:"kern",size:i},{type:"elem",elem:r}]},e);return Be.makeSpan(["mord","accentunder"],[a],e)},"htmlBuilder"),mathmlBuilder:o((t,e)=>{var r=ou.mathMLnode(t.label),n=new et.MathNode("munder",[fn(t.base,e),r]);return n.setAttribute("accentunder","true"),n},"mathmlBuilder")});o4=o(t=>{var e=new et.MathNode("mpadded",t?[t]:[]);return e.setAttribute("width","+0.6em"),e.setAttribute("lspace","0.3em"),e},"paddedNode");vt({type:"xArrow",names:["\\xleftarrow","\\xrightarrow","\\xLeftarrow","\\xRightarrow","\\xleftrightarrow","\\xLeftrightarrow","\\xhookleftarrow","\\xhookrightarrow","\\xmapsto","\\xrightharpoondown","\\xrightharpoonup","\\xleftharpoondown","\\xleftharpoonup","\\xrightleftharpoons","\\xleftrightharpoons","\\xlongequal","\\xtwoheadrightarrow","\\xtwoheadleftarrow","\\xtofrom","\\xrightleftarrows","\\xrightequilibrium","\\xleftequilibrium","\\\\cdrightarrow","\\\\cdleftarrow","\\\\cdlongequal"],props:{numArgs:1,numOptionalArgs:1},handler(t,e,r){var{parser:n,funcName:i}=t;return{type:"xArrow",mode:n.mode,label:i,body:e[0],below:r[0]}},htmlBuilder(t,e){var r=e.style,n=e.havingStyle(r.sup()),i=Be.wrapFragment(Cr(t.body,n,e),e),a=t.label.slice(0,2)==="\\x"?"x":"cd";i.classes.push(a+"-arrow-pad");var s;t.below&&(n=e.havingStyle(r.sub()),s=Be.wrapFragment(Cr(t.below,n,e),e),s.classes.push(a+"-arrow-pad"));var l=ou.svgSpan(t,e),u=-e.fontMetrics().axisHeight+.5*l.height,h=-e.fontMetrics().axisHeight-.5*l.height-.111;(i.depth>.25||t.label==="\\xleftequilibrium")&&(h-=i.depth);var f;if(s){var d=-e.fontMetrics().axisHeight+s.height+.5*l.height+.111;f=Be.makeVList({positionType:"individualShift",children:[{type:"elem",elem:i,shift:h},{type:"elem",elem:l,shift:u},{type:"elem",elem:s,shift:d}]},e)}else f=Be.makeVList({positionType:"individualShift",children:[{type:"elem",elem:i,shift:h},{type:"elem",elem:l,shift:u}]},e);return f.children[0].children[0].children[1].classes.push("svg-align"),Be.makeSpan(["mrel","x-arrow"],[f],e)},mathmlBuilder(t,e){var r=ou.mathMLnode(t.label);r.setAttribute("minsize",t.label.charAt(0)==="x"?"1.75em":"3.0em");var n;if(t.body){var i=o4(fn(t.body,e));if(t.below){var a=o4(fn(t.below,e));n=new et.MathNode("munderover",[r,a,i])}else n=new et.MathNode("mover",[r,i])}else if(t.below){var s=o4(fn(t.below,e));n=new et.MathNode("munder",[r,s])}else n=o4(),n=new et.MathNode("mover",[r,n]);return n}});Vxe=Be.makeSpan;o(YG,"htmlBuilder$9");o(WG,"mathmlBuilder$8");vt({type:"mclass",names:["\\mathord","\\mathbin","\\mathrel","\\mathopen","\\mathclose","\\mathpunct","\\mathinner"],props:{numArgs:1,primitive:!0},handler(t,e){var{parser:r,funcName:n}=t,i=e[0];return{type:"mclass",mode:r.mode,mclass:"m"+n.slice(5),body:ui(i),isCharacterBox:Vt.isCharacterBox(i)}},htmlBuilder:YG,mathmlBuilder:WG});k4=o(t=>{var e=t.type==="ordgroup"&&t.body.length?t.body[0]:t;return e.type==="atom"&&(e.family==="bin"||e.family==="rel")?"m"+e.family:"mord"},"binrelClass");vt({type:"mclass",names:["\\@binrel"],props:{numArgs:2},handler(t,e){var{parser:r}=t;return{type:"mclass",mode:r.mode,mclass:k4(e[0]),body:ui(e[1]),isCharacterBox:Vt.isCharacterBox(e[1])}}});vt({type:"mclass",names:["\\stackrel","\\overset","\\underset"],props:{numArgs:2},handler(t,e){var{parser:r,funcName:n}=t,i=e[1],a=e[0],s;n!=="\\stackrel"?s=k4(i):s="mrel";var l={type:"op",mode:i.mode,limits:!0,alwaysHandleSupSub:!0,parentIsSupSub:!1,symbol:!1,suppressBaseShift:n!=="\\stackrel",body:ui(i)},u={type:"supsub",mode:a.mode,base:l,sup:n==="\\underset"?null:a,sub:n==="\\underset"?a:null};return{type:"mclass",mode:r.mode,mclass:s,body:[u],isCharacterBox:Vt.isCharacterBox(u)}},htmlBuilder:YG,mathmlBuilder:WG});vt({type:"pmb",names:["\\pmb"],props:{numArgs:1,allowedInText:!0},handler(t,e){var{parser:r}=t;return{type:"pmb",mode:r.mode,mclass:k4(e[0]),body:ui(e[0])}},htmlBuilder(t,e){var r=Ri(t.body,e,!0),n=Be.makeSpan([t.mclass],r,e);return n.style.textShadow="0.02em 0.01em 0.04px",n},mathmlBuilder(t,e){var r=gs(t.body,e),n=new et.MathNode("mstyle",r);return n.setAttribute("style","text-shadow: 0.02em 0.01em 0.04px"),n}});Uxe={">":"\\\\cdrightarrow","<":"\\\\cdleftarrow","=":"\\\\cdlongequal",A:"\\uparrow",V:"\\downarrow","|":"\\Vert",".":"no arrow"},oG=o(()=>({type:"styling",body:[],mode:"math",style:"display"}),"newCell"),lG=o(t=>t.type==="textord"&&t.text==="@","isStartOfArrow"),Hxe=o((t,e)=>(t.type==="mathord"||t.type==="atom")&&t.text===e,"isLabelEnd");o(Yxe,"cdArrow");o(Wxe,"parseCD");vt({type:"cdlabel",names:["\\\\cdleft","\\\\cdright"],props:{numArgs:1},handler(t,e){var{parser:r,funcName:n}=t;return{type:"cdlabel",mode:r.mode,side:n.slice(4),label:e[0]}},htmlBuilder(t,e){var r=e.havingStyle(e.style.sup()),n=Be.wrapFragment(Cr(t.label,r,e),e);return n.classes.push("cd-label-"+t.side),n.style.bottom=ct(.8-n.depth),n.height=0,n.depth=0,n},mathmlBuilder(t,e){var r=new et.MathNode("mrow",[fn(t.label,e)]);return r=new et.MathNode("mpadded",[r]),r.setAttribute("width","0"),t.side==="left"&&r.setAttribute("lspace","-1width"),r.setAttribute("voffset","0.7em"),r=new et.MathNode("mstyle",[r]),r.setAttribute("displaystyle","false"),r.setAttribute("scriptlevel","1"),r}});vt({type:"cdlabelparent",names:["\\\\cdparent"],props:{numArgs:1},handler(t,e){var{parser:r}=t;return{type:"cdlabelparent",mode:r.mode,fragment:e[0]}},htmlBuilder(t,e){var r=Be.wrapFragment(Cr(t.fragment,e),e);return r.classes.push("cd-vert-arrow"),r},mathmlBuilder(t,e){return new et.MathNode("mrow",[fn(t.fragment,e)])}});vt({type:"textord",names:["\\@char"],props:{numArgs:1,allowedInText:!0},handler(t,e){for(var{parser:r}=t,n=ir(e[0],"ordgroup"),i=n.body,a="",s=0;s=1114111)throw new nt("\\@char with invalid code point "+a);return u<=65535?h=String.fromCharCode(u):(u-=65536,h=String.fromCharCode((u>>10)+55296,(u&1023)+56320)),{type:"textord",mode:r.mode,text:h}}});qG=o((t,e)=>{var r=Ri(t.body,e.withColor(t.color),!1);return Be.makeFragment(r)},"htmlBuilder$8"),XG=o((t,e)=>{var r=gs(t.body,e.withColor(t.color)),n=new et.MathNode("mstyle",r);return n.setAttribute("mathcolor",t.color),n},"mathmlBuilder$7");vt({type:"color",names:["\\textcolor"],props:{numArgs:2,allowedInText:!0,argTypes:["color","original"]},handler(t,e){var{parser:r}=t,n=ir(e[0],"color-token").color,i=e[1];return{type:"color",mode:r.mode,color:n,body:ui(i)}},htmlBuilder:qG,mathmlBuilder:XG});vt({type:"color",names:["\\color"],props:{numArgs:1,allowedInText:!0,argTypes:["color"]},handler(t,e){var{parser:r,breakOnTokenText:n}=t,i=ir(e[0],"color-token").color;r.gullet.macros.set("\\current@color",i);var a=r.parseExpression(!0,n);return{type:"color",mode:r.mode,color:i,body:a}},htmlBuilder:qG,mathmlBuilder:XG});vt({type:"cr",names:["\\\\"],props:{numArgs:0,numOptionalArgs:0,allowedInText:!0},handler(t,e,r){var{parser:n}=t,i=n.gullet.future().text==="["?n.parseSizeGroup(!0):null,a=!n.settings.displayMode||!n.settings.useStrictBehavior("newLineInDisplayMode","In LaTeX, \\\\ or \\newline does nothing in display mode");return{type:"cr",mode:n.mode,newLine:a,size:i&&ir(i,"size").value}},htmlBuilder(t,e){var r=Be.makeSpan(["mspace"],[],e);return t.newLine&&(r.classes.push("newline"),t.size&&(r.style.marginTop=ct(Hn(t.size,e)))),r},mathmlBuilder(t,e){var r=new et.MathNode("mspace");return t.newLine&&(r.setAttribute("linebreak","newline"),t.size&&r.setAttribute("height",ct(Hn(t.size,e)))),r}});A7={"\\global":"\\global","\\long":"\\\\globallong","\\\\globallong":"\\\\globallong","\\def":"\\gdef","\\gdef":"\\gdef","\\edef":"\\xdef","\\xdef":"\\xdef","\\let":"\\\\globallet","\\futurelet":"\\\\globalfuture"},jG=o(t=>{var e=t.text;if(/^(?:[\\{}$&#^_]|EOF)$/.test(e))throw new nt("Expected a control sequence",t);return e},"checkControlSequence"),qxe=o(t=>{var e=t.gullet.popToken();return e.text==="="&&(e=t.gullet.popToken(),e.text===" "&&(e=t.gullet.popToken())),e},"getRHS"),KG=o((t,e,r,n)=>{var i=t.gullet.macros.get(r.text);i==null&&(r.noexpand=!0,i={tokens:[r],numArgs:0,unexpandable:!t.gullet.isExpandable(r.text)}),t.gullet.macros.set(e,i,n)},"letCommand");vt({type:"internal",names:["\\global","\\long","\\\\globallong"],props:{numArgs:0,allowedInText:!0},handler(t){var{parser:e,funcName:r}=t;e.consumeSpaces();var n=e.fetch();if(A7[n.text])return(r==="\\global"||r==="\\\\globallong")&&(n.text=A7[n.text]),ir(e.parseFunction(),"internal");throw new nt("Invalid token after macro prefix",n)}});vt({type:"internal",names:["\\def","\\gdef","\\edef","\\xdef"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler(t){var{parser:e,funcName:r}=t,n=e.gullet.popToken(),i=n.text;if(/^(?:[\\{}$&#^_]|EOF)$/.test(i))throw new nt("Expected a control sequence",n);for(var a=0,s,l=[[]];e.gullet.future().text!=="{";)if(n=e.gullet.popToken(),n.text==="#"){if(e.gullet.future().text==="{"){s=e.gullet.future(),l[a].push("{");break}if(n=e.gullet.popToken(),!/^[1-9]$/.test(n.text))throw new nt('Invalid argument number "'+n.text+'"');if(parseInt(n.text)!==a+1)throw new nt('Argument number "'+n.text+'" out of order');a++,l.push([])}else{if(n.text==="EOF")throw new nt("Expected a macro definition");l[a].push(n.text)}var{tokens:u}=e.gullet.consumeArg();return s&&u.unshift(s),(r==="\\edef"||r==="\\xdef")&&(u=e.gullet.expandTokens(u),u.reverse()),e.gullet.macros.set(i,{tokens:u,numArgs:a,delimiters:l},r===A7[r]),{type:"internal",mode:e.mode}}});vt({type:"internal",names:["\\let","\\\\globallet"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler(t){var{parser:e,funcName:r}=t,n=jG(e.gullet.popToken());e.gullet.consumeSpaces();var i=qxe(e);return KG(e,n,i,r==="\\\\globallet"),{type:"internal",mode:e.mode}}});vt({type:"internal",names:["\\futurelet","\\\\globalfuture"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler(t){var{parser:e,funcName:r}=t,n=jG(e.gullet.popToken()),i=e.gullet.popToken(),a=e.gullet.popToken();return KG(e,n,a,r==="\\\\globalfuture"),e.gullet.pushToken(a),e.gullet.pushToken(i),{type:"internal",mode:e.mode}}});ey=o(function(e,r,n){var i=wn.math[e]&&wn.math[e].replace,a=M7(i||e,r,n);if(!a)throw new Error("Unsupported symbol "+e+" and font size "+r+".");return a},"getMetrics"),z7=o(function(e,r,n,i){var a=n.havingBaseStyle(r),s=Be.makeSpan(i.concat(a.sizingClasses(n)),[e],n),l=a.sizeMultiplier/n.sizeMultiplier;return s.height*=l,s.depth*=l,s.maxFontSize=a.sizeMultiplier,s},"styleWrap"),QG=o(function(e,r,n){var i=r.havingBaseStyle(n),a=(1-r.sizeMultiplier/i.sizeMultiplier)*r.fontMetrics().axisHeight;e.classes.push("delimcenter"),e.style.top=ct(a),e.height-=a,e.depth+=a},"centerSpan"),Xxe=o(function(e,r,n,i,a,s){var l=Be.makeSymbol(e,"Main-Regular",a,i),u=z7(l,r,i,s);return n&&QG(u,i,r),u},"makeSmallDelim"),jxe=o(function(e,r,n,i){return Be.makeSymbol(e,"Size"+r+"-Regular",n,i)},"mathrmSize"),ZG=o(function(e,r,n,i,a,s){var l=jxe(e,r,a,i),u=z7(Be.makeSpan(["delimsizing","size"+r],[l],i),Ht.TEXT,i,s);return n&&QG(u,i,Ht.TEXT),u},"makeLargeDelim"),h7=o(function(e,r,n){var i;r==="Size1-Regular"?i="delim-size1":i="delim-size4";var a=Be.makeSpan(["delimsizinginner",i],[Be.makeSpan([],[Be.makeSymbol(e,r,n)])]);return{type:"elem",elem:a}},"makeGlyphSpan"),f7=o(function(e,r,n){var i=Zl["Size4-Regular"][e.charCodeAt(0)]?Zl["Size4-Regular"][e.charCodeAt(0)][4]:Zl["Size1-Regular"][e.charCodeAt(0)][4],a=new Jl("inner",nxe(e,Math.round(1e3*r))),s=new ll([a],{width:ct(i),height:ct(r),style:"width:"+ct(i),viewBox:"0 0 "+1e3*i+" "+Math.round(1e3*r),preserveAspectRatio:"xMinYMin"}),l=Be.makeSvgSpan([],[s],n);return l.height=r,l.style.height=ct(r),l.style.width=ct(i),{type:"elem",elem:l}},"makeInner"),_7=.008,l4={type:"kern",size:-1*_7},Kxe=["|","\\lvert","\\rvert","\\vert"],Qxe=["\\|","\\lVert","\\rVert","\\Vert"],JG=o(function(e,r,n,i,a,s){var l,u,h,f,d="",p=0;l=h=f=e,u=null;var m="Size1-Regular";e==="\\uparrow"?h=f="\u23D0":e==="\\Uparrow"?h=f="\u2016":e==="\\downarrow"?l=h="\u23D0":e==="\\Downarrow"?l=h="\u2016":e==="\\updownarrow"?(l="\\uparrow",h="\u23D0",f="\\downarrow"):e==="\\Updownarrow"?(l="\\Uparrow",h="\u2016",f="\\Downarrow"):Vt.contains(Kxe,e)?(h="\u2223",d="vert",p=333):Vt.contains(Qxe,e)?(h="\u2225",d="doublevert",p=556):e==="["||e==="\\lbrack"?(l="\u23A1",h="\u23A2",f="\u23A3",m="Size4-Regular",d="lbrack",p=667):e==="]"||e==="\\rbrack"?(l="\u23A4",h="\u23A5",f="\u23A6",m="Size4-Regular",d="rbrack",p=667):e==="\\lfloor"||e==="\u230A"?(h=l="\u23A2",f="\u23A3",m="Size4-Regular",d="lfloor",p=667):e==="\\lceil"||e==="\u2308"?(l="\u23A1",h=f="\u23A2",m="Size4-Regular",d="lceil",p=667):e==="\\rfloor"||e==="\u230B"?(h=l="\u23A5",f="\u23A6",m="Size4-Regular",d="rfloor",p=667):e==="\\rceil"||e==="\u2309"?(l="\u23A4",h=f="\u23A5",m="Size4-Regular",d="rceil",p=667):e==="("||e==="\\lparen"?(l="\u239B",h="\u239C",f="\u239D",m="Size4-Regular",d="lparen",p=875):e===")"||e==="\\rparen"?(l="\u239E",h="\u239F",f="\u23A0",m="Size4-Regular",d="rparen",p=875):e==="\\{"||e==="\\lbrace"?(l="\u23A7",u="\u23A8",f="\u23A9",h="\u23AA",m="Size4-Regular"):e==="\\}"||e==="\\rbrace"?(l="\u23AB",u="\u23AC",f="\u23AD",h="\u23AA",m="Size4-Regular"):e==="\\lgroup"||e==="\u27EE"?(l="\u23A7",f="\u23A9",h="\u23AA",m="Size4-Regular"):e==="\\rgroup"||e==="\u27EF"?(l="\u23AB",f="\u23AD",h="\u23AA",m="Size4-Regular"):e==="\\lmoustache"||e==="\u23B0"?(l="\u23A7",f="\u23AD",h="\u23AA",m="Size4-Regular"):(e==="\\rmoustache"||e==="\u23B1")&&(l="\u23AB",f="\u23A9",h="\u23AA",m="Size4-Regular");var g=ey(l,m,a),y=g.height+g.depth,v=ey(h,m,a),x=v.height+v.depth,b=ey(f,m,a),w=b.height+b.depth,S=0,T=1;if(u!==null){var E=ey(u,m,a);S=E.height+E.depth,T=2}var _=y+w+S,A=Math.max(0,Math.ceil((r-_)/(T*x))),L=_+A*T*x,M=i.fontMetrics().axisHeight;n&&(M*=i.sizeMultiplier);var N=L/2-M,k=[];if(d.length>0){var I=L-y-w,C=Math.round(L*1e3),O=ixe(d,Math.round(I*1e3)),D=new Jl(d,O),P=(p/1e3).toFixed(3)+"em",F=(C/1e3).toFixed(3)+"em",B=new ll([D],{width:P,height:F,viewBox:"0 0 "+p+" "+C}),$=Be.makeSvgSpan([],[B],i);$.height=C/1e3,$.style.width=P,$.style.height=F,k.push({type:"elem",elem:$})}else{if(k.push(h7(f,m,a)),k.push(l4),u===null){var z=L-y-w+2*_7;k.push(f7(h,z,i))}else{var Y=(L-y-w-S)/2+2*_7;k.push(f7(h,Y,i)),k.push(l4),k.push(h7(u,m,a)),k.push(l4),k.push(f7(h,Y,i))}k.push(l4),k.push(h7(l,m,a))}var Q=i.havingBaseStyle(Ht.TEXT),X=Be.makeVList({positionType:"bottom",positionData:N,children:k},Q);return z7(Be.makeSpan(["delimsizing","mult"],[X],Q),Ht.TEXT,i,s)},"makeStackedDelim"),d7=80,p7=.08,m7=o(function(e,r,n,i,a){var s=rxe(e,i,n),l=new Jl(e,s),u=new ll([l],{width:"400em",height:ct(r),viewBox:"0 0 400000 "+n,preserveAspectRatio:"xMinYMin slice"});return Be.makeSvgSpan(["hide-tail"],[u],a)},"sqrtSvg"),Zxe=o(function(e,r){var n=r.havingBaseSizing(),i=n$("\\surd",e*n.sizeMultiplier,r$,n),a=n.sizeMultiplier,s=Math.max(0,r.minRuleThickness-r.fontMetrics().sqrtRuleThickness),l,u=0,h=0,f=0,d;return i.type==="small"?(f=1e3+1e3*s+d7,e<1?a=1:e<1.4&&(a=.7),u=(1+s+p7)/a,h=(1+s)/a,l=m7("sqrtMain",u,f,s,r),l.style.minWidth="0.853em",d=.833/a):i.type==="large"?(f=(1e3+d7)*ty[i.size],h=(ty[i.size]+s)/a,u=(ty[i.size]+s+p7)/a,l=m7("sqrtSize"+i.size,u,f,s,r),l.style.minWidth="1.02em",d=1/a):(u=e+s+p7,h=e+s,f=Math.floor(1e3*e+s)+d7,l=m7("sqrtTall",u,f,s,r),l.style.minWidth="0.742em",d=1.056),l.height=h,l.style.height=ct(u),{span:l,advanceWidth:d,ruleWidth:(r.fontMetrics().sqrtRuleThickness+s)*a}},"makeSqrtImage"),e$=["(","\\lparen",")","\\rparen","[","\\lbrack","]","\\rbrack","\\{","\\lbrace","\\}","\\rbrace","\\lfloor","\\rfloor","\u230A","\u230B","\\lceil","\\rceil","\u2308","\u2309","\\surd"],Jxe=["\\uparrow","\\downarrow","\\updownarrow","\\Uparrow","\\Downarrow","\\Updownarrow","|","\\|","\\vert","\\Vert","\\lvert","\\rvert","\\lVert","\\rVert","\\lgroup","\\rgroup","\u27EE","\u27EF","\\lmoustache","\\rmoustache","\u23B0","\u23B1"],t$=["<",">","\\langle","\\rangle","/","\\backslash","\\lt","\\gt"],ty=[0,1.2,1.8,2.4,3],ebe=o(function(e,r,n,i,a){if(e==="<"||e==="\\lt"||e==="\u27E8"?e="\\langle":(e===">"||e==="\\gt"||e==="\u27E9")&&(e="\\rangle"),Vt.contains(e$,e)||Vt.contains(t$,e))return ZG(e,r,!1,n,i,a);if(Vt.contains(Jxe,e))return JG(e,ty[r],!1,n,i,a);throw new nt("Illegal delimiter: '"+e+"'")},"makeSizedDelim"),tbe=[{type:"small",style:Ht.SCRIPTSCRIPT},{type:"small",style:Ht.SCRIPT},{type:"small",style:Ht.TEXT},{type:"large",size:1},{type:"large",size:2},{type:"large",size:3},{type:"large",size:4}],rbe=[{type:"small",style:Ht.SCRIPTSCRIPT},{type:"small",style:Ht.SCRIPT},{type:"small",style:Ht.TEXT},{type:"stack"}],r$=[{type:"small",style:Ht.SCRIPTSCRIPT},{type:"small",style:Ht.SCRIPT},{type:"small",style:Ht.TEXT},{type:"large",size:1},{type:"large",size:2},{type:"large",size:3},{type:"large",size:4},{type:"stack"}],nbe=o(function(e){if(e.type==="small")return"Main-Regular";if(e.type==="large")return"Size"+e.size+"-Regular";if(e.type==="stack")return"Size4-Regular";throw new Error("Add support for delim type '"+e.type+"' here.")},"delimTypeToFont"),n$=o(function(e,r,n,i){for(var a=Math.min(2,3-i.style.size),s=a;sr)return n[s]}return n[n.length-1]},"traverseSequence"),i$=o(function(e,r,n,i,a,s){e==="<"||e==="\\lt"||e==="\u27E8"?e="\\langle":(e===">"||e==="\\gt"||e==="\u27E9")&&(e="\\rangle");var l;Vt.contains(t$,e)?l=tbe:Vt.contains(e$,e)?l=r$:l=rbe;var u=n$(e,r,l,i);return u.type==="small"?Xxe(e,u.style,n,i,a,s):u.type==="large"?ZG(e,u.size,n,i,a,s):JG(e,r,n,i,a,s)},"makeCustomSizedDelim"),ibe=o(function(e,r,n,i,a,s){var l=i.fontMetrics().axisHeight*i.sizeMultiplier,u=901,h=5/i.fontMetrics().ptPerEm,f=Math.max(r-l,n+l),d=Math.max(f/500*u,2*f-h);return i$(e,d,!0,i,a,s)},"makeLeftRightDelim"),au={sqrtImage:Zxe,sizedDelim:ebe,sizeToMaxHeight:ty,customSizedDelim:i$,leftRightDelim:ibe},cG={"\\bigl":{mclass:"mopen",size:1},"\\Bigl":{mclass:"mopen",size:2},"\\biggl":{mclass:"mopen",size:3},"\\Biggl":{mclass:"mopen",size:4},"\\bigr":{mclass:"mclose",size:1},"\\Bigr":{mclass:"mclose",size:2},"\\biggr":{mclass:"mclose",size:3},"\\Biggr":{mclass:"mclose",size:4},"\\bigm":{mclass:"mrel",size:1},"\\Bigm":{mclass:"mrel",size:2},"\\biggm":{mclass:"mrel",size:3},"\\Biggm":{mclass:"mrel",size:4},"\\big":{mclass:"mord",size:1},"\\Big":{mclass:"mord",size:2},"\\bigg":{mclass:"mord",size:3},"\\Bigg":{mclass:"mord",size:4}},abe=["(","\\lparen",")","\\rparen","[","\\lbrack","]","\\rbrack","\\{","\\lbrace","\\}","\\rbrace","\\lfloor","\\rfloor","\u230A","\u230B","\\lceil","\\rceil","\u2308","\u2309","<",">","\\langle","\u27E8","\\rangle","\u27E9","\\lt","\\gt","\\lvert","\\rvert","\\lVert","\\rVert","\\lgroup","\\rgroup","\u27EE","\u27EF","\\lmoustache","\\rmoustache","\u23B0","\u23B1","/","\\backslash","|","\\vert","\\|","\\Vert","\\uparrow","\\Uparrow","\\downarrow","\\Downarrow","\\updownarrow","\\Updownarrow","."];o(E4,"checkDelimiter");vt({type:"delimsizing",names:["\\bigl","\\Bigl","\\biggl","\\Biggl","\\bigr","\\Bigr","\\biggr","\\Biggr","\\bigm","\\Bigm","\\biggm","\\Biggm","\\big","\\Big","\\bigg","\\Bigg"],props:{numArgs:1,argTypes:["primitive"]},handler:o((t,e)=>{var r=E4(e[0],t);return{type:"delimsizing",mode:t.parser.mode,size:cG[t.funcName].size,mclass:cG[t.funcName].mclass,delim:r.text}},"handler"),htmlBuilder:o((t,e)=>t.delim==="."?Be.makeSpan([t.mclass]):au.sizedDelim(t.delim,t.size,e,t.mode,[t.mclass]),"htmlBuilder"),mathmlBuilder:o(t=>{var e=[];t.delim!=="."&&e.push(_o(t.delim,t.mode));var r=new et.MathNode("mo",e);t.mclass==="mopen"||t.mclass==="mclose"?r.setAttribute("fence","true"):r.setAttribute("fence","false"),r.setAttribute("stretchy","true");var n=ct(au.sizeToMaxHeight[t.size]);return r.setAttribute("minsize",n),r.setAttribute("maxsize",n),r},"mathmlBuilder")});o(uG,"assertParsed");vt({type:"leftright-right",names:["\\right"],props:{numArgs:1,primitive:!0},handler:o((t,e)=>{var r=t.parser.gullet.macros.get("\\current@color");if(r&&typeof r!="string")throw new nt("\\current@color set to non-string in \\right");return{type:"leftright-right",mode:t.parser.mode,delim:E4(e[0],t).text,color:r}},"handler")});vt({type:"leftright",names:["\\left"],props:{numArgs:1,primitive:!0},handler:o((t,e)=>{var r=E4(e[0],t),n=t.parser;++n.leftrightDepth;var i=n.parseExpression(!1);--n.leftrightDepth,n.expect("\\right",!1);var a=ir(n.parseFunction(),"leftright-right");return{type:"leftright",mode:n.mode,body:i,left:r.text,right:a.delim,rightColor:a.color}},"handler"),htmlBuilder:o((t,e)=>{uG(t);for(var r=Ri(t.body,e,!0,["mopen","mclose"]),n=0,i=0,a=!1,s=0;s{uG(t);var r=gs(t.body,e);if(t.left!=="."){var n=new et.MathNode("mo",[_o(t.left,t.mode)]);n.setAttribute("fence","true"),r.unshift(n)}if(t.right!=="."){var i=new et.MathNode("mo",[_o(t.right,t.mode)]);i.setAttribute("fence","true"),t.rightColor&&i.setAttribute("mathcolor",t.rightColor),r.push(i)}return O7(r)},"mathmlBuilder")});vt({type:"middle",names:["\\middle"],props:{numArgs:1,primitive:!0},handler:o((t,e)=>{var r=E4(e[0],t);if(!t.parser.leftrightDepth)throw new nt("\\middle without preceding \\left",r);return{type:"middle",mode:t.parser.mode,delim:r.text}},"handler"),htmlBuilder:o((t,e)=>{var r;if(t.delim===".")r=sy(e,[]);else{r=au.sizedDelim(t.delim,1,e,t.mode,[]);var n={delim:t.delim,options:e};r.isMiddle=n}return r},"htmlBuilder"),mathmlBuilder:o((t,e)=>{var r=t.delim==="\\vert"||t.delim==="|"?_o("|","text"):_o(t.delim,t.mode),n=new et.MathNode("mo",[r]);return n.setAttribute("fence","true"),n.setAttribute("lspace","0.05em"),n.setAttribute("rspace","0.05em"),n},"mathmlBuilder")});G7=o((t,e)=>{var r=Be.wrapFragment(Cr(t.body,e),e),n=t.label.slice(1),i=e.sizeMultiplier,a,s=0,l=Vt.isCharacterBox(t.body);if(n==="sout")a=Be.makeSpan(["stretchy","sout"]),a.height=e.fontMetrics().defaultRuleThickness/i,s=-.5*e.fontMetrics().xHeight;else if(n==="phase"){var u=Hn({number:.6,unit:"pt"},e),h=Hn({number:.35,unit:"ex"},e),f=e.havingBaseSizing();i=i/f.sizeMultiplier;var d=r.height+r.depth+u+h;r.style.paddingLeft=ct(d/2+u);var p=Math.floor(1e3*d*i),m=exe(p),g=new ll([new Jl("phase",m)],{width:"400em",height:ct(p/1e3),viewBox:"0 0 400000 "+p,preserveAspectRatio:"xMinYMin slice"});a=Be.makeSvgSpan(["hide-tail"],[g],e),a.style.height=ct(d),s=r.depth+u+h}else{/cancel/.test(n)?l||r.classes.push("cancel-pad"):n==="angl"?r.classes.push("anglpad"):r.classes.push("boxpad");var y=0,v=0,x=0;/box/.test(n)?(x=Math.max(e.fontMetrics().fboxrule,e.minRuleThickness),y=e.fontMetrics().fboxsep+(n==="colorbox"?0:x),v=y):n==="angl"?(x=Math.max(e.fontMetrics().defaultRuleThickness,e.minRuleThickness),y=4*x,v=Math.max(0,.25-r.depth)):(y=l?.2:0,v=y),a=ou.encloseSpan(r,n,y,v,e),/fbox|boxed|fcolorbox/.test(n)?(a.style.borderStyle="solid",a.style.borderWidth=ct(x)):n==="angl"&&x!==.049&&(a.style.borderTopWidth=ct(x),a.style.borderRightWidth=ct(x)),s=r.depth+v,t.backgroundColor&&(a.style.backgroundColor=t.backgroundColor,t.borderColor&&(a.style.borderColor=t.borderColor))}var b;if(t.backgroundColor)b=Be.makeVList({positionType:"individualShift",children:[{type:"elem",elem:a,shift:s},{type:"elem",elem:r,shift:0}]},e);else{var w=/cancel|phase/.test(n)?["svg-align"]:[];b=Be.makeVList({positionType:"individualShift",children:[{type:"elem",elem:r,shift:0},{type:"elem",elem:a,shift:s,wrapperClasses:w}]},e)}return/cancel/.test(n)&&(b.height=r.height,b.depth=r.depth),/cancel/.test(n)&&!l?Be.makeSpan(["mord","cancel-lap"],[b],e):Be.makeSpan(["mord"],[b],e)},"htmlBuilder$7"),$7=o((t,e)=>{var r=0,n=new et.MathNode(t.label.indexOf("colorbox")>-1?"mpadded":"menclose",[fn(t.body,e)]);switch(t.label){case"\\cancel":n.setAttribute("notation","updiagonalstrike");break;case"\\bcancel":n.setAttribute("notation","downdiagonalstrike");break;case"\\phase":n.setAttribute("notation","phasorangle");break;case"\\sout":n.setAttribute("notation","horizontalstrike");break;case"\\fbox":n.setAttribute("notation","box");break;case"\\angl":n.setAttribute("notation","actuarial");break;case"\\fcolorbox":case"\\colorbox":if(r=e.fontMetrics().fboxsep*e.fontMetrics().ptPerEm,n.setAttribute("width","+"+2*r+"pt"),n.setAttribute("height","+"+2*r+"pt"),n.setAttribute("lspace",r+"pt"),n.setAttribute("voffset",r+"pt"),t.label==="\\fcolorbox"){var i=Math.max(e.fontMetrics().fboxrule,e.minRuleThickness);n.setAttribute("style","border: "+i+"em solid "+String(t.borderColor))}break;case"\\xcancel":n.setAttribute("notation","updiagonalstrike downdiagonalstrike");break}return t.backgroundColor&&n.setAttribute("mathbackground",t.backgroundColor),n},"mathmlBuilder$6");vt({type:"enclose",names:["\\colorbox"],props:{numArgs:2,allowedInText:!0,argTypes:["color","text"]},handler(t,e,r){var{parser:n,funcName:i}=t,a=ir(e[0],"color-token").color,s=e[1];return{type:"enclose",mode:n.mode,label:i,backgroundColor:a,body:s}},htmlBuilder:G7,mathmlBuilder:$7});vt({type:"enclose",names:["\\fcolorbox"],props:{numArgs:3,allowedInText:!0,argTypes:["color","color","text"]},handler(t,e,r){var{parser:n,funcName:i}=t,a=ir(e[0],"color-token").color,s=ir(e[1],"color-token").color,l=e[2];return{type:"enclose",mode:n.mode,label:i,backgroundColor:s,borderColor:a,body:l}},htmlBuilder:G7,mathmlBuilder:$7});vt({type:"enclose",names:["\\fbox"],props:{numArgs:1,argTypes:["hbox"],allowedInText:!0},handler(t,e){var{parser:r}=t;return{type:"enclose",mode:r.mode,label:"\\fbox",body:e[0]}}});vt({type:"enclose",names:["\\cancel","\\bcancel","\\xcancel","\\sout","\\phase"],props:{numArgs:1},handler(t,e){var{parser:r,funcName:n}=t,i=e[0];return{type:"enclose",mode:r.mode,label:n,body:i}},htmlBuilder:G7,mathmlBuilder:$7});vt({type:"enclose",names:["\\angl"],props:{numArgs:1,argTypes:["hbox"],allowedInText:!1},handler(t,e){var{parser:r}=t;return{type:"enclose",mode:r.mode,label:"\\angl",body:e[0]}}});a$={};o(ec,"defineEnvironment");s$={};o(le,"defineMacro");o(hG,"getHLines");C4=o(t=>{var e=t.parser.settings;if(!e.displayMode)throw new nt("{"+t.envName+"} can be used only in display mode.")},"validateAmsEnvironmentContext");o(V7,"getAutoTag");o(mh,"parseArray");o(U7,"dCellStyle");tc=o(function(e,r){var n,i,a=e.body.length,s=e.hLinesBeforeRow,l=0,u=new Array(a),h=[],f=Math.max(r.fontMetrics().arrayRuleWidth,r.minRuleThickness),d=1/r.fontMetrics().ptPerEm,p=5*d;if(e.colSeparationType&&e.colSeparationType==="small"){var m=r.havingStyle(Ht.SCRIPT).sizeMultiplier;p=.2778*(m/r.sizeMultiplier)}var g=e.colSeparationType==="CD"?Hn({number:3,unit:"ex"},r):12*d,y=3*d,v=e.arraystretch*g,x=.7*v,b=.3*v,w=0;function S(ke){for(var Ie=0;Ie0&&(w+=.25),h.push({pos:w,isDashed:ke[Ie]})}for(o(S,"setHLinePos"),S(s[0]),n=0;n0&&(N+=b,_ke))for(n=0;n=l)){var J=void 0;(i>0||e.hskipBeforeAndAfter)&&(J=Vt.deflt(Y.pregap,p),J!==0&&(O=Be.makeSpan(["arraycolsep"],[]),O.style.width=ct(J),C.push(O)));var Z=[];for(n=0;n0){for(var se=Be.makeLineSpan("hline",r,f),ce=Be.makeLineSpan("hdashline",r,f),ue=[{type:"elem",elem:u,shift:0}];h.length>0;){var te=h.pop(),De=te.pos-k;te.isDashed?ue.push({type:"elem",elem:ce,shift:De}):ue.push({type:"elem",elem:se,shift:De})}u=Be.makeVList({positionType:"individualShift",children:ue},r)}if(P.length===0)return Be.makeSpan(["mord"],[u],r);var oe=Be.makeVList({positionType:"individualShift",children:P},r);return oe=Be.makeSpan(["tag"],[oe],r),Be.makeFragment([u,oe])},"htmlBuilder"),sbe={c:"center ",l:"left ",r:"right "},rc=o(function(e,r){for(var n=[],i=new et.MathNode("mtd",[],["mtr-glue"]),a=new et.MathNode("mtd",[],["mml-eqn-num"]),s=0;s0){var g=e.cols,y="",v=!1,x=0,b=g.length;g[0].type==="separator"&&(p+="top ",x=1),g[g.length-1].type==="separator"&&(p+="bottom ",b-=1);for(var w=x;w0?"left ":"",p+=A[A.length-1].length>0?"right ":"";for(var L=1;L-1?"alignat":"align",a=e.envName==="split",s=mh(e.parser,{cols:n,addJot:!0,autoTag:a?void 0:V7(e.envName),emptySingleRow:!0,colSeparationType:i,maxNumCols:a?2:void 0,leqno:e.parser.settings.leqno},"display"),l,u=0,h={type:"ordgroup",mode:e.mode,body:[]};if(r[0]&&r[0].type==="ordgroup"){for(var f="",d=0;d0&&m&&(v=1),n[g]={type:"align",align:y,pregap:v,postgap:0}}return s.colSeparationType=m?"align":"alignat",s},"alignedHandler");ec({type:"array",names:["array","darray"],props:{numArgs:1},handler(t,e){var r=T4(e[0]),n=r?[e[0]]:ir(e[0],"ordgroup").body,i=n.map(function(s){var l=B7(s),u=l.text;if("lcr".indexOf(u)!==-1)return{type:"align",align:u};if(u==="|")return{type:"separator",separator:"|"};if(u===":")return{type:"separator",separator:":"};throw new nt("Unknown column alignment: "+u,s)}),a={cols:i,hskipBeforeAndAfter:!0,maxNumCols:i.length};return mh(t.parser,a,U7(t.envName))},htmlBuilder:tc,mathmlBuilder:rc});ec({type:"array",names:["matrix","pmatrix","bmatrix","Bmatrix","vmatrix","Vmatrix","matrix*","pmatrix*","bmatrix*","Bmatrix*","vmatrix*","Vmatrix*"],props:{numArgs:0},handler(t){var e={matrix:null,pmatrix:["(",")"],bmatrix:["[","]"],Bmatrix:["\\{","\\}"],vmatrix:["|","|"],Vmatrix:["\\Vert","\\Vert"]}[t.envName.replace("*","")],r="c",n={hskipBeforeAndAfter:!1,cols:[{type:"align",align:r}]};if(t.envName.charAt(t.envName.length-1)==="*"){var i=t.parser;if(i.consumeSpaces(),i.fetch().text==="["){if(i.consume(),i.consumeSpaces(),r=i.fetch().text,"lcr".indexOf(r)===-1)throw new nt("Expected l or c or r",i.nextToken);i.consume(),i.consumeSpaces(),i.expect("]"),i.consume(),n.cols=[{type:"align",align:r}]}}var a=mh(t.parser,n,U7(t.envName)),s=Math.max(0,...a.body.map(l=>l.length));return a.cols=new Array(s).fill({type:"align",align:r}),e?{type:"leftright",mode:t.mode,body:[a],left:e[0],right:e[1],rightColor:void 0}:a},htmlBuilder:tc,mathmlBuilder:rc});ec({type:"array",names:["smallmatrix"],props:{numArgs:0},handler(t){var e={arraystretch:.5},r=mh(t.parser,e,"script");return r.colSeparationType="small",r},htmlBuilder:tc,mathmlBuilder:rc});ec({type:"array",names:["subarray"],props:{numArgs:1},handler(t,e){var r=T4(e[0]),n=r?[e[0]]:ir(e[0],"ordgroup").body,i=n.map(function(s){var l=B7(s),u=l.text;if("lc".indexOf(u)!==-1)return{type:"align",align:u};throw new nt("Unknown column alignment: "+u,s)});if(i.length>1)throw new nt("{subarray} can contain only one column");var a={cols:i,hskipBeforeAndAfter:!1,arraystretch:.5};if(a=mh(t.parser,a,"script"),a.body.length>0&&a.body[0].length>1)throw new nt("{subarray} can contain only one column");return a},htmlBuilder:tc,mathmlBuilder:rc});ec({type:"array",names:["cases","dcases","rcases","drcases"],props:{numArgs:0},handler(t){var e={arraystretch:1.2,cols:[{type:"align",align:"l",pregap:0,postgap:1},{type:"align",align:"l",pregap:0,postgap:0}]},r=mh(t.parser,e,U7(t.envName));return{type:"leftright",mode:t.mode,body:[r],left:t.envName.indexOf("r")>-1?".":"\\{",right:t.envName.indexOf("r")>-1?"\\}":".",rightColor:void 0}},htmlBuilder:tc,mathmlBuilder:rc});ec({type:"array",names:["align","align*","aligned","split"],props:{numArgs:0},handler:o$,htmlBuilder:tc,mathmlBuilder:rc});ec({type:"array",names:["gathered","gather","gather*"],props:{numArgs:0},handler(t){Vt.contains(["gather","gather*"],t.envName)&&C4(t);var e={cols:[{type:"align",align:"c"}],addJot:!0,colSeparationType:"gather",autoTag:V7(t.envName),emptySingleRow:!0,leqno:t.parser.settings.leqno};return mh(t.parser,e,"display")},htmlBuilder:tc,mathmlBuilder:rc});ec({type:"array",names:["alignat","alignat*","alignedat"],props:{numArgs:1},handler:o$,htmlBuilder:tc,mathmlBuilder:rc});ec({type:"array",names:["equation","equation*"],props:{numArgs:0},handler(t){C4(t);var e={autoTag:V7(t.envName),emptySingleRow:!0,singleRow:!0,maxNumCols:1,leqno:t.parser.settings.leqno};return mh(t.parser,e,"display")},htmlBuilder:tc,mathmlBuilder:rc});ec({type:"array",names:["CD"],props:{numArgs:0},handler(t){return C4(t),Wxe(t.parser)},htmlBuilder:tc,mathmlBuilder:rc});le("\\nonumber","\\gdef\\@eqnsw{0}");le("\\notag","\\nonumber");vt({type:"text",names:["\\hline","\\hdashline"],props:{numArgs:0,allowedInText:!0,allowedInMath:!0},handler(t,e){throw new nt(t.funcName+" valid only within array environment")}});fG=a$;vt({type:"environment",names:["\\begin","\\end"],props:{numArgs:1,argTypes:["text"]},handler(t,e){var{parser:r,funcName:n}=t,i=e[0];if(i.type!=="ordgroup")throw new nt("Invalid environment name",i);for(var a="",s=0;s{var r=t.font,n=e.withFont(r);return Cr(t.body,n)},"htmlBuilder$5"),c$=o((t,e)=>{var r=t.font,n=e.withFont(r);return fn(t.body,n)},"mathmlBuilder$4"),dG={"\\Bbb":"\\mathbb","\\bold":"\\mathbf","\\frak":"\\mathfrak","\\bm":"\\boldsymbol"};vt({type:"font",names:["\\mathrm","\\mathit","\\mathbf","\\mathnormal","\\mathbb","\\mathcal","\\mathfrak","\\mathscr","\\mathsf","\\mathtt","\\Bbb","\\bold","\\frak"],props:{numArgs:1,allowedInArgument:!0},handler:o((t,e)=>{var{parser:r,funcName:n}=t,i=y4(e[0]),a=n;return a in dG&&(a=dG[a]),{type:"font",mode:r.mode,font:a.slice(1),body:i}},"handler"),htmlBuilder:l$,mathmlBuilder:c$});vt({type:"mclass",names:["\\boldsymbol","\\bm"],props:{numArgs:1},handler:o((t,e)=>{var{parser:r}=t,n=e[0],i=Vt.isCharacterBox(n);return{type:"mclass",mode:r.mode,mclass:k4(n),body:[{type:"font",mode:r.mode,font:"boldsymbol",body:n}],isCharacterBox:i}},"handler")});vt({type:"font",names:["\\rm","\\sf","\\tt","\\bf","\\it","\\cal"],props:{numArgs:0,allowedInText:!0},handler:o((t,e)=>{var{parser:r,funcName:n,breakOnTokenText:i}=t,{mode:a}=r,s=r.parseExpression(!0,i),l="math"+n.slice(1);return{type:"font",mode:a,font:l,body:{type:"ordgroup",mode:r.mode,body:s}}},"handler"),htmlBuilder:l$,mathmlBuilder:c$});u$=o((t,e)=>{var r=e;return t==="display"?r=r.id>=Ht.SCRIPT.id?r.text():Ht.DISPLAY:t==="text"&&r.size===Ht.DISPLAY.size?r=Ht.TEXT:t==="script"?r=Ht.SCRIPT:t==="scriptscript"&&(r=Ht.SCRIPTSCRIPT),r},"adjustStyle"),H7=o((t,e)=>{var r=u$(t.size,e.style),n=r.fracNum(),i=r.fracDen(),a;a=e.havingStyle(n);var s=Cr(t.numer,a,e);if(t.continued){var l=8.5/e.fontMetrics().ptPerEm,u=3.5/e.fontMetrics().ptPerEm;s.height=s.height0?g=3*p:g=7*p,y=e.fontMetrics().denom1):(d>0?(m=e.fontMetrics().num2,g=p):(m=e.fontMetrics().num3,g=3*p),y=e.fontMetrics().denom2);var v;if(f){var b=e.fontMetrics().axisHeight;m-s.depth-(b+.5*d){var r=new et.MathNode("mfrac",[fn(t.numer,e),fn(t.denom,e)]);if(!t.hasBarLine)r.setAttribute("linethickness","0px");else if(t.barSize){var n=Hn(t.barSize,e);r.setAttribute("linethickness",ct(n))}var i=u$(t.size,e.style);if(i.size!==e.style.size){r=new et.MathNode("mstyle",[r]);var a=i.size===Ht.DISPLAY.size?"true":"false";r.setAttribute("displaystyle",a),r.setAttribute("scriptlevel","0")}if(t.leftDelim!=null||t.rightDelim!=null){var s=[];if(t.leftDelim!=null){var l=new et.MathNode("mo",[new et.TextNode(t.leftDelim.replace("\\",""))]);l.setAttribute("fence","true"),s.push(l)}if(s.push(r),t.rightDelim!=null){var u=new et.MathNode("mo",[new et.TextNode(t.rightDelim.replace("\\",""))]);u.setAttribute("fence","true"),s.push(u)}return O7(s)}return r},"mathmlBuilder$3");vt({type:"genfrac",names:["\\dfrac","\\frac","\\tfrac","\\dbinom","\\binom","\\tbinom","\\\\atopfrac","\\\\bracefrac","\\\\brackfrac"],props:{numArgs:2,allowedInArgument:!0},handler:o((t,e)=>{var{parser:r,funcName:n}=t,i=e[0],a=e[1],s,l=null,u=null,h="auto";switch(n){case"\\dfrac":case"\\frac":case"\\tfrac":s=!0;break;case"\\\\atopfrac":s=!1;break;case"\\dbinom":case"\\binom":case"\\tbinom":s=!1,l="(",u=")";break;case"\\\\bracefrac":s=!1,l="\\{",u="\\}";break;case"\\\\brackfrac":s=!1,l="[",u="]";break;default:throw new Error("Unrecognized genfrac command")}switch(n){case"\\dfrac":case"\\dbinom":h="display";break;case"\\tfrac":case"\\tbinom":h="text";break}return{type:"genfrac",mode:r.mode,continued:!1,numer:i,denom:a,hasBarLine:s,leftDelim:l,rightDelim:u,size:h,barSize:null}},"handler"),htmlBuilder:H7,mathmlBuilder:Y7});vt({type:"genfrac",names:["\\cfrac"],props:{numArgs:2},handler:o((t,e)=>{var{parser:r,funcName:n}=t,i=e[0],a=e[1];return{type:"genfrac",mode:r.mode,continued:!0,numer:i,denom:a,hasBarLine:!0,leftDelim:null,rightDelim:null,size:"display",barSize:null}},"handler")});vt({type:"infix",names:["\\over","\\choose","\\atop","\\brace","\\brack"],props:{numArgs:0,infix:!0},handler(t){var{parser:e,funcName:r,token:n}=t,i;switch(r){case"\\over":i="\\frac";break;case"\\choose":i="\\binom";break;case"\\atop":i="\\\\atopfrac";break;case"\\brace":i="\\\\bracefrac";break;case"\\brack":i="\\\\brackfrac";break;default:throw new Error("Unrecognized infix genfrac command")}return{type:"infix",mode:e.mode,replaceWith:i,token:n}}});pG=["display","text","script","scriptscript"],mG=o(function(e){var r=null;return e.length>0&&(r=e,r=r==="."?null:r),r},"delimFromValue");vt({type:"genfrac",names:["\\genfrac"],props:{numArgs:6,allowedInArgument:!0,argTypes:["math","math","size","text","math","math"]},handler(t,e){var{parser:r}=t,n=e[4],i=e[5],a=y4(e[0]),s=a.type==="atom"&&a.family==="open"?mG(a.text):null,l=y4(e[1]),u=l.type==="atom"&&l.family==="close"?mG(l.text):null,h=ir(e[2],"size"),f,d=null;h.isBlank?f=!0:(d=h.value,f=d.number>0);var p="auto",m=e[3];if(m.type==="ordgroup"){if(m.body.length>0){var g=ir(m.body[0],"textord");p=pG[Number(g.text)]}}else m=ir(m,"textord"),p=pG[Number(m.text)];return{type:"genfrac",mode:r.mode,numer:n,denom:i,continued:!1,hasBarLine:f,barSize:d,leftDelim:s,rightDelim:u,size:p}},htmlBuilder:H7,mathmlBuilder:Y7});vt({type:"infix",names:["\\above"],props:{numArgs:1,argTypes:["size"],infix:!0},handler(t,e){var{parser:r,funcName:n,token:i}=t;return{type:"infix",mode:r.mode,replaceWith:"\\\\abovefrac",size:ir(e[0],"size").value,token:i}}});vt({type:"genfrac",names:["\\\\abovefrac"],props:{numArgs:3,argTypes:["math","size","math"]},handler:o((t,e)=>{var{parser:r,funcName:n}=t,i=e[0],a=z2e(ir(e[1],"infix").size),s=e[2],l=a.number>0;return{type:"genfrac",mode:r.mode,numer:i,denom:s,continued:!1,hasBarLine:l,barSize:a,leftDelim:null,rightDelim:null,size:"auto"}},"handler"),htmlBuilder:H7,mathmlBuilder:Y7});h$=o((t,e)=>{var r=e.style,n,i;t.type==="supsub"?(n=t.sup?Cr(t.sup,e.havingStyle(r.sup()),e):Cr(t.sub,e.havingStyle(r.sub()),e),i=ir(t.base,"horizBrace")):i=ir(t,"horizBrace");var a=Cr(i.base,e.havingBaseStyle(Ht.DISPLAY)),s=ou.svgSpan(i,e),l;if(i.isOver?(l=Be.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:a},{type:"kern",size:.1},{type:"elem",elem:s}]},e),l.children[0].children[0].children[1].classes.push("svg-align")):(l=Be.makeVList({positionType:"bottom",positionData:a.depth+.1+s.height,children:[{type:"elem",elem:s},{type:"kern",size:.1},{type:"elem",elem:a}]},e),l.children[0].children[0].children[0].classes.push("svg-align")),n){var u=Be.makeSpan(["mord",i.isOver?"mover":"munder"],[l],e);i.isOver?l=Be.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:u},{type:"kern",size:.2},{type:"elem",elem:n}]},e):l=Be.makeVList({positionType:"bottom",positionData:u.depth+.2+n.height+n.depth,children:[{type:"elem",elem:n},{type:"kern",size:.2},{type:"elem",elem:u}]},e)}return Be.makeSpan(["mord",i.isOver?"mover":"munder"],[l],e)},"htmlBuilder$3"),obe=o((t,e)=>{var r=ou.mathMLnode(t.label);return new et.MathNode(t.isOver?"mover":"munder",[fn(t.base,e),r])},"mathmlBuilder$2");vt({type:"horizBrace",names:["\\overbrace","\\underbrace"],props:{numArgs:1},handler(t,e){var{parser:r,funcName:n}=t;return{type:"horizBrace",mode:r.mode,label:n,isOver:/^\\over/.test(n),base:e[0]}},htmlBuilder:h$,mathmlBuilder:obe});vt({type:"href",names:["\\href"],props:{numArgs:2,argTypes:["url","original"],allowedInText:!0},handler:o((t,e)=>{var{parser:r}=t,n=e[1],i=ir(e[0],"url").url;return r.settings.isTrusted({command:"\\href",url:i})?{type:"href",mode:r.mode,href:i,body:ui(n)}:r.formatUnsupportedCmd("\\href")},"handler"),htmlBuilder:o((t,e)=>{var r=Ri(t.body,e,!1);return Be.makeAnchor(t.href,[],r,e)},"htmlBuilder"),mathmlBuilder:o((t,e)=>{var r=ph(t.body,e);return r instanceof ps||(r=new ps("mrow",[r])),r.setAttribute("href",t.href),r},"mathmlBuilder")});vt({type:"href",names:["\\url"],props:{numArgs:1,argTypes:["url"],allowedInText:!0},handler:o((t,e)=>{var{parser:r}=t,n=ir(e[0],"url").url;if(!r.settings.isTrusted({command:"\\url",url:n}))return r.formatUnsupportedCmd("\\url");for(var i=[],a=0;a{var{parser:r,funcName:n,token:i}=t,a=ir(e[0],"raw").string,s=e[1];r.settings.strict&&r.settings.reportNonstrict("htmlExtension","HTML extension is disabled on strict mode");var l,u={};switch(n){case"\\htmlClass":u.class=a,l={command:"\\htmlClass",class:a};break;case"\\htmlId":u.id=a,l={command:"\\htmlId",id:a};break;case"\\htmlStyle":u.style=a,l={command:"\\htmlStyle",style:a};break;case"\\htmlData":{for(var h=a.split(","),f=0;f{var r=Ri(t.body,e,!1),n=["enclosing"];t.attributes.class&&n.push(...t.attributes.class.trim().split(/\s+/));var i=Be.makeSpan(n,r,e);for(var a in t.attributes)a!=="class"&&t.attributes.hasOwnProperty(a)&&i.setAttribute(a,t.attributes[a]);return i},"htmlBuilder"),mathmlBuilder:o((t,e)=>ph(t.body,e),"mathmlBuilder")});vt({type:"htmlmathml",names:["\\html@mathml"],props:{numArgs:2,allowedInText:!0},handler:o((t,e)=>{var{parser:r}=t;return{type:"htmlmathml",mode:r.mode,html:ui(e[0]),mathml:ui(e[1])}},"handler"),htmlBuilder:o((t,e)=>{var r=Ri(t.html,e,!1);return Be.makeFragment(r)},"htmlBuilder"),mathmlBuilder:o((t,e)=>ph(t.mathml,e),"mathmlBuilder")});g7=o(function(e){if(/^[-+]? *(\d+(\.\d*)?|\.\d+)$/.test(e))return{number:+e,unit:"bp"};var r=/([-+]?) *(\d+(?:\.\d*)?|\.\d+) *([a-z]{2})/.exec(e);if(!r)throw new nt("Invalid size: '"+e+"' in \\includegraphics");var n={number:+(r[1]+r[2]),unit:r[3]};if(!DG(n))throw new nt("Invalid unit: '"+n.unit+"' in \\includegraphics.");return n},"sizeData");vt({type:"includegraphics",names:["\\includegraphics"],props:{numArgs:1,numOptionalArgs:1,argTypes:["raw","url"],allowedInText:!1},handler:o((t,e,r)=>{var{parser:n}=t,i={number:0,unit:"em"},a={number:.9,unit:"em"},s={number:0,unit:"em"},l="";if(r[0])for(var u=ir(r[0],"raw").string,h=u.split(","),f=0;f{var r=Hn(t.height,e),n=0;t.totalheight.number>0&&(n=Hn(t.totalheight,e)-r);var i=0;t.width.number>0&&(i=Hn(t.width,e));var a={height:ct(r+n)};i>0&&(a.width=ct(i)),n>0&&(a.verticalAlign=ct(-n));var s=new T7(t.src,t.alt,a);return s.height=r,s.depth=n,s},"htmlBuilder"),mathmlBuilder:o((t,e)=>{var r=new et.MathNode("mglyph",[]);r.setAttribute("alt",t.alt);var n=Hn(t.height,e),i=0;if(t.totalheight.number>0&&(i=Hn(t.totalheight,e)-n,r.setAttribute("valign",ct(-i))),r.setAttribute("height",ct(n+i)),t.width.number>0){var a=Hn(t.width,e);r.setAttribute("width",ct(a))}return r.setAttribute("src",t.src),r},"mathmlBuilder")});vt({type:"kern",names:["\\kern","\\mkern","\\hskip","\\mskip"],props:{numArgs:1,argTypes:["size"],primitive:!0,allowedInText:!0},handler(t,e){var{parser:r,funcName:n}=t,i=ir(e[0],"size");if(r.settings.strict){var a=n[1]==="m",s=i.value.unit==="mu";a?(s||r.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+n+" supports only mu units, "+("not "+i.value.unit+" units")),r.mode!=="math"&&r.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+n+" works only in math mode")):s&&r.settings.reportNonstrict("mathVsTextUnits","LaTeX's "+n+" doesn't support mu units")}return{type:"kern",mode:r.mode,dimension:i.value}},htmlBuilder(t,e){return Be.makeGlue(t.dimension,e)},mathmlBuilder(t,e){var r=Hn(t.dimension,e);return new et.SpaceNode(r)}});vt({type:"lap",names:["\\mathllap","\\mathrlap","\\mathclap"],props:{numArgs:1,allowedInText:!0},handler:o((t,e)=>{var{parser:r,funcName:n}=t,i=e[0];return{type:"lap",mode:r.mode,alignment:n.slice(5),body:i}},"handler"),htmlBuilder:o((t,e)=>{var r;t.alignment==="clap"?(r=Be.makeSpan([],[Cr(t.body,e)]),r=Be.makeSpan(["inner"],[r],e)):r=Be.makeSpan(["inner"],[Cr(t.body,e)]);var n=Be.makeSpan(["fix"],[]),i=Be.makeSpan([t.alignment],[r,n],e),a=Be.makeSpan(["strut"]);return a.style.height=ct(i.height+i.depth),i.depth&&(a.style.verticalAlign=ct(-i.depth)),i.children.unshift(a),i=Be.makeSpan(["thinbox"],[i],e),Be.makeSpan(["mord","vbox"],[i],e)},"htmlBuilder"),mathmlBuilder:o((t,e)=>{var r=new et.MathNode("mpadded",[fn(t.body,e)]);if(t.alignment!=="rlap"){var n=t.alignment==="llap"?"-1":"-0.5";r.setAttribute("lspace",n+"width")}return r.setAttribute("width","0px"),r},"mathmlBuilder")});vt({type:"styling",names:["\\(","$"],props:{numArgs:0,allowedInText:!0,allowedInMath:!1},handler(t,e){var{funcName:r,parser:n}=t,i=n.mode;n.switchMode("math");var a=r==="\\("?"\\)":"$",s=n.parseExpression(!1,a);return n.expect(a),n.switchMode(i),{type:"styling",mode:n.mode,style:"text",body:s}}});vt({type:"text",names:["\\)","\\]"],props:{numArgs:0,allowedInText:!0,allowedInMath:!1},handler(t,e){throw new nt("Mismatched "+t.funcName)}});gG=o((t,e)=>{switch(e.style.size){case Ht.DISPLAY.size:return t.display;case Ht.TEXT.size:return t.text;case Ht.SCRIPT.size:return t.script;case Ht.SCRIPTSCRIPT.size:return t.scriptscript;default:return t.text}},"chooseMathStyle");vt({type:"mathchoice",names:["\\mathchoice"],props:{numArgs:4,primitive:!0},handler:o((t,e)=>{var{parser:r}=t;return{type:"mathchoice",mode:r.mode,display:ui(e[0]),text:ui(e[1]),script:ui(e[2]),scriptscript:ui(e[3])}},"handler"),htmlBuilder:o((t,e)=>{var r=gG(t,e),n=Ri(r,e,!1);return Be.makeFragment(n)},"htmlBuilder"),mathmlBuilder:o((t,e)=>{var r=gG(t,e);return ph(r,e)},"mathmlBuilder")});f$=o((t,e,r,n,i,a,s)=>{t=Be.makeSpan([],[t]);var l=r&&Vt.isCharacterBox(r),u,h;if(e){var f=Cr(e,n.havingStyle(i.sup()),n);h={elem:f,kern:Math.max(n.fontMetrics().bigOpSpacing1,n.fontMetrics().bigOpSpacing3-f.depth)}}if(r){var d=Cr(r,n.havingStyle(i.sub()),n);u={elem:d,kern:Math.max(n.fontMetrics().bigOpSpacing2,n.fontMetrics().bigOpSpacing4-d.height)}}var p;if(h&&u){var m=n.fontMetrics().bigOpSpacing5+u.elem.height+u.elem.depth+u.kern+t.depth+s;p=Be.makeVList({positionType:"bottom",positionData:m,children:[{type:"kern",size:n.fontMetrics().bigOpSpacing5},{type:"elem",elem:u.elem,marginLeft:ct(-a)},{type:"kern",size:u.kern},{type:"elem",elem:t},{type:"kern",size:h.kern},{type:"elem",elem:h.elem,marginLeft:ct(a)},{type:"kern",size:n.fontMetrics().bigOpSpacing5}]},n)}else if(u){var g=t.height-s;p=Be.makeVList({positionType:"top",positionData:g,children:[{type:"kern",size:n.fontMetrics().bigOpSpacing5},{type:"elem",elem:u.elem,marginLeft:ct(-a)},{type:"kern",size:u.kern},{type:"elem",elem:t}]},n)}else if(h){var y=t.depth+s;p=Be.makeVList({positionType:"bottom",positionData:y,children:[{type:"elem",elem:t},{type:"kern",size:h.kern},{type:"elem",elem:h.elem,marginLeft:ct(a)},{type:"kern",size:n.fontMetrics().bigOpSpacing5}]},n)}else return t;var v=[p];if(u&&a!==0&&!l){var x=Be.makeSpan(["mspace"],[],n);x.style.marginRight=ct(a),v.unshift(x)}return Be.makeSpan(["mop","op-limits"],v,n)},"assembleSupSub"),d$=["\\smallint"],xp=o((t,e)=>{var r,n,i=!1,a;t.type==="supsub"?(r=t.sup,n=t.sub,a=ir(t.base,"op"),i=!0):a=ir(t,"op");var s=e.style,l=!1;s.size===Ht.DISPLAY.size&&a.symbol&&!Vt.contains(d$,a.name)&&(l=!0);var u;if(a.symbol){var h=l?"Size2-Regular":"Size1-Regular",f="";if((a.name==="\\oiint"||a.name==="\\oiiint")&&(f=a.name.slice(1),a.name=f==="oiint"?"\\iint":"\\iiint"),u=Be.makeSymbol(a.name,h,"math",e,["mop","op-symbol",l?"large-op":"small-op"]),f.length>0){var d=u.italic,p=Be.staticSvg(f+"Size"+(l?"2":"1"),e);u=Be.makeVList({positionType:"individualShift",children:[{type:"elem",elem:u,shift:0},{type:"elem",elem:p,shift:l?.08:0}]},e),a.name="\\"+f,u.classes.unshift("mop"),u.italic=d}}else if(a.body){var m=Ri(a.body,e,!0);m.length===1&&m[0]instanceof ms?(u=m[0],u.classes[0]="mop"):u=Be.makeSpan(["mop"],m,e)}else{for(var g=[],y=1;y{var r;if(t.symbol)r=new ps("mo",[_o(t.name,t.mode)]),Vt.contains(d$,t.name)&&r.setAttribute("largeop","false");else if(t.body)r=new ps("mo",gs(t.body,e));else{r=new ps("mi",[new qf(t.name.slice(1))]);var n=new ps("mo",[_o("\u2061","text")]);t.parentIsSupSub?r=new ps("mrow",[r,n]):r=$G([r,n])}return r},"mathmlBuilder$1"),lbe={"\u220F":"\\prod","\u2210":"\\coprod","\u2211":"\\sum","\u22C0":"\\bigwedge","\u22C1":"\\bigvee","\u22C2":"\\bigcap","\u22C3":"\\bigcup","\u2A00":"\\bigodot","\u2A01":"\\bigoplus","\u2A02":"\\bigotimes","\u2A04":"\\biguplus","\u2A06":"\\bigsqcup"};vt({type:"op",names:["\\coprod","\\bigvee","\\bigwedge","\\biguplus","\\bigcap","\\bigcup","\\intop","\\prod","\\sum","\\bigotimes","\\bigoplus","\\bigodot","\\bigsqcup","\\smallint","\u220F","\u2210","\u2211","\u22C0","\u22C1","\u22C2","\u22C3","\u2A00","\u2A01","\u2A02","\u2A04","\u2A06"],props:{numArgs:0},handler:o((t,e)=>{var{parser:r,funcName:n}=t,i=n;return i.length===1&&(i=lbe[i]),{type:"op",mode:r.mode,limits:!0,parentIsSupSub:!1,symbol:!0,name:i}},"handler"),htmlBuilder:xp,mathmlBuilder:oy});vt({type:"op",names:["\\mathop"],props:{numArgs:1,primitive:!0},handler:o((t,e)=>{var{parser:r}=t,n=e[0];return{type:"op",mode:r.mode,limits:!1,parentIsSupSub:!1,symbol:!1,body:ui(n)}},"handler"),htmlBuilder:xp,mathmlBuilder:oy});cbe={"\u222B":"\\int","\u222C":"\\iint","\u222D":"\\iiint","\u222E":"\\oint","\u222F":"\\oiint","\u2230":"\\oiiint"};vt({type:"op",names:["\\arcsin","\\arccos","\\arctan","\\arctg","\\arcctg","\\arg","\\ch","\\cos","\\cosec","\\cosh","\\cot","\\cotg","\\coth","\\csc","\\ctg","\\cth","\\deg","\\dim","\\exp","\\hom","\\ker","\\lg","\\ln","\\log","\\sec","\\sin","\\sinh","\\sh","\\tan","\\tanh","\\tg","\\th"],props:{numArgs:0},handler(t){var{parser:e,funcName:r}=t;return{type:"op",mode:e.mode,limits:!1,parentIsSupSub:!1,symbol:!1,name:r}},htmlBuilder:xp,mathmlBuilder:oy});vt({type:"op",names:["\\det","\\gcd","\\inf","\\lim","\\max","\\min","\\Pr","\\sup"],props:{numArgs:0},handler(t){var{parser:e,funcName:r}=t;return{type:"op",mode:e.mode,limits:!0,parentIsSupSub:!1,symbol:!1,name:r}},htmlBuilder:xp,mathmlBuilder:oy});vt({type:"op",names:["\\int","\\iint","\\iiint","\\oint","\\oiint","\\oiiint","\u222B","\u222C","\u222D","\u222E","\u222F","\u2230"],props:{numArgs:0},handler(t){var{parser:e,funcName:r}=t,n=r;return n.length===1&&(n=cbe[n]),{type:"op",mode:e.mode,limits:!1,parentIsSupSub:!1,symbol:!0,name:n}},htmlBuilder:xp,mathmlBuilder:oy});p$=o((t,e)=>{var r,n,i=!1,a;t.type==="supsub"?(r=t.sup,n=t.sub,a=ir(t.base,"operatorname"),i=!0):a=ir(t,"operatorname");var s;if(a.body.length>0){for(var l=a.body.map(d=>{var p=d.text;return typeof p=="string"?{type:"textord",mode:d.mode,text:p}:d}),u=Ri(l,e.withFont("mathrm"),!0),h=0;h{for(var r=gs(t.body,e.withFont("mathrm")),n=!0,i=0;if.toText()).join("");r=[new et.TextNode(l)]}var u=new et.MathNode("mi",r);u.setAttribute("mathvariant","normal");var h=new et.MathNode("mo",[_o("\u2061","text")]);return t.parentIsSupSub?new et.MathNode("mrow",[u,h]):et.newDocumentFragment([u,h])},"mathmlBuilder");vt({type:"operatorname",names:["\\operatorname@","\\operatornamewithlimits"],props:{numArgs:1},handler:o((t,e)=>{var{parser:r,funcName:n}=t,i=e[0];return{type:"operatorname",mode:r.mode,body:ui(i),alwaysHandleSupSub:n==="\\operatornamewithlimits",limits:!1,parentIsSupSub:!1}},"handler"),htmlBuilder:p$,mathmlBuilder:ube});le("\\operatorname","\\@ifstar\\operatornamewithlimits\\operatorname@");Kf({type:"ordgroup",htmlBuilder(t,e){return t.semisimple?Be.makeFragment(Ri(t.body,e,!1)):Be.makeSpan(["mord"],Ri(t.body,e,!0),e)},mathmlBuilder(t,e){return ph(t.body,e,!0)}});vt({type:"overline",names:["\\overline"],props:{numArgs:1},handler(t,e){var{parser:r}=t,n=e[0];return{type:"overline",mode:r.mode,body:n}},htmlBuilder(t,e){var r=Cr(t.body,e.havingCrampedStyle()),n=Be.makeLineSpan("overline-line",e),i=e.fontMetrics().defaultRuleThickness,a=Be.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:r},{type:"kern",size:3*i},{type:"elem",elem:n},{type:"kern",size:i}]},e);return Be.makeSpan(["mord","overline"],[a],e)},mathmlBuilder(t,e){var r=new et.MathNode("mo",[new et.TextNode("\u203E")]);r.setAttribute("stretchy","true");var n=new et.MathNode("mover",[fn(t.body,e),r]);return n.setAttribute("accent","true"),n}});vt({type:"phantom",names:["\\phantom"],props:{numArgs:1,allowedInText:!0},handler:o((t,e)=>{var{parser:r}=t,n=e[0];return{type:"phantom",mode:r.mode,body:ui(n)}},"handler"),htmlBuilder:o((t,e)=>{var r=Ri(t.body,e.withPhantom(),!1);return Be.makeFragment(r)},"htmlBuilder"),mathmlBuilder:o((t,e)=>{var r=gs(t.body,e);return new et.MathNode("mphantom",r)},"mathmlBuilder")});vt({type:"hphantom",names:["\\hphantom"],props:{numArgs:1,allowedInText:!0},handler:o((t,e)=>{var{parser:r}=t,n=e[0];return{type:"hphantom",mode:r.mode,body:n}},"handler"),htmlBuilder:o((t,e)=>{var r=Be.makeSpan([],[Cr(t.body,e.withPhantom())]);if(r.height=0,r.depth=0,r.children)for(var n=0;n{var r=gs(ui(t.body),e),n=new et.MathNode("mphantom",r),i=new et.MathNode("mpadded",[n]);return i.setAttribute("height","0px"),i.setAttribute("depth","0px"),i},"mathmlBuilder")});vt({type:"vphantom",names:["\\vphantom"],props:{numArgs:1,allowedInText:!0},handler:o((t,e)=>{var{parser:r}=t,n=e[0];return{type:"vphantom",mode:r.mode,body:n}},"handler"),htmlBuilder:o((t,e)=>{var r=Be.makeSpan(["inner"],[Cr(t.body,e.withPhantom())]),n=Be.makeSpan(["fix"],[]);return Be.makeSpan(["mord","rlap"],[r,n],e)},"htmlBuilder"),mathmlBuilder:o((t,e)=>{var r=gs(ui(t.body),e),n=new et.MathNode("mphantom",r),i=new et.MathNode("mpadded",[n]);return i.setAttribute("width","0px"),i},"mathmlBuilder")});vt({type:"raisebox",names:["\\raisebox"],props:{numArgs:2,argTypes:["size","hbox"],allowedInText:!0},handler(t,e){var{parser:r}=t,n=ir(e[0],"size").value,i=e[1];return{type:"raisebox",mode:r.mode,dy:n,body:i}},htmlBuilder(t,e){var r=Cr(t.body,e),n=Hn(t.dy,e);return Be.makeVList({positionType:"shift",positionData:-n,children:[{type:"elem",elem:r}]},e)},mathmlBuilder(t,e){var r=new et.MathNode("mpadded",[fn(t.body,e)]),n=t.dy.number+t.dy.unit;return r.setAttribute("voffset",n),r}});vt({type:"internal",names:["\\relax"],props:{numArgs:0,allowedInText:!0},handler(t){var{parser:e}=t;return{type:"internal",mode:e.mode}}});vt({type:"rule",names:["\\rule"],props:{numArgs:2,numOptionalArgs:1,argTypes:["size","size","size"]},handler(t,e,r){var{parser:n}=t,i=r[0],a=ir(e[0],"size"),s=ir(e[1],"size");return{type:"rule",mode:n.mode,shift:i&&ir(i,"size").value,width:a.value,height:s.value}},htmlBuilder(t,e){var r=Be.makeSpan(["mord","rule"],[],e),n=Hn(t.width,e),i=Hn(t.height,e),a=t.shift?Hn(t.shift,e):0;return r.style.borderRightWidth=ct(n),r.style.borderTopWidth=ct(i),r.style.bottom=ct(a),r.width=n,r.height=i+a,r.depth=-a,r.maxFontSize=i*1.125*e.sizeMultiplier,r},mathmlBuilder(t,e){var r=Hn(t.width,e),n=Hn(t.height,e),i=t.shift?Hn(t.shift,e):0,a=e.color&&e.getColor()||"black",s=new et.MathNode("mspace");s.setAttribute("mathbackground",a),s.setAttribute("width",ct(r)),s.setAttribute("height",ct(n));var l=new et.MathNode("mpadded",[s]);return i>=0?l.setAttribute("height",ct(i)):(l.setAttribute("height",ct(i)),l.setAttribute("depth",ct(-i))),l.setAttribute("voffset",ct(i)),l}});o(m$,"sizingGroup");yG=["\\tiny","\\sixptsize","\\scriptsize","\\footnotesize","\\small","\\normalsize","\\large","\\Large","\\LARGE","\\huge","\\Huge"],hbe=o((t,e)=>{var r=e.havingSize(t.size);return m$(t.body,r,e)},"htmlBuilder");vt({type:"sizing",names:yG,props:{numArgs:0,allowedInText:!0},handler:o((t,e)=>{var{breakOnTokenText:r,funcName:n,parser:i}=t,a=i.parseExpression(!1,r);return{type:"sizing",mode:i.mode,size:yG.indexOf(n)+1,body:a}},"handler"),htmlBuilder:hbe,mathmlBuilder:o((t,e)=>{var r=e.havingSize(t.size),n=gs(t.body,r),i=new et.MathNode("mstyle",n);return i.setAttribute("mathsize",ct(r.sizeMultiplier)),i},"mathmlBuilder")});vt({type:"smash",names:["\\smash"],props:{numArgs:1,numOptionalArgs:1,allowedInText:!0},handler:o((t,e,r)=>{var{parser:n}=t,i=!1,a=!1,s=r[0]&&ir(r[0],"ordgroup");if(s)for(var l="",u=0;u{var r=Be.makeSpan([],[Cr(t.body,e)]);if(!t.smashHeight&&!t.smashDepth)return r;if(t.smashHeight&&(r.height=0,r.children))for(var n=0;n{var r=new et.MathNode("mpadded",[fn(t.body,e)]);return t.smashHeight&&r.setAttribute("height","0px"),t.smashDepth&&r.setAttribute("depth","0px"),r},"mathmlBuilder")});vt({type:"sqrt",names:["\\sqrt"],props:{numArgs:1,numOptionalArgs:1},handler(t,e,r){var{parser:n}=t,i=r[0],a=e[0];return{type:"sqrt",mode:n.mode,body:a,index:i}},htmlBuilder(t,e){var r=Cr(t.body,e.havingCrampedStyle());r.height===0&&(r.height=e.fontMetrics().xHeight),r=Be.wrapFragment(r,e);var n=e.fontMetrics(),i=n.defaultRuleThickness,a=i;e.style.idr.height+r.depth+s&&(s=(s+d-r.height-r.depth)/2);var p=u.height-r.height-s-h;r.style.paddingLeft=ct(f);var m=Be.makeVList({positionType:"firstBaseline",children:[{type:"elem",elem:r,wrapperClasses:["svg-align"]},{type:"kern",size:-(r.height+p)},{type:"elem",elem:u},{type:"kern",size:h}]},e);if(t.index){var g=e.havingStyle(Ht.SCRIPTSCRIPT),y=Cr(t.index,g,e),v=.6*(m.height-m.depth),x=Be.makeVList({positionType:"shift",positionData:-v,children:[{type:"elem",elem:y}]},e),b=Be.makeSpan(["root"],[x]);return Be.makeSpan(["mord","sqrt"],[b,m],e)}else return Be.makeSpan(["mord","sqrt"],[m],e)},mathmlBuilder(t,e){var{body:r,index:n}=t;return n?new et.MathNode("mroot",[fn(r,e),fn(n,e)]):new et.MathNode("msqrt",[fn(r,e)])}});vG={display:Ht.DISPLAY,text:Ht.TEXT,script:Ht.SCRIPT,scriptscript:Ht.SCRIPTSCRIPT};vt({type:"styling",names:["\\displaystyle","\\textstyle","\\scriptstyle","\\scriptscriptstyle"],props:{numArgs:0,allowedInText:!0,primitive:!0},handler(t,e){var{breakOnTokenText:r,funcName:n,parser:i}=t,a=i.parseExpression(!0,r),s=n.slice(1,n.length-5);return{type:"styling",mode:i.mode,style:s,body:a}},htmlBuilder(t,e){var r=vG[t.style],n=e.havingStyle(r).withFont("");return m$(t.body,n,e)},mathmlBuilder(t,e){var r=vG[t.style],n=e.havingStyle(r),i=gs(t.body,n),a=new et.MathNode("mstyle",i),s={display:["0","true"],text:["0","false"],script:["1","false"],scriptscript:["2","false"]},l=s[t.style];return a.setAttribute("scriptlevel",l[0]),a.setAttribute("displaystyle",l[1]),a}});fbe=o(function(e,r){var n=e.base;if(n)if(n.type==="op"){var i=n.limits&&(r.style.size===Ht.DISPLAY.size||n.alwaysHandleSupSub);return i?xp:null}else if(n.type==="operatorname"){var a=n.alwaysHandleSupSub&&(r.style.size===Ht.DISPLAY.size||n.limits);return a?p$:null}else{if(n.type==="accent")return Vt.isCharacterBox(n.base)?F7:null;if(n.type==="horizBrace"){var s=!e.sub;return s===n.isOver?h$:null}else return null}else return null},"htmlBuilderDelegate");Kf({type:"supsub",htmlBuilder(t,e){var r=fbe(t,e);if(r)return r(t,e);var{base:n,sup:i,sub:a}=t,s=Cr(n,e),l,u,h=e.fontMetrics(),f=0,d=0,p=n&&Vt.isCharacterBox(n);if(i){var m=e.havingStyle(e.style.sup());l=Cr(i,m,e),p||(f=s.height-m.fontMetrics().supDrop*m.sizeMultiplier/e.sizeMultiplier)}if(a){var g=e.havingStyle(e.style.sub());u=Cr(a,g,e),p||(d=s.depth+g.fontMetrics().subDrop*g.sizeMultiplier/e.sizeMultiplier)}var y;e.style===Ht.DISPLAY?y=h.sup1:e.style.cramped?y=h.sup3:y=h.sup2;var v=e.sizeMultiplier,x=ct(.5/h.ptPerEm/v),b=null;if(u){var w=t.base&&t.base.type==="op"&&t.base.name&&(t.base.name==="\\oiint"||t.base.name==="\\oiiint");(s instanceof ms||w)&&(b=ct(-s.italic))}var S;if(l&&u){f=Math.max(f,y,l.depth+.25*h.xHeight),d=Math.max(d,h.sub2);var T=h.defaultRuleThickness,E=4*T;if(f-l.depth-(u.height-d)0&&(f+=_,d-=_)}var A=[{type:"elem",elem:u,shift:d,marginRight:x,marginLeft:b},{type:"elem",elem:l,shift:-f,marginRight:x}];S=Be.makeVList({positionType:"individualShift",children:A},e)}else if(u){d=Math.max(d,h.sub1,u.height-.8*h.xHeight);var L=[{type:"elem",elem:u,marginLeft:b,marginRight:x}];S=Be.makeVList({positionType:"shift",positionData:d,children:L},e)}else if(l)f=Math.max(f,y,l.depth+.25*h.xHeight),S=Be.makeVList({positionType:"shift",positionData:-f,children:[{type:"elem",elem:l,marginRight:x}]},e);else throw new Error("supsub must have either sup or sub.");var M=E7(s,"right")||"mord";return Be.makeSpan([M],[s,Be.makeSpan(["msupsub"],[S])],e)},mathmlBuilder(t,e){var r=!1,n,i;t.base&&t.base.type==="horizBrace"&&(i=!!t.sup,i===t.base.isOver&&(r=!0,n=t.base.isOver)),t.base&&(t.base.type==="op"||t.base.type==="operatorname")&&(t.base.parentIsSupSub=!0);var a=[fn(t.base,e)];t.sub&&a.push(fn(t.sub,e)),t.sup&&a.push(fn(t.sup,e));var s;if(r)s=n?"mover":"munder";else if(t.sub)if(t.sup){var h=t.base;h&&h.type==="op"&&h.limits&&e.style===Ht.DISPLAY||h&&h.type==="operatorname"&&h.alwaysHandleSupSub&&(e.style===Ht.DISPLAY||h.limits)?s="munderover":s="msubsup"}else{var u=t.base;u&&u.type==="op"&&u.limits&&(e.style===Ht.DISPLAY||u.alwaysHandleSupSub)||u&&u.type==="operatorname"&&u.alwaysHandleSupSub&&(u.limits||e.style===Ht.DISPLAY)?s="munder":s="msub"}else{var l=t.base;l&&l.type==="op"&&l.limits&&(e.style===Ht.DISPLAY||l.alwaysHandleSupSub)||l&&l.type==="operatorname"&&l.alwaysHandleSupSub&&(l.limits||e.style===Ht.DISPLAY)?s="mover":s="msup"}return new et.MathNode(s,a)}});Kf({type:"atom",htmlBuilder(t,e){return Be.mathsym(t.text,t.mode,e,["m"+t.family])},mathmlBuilder(t,e){var r=new et.MathNode("mo",[_o(t.text,t.mode)]);if(t.family==="bin"){var n=P7(t,e);n==="bold-italic"&&r.setAttribute("mathvariant",n)}else t.family==="punct"?r.setAttribute("separator","true"):(t.family==="open"||t.family==="close")&&r.setAttribute("stretchy","false");return r}});g$={mi:"italic",mn:"normal",mtext:"normal"};Kf({type:"mathord",htmlBuilder(t,e){return Be.makeOrd(t,e,"mathord")},mathmlBuilder(t,e){var r=new et.MathNode("mi",[_o(t.text,t.mode,e)]),n=P7(t,e)||"italic";return n!==g$[r.type]&&r.setAttribute("mathvariant",n),r}});Kf({type:"textord",htmlBuilder(t,e){return Be.makeOrd(t,e,"textord")},mathmlBuilder(t,e){var r=_o(t.text,t.mode,e),n=P7(t,e)||"normal",i;return t.mode==="text"?i=new et.MathNode("mtext",[r]):/[0-9]/.test(t.text)?i=new et.MathNode("mn",[r]):t.text==="\\prime"?i=new et.MathNode("mo",[r]):i=new et.MathNode("mi",[r]),n!==g$[i.type]&&i.setAttribute("mathvariant",n),i}});y7={"\\nobreak":"nobreak","\\allowbreak":"allowbreak"},v7={" ":{},"\\ ":{},"~":{className:"nobreak"},"\\space":{},"\\nobreakspace":{className:"nobreak"}};Kf({type:"spacing",htmlBuilder(t,e){if(v7.hasOwnProperty(t.text)){var r=v7[t.text].className||"";if(t.mode==="text"){var n=Be.makeOrd(t,e,"textord");return n.classes.push(r),n}else return Be.makeSpan(["mspace",r],[Be.mathsym(t.text,t.mode,e)],e)}else{if(y7.hasOwnProperty(t.text))return Be.makeSpan(["mspace",y7[t.text]],[],e);throw new nt('Unknown type of space "'+t.text+'"')}},mathmlBuilder(t,e){var r;if(v7.hasOwnProperty(t.text))r=new et.MathNode("mtext",[new et.TextNode("\xA0")]);else{if(y7.hasOwnProperty(t.text))return new et.MathNode("mspace");throw new nt('Unknown type of space "'+t.text+'"')}return r}});xG=o(()=>{var t=new et.MathNode("mtd",[]);return t.setAttribute("width","50%"),t},"pad");Kf({type:"tag",mathmlBuilder(t,e){var r=new et.MathNode("mtable",[new et.MathNode("mtr",[xG(),new et.MathNode("mtd",[ph(t.body,e)]),xG(),new et.MathNode("mtd",[ph(t.tag,e)])])]);return r.setAttribute("width","100%"),r}});bG={"\\text":void 0,"\\textrm":"textrm","\\textsf":"textsf","\\texttt":"texttt","\\textnormal":"textrm"},wG={"\\textbf":"textbf","\\textmd":"textmd"},dbe={"\\textit":"textit","\\textup":"textup"},TG=o((t,e)=>{var r=t.font;if(r){if(bG[r])return e.withTextFontFamily(bG[r]);if(wG[r])return e.withTextFontWeight(wG[r]);if(r==="\\emph")return e.fontShape==="textit"?e.withTextFontShape("textup"):e.withTextFontShape("textit")}else return e;return e.withTextFontShape(dbe[r])},"optionsWithFont");vt({type:"text",names:["\\text","\\textrm","\\textsf","\\texttt","\\textnormal","\\textbf","\\textmd","\\textit","\\textup","\\emph"],props:{numArgs:1,argTypes:["text"],allowedInArgument:!0,allowedInText:!0},handler(t,e){var{parser:r,funcName:n}=t,i=e[0];return{type:"text",mode:r.mode,body:ui(i),font:n}},htmlBuilder(t,e){var r=TG(t,e),n=Ri(t.body,r,!0);return Be.makeSpan(["mord","text"],n,r)},mathmlBuilder(t,e){var r=TG(t,e);return ph(t.body,r)}});vt({type:"underline",names:["\\underline"],props:{numArgs:1,allowedInText:!0},handler(t,e){var{parser:r}=t;return{type:"underline",mode:r.mode,body:e[0]}},htmlBuilder(t,e){var r=Cr(t.body,e),n=Be.makeLineSpan("underline-line",e),i=e.fontMetrics().defaultRuleThickness,a=Be.makeVList({positionType:"top",positionData:r.height,children:[{type:"kern",size:i},{type:"elem",elem:n},{type:"kern",size:3*i},{type:"elem",elem:r}]},e);return Be.makeSpan(["mord","underline"],[a],e)},mathmlBuilder(t,e){var r=new et.MathNode("mo",[new et.TextNode("\u203E")]);r.setAttribute("stretchy","true");var n=new et.MathNode("munder",[fn(t.body,e),r]);return n.setAttribute("accentunder","true"),n}});vt({type:"vcenter",names:["\\vcenter"],props:{numArgs:1,argTypes:["original"],allowedInText:!1},handler(t,e){var{parser:r}=t;return{type:"vcenter",mode:r.mode,body:e[0]}},htmlBuilder(t,e){var r=Cr(t.body,e),n=e.fontMetrics().axisHeight,i=.5*(r.height-n-(r.depth+n));return Be.makeVList({positionType:"shift",positionData:i,children:[{type:"elem",elem:r}]},e)},mathmlBuilder(t,e){return new et.MathNode("mpadded",[fn(t.body,e)],["vcenter"])}});vt({type:"verb",names:["\\verb"],props:{numArgs:0,allowedInText:!0},handler(t,e,r){throw new nt("\\verb ended by end of line instead of matching delimiter")},htmlBuilder(t,e){for(var r=kG(t),n=[],i=e.havingStyle(e.style.text()),a=0;at.body.replace(/ /g,t.star?"\u2423":"\xA0"),"makeVerb"),fh=zG,y$=`[ \r + ]`,pbe="\\\\[a-zA-Z@]+",mbe="\\\\[^\uD800-\uDFFF]",gbe="("+pbe+")"+y$+"*",ybe=`\\\\( +|[ \r ]+ +?)[ \r ]*`,L7="[\u0300-\u036F]",vbe=new RegExp(L7+"+$"),xbe="("+y$+"+)|"+(ybe+"|")+"([!-\\[\\]-\u2027\u202A-\uD7FF\uF900-\uFFFF]"+(L7+"*")+"|[\uD800-\uDBFF][\uDC00-\uDFFF]"+(L7+"*")+"|\\\\verb\\*([^]).*?\\4|\\\\verb([^*a-zA-Z]).*?\\5"+("|"+gbe)+("|"+mbe+")"),v4=class{static{o(this,"Lexer")}constructor(e,r){this.input=void 0,this.settings=void 0,this.tokenRegex=void 0,this.catcodes=void 0,this.input=e,this.settings=r,this.tokenRegex=new RegExp(xbe,"g"),this.catcodes={"%":14,"~":13}}setCatcode(e,r){this.catcodes[e]=r}lex(){var e=this.input,r=this.tokenRegex.lastIndex;if(r===e.length)return new Ao("EOF",new Xs(this,r,r));var n=this.tokenRegex.exec(e);if(n===null||n.index!==r)throw new nt("Unexpected character: '"+e[r]+"'",new Ao(e[r],new Xs(this,r,r+1)));var i=n[6]||n[3]||(n[2]?"\\ ":" ");if(this.catcodes[i]===14){var a=e.indexOf(` +`,this.tokenRegex.lastIndex);return a===-1?(this.tokenRegex.lastIndex=e.length,this.settings.reportNonstrict("commentAtEnd","% comment has no terminating newline; LaTeX would fail because of commenting the end of math mode (e.g. $)")):this.tokenRegex.lastIndex=a+1,this.lex()}return new Ao(i,new Xs(this,r,this.tokenRegex.lastIndex))}},D7=class{static{o(this,"Namespace")}constructor(e,r){e===void 0&&(e={}),r===void 0&&(r={}),this.current=void 0,this.builtins=void 0,this.undefStack=void 0,this.current=r,this.builtins=e,this.undefStack=[]}beginGroup(){this.undefStack.push({})}endGroup(){if(this.undefStack.length===0)throw new nt("Unbalanced namespace destruction: attempt to pop global namespace; please report this as a bug");var e=this.undefStack.pop();for(var r in e)e.hasOwnProperty(r)&&(e[r]==null?delete this.current[r]:this.current[r]=e[r])}endGroups(){for(;this.undefStack.length>0;)this.endGroup()}has(e){return this.current.hasOwnProperty(e)||this.builtins.hasOwnProperty(e)}get(e){return this.current.hasOwnProperty(e)?this.current[e]:this.builtins[e]}set(e,r,n){if(n===void 0&&(n=!1),n){for(var i=0;i0&&(this.undefStack[this.undefStack.length-1][e]=r)}else{var a=this.undefStack[this.undefStack.length-1];a&&!a.hasOwnProperty(e)&&(a[e]=this.current[e])}r==null?delete this.current[e]:this.current[e]=r}},bbe=s$;le("\\noexpand",function(t){var e=t.popToken();return t.isExpandable(e.text)&&(e.noexpand=!0,e.treatAsRelax=!0),{tokens:[e],numArgs:0}});le("\\expandafter",function(t){var e=t.popToken();return t.expandOnce(!0),{tokens:[e],numArgs:0}});le("\\@firstoftwo",function(t){var e=t.consumeArgs(2);return{tokens:e[0],numArgs:0}});le("\\@secondoftwo",function(t){var e=t.consumeArgs(2);return{tokens:e[1],numArgs:0}});le("\\@ifnextchar",function(t){var e=t.consumeArgs(3);t.consumeSpaces();var r=t.future();return e[0].length===1&&e[0][0].text===r.text?{tokens:e[1],numArgs:0}:{tokens:e[2],numArgs:0}});le("\\@ifstar","\\@ifnextchar *{\\@firstoftwo{#1}}");le("\\TextOrMath",function(t){var e=t.consumeArgs(2);return t.mode==="text"?{tokens:e[0],numArgs:0}:{tokens:e[1],numArgs:0}});EG={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,a:10,A:10,b:11,B:11,c:12,C:12,d:13,D:13,e:14,E:14,f:15,F:15};le("\\char",function(t){var e=t.popToken(),r,n="";if(e.text==="'")r=8,e=t.popToken();else if(e.text==='"')r=16,e=t.popToken();else if(e.text==="`")if(e=t.popToken(),e.text[0]==="\\")n=e.text.charCodeAt(1);else{if(e.text==="EOF")throw new nt("\\char` missing argument");n=e.text.charCodeAt(0)}else r=10;if(r){if(n=EG[e.text],n==null||n>=r)throw new nt("Invalid base-"+r+" digit "+e.text);for(var i;(i=EG[t.future().text])!=null&&i{var n=t.consumeArg().tokens;if(n.length!==1)throw new nt("\\newcommand's first argument must be a macro name");var i=n[0].text,a=t.isDefined(i);if(a&&!e)throw new nt("\\newcommand{"+i+"} attempting to redefine "+(i+"; use \\renewcommand"));if(!a&&!r)throw new nt("\\renewcommand{"+i+"} when command "+i+" does not yet exist; use \\newcommand");var s=0;if(n=t.consumeArg().tokens,n.length===1&&n[0].text==="["){for(var l="",u=t.expandNextToken();u.text!=="]"&&u.text!=="EOF";)l+=u.text,u=t.expandNextToken();if(!l.match(/^\s*[0-9]+\s*$/))throw new nt("Invalid number of arguments: "+l);s=parseInt(l),n=t.consumeArg().tokens}return t.macros.set(i,{tokens:n,numArgs:s}),""},"newcommand");le("\\newcommand",t=>W7(t,!1,!0));le("\\renewcommand",t=>W7(t,!0,!1));le("\\providecommand",t=>W7(t,!0,!0));le("\\message",t=>{var e=t.consumeArgs(1)[0];return console.log(e.reverse().map(r=>r.text).join("")),""});le("\\errmessage",t=>{var e=t.consumeArgs(1)[0];return console.error(e.reverse().map(r=>r.text).join("")),""});le("\\show",t=>{var e=t.popToken(),r=e.text;return console.log(e,t.macros.get(r),fh[r],wn.math[r],wn.text[r]),""});le("\\bgroup","{");le("\\egroup","}");le("~","\\nobreakspace");le("\\lq","`");le("\\rq","'");le("\\aa","\\r a");le("\\AA","\\r A");le("\\textcopyright","\\html@mathml{\\textcircled{c}}{\\char`\xA9}");le("\\copyright","\\TextOrMath{\\textcopyright}{\\text{\\textcopyright}}");le("\\textregistered","\\html@mathml{\\textcircled{\\scriptsize R}}{\\char`\xAE}");le("\u212C","\\mathscr{B}");le("\u2130","\\mathscr{E}");le("\u2131","\\mathscr{F}");le("\u210B","\\mathscr{H}");le("\u2110","\\mathscr{I}");le("\u2112","\\mathscr{L}");le("\u2133","\\mathscr{M}");le("\u211B","\\mathscr{R}");le("\u212D","\\mathfrak{C}");le("\u210C","\\mathfrak{H}");le("\u2128","\\mathfrak{Z}");le("\\Bbbk","\\Bbb{k}");le("\xB7","\\cdotp");le("\\llap","\\mathllap{\\textrm{#1}}");le("\\rlap","\\mathrlap{\\textrm{#1}}");le("\\clap","\\mathclap{\\textrm{#1}}");le("\\mathstrut","\\vphantom{(}");le("\\underbar","\\underline{\\text{#1}}");le("\\not",'\\html@mathml{\\mathrel{\\mathrlap\\@not}}{\\char"338}');le("\\neq","\\html@mathml{\\mathrel{\\not=}}{\\mathrel{\\char`\u2260}}");le("\\ne","\\neq");le("\u2260","\\neq");le("\\notin","\\html@mathml{\\mathrel{{\\in}\\mathllap{/\\mskip1mu}}}{\\mathrel{\\char`\u2209}}");le("\u2209","\\notin");le("\u2258","\\html@mathml{\\mathrel{=\\kern{-1em}\\raisebox{0.4em}{$\\scriptsize\\frown$}}}{\\mathrel{\\char`\u2258}}");le("\u2259","\\html@mathml{\\stackrel{\\tiny\\wedge}{=}}{\\mathrel{\\char`\u2258}}");le("\u225A","\\html@mathml{\\stackrel{\\tiny\\vee}{=}}{\\mathrel{\\char`\u225A}}");le("\u225B","\\html@mathml{\\stackrel{\\scriptsize\\star}{=}}{\\mathrel{\\char`\u225B}}");le("\u225D","\\html@mathml{\\stackrel{\\tiny\\mathrm{def}}{=}}{\\mathrel{\\char`\u225D}}");le("\u225E","\\html@mathml{\\stackrel{\\tiny\\mathrm{m}}{=}}{\\mathrel{\\char`\u225E}}");le("\u225F","\\html@mathml{\\stackrel{\\tiny?}{=}}{\\mathrel{\\char`\u225F}}");le("\u27C2","\\perp");le("\u203C","\\mathclose{!\\mkern-0.8mu!}");le("\u220C","\\notni");le("\u231C","\\ulcorner");le("\u231D","\\urcorner");le("\u231E","\\llcorner");le("\u231F","\\lrcorner");le("\xA9","\\copyright");le("\xAE","\\textregistered");le("\uFE0F","\\textregistered");le("\\ulcorner",'\\html@mathml{\\@ulcorner}{\\mathop{\\char"231c}}');le("\\urcorner",'\\html@mathml{\\@urcorner}{\\mathop{\\char"231d}}');le("\\llcorner",'\\html@mathml{\\@llcorner}{\\mathop{\\char"231e}}');le("\\lrcorner",'\\html@mathml{\\@lrcorner}{\\mathop{\\char"231f}}');le("\\vdots","\\mathord{\\varvdots\\rule{0pt}{15pt}}");le("\u22EE","\\vdots");le("\\varGamma","\\mathit{\\Gamma}");le("\\varDelta","\\mathit{\\Delta}");le("\\varTheta","\\mathit{\\Theta}");le("\\varLambda","\\mathit{\\Lambda}");le("\\varXi","\\mathit{\\Xi}");le("\\varPi","\\mathit{\\Pi}");le("\\varSigma","\\mathit{\\Sigma}");le("\\varUpsilon","\\mathit{\\Upsilon}");le("\\varPhi","\\mathit{\\Phi}");le("\\varPsi","\\mathit{\\Psi}");le("\\varOmega","\\mathit{\\Omega}");le("\\substack","\\begin{subarray}{c}#1\\end{subarray}");le("\\colon","\\nobreak\\mskip2mu\\mathpunct{}\\mathchoice{\\mkern-3mu}{\\mkern-3mu}{}{}{:}\\mskip6mu\\relax");le("\\boxed","\\fbox{$\\displaystyle{#1}$}");le("\\iff","\\DOTSB\\;\\Longleftrightarrow\\;");le("\\implies","\\DOTSB\\;\\Longrightarrow\\;");le("\\impliedby","\\DOTSB\\;\\Longleftarrow\\;");CG={",":"\\dotsc","\\not":"\\dotsb","+":"\\dotsb","=":"\\dotsb","<":"\\dotsb",">":"\\dotsb","-":"\\dotsb","*":"\\dotsb",":":"\\dotsb","\\DOTSB":"\\dotsb","\\coprod":"\\dotsb","\\bigvee":"\\dotsb","\\bigwedge":"\\dotsb","\\biguplus":"\\dotsb","\\bigcap":"\\dotsb","\\bigcup":"\\dotsb","\\prod":"\\dotsb","\\sum":"\\dotsb","\\bigotimes":"\\dotsb","\\bigoplus":"\\dotsb","\\bigodot":"\\dotsb","\\bigsqcup":"\\dotsb","\\And":"\\dotsb","\\longrightarrow":"\\dotsb","\\Longrightarrow":"\\dotsb","\\longleftarrow":"\\dotsb","\\Longleftarrow":"\\dotsb","\\longleftrightarrow":"\\dotsb","\\Longleftrightarrow":"\\dotsb","\\mapsto":"\\dotsb","\\longmapsto":"\\dotsb","\\hookrightarrow":"\\dotsb","\\doteq":"\\dotsb","\\mathbin":"\\dotsb","\\mathrel":"\\dotsb","\\relbar":"\\dotsb","\\Relbar":"\\dotsb","\\xrightarrow":"\\dotsb","\\xleftarrow":"\\dotsb","\\DOTSI":"\\dotsi","\\int":"\\dotsi","\\oint":"\\dotsi","\\iint":"\\dotsi","\\iiint":"\\dotsi","\\iiiint":"\\dotsi","\\idotsint":"\\dotsi","\\DOTSX":"\\dotsx"};le("\\dots",function(t){var e="\\dotso",r=t.expandAfterFuture().text;return r in CG?e=CG[r]:(r.slice(0,4)==="\\not"||r in wn.math&&Vt.contains(["bin","rel"],wn.math[r].group))&&(e="\\dotsb"),e});q7={")":!0,"]":!0,"\\rbrack":!0,"\\}":!0,"\\rbrace":!0,"\\rangle":!0,"\\rceil":!0,"\\rfloor":!0,"\\rgroup":!0,"\\rmoustache":!0,"\\right":!0,"\\bigr":!0,"\\biggr":!0,"\\Bigr":!0,"\\Biggr":!0,$:!0,";":!0,".":!0,",":!0};le("\\dotso",function(t){var e=t.future().text;return e in q7?"\\ldots\\,":"\\ldots"});le("\\dotsc",function(t){var e=t.future().text;return e in q7&&e!==","?"\\ldots\\,":"\\ldots"});le("\\cdots",function(t){var e=t.future().text;return e in q7?"\\@cdots\\,":"\\@cdots"});le("\\dotsb","\\cdots");le("\\dotsm","\\cdots");le("\\dotsi","\\!\\cdots");le("\\dotsx","\\ldots\\,");le("\\DOTSI","\\relax");le("\\DOTSB","\\relax");le("\\DOTSX","\\relax");le("\\tmspace","\\TextOrMath{\\kern#1#3}{\\mskip#1#2}\\relax");le("\\,","\\tmspace+{3mu}{.1667em}");le("\\thinspace","\\,");le("\\>","\\mskip{4mu}");le("\\:","\\tmspace+{4mu}{.2222em}");le("\\medspace","\\:");le("\\;","\\tmspace+{5mu}{.2777em}");le("\\thickspace","\\;");le("\\!","\\tmspace-{3mu}{.1667em}");le("\\negthinspace","\\!");le("\\negmedspace","\\tmspace-{4mu}{.2222em}");le("\\negthickspace","\\tmspace-{5mu}{.277em}");le("\\enspace","\\kern.5em ");le("\\enskip","\\hskip.5em\\relax");le("\\quad","\\hskip1em\\relax");le("\\qquad","\\hskip2em\\relax");le("\\tag","\\@ifstar\\tag@literal\\tag@paren");le("\\tag@paren","\\tag@literal{({#1})}");le("\\tag@literal",t=>{if(t.macros.get("\\df@tag"))throw new nt("Multiple \\tag");return"\\gdef\\df@tag{\\text{#1}}"});le("\\bmod","\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}\\mathbin{\\rm mod}\\mathchoice{\\mskip1mu}{\\mskip1mu}{\\mskip5mu}{\\mskip5mu}");le("\\pod","\\allowbreak\\mathchoice{\\mkern18mu}{\\mkern8mu}{\\mkern8mu}{\\mkern8mu}(#1)");le("\\pmod","\\pod{{\\rm mod}\\mkern6mu#1}");le("\\mod","\\allowbreak\\mathchoice{\\mkern18mu}{\\mkern12mu}{\\mkern12mu}{\\mkern12mu}{\\rm mod}\\,\\,#1");le("\\newline","\\\\\\relax");le("\\TeX","\\textrm{\\html@mathml{T\\kern-.1667em\\raisebox{-.5ex}{E}\\kern-.125emX}{TeX}}");v$=ct(Zl["Main-Regular"][84][1]-.7*Zl["Main-Regular"][65][1]);le("\\LaTeX","\\textrm{\\html@mathml{"+("L\\kern-.36em\\raisebox{"+v$+"}{\\scriptstyle A}")+"\\kern-.15em\\TeX}{LaTeX}}");le("\\KaTeX","\\textrm{\\html@mathml{"+("K\\kern-.17em\\raisebox{"+v$+"}{\\scriptstyle A}")+"\\kern-.15em\\TeX}{KaTeX}}");le("\\hspace","\\@ifstar\\@hspacer\\@hspace");le("\\@hspace","\\hskip #1\\relax");le("\\@hspacer","\\rule{0pt}{0pt}\\hskip #1\\relax");le("\\ordinarycolon",":");le("\\vcentcolon","\\mathrel{\\mathop\\ordinarycolon}");le("\\dblcolon",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-.9mu}\\vcentcolon}}{\\mathop{\\char"2237}}');le("\\coloneqq",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}=}}{\\mathop{\\char"2254}}');le("\\Coloneqq",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}=}}{\\mathop{\\char"2237\\char"3d}}');le("\\coloneq",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}{\\mathop{\\char"3a\\char"2212}}');le("\\Coloneq",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\mathrel{-}}}{\\mathop{\\char"2237\\char"2212}}');le("\\eqqcolon",'\\html@mathml{\\mathrel{=\\mathrel{\\mkern-1.2mu}\\vcentcolon}}{\\mathop{\\char"2255}}');le("\\Eqqcolon",'\\html@mathml{\\mathrel{=\\mathrel{\\mkern-1.2mu}\\dblcolon}}{\\mathop{\\char"3d\\char"2237}}');le("\\eqcolon",'\\html@mathml{\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\vcentcolon}}{\\mathop{\\char"2239}}');le("\\Eqcolon",'\\html@mathml{\\mathrel{\\mathrel{-}\\mathrel{\\mkern-1.2mu}\\dblcolon}}{\\mathop{\\char"2212\\char"2237}}');le("\\colonapprox",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\approx}}{\\mathop{\\char"3a\\char"2248}}');le("\\Colonapprox",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\approx}}{\\mathop{\\char"2237\\char"2248}}');le("\\colonsim",'\\html@mathml{\\mathrel{\\vcentcolon\\mathrel{\\mkern-1.2mu}\\sim}}{\\mathop{\\char"3a\\char"223c}}');le("\\Colonsim",'\\html@mathml{\\mathrel{\\dblcolon\\mathrel{\\mkern-1.2mu}\\sim}}{\\mathop{\\char"2237\\char"223c}}');le("\u2237","\\dblcolon");le("\u2239","\\eqcolon");le("\u2254","\\coloneqq");le("\u2255","\\eqqcolon");le("\u2A74","\\Coloneqq");le("\\ratio","\\vcentcolon");le("\\coloncolon","\\dblcolon");le("\\colonequals","\\coloneqq");le("\\coloncolonequals","\\Coloneqq");le("\\equalscolon","\\eqqcolon");le("\\equalscoloncolon","\\Eqqcolon");le("\\colonminus","\\coloneq");le("\\coloncolonminus","\\Coloneq");le("\\minuscolon","\\eqcolon");le("\\minuscoloncolon","\\Eqcolon");le("\\coloncolonapprox","\\Colonapprox");le("\\coloncolonsim","\\Colonsim");le("\\simcolon","\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\vcentcolon}");le("\\simcoloncolon","\\mathrel{\\sim\\mathrel{\\mkern-1.2mu}\\dblcolon}");le("\\approxcolon","\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\vcentcolon}");le("\\approxcoloncolon","\\mathrel{\\approx\\mathrel{\\mkern-1.2mu}\\dblcolon}");le("\\notni","\\html@mathml{\\not\\ni}{\\mathrel{\\char`\u220C}}");le("\\limsup","\\DOTSB\\operatorname*{lim\\,sup}");le("\\liminf","\\DOTSB\\operatorname*{lim\\,inf}");le("\\injlim","\\DOTSB\\operatorname*{inj\\,lim}");le("\\projlim","\\DOTSB\\operatorname*{proj\\,lim}");le("\\varlimsup","\\DOTSB\\operatorname*{\\overline{lim}}");le("\\varliminf","\\DOTSB\\operatorname*{\\underline{lim}}");le("\\varinjlim","\\DOTSB\\operatorname*{\\underrightarrow{lim}}");le("\\varprojlim","\\DOTSB\\operatorname*{\\underleftarrow{lim}}");le("\\gvertneqq","\\html@mathml{\\@gvertneqq}{\u2269}");le("\\lvertneqq","\\html@mathml{\\@lvertneqq}{\u2268}");le("\\ngeqq","\\html@mathml{\\@ngeqq}{\u2271}");le("\\ngeqslant","\\html@mathml{\\@ngeqslant}{\u2271}");le("\\nleqq","\\html@mathml{\\@nleqq}{\u2270}");le("\\nleqslant","\\html@mathml{\\@nleqslant}{\u2270}");le("\\nshortmid","\\html@mathml{\\@nshortmid}{\u2224}");le("\\nshortparallel","\\html@mathml{\\@nshortparallel}{\u2226}");le("\\nsubseteqq","\\html@mathml{\\@nsubseteqq}{\u2288}");le("\\nsupseteqq","\\html@mathml{\\@nsupseteqq}{\u2289}");le("\\varsubsetneq","\\html@mathml{\\@varsubsetneq}{\u228A}");le("\\varsubsetneqq","\\html@mathml{\\@varsubsetneqq}{\u2ACB}");le("\\varsupsetneq","\\html@mathml{\\@varsupsetneq}{\u228B}");le("\\varsupsetneqq","\\html@mathml{\\@varsupsetneqq}{\u2ACC}");le("\\imath","\\html@mathml{\\@imath}{\u0131}");le("\\jmath","\\html@mathml{\\@jmath}{\u0237}");le("\\llbracket","\\html@mathml{\\mathopen{[\\mkern-3.2mu[}}{\\mathopen{\\char`\u27E6}}");le("\\rrbracket","\\html@mathml{\\mathclose{]\\mkern-3.2mu]}}{\\mathclose{\\char`\u27E7}}");le("\u27E6","\\llbracket");le("\u27E7","\\rrbracket");le("\\lBrace","\\html@mathml{\\mathopen{\\{\\mkern-3.2mu[}}{\\mathopen{\\char`\u2983}}");le("\\rBrace","\\html@mathml{\\mathclose{]\\mkern-3.2mu\\}}}{\\mathclose{\\char`\u2984}}");le("\u2983","\\lBrace");le("\u2984","\\rBrace");le("\\minuso","\\mathbin{\\html@mathml{{\\mathrlap{\\mathchoice{\\kern{0.145em}}{\\kern{0.145em}}{\\kern{0.1015em}}{\\kern{0.0725em}}\\circ}{-}}}{\\char`\u29B5}}");le("\u29B5","\\minuso");le("\\darr","\\downarrow");le("\\dArr","\\Downarrow");le("\\Darr","\\Downarrow");le("\\lang","\\langle");le("\\rang","\\rangle");le("\\uarr","\\uparrow");le("\\uArr","\\Uparrow");le("\\Uarr","\\Uparrow");le("\\N","\\mathbb{N}");le("\\R","\\mathbb{R}");le("\\Z","\\mathbb{Z}");le("\\alef","\\aleph");le("\\alefsym","\\aleph");le("\\Alpha","\\mathrm{A}");le("\\Beta","\\mathrm{B}");le("\\bull","\\bullet");le("\\Chi","\\mathrm{X}");le("\\clubs","\\clubsuit");le("\\cnums","\\mathbb{C}");le("\\Complex","\\mathbb{C}");le("\\Dagger","\\ddagger");le("\\diamonds","\\diamondsuit");le("\\empty","\\emptyset");le("\\Epsilon","\\mathrm{E}");le("\\Eta","\\mathrm{H}");le("\\exist","\\exists");le("\\harr","\\leftrightarrow");le("\\hArr","\\Leftrightarrow");le("\\Harr","\\Leftrightarrow");le("\\hearts","\\heartsuit");le("\\image","\\Im");le("\\infin","\\infty");le("\\Iota","\\mathrm{I}");le("\\isin","\\in");le("\\Kappa","\\mathrm{K}");le("\\larr","\\leftarrow");le("\\lArr","\\Leftarrow");le("\\Larr","\\Leftarrow");le("\\lrarr","\\leftrightarrow");le("\\lrArr","\\Leftrightarrow");le("\\Lrarr","\\Leftrightarrow");le("\\Mu","\\mathrm{M}");le("\\natnums","\\mathbb{N}");le("\\Nu","\\mathrm{N}");le("\\Omicron","\\mathrm{O}");le("\\plusmn","\\pm");le("\\rarr","\\rightarrow");le("\\rArr","\\Rightarrow");le("\\Rarr","\\Rightarrow");le("\\real","\\Re");le("\\reals","\\mathbb{R}");le("\\Reals","\\mathbb{R}");le("\\Rho","\\mathrm{P}");le("\\sdot","\\cdot");le("\\sect","\\S");le("\\spades","\\spadesuit");le("\\sub","\\subset");le("\\sube","\\subseteq");le("\\supe","\\supseteq");le("\\Tau","\\mathrm{T}");le("\\thetasym","\\vartheta");le("\\weierp","\\wp");le("\\Zeta","\\mathrm{Z}");le("\\argmin","\\DOTSB\\operatorname*{arg\\,min}");le("\\argmax","\\DOTSB\\operatorname*{arg\\,max}");le("\\plim","\\DOTSB\\mathop{\\operatorname{plim}}\\limits");le("\\bra","\\mathinner{\\langle{#1}|}");le("\\ket","\\mathinner{|{#1}\\rangle}");le("\\braket","\\mathinner{\\langle{#1}\\rangle}");le("\\Bra","\\left\\langle#1\\right|");le("\\Ket","\\left|#1\\right\\rangle");x$=o(t=>e=>{var r=e.consumeArg().tokens,n=e.consumeArg().tokens,i=e.consumeArg().tokens,a=e.consumeArg().tokens,s=e.macros.get("|"),l=e.macros.get("\\|");e.macros.beginGroup();var u=o(d=>p=>{t&&(p.macros.set("|",s),i.length&&p.macros.set("\\|",l));var m=d;if(!d&&i.length){var g=p.future();g.text==="|"&&(p.popToken(),m=!0)}return{tokens:m?i:n,numArgs:0}},"midMacro");e.macros.set("|",u(!1)),i.length&&e.macros.set("\\|",u(!0));var h=e.consumeArg().tokens,f=e.expandTokens([...a,...h,...r]);return e.macros.endGroup(),{tokens:f.reverse(),numArgs:0}},"braketHelper");le("\\bra@ket",x$(!1));le("\\bra@set",x$(!0));le("\\Braket","\\bra@ket{\\left\\langle}{\\,\\middle\\vert\\,}{\\,\\middle\\vert\\,}{\\right\\rangle}");le("\\Set","\\bra@set{\\left\\{\\:}{\\;\\middle\\vert\\;}{\\;\\middle\\Vert\\;}{\\:\\right\\}}");le("\\set","\\bra@set{\\{\\,}{\\mid}{}{\\,\\}}");le("\\angln","{\\angl n}");le("\\blue","\\textcolor{##6495ed}{#1}");le("\\orange","\\textcolor{##ffa500}{#1}");le("\\pink","\\textcolor{##ff00af}{#1}");le("\\red","\\textcolor{##df0030}{#1}");le("\\green","\\textcolor{##28ae7b}{#1}");le("\\gray","\\textcolor{gray}{#1}");le("\\purple","\\textcolor{##9d38bd}{#1}");le("\\blueA","\\textcolor{##ccfaff}{#1}");le("\\blueB","\\textcolor{##80f6ff}{#1}");le("\\blueC","\\textcolor{##63d9ea}{#1}");le("\\blueD","\\textcolor{##11accd}{#1}");le("\\blueE","\\textcolor{##0c7f99}{#1}");le("\\tealA","\\textcolor{##94fff5}{#1}");le("\\tealB","\\textcolor{##26edd5}{#1}");le("\\tealC","\\textcolor{##01d1c1}{#1}");le("\\tealD","\\textcolor{##01a995}{#1}");le("\\tealE","\\textcolor{##208170}{#1}");le("\\greenA","\\textcolor{##b6ffb0}{#1}");le("\\greenB","\\textcolor{##8af281}{#1}");le("\\greenC","\\textcolor{##74cf70}{#1}");le("\\greenD","\\textcolor{##1fab54}{#1}");le("\\greenE","\\textcolor{##0d923f}{#1}");le("\\goldA","\\textcolor{##ffd0a9}{#1}");le("\\goldB","\\textcolor{##ffbb71}{#1}");le("\\goldC","\\textcolor{##ff9c39}{#1}");le("\\goldD","\\textcolor{##e07d10}{#1}");le("\\goldE","\\textcolor{##a75a05}{#1}");le("\\redA","\\textcolor{##fca9a9}{#1}");le("\\redB","\\textcolor{##ff8482}{#1}");le("\\redC","\\textcolor{##f9685d}{#1}");le("\\redD","\\textcolor{##e84d39}{#1}");le("\\redE","\\textcolor{##bc2612}{#1}");le("\\maroonA","\\textcolor{##ffbde0}{#1}");le("\\maroonB","\\textcolor{##ff92c6}{#1}");le("\\maroonC","\\textcolor{##ed5fa6}{#1}");le("\\maroonD","\\textcolor{##ca337c}{#1}");le("\\maroonE","\\textcolor{##9e034e}{#1}");le("\\purpleA","\\textcolor{##ddd7ff}{#1}");le("\\purpleB","\\textcolor{##c6b9fc}{#1}");le("\\purpleC","\\textcolor{##aa87ff}{#1}");le("\\purpleD","\\textcolor{##7854ab}{#1}");le("\\purpleE","\\textcolor{##543b78}{#1}");le("\\mintA","\\textcolor{##f5f9e8}{#1}");le("\\mintB","\\textcolor{##edf2df}{#1}");le("\\mintC","\\textcolor{##e0e5cc}{#1}");le("\\grayA","\\textcolor{##f6f7f7}{#1}");le("\\grayB","\\textcolor{##f0f1f2}{#1}");le("\\grayC","\\textcolor{##e3e5e6}{#1}");le("\\grayD","\\textcolor{##d6d8da}{#1}");le("\\grayE","\\textcolor{##babec2}{#1}");le("\\grayF","\\textcolor{##888d93}{#1}");le("\\grayG","\\textcolor{##626569}{#1}");le("\\grayH","\\textcolor{##3b3e40}{#1}");le("\\grayI","\\textcolor{##21242c}{#1}");le("\\kaBlue","\\textcolor{##314453}{#1}");le("\\kaGreen","\\textcolor{##71B307}{#1}");b$={"^":!0,_:!0,"\\limits":!0,"\\nolimits":!0},R7=class{static{o(this,"MacroExpander")}constructor(e,r,n){this.settings=void 0,this.expansionCount=void 0,this.lexer=void 0,this.macros=void 0,this.stack=void 0,this.mode=void 0,this.settings=r,this.expansionCount=0,this.feed(e),this.macros=new D7(bbe,r.macros),this.mode=n,this.stack=[]}feed(e){this.lexer=new v4(e,this.settings)}switchMode(e){this.mode=e}beginGroup(){this.macros.beginGroup()}endGroup(){this.macros.endGroup()}endGroups(){this.macros.endGroups()}future(){return this.stack.length===0&&this.pushToken(this.lexer.lex()),this.stack[this.stack.length-1]}popToken(){return this.future(),this.stack.pop()}pushToken(e){this.stack.push(e)}pushTokens(e){this.stack.push(...e)}scanArgument(e){var r,n,i;if(e){if(this.consumeSpaces(),this.future().text!=="[")return null;r=this.popToken(),{tokens:i,end:n}=this.consumeArg(["]"])}else({tokens:i,start:r,end:n}=this.consumeArg());return this.pushToken(new Ao("EOF",n.loc)),this.pushTokens(i),r.range(n,"")}consumeSpaces(){for(;;){var e=this.future();if(e.text===" ")this.stack.pop();else break}}consumeArg(e){var r=[],n=e&&e.length>0;n||this.consumeSpaces();var i=this.future(),a,s=0,l=0;do{if(a=this.popToken(),r.push(a),a.text==="{")++s;else if(a.text==="}"){if(--s,s===-1)throw new nt("Extra }",a)}else if(a.text==="EOF")throw new nt("Unexpected end of input in a macro argument, expected '"+(e&&n?e[l]:"}")+"'",a);if(e&&n)if((s===0||s===1&&e[l]==="{")&&a.text===e[l]){if(++l,l===e.length){r.splice(-l,l);break}}else l=0}while(s!==0||n);return i.text==="{"&&r[r.length-1].text==="}"&&(r.pop(),r.shift()),r.reverse(),{tokens:r,start:i,end:a}}consumeArgs(e,r){if(r){if(r.length!==e+1)throw new nt("The length of delimiters doesn't match the number of args!");for(var n=r[0],i=0;ithis.settings.maxExpand)throw new nt("Too many expansions: infinite loop or need to increase maxExpand setting")}expandOnce(e){var r=this.popToken(),n=r.text,i=r.noexpand?null:this._getExpansion(n);if(i==null||e&&i.unexpandable){if(e&&i==null&&n[0]==="\\"&&!this.isDefined(n))throw new nt("Undefined control sequence: "+n);return this.pushToken(r),!1}this.countExpansion(1);var a=i.tokens,s=this.consumeArgs(i.numArgs,i.delimiters);if(i.numArgs){a=a.slice();for(var l=a.length-1;l>=0;--l){var u=a[l];if(u.text==="#"){if(l===0)throw new nt("Incomplete placeholder at end of macro body",u);if(u=a[--l],u.text==="#")a.splice(l+1,1);else if(/^[1-9]$/.test(u.text))a.splice(l,2,...s[+u.text-1]);else throw new nt("Not a valid argument number",u)}}}return this.pushTokens(a),a.length}expandAfterFuture(){return this.expandOnce(),this.future()}expandNextToken(){for(;;)if(this.expandOnce()===!1){var e=this.stack.pop();return e.treatAsRelax&&(e.text="\\relax"),e}throw new Error}expandMacro(e){return this.macros.has(e)?this.expandTokens([new Ao(e)]):void 0}expandTokens(e){var r=[],n=this.stack.length;for(this.pushTokens(e);this.stack.length>n;)if(this.expandOnce(!0)===!1){var i=this.stack.pop();i.treatAsRelax&&(i.noexpand=!1,i.treatAsRelax=!1),r.push(i)}return this.countExpansion(r.length),r}expandMacroAsText(e){var r=this.expandMacro(e);return r&&r.map(n=>n.text).join("")}_getExpansion(e){var r=this.macros.get(e);if(r==null)return r;if(e.length===1){var n=this.lexer.catcodes[e];if(n!=null&&n!==13)return}var i=typeof r=="function"?r(this):r;if(typeof i=="string"){var a=0;if(i.indexOf("#")!==-1)for(var s=i.replace(/##/g,"");s.indexOf("#"+(a+1))!==-1;)++a;for(var l=new v4(i,this.settings),u=[],h=l.lex();h.text!=="EOF";)u.push(h),h=l.lex();u.reverse();var f={tokens:u,numArgs:a};return f}return i}isDefined(e){return this.macros.has(e)||fh.hasOwnProperty(e)||wn.math.hasOwnProperty(e)||wn.text.hasOwnProperty(e)||b$.hasOwnProperty(e)}isExpandable(e){var r=this.macros.get(e);return r!=null?typeof r=="string"||typeof r=="function"||!r.unexpandable:fh.hasOwnProperty(e)&&!fh[e].primitive}},SG=/^[₊₋₌₍₎₀₁₂₃₄₅₆₇₈₉ₐₑₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓᵦᵧᵨᵩᵪ]/,c4=Object.freeze({"\u208A":"+","\u208B":"-","\u208C":"=","\u208D":"(","\u208E":")","\u2080":"0","\u2081":"1","\u2082":"2","\u2083":"3","\u2084":"4","\u2085":"5","\u2086":"6","\u2087":"7","\u2088":"8","\u2089":"9","\u2090":"a","\u2091":"e","\u2095":"h","\u1D62":"i","\u2C7C":"j","\u2096":"k","\u2097":"l","\u2098":"m","\u2099":"n","\u2092":"o","\u209A":"p","\u1D63":"r","\u209B":"s","\u209C":"t","\u1D64":"u","\u1D65":"v","\u2093":"x","\u1D66":"\u03B2","\u1D67":"\u03B3","\u1D68":"\u03C1","\u1D69":"\u03D5","\u1D6A":"\u03C7","\u207A":"+","\u207B":"-","\u207C":"=","\u207D":"(","\u207E":")","\u2070":"0","\xB9":"1","\xB2":"2","\xB3":"3","\u2074":"4","\u2075":"5","\u2076":"6","\u2077":"7","\u2078":"8","\u2079":"9","\u1D2C":"A","\u1D2E":"B","\u1D30":"D","\u1D31":"E","\u1D33":"G","\u1D34":"H","\u1D35":"I","\u1D36":"J","\u1D37":"K","\u1D38":"L","\u1D39":"M","\u1D3A":"N","\u1D3C":"O","\u1D3E":"P","\u1D3F":"R","\u1D40":"T","\u1D41":"U","\u2C7D":"V","\u1D42":"W","\u1D43":"a","\u1D47":"b","\u1D9C":"c","\u1D48":"d","\u1D49":"e","\u1DA0":"f","\u1D4D":"g",\u02B0:"h","\u2071":"i",\u02B2:"j","\u1D4F":"k",\u02E1:"l","\u1D50":"m",\u207F:"n","\u1D52":"o","\u1D56":"p",\u02B3:"r",\u02E2:"s","\u1D57":"t","\u1D58":"u","\u1D5B":"v",\u02B7:"w",\u02E3:"x",\u02B8:"y","\u1DBB":"z","\u1D5D":"\u03B2","\u1D5E":"\u03B3","\u1D5F":"\u03B4","\u1D60":"\u03D5","\u1D61":"\u03C7","\u1DBF":"\u03B8"}),x7={"\u0301":{text:"\\'",math:"\\acute"},"\u0300":{text:"\\`",math:"\\grave"},"\u0308":{text:'\\"',math:"\\ddot"},"\u0303":{text:"\\~",math:"\\tilde"},"\u0304":{text:"\\=",math:"\\bar"},"\u0306":{text:"\\u",math:"\\breve"},"\u030C":{text:"\\v",math:"\\check"},"\u0302":{text:"\\^",math:"\\hat"},"\u0307":{text:"\\.",math:"\\dot"},"\u030A":{text:"\\r",math:"\\mathring"},"\u030B":{text:"\\H"},"\u0327":{text:"\\c"}},AG={\u00E1:"a\u0301",\u00E0:"a\u0300",\u00E4:"a\u0308",\u01DF:"a\u0308\u0304",\u00E3:"a\u0303",\u0101:"a\u0304",\u0103:"a\u0306",\u1EAF:"a\u0306\u0301",\u1EB1:"a\u0306\u0300",\u1EB5:"a\u0306\u0303",\u01CE:"a\u030C",\u00E2:"a\u0302",\u1EA5:"a\u0302\u0301",\u1EA7:"a\u0302\u0300",\u1EAB:"a\u0302\u0303",\u0227:"a\u0307",\u01E1:"a\u0307\u0304",\u00E5:"a\u030A",\u01FB:"a\u030A\u0301",\u1E03:"b\u0307",\u0107:"c\u0301",\u1E09:"c\u0327\u0301",\u010D:"c\u030C",\u0109:"c\u0302",\u010B:"c\u0307",\u00E7:"c\u0327",\u010F:"d\u030C",\u1E0B:"d\u0307",\u1E11:"d\u0327",\u00E9:"e\u0301",\u00E8:"e\u0300",\u00EB:"e\u0308",\u1EBD:"e\u0303",\u0113:"e\u0304",\u1E17:"e\u0304\u0301",\u1E15:"e\u0304\u0300",\u0115:"e\u0306",\u1E1D:"e\u0327\u0306",\u011B:"e\u030C",\u00EA:"e\u0302",\u1EBF:"e\u0302\u0301",\u1EC1:"e\u0302\u0300",\u1EC5:"e\u0302\u0303",\u0117:"e\u0307",\u0229:"e\u0327",\u1E1F:"f\u0307",\u01F5:"g\u0301",\u1E21:"g\u0304",\u011F:"g\u0306",\u01E7:"g\u030C",\u011D:"g\u0302",\u0121:"g\u0307",\u0123:"g\u0327",\u1E27:"h\u0308",\u021F:"h\u030C",\u0125:"h\u0302",\u1E23:"h\u0307",\u1E29:"h\u0327",\u00ED:"i\u0301",\u00EC:"i\u0300",\u00EF:"i\u0308",\u1E2F:"i\u0308\u0301",\u0129:"i\u0303",\u012B:"i\u0304",\u012D:"i\u0306",\u01D0:"i\u030C",\u00EE:"i\u0302",\u01F0:"j\u030C",\u0135:"j\u0302",\u1E31:"k\u0301",\u01E9:"k\u030C",\u0137:"k\u0327",\u013A:"l\u0301",\u013E:"l\u030C",\u013C:"l\u0327",\u1E3F:"m\u0301",\u1E41:"m\u0307",\u0144:"n\u0301",\u01F9:"n\u0300",\u00F1:"n\u0303",\u0148:"n\u030C",\u1E45:"n\u0307",\u0146:"n\u0327",\u00F3:"o\u0301",\u00F2:"o\u0300",\u00F6:"o\u0308",\u022B:"o\u0308\u0304",\u00F5:"o\u0303",\u1E4D:"o\u0303\u0301",\u1E4F:"o\u0303\u0308",\u022D:"o\u0303\u0304",\u014D:"o\u0304",\u1E53:"o\u0304\u0301",\u1E51:"o\u0304\u0300",\u014F:"o\u0306",\u01D2:"o\u030C",\u00F4:"o\u0302",\u1ED1:"o\u0302\u0301",\u1ED3:"o\u0302\u0300",\u1ED7:"o\u0302\u0303",\u022F:"o\u0307",\u0231:"o\u0307\u0304",\u0151:"o\u030B",\u1E55:"p\u0301",\u1E57:"p\u0307",\u0155:"r\u0301",\u0159:"r\u030C",\u1E59:"r\u0307",\u0157:"r\u0327",\u015B:"s\u0301",\u1E65:"s\u0301\u0307",\u0161:"s\u030C",\u1E67:"s\u030C\u0307",\u015D:"s\u0302",\u1E61:"s\u0307",\u015F:"s\u0327",\u1E97:"t\u0308",\u0165:"t\u030C",\u1E6B:"t\u0307",\u0163:"t\u0327",\u00FA:"u\u0301",\u00F9:"u\u0300",\u00FC:"u\u0308",\u01D8:"u\u0308\u0301",\u01DC:"u\u0308\u0300",\u01D6:"u\u0308\u0304",\u01DA:"u\u0308\u030C",\u0169:"u\u0303",\u1E79:"u\u0303\u0301",\u016B:"u\u0304",\u1E7B:"u\u0304\u0308",\u016D:"u\u0306",\u01D4:"u\u030C",\u00FB:"u\u0302",\u016F:"u\u030A",\u0171:"u\u030B",\u1E7D:"v\u0303",\u1E83:"w\u0301",\u1E81:"w\u0300",\u1E85:"w\u0308",\u0175:"w\u0302",\u1E87:"w\u0307",\u1E98:"w\u030A",\u1E8D:"x\u0308",\u1E8B:"x\u0307",\u00FD:"y\u0301",\u1EF3:"y\u0300",\u00FF:"y\u0308",\u1EF9:"y\u0303",\u0233:"y\u0304",\u0177:"y\u0302",\u1E8F:"y\u0307",\u1E99:"y\u030A",\u017A:"z\u0301",\u017E:"z\u030C",\u1E91:"z\u0302",\u017C:"z\u0307",\u00C1:"A\u0301",\u00C0:"A\u0300",\u00C4:"A\u0308",\u01DE:"A\u0308\u0304",\u00C3:"A\u0303",\u0100:"A\u0304",\u0102:"A\u0306",\u1EAE:"A\u0306\u0301",\u1EB0:"A\u0306\u0300",\u1EB4:"A\u0306\u0303",\u01CD:"A\u030C",\u00C2:"A\u0302",\u1EA4:"A\u0302\u0301",\u1EA6:"A\u0302\u0300",\u1EAA:"A\u0302\u0303",\u0226:"A\u0307",\u01E0:"A\u0307\u0304",\u00C5:"A\u030A",\u01FA:"A\u030A\u0301",\u1E02:"B\u0307",\u0106:"C\u0301",\u1E08:"C\u0327\u0301",\u010C:"C\u030C",\u0108:"C\u0302",\u010A:"C\u0307",\u00C7:"C\u0327",\u010E:"D\u030C",\u1E0A:"D\u0307",\u1E10:"D\u0327",\u00C9:"E\u0301",\u00C8:"E\u0300",\u00CB:"E\u0308",\u1EBC:"E\u0303",\u0112:"E\u0304",\u1E16:"E\u0304\u0301",\u1E14:"E\u0304\u0300",\u0114:"E\u0306",\u1E1C:"E\u0327\u0306",\u011A:"E\u030C",\u00CA:"E\u0302",\u1EBE:"E\u0302\u0301",\u1EC0:"E\u0302\u0300",\u1EC4:"E\u0302\u0303",\u0116:"E\u0307",\u0228:"E\u0327",\u1E1E:"F\u0307",\u01F4:"G\u0301",\u1E20:"G\u0304",\u011E:"G\u0306",\u01E6:"G\u030C",\u011C:"G\u0302",\u0120:"G\u0307",\u0122:"G\u0327",\u1E26:"H\u0308",\u021E:"H\u030C",\u0124:"H\u0302",\u1E22:"H\u0307",\u1E28:"H\u0327",\u00CD:"I\u0301",\u00CC:"I\u0300",\u00CF:"I\u0308",\u1E2E:"I\u0308\u0301",\u0128:"I\u0303",\u012A:"I\u0304",\u012C:"I\u0306",\u01CF:"I\u030C",\u00CE:"I\u0302",\u0130:"I\u0307",\u0134:"J\u0302",\u1E30:"K\u0301",\u01E8:"K\u030C",\u0136:"K\u0327",\u0139:"L\u0301",\u013D:"L\u030C",\u013B:"L\u0327",\u1E3E:"M\u0301",\u1E40:"M\u0307",\u0143:"N\u0301",\u01F8:"N\u0300",\u00D1:"N\u0303",\u0147:"N\u030C",\u1E44:"N\u0307",\u0145:"N\u0327",\u00D3:"O\u0301",\u00D2:"O\u0300",\u00D6:"O\u0308",\u022A:"O\u0308\u0304",\u00D5:"O\u0303",\u1E4C:"O\u0303\u0301",\u1E4E:"O\u0303\u0308",\u022C:"O\u0303\u0304",\u014C:"O\u0304",\u1E52:"O\u0304\u0301",\u1E50:"O\u0304\u0300",\u014E:"O\u0306",\u01D1:"O\u030C",\u00D4:"O\u0302",\u1ED0:"O\u0302\u0301",\u1ED2:"O\u0302\u0300",\u1ED6:"O\u0302\u0303",\u022E:"O\u0307",\u0230:"O\u0307\u0304",\u0150:"O\u030B",\u1E54:"P\u0301",\u1E56:"P\u0307",\u0154:"R\u0301",\u0158:"R\u030C",\u1E58:"R\u0307",\u0156:"R\u0327",\u015A:"S\u0301",\u1E64:"S\u0301\u0307",\u0160:"S\u030C",\u1E66:"S\u030C\u0307",\u015C:"S\u0302",\u1E60:"S\u0307",\u015E:"S\u0327",\u0164:"T\u030C",\u1E6A:"T\u0307",\u0162:"T\u0327",\u00DA:"U\u0301",\u00D9:"U\u0300",\u00DC:"U\u0308",\u01D7:"U\u0308\u0301",\u01DB:"U\u0308\u0300",\u01D5:"U\u0308\u0304",\u01D9:"U\u0308\u030C",\u0168:"U\u0303",\u1E78:"U\u0303\u0301",\u016A:"U\u0304",\u1E7A:"U\u0304\u0308",\u016C:"U\u0306",\u01D3:"U\u030C",\u00DB:"U\u0302",\u016E:"U\u030A",\u0170:"U\u030B",\u1E7C:"V\u0303",\u1E82:"W\u0301",\u1E80:"W\u0300",\u1E84:"W\u0308",\u0174:"W\u0302",\u1E86:"W\u0307",\u1E8C:"X\u0308",\u1E8A:"X\u0307",\u00DD:"Y\u0301",\u1EF2:"Y\u0300",\u0178:"Y\u0308",\u1EF8:"Y\u0303",\u0232:"Y\u0304",\u0176:"Y\u0302",\u1E8E:"Y\u0307",\u0179:"Z\u0301",\u017D:"Z\u030C",\u1E90:"Z\u0302",\u017B:"Z\u0307",\u03AC:"\u03B1\u0301",\u1F70:"\u03B1\u0300",\u1FB1:"\u03B1\u0304",\u1FB0:"\u03B1\u0306",\u03AD:"\u03B5\u0301",\u1F72:"\u03B5\u0300",\u03AE:"\u03B7\u0301",\u1F74:"\u03B7\u0300",\u03AF:"\u03B9\u0301",\u1F76:"\u03B9\u0300",\u03CA:"\u03B9\u0308",\u0390:"\u03B9\u0308\u0301",\u1FD2:"\u03B9\u0308\u0300",\u1FD1:"\u03B9\u0304",\u1FD0:"\u03B9\u0306",\u03CC:"\u03BF\u0301",\u1F78:"\u03BF\u0300",\u03CD:"\u03C5\u0301",\u1F7A:"\u03C5\u0300",\u03CB:"\u03C5\u0308",\u03B0:"\u03C5\u0308\u0301",\u1FE2:"\u03C5\u0308\u0300",\u1FE1:"\u03C5\u0304",\u1FE0:"\u03C5\u0306",\u03CE:"\u03C9\u0301",\u1F7C:"\u03C9\u0300",\u038E:"\u03A5\u0301",\u1FEA:"\u03A5\u0300",\u03AB:"\u03A5\u0308",\u1FE9:"\u03A5\u0304",\u1FE8:"\u03A5\u0306",\u038F:"\u03A9\u0301",\u1FFA:"\u03A9\u0300"},x4=class t{static{o(this,"Parser")}constructor(e,r){this.mode=void 0,this.gullet=void 0,this.settings=void 0,this.leftrightDepth=void 0,this.nextToken=void 0,this.mode="math",this.gullet=new R7(e,r,this.mode),this.settings=r,this.leftrightDepth=0}expect(e,r){if(r===void 0&&(r=!0),this.fetch().text!==e)throw new nt("Expected '"+e+"', got '"+this.fetch().text+"'",this.fetch());r&&this.consume()}consume(){this.nextToken=null}fetch(){return this.nextToken==null&&(this.nextToken=this.gullet.expandNextToken()),this.nextToken}switchMode(e){this.mode=e,this.gullet.switchMode(e)}parse(){this.settings.globalGroup||this.gullet.beginGroup(),this.settings.colorIsTextColor&&this.gullet.macros.set("\\color","\\textcolor");try{var e=this.parseExpression(!1);return this.expect("EOF"),this.settings.globalGroup||this.gullet.endGroup(),e}finally{this.gullet.endGroups()}}subparse(e){var r=this.nextToken;this.consume(),this.gullet.pushToken(new Ao("}")),this.gullet.pushTokens(e);var n=this.parseExpression(!1);return this.expect("}"),this.nextToken=r,n}parseExpression(e,r){for(var n=[];;){this.mode==="math"&&this.consumeSpaces();var i=this.fetch();if(t.endOfExpression.indexOf(i.text)!==-1||r&&i.text===r||e&&fh[i.text]&&fh[i.text].infix)break;var a=this.parseAtom(r);if(a){if(a.type==="internal")continue}else break;n.push(a)}return this.mode==="text"&&this.formLigatures(n),this.handleInfixNodes(n)}handleInfixNodes(e){for(var r=-1,n,i=0;i=0&&this.settings.reportNonstrict("unicodeTextInMathMode",'Latin-1/Unicode text character "'+r[0]+'" used in math mode',e);var l=wn[this.mode][r].group,u=Xs.range(e),h;if(hxe.hasOwnProperty(l)){var f=l;h={type:"atom",mode:this.mode,family:f,loc:u,text:r}}else h={type:l,mode:this.mode,loc:u,text:r};s=h}else if(r.charCodeAt(0)>=128)this.settings.strict&&(LG(r.charCodeAt(0))?this.mode==="math"&&this.settings.reportNonstrict("unicodeTextInMathMode",'Unicode text character "'+r[0]+'" used in math mode',e):this.settings.reportNonstrict("unknownSymbol",'Unrecognized Unicode character "'+r[0]+'"'+(" ("+r.charCodeAt(0)+")"),e)),s={type:"textord",mode:"text",loc:Xs.range(e),text:r};else return null;if(this.consume(),a)for(var d=0;d{e.tagName==="A"&&e.hasAttribute("target")&&e.setAttribute(t,e.getAttribute("target")??"")}),bp.default.addHook("afterSanitizeAttributes",e=>{e.tagName==="A"&&e.hasAttribute(t)&&(e.setAttribute("target",e.getAttribute(t)??""),e.removeAttribute(t),e.getAttribute("target")==="_blank"&&e.setAttribute("rel","noopener"))})}var bp,Qf,Cbe,Sbe,A$,C$,qr,_be,Lbe,Dbe,Rbe,_$,Nbe,yr,Mbe,Ibe,gh,K7,Obe,Pbe,S$,Q7,Ni,Zf,yh,We,rr=R(()=>{"use strict";bp=Xi(o7(),1),Qf=//gi,Cbe=o(t=>t?_$(t).replace(/\\n/g,"#br#").split("#br#"):[""],"getRows"),Sbe=(()=>{let t=!1;return()=>{t||(Abe(),t=!0)}})();o(Abe,"setupDompurifyHooks");A$=o(t=>(Sbe(),bp.default.sanitize(t)),"removeScript"),C$=o((t,e)=>{if(e.flowchart?.htmlLabels!==!1){let r=e.securityLevel;r==="antiscript"||r==="strict"?t=A$(t):r!=="loose"&&(t=_$(t),t=t.replace(//g,">"),t=t.replace(/=/g,"="),t=Rbe(t))}return t},"sanitizeMore"),qr=o((t,e)=>t&&(e.dompurifyConfig?t=bp.default.sanitize(C$(t,e),e.dompurifyConfig).toString():t=bp.default.sanitize(C$(t,e),{FORBID_TAGS:["style"]}).toString(),t),"sanitizeText"),_be=o((t,e)=>typeof t=="string"?qr(t,e):t.flat().map(r=>qr(r,e)),"sanitizeTextOrArray"),Lbe=o(t=>Qf.test(t),"hasBreaks"),Dbe=o(t=>t.split(Qf),"splitBreaks"),Rbe=o(t=>t.replace(/#br#/g,"
    "),"placeholderToBreak"),_$=o(t=>t.replace(Qf,"#br#"),"breakToPlaceholder"),Nbe=o(t=>{let e="";return t&&(e=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search,e=e.replaceAll(/\(/g,"\\("),e=e.replaceAll(/\)/g,"\\)")),e},"getUrl"),yr=o(t=>!(t===!1||["false","null","0"].includes(String(t).trim().toLowerCase())),"evaluate"),Mbe=o(function(...t){let e=t.filter(r=>!isNaN(r));return Math.max(...e)},"getMax"),Ibe=o(function(...t){let e=t.filter(r=>!isNaN(r));return Math.min(...e)},"getMin"),gh=o(function(t){let e=t.split(/(,)/),r=[];for(let n=0;n0&&n+1Math.max(0,t.split(e).length-1),"countOccurrence"),Obe=o((t,e)=>{let r=K7(t,"~"),n=K7(e,"~");return r===1&&n===1},"shouldCombineSets"),Pbe=o(t=>{let e=K7(t,"~"),r=!1;if(e<=1)return t;e%2!==0&&t.startsWith("~")&&(t=t.substring(1),r=!0);let n=[...t],i=n.indexOf("~"),a=n.lastIndexOf("~");for(;i!==-1&&a!==-1&&i!==a;)n[i]="<",n[a]=">",i=n.indexOf("~"),a=n.lastIndexOf("~");return r&&n.unshift("~"),n.join("")},"processSet"),S$=o(()=>window.MathMLElement!==void 0,"isMathMLSupported"),Q7=/\$\$(.*)\$\$/g,Ni=o(t=>(t.match(Q7)?.length??0)>0,"hasKatex"),Zf=o(async(t,e)=>{t=await yh(t,e);let r=document.createElement("div");r.innerHTML=t,r.id="katex-temp",r.style.visibility="hidden",r.style.position="absolute",r.style.top="0",document.querySelector("body")?.insertAdjacentElement("beforeend",r);let i={width:r.clientWidth,height:r.clientHeight};return r.remove(),i},"calculateMathMLDimensions"),yh=o(async(t,e)=>{if(!Ni(t))return t;if(!(S$()||e.legacyMathML||e.forceLegacyMathML))return t.replace(Q7,"MathML is unsupported in this environment.");let{default:r}=await Promise.resolve().then(()=>(E$(),k$)),n=e.forceLegacyMathML||!S$()&&e.legacyMathML?"htmlAndMathml":"mathml";return t.split(Qf).map(i=>Ni(i)?`

  • ${r}
  • +`}checkbox({checked:e}){return"'}paragraph({tokens:e}){return`

    ${this.parser.parseInline(e)}

    +`}table(e){let r="",n="";for(let a=0;a${i}`),` + +`+r+` +`+i+`
    +`}tablerow({text:e}){return` +${e} +`}tablecell(e){let r=this.parser.parseInline(e.tokens),n=e.header?"th":"td";return(e.align?`<${n} align="${e.align}">`:`<${n}>`)+r+` +`}strong({tokens:e}){return`${this.parser.parseInline(e)}`}em({tokens:e}){return`${this.parser.parseInline(e)}`}codespan({text:e}){return`${e}`}br(e){return"
    "}del({tokens:e}){return`${this.parser.parseInline(e)}`}link({href:e,title:r,tokens:n}){let i=this.parser.parseInline(n),a=ZX(e);if(a===null)return i;e=a;let s='
    ",s}image({href:e,title:r,text:n}){let i=ZX(e);if(i===null)return n;e=i;let a=`${n}{let l=a[s].flat(1/0);n=n.concat(this.walkTokens(l,r))}):a.tokens&&(n=n.concat(this.walkTokens(a.tokens,r)))}}return n}use(...e){let r=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach(n=>{let i={...n};if(i.async=this.defaults.async||i.async||!1,n.extensions&&(n.extensions.forEach(a=>{if(!a.name)throw new Error("extension name required");if("renderer"in a){let s=r.renderers[a.name];s?r.renderers[a.name]=function(...l){let u=a.renderer.apply(this,l);return u===!1&&(u=s.apply(this,l)),u}:r.renderers[a.name]=a.renderer}if("tokenizer"in a){if(!a.level||a.level!=="block"&&a.level!=="inline")throw new Error("extension level must be 'block' or 'inline'");let s=r[a.level];s?s.unshift(a.tokenizer):r[a.level]=[a.tokenizer],a.start&&(a.level==="block"?r.startBlock?r.startBlock.push(a.start):r.startBlock=[a.start]:a.level==="inline"&&(r.startInline?r.startInline.push(a.start):r.startInline=[a.start]))}"childTokens"in a&&a.childTokens&&(r.childTokens[a.name]=a.childTokens)}),i.extensions=r),n.renderer){let a=this.defaults.renderer||new fm(this.defaults);for(let s in n.renderer){if(!(s in a))throw new Error(`renderer '${s}' does not exist`);if(["options","parser"].includes(s))continue;let l=s,u=n.renderer[l];n.useNewRenderer||(u=this.#t(u,l,a));let h=a[l];a[l]=(...f)=>{let d=u.apply(a,f);return d===!1&&(d=h.apply(a,f)),d||""}}i.renderer=a}if(n.tokenizer){let a=this.defaults.tokenizer||new hm(this.defaults);for(let s in n.tokenizer){if(!(s in a))throw new Error(`tokenizer '${s}' does not exist`);if(["options","rules","lexer"].includes(s))continue;let l=s,u=n.tokenizer[l],h=a[l];a[l]=(...f)=>{let d=u.apply(a,f);return d===!1&&(d=h.apply(a,f)),d}}i.tokenizer=a}if(n.hooks){let a=this.defaults.hooks||new um;for(let s in n.hooks){if(!(s in a))throw new Error(`hook '${s}' does not exist`);if(s==="options")continue;let l=s,u=n.hooks[l],h=a[l];um.passThroughHooks.has(s)?a[l]=f=>{if(this.defaults.async)return Promise.resolve(u.call(a,f)).then(p=>h.call(a,p));let d=u.call(a,f);return h.call(a,d)}:a[l]=(...f)=>{let d=u.apply(a,f);return d===!1&&(d=h.apply(a,f)),d}}i.hooks=a}if(n.walkTokens){let a=this.defaults.walkTokens,s=n.walkTokens;i.walkTokens=function(l){let u=[];return u.push(s.call(this,l)),a&&(u=u.concat(a.call(this,l))),u}}this.defaults={...this.defaults,...i}}),this}#t(e,r,n){switch(r){case"heading":return function(i){return!i.type||i.type!==r?e.apply(this,arguments):e.call(this,n.parser.parseInline(i.tokens),i.depth,t7e(n.parser.parseInline(i.tokens,n.parser.textRenderer)))};case"code":return function(i){return!i.type||i.type!==r?e.apply(this,arguments):e.call(this,i.text,i.lang,!!i.escaped)};case"table":return function(i){if(!i.type||i.type!==r)return e.apply(this,arguments);let a="",s="";for(let u=0;u0&&f.tokens[0].type==="paragraph"?(f.tokens[0].text=g+" "+f.tokens[0].text,f.tokens[0].tokens&&f.tokens[0].tokens.length>0&&f.tokens[0].tokens[0].type==="text"&&(f.tokens[0].tokens[0].text=g+" "+f.tokens[0].tokens[0].text)):f.tokens.unshift({type:"text",text:g+" "}):m+=g+" "}m+=this.parser.parse(f.tokens,l),u+=this.listitem({type:"list_item",raw:m,text:m,task:p,checked:!!d,loose:l,tokens:f.tokens})}return e.call(this,u,a,s)};case"html":return function(i){return!i.type||i.type!==r?e.apply(this,arguments):e.call(this,i.text,i.block)};case"paragraph":return function(i){return!i.type||i.type!==r?e.apply(this,arguments):e.call(this,this.parser.parseInline(i.tokens))};case"escape":return function(i){return!i.type||i.type!==r?e.apply(this,arguments):e.call(this,i.text)};case"link":return function(i){return!i.type||i.type!==r?e.apply(this,arguments):e.call(this,i.href,i.title,this.parser.parseInline(i.tokens))};case"image":return function(i){return!i.type||i.type!==r?e.apply(this,arguments):e.call(this,i.href,i.title,i.text)};case"strong":return function(i){return!i.type||i.type!==r?e.apply(this,arguments):e.call(this,this.parser.parseInline(i.tokens))};case"em":return function(i){return!i.type||i.type!==r?e.apply(this,arguments):e.call(this,this.parser.parseInline(i.tokens))};case"codespan":return function(i){return!i.type||i.type!==r?e.apply(this,arguments):e.call(this,i.text)};case"del":return function(i){return!i.type||i.type!==r?e.apply(this,arguments):e.call(this,this.parser.parseInline(i.tokens))};case"text":return function(i){return!i.type||i.type!==r?e.apply(this,arguments):e.call(this,i.text)}}return e}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,r){return Su.lex(e,r??this.defaults)}parser(e,r){return Au.parse(e,r??this.defaults)}#e(e,r){return(n,i)=>{let a={...i},s={...this.defaults,...a};this.defaults.async===!0&&a.async===!1&&(s.silent||console.warn("marked(): The async option was set to true by an extension. The async: false option sent to parse will be ignored."),s.async=!0);let l=this.#r(!!s.silent,!!s.async);if(typeof n>"u"||n===null)return l(new Error("marked(): input parameter is undefined or null"));if(typeof n!="string")return l(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(n)+", string expected"));if(s.hooks&&(s.hooks.options=s),s.async)return Promise.resolve(s.hooks?s.hooks.preprocess(n):n).then(u=>e(u,s)).then(u=>s.hooks?s.hooks.processAllTokens(u):u).then(u=>s.walkTokens?Promise.all(this.walkTokens(u,s.walkTokens)).then(()=>u):u).then(u=>r(u,s)).then(u=>s.hooks?s.hooks.postprocess(u):u).catch(l);try{s.hooks&&(n=s.hooks.preprocess(n));let u=e(n,s);s.hooks&&(u=s.hooks.processAllTokens(u)),s.walkTokens&&this.walkTokens(u,s.walkTokens);let h=r(u,s);return s.hooks&&(h=s.hooks.postprocess(h)),h}catch(u){return l(u)}}}#r(e,r){return n=>{if(n.message+=` +Please report this to https://github.com/markedjs/marked.`,e){let i="

    An error occurred:

    "+ro(n.message+"",!0)+"
    ";return r?Promise.resolve(i):i}if(r)return Promise.reject(n);throw n}}},Cd=new p9;o(jr,"marked");jr.options=jr.setOptions=function(t){return Cd.setOptions(t),jr.defaults=Cd.defaults,rj(jr.defaults),jr};jr.getDefaults=m9;jr.defaults=Sd;jr.use=function(...t){return Cd.use(...t),jr.defaults=Cd.defaults,rj(jr.defaults),jr};jr.walkTokens=function(t,e){return Cd.walkTokens(t,e)};jr.parseInline=Cd.parseInline;jr.Parser=Au;jr.parser=Au.parse;jr.Renderer=fm;jr.TextRenderer=yv;jr.Lexer=Su;jr.lexer=Su.lex;jr.Tokenizer=hm;jr.Hooks=um;jr.parse=jr;mkt=jr.options,gkt=jr.setOptions,ykt=jr.use,vkt=jr.walkTokens,xkt=jr.parseInline,bkt=Au.parse,wkt=Su.lex});function R7e(t,{markdownAutoWrap:e}){let n=t.replace(//g,` +`).replace(/\n{2,}/g,` +`),i=Gb(n);return e===!1?i.replace(/ /g," "):i}function dj(t,e={}){let r=R7e(t,e),n=jr.lexer(r),i=[[]],a=0;function s(l,u="normal"){l.type==="text"?l.text.split(` +`).forEach((f,d)=>{d!==0&&(a++,i.push([])),f.split(" ").forEach(p=>{p&&i[a].push({content:p,type:u})})}):l.type==="strong"||l.type==="em"?l.tokens.forEach(h=>{s(h,l.type)}):l.type==="html"&&i[a].push({content:l.text,type:"normal"})}return o(s,"processNode"),n.forEach(l=>{l.type==="paragraph"?l.tokens?.forEach(u=>{s(u)}):l.type==="html"&&i[a].push({content:l.text,type:"normal"})}),i}function pj(t,{markdownAutoWrap:e}={}){let r=jr.lexer(t);function n(i){return i.type==="text"?e===!1?i.text.replace(/\n */g,"
    ").replace(/ /g," "):i.text.replace(/\n */g,"
    "):i.type==="strong"?`${i.tokens?.map(n).join("")}`:i.type==="em"?`${i.tokens?.map(n).join("")}`:i.type==="paragraph"?`

    ${i.tokens?.map(n).join("")}

    `:i.type==="space"?"":i.type==="html"?`${i.text}`:`Unsupported markdown: ${i.type}`}return o(n,"output"),r.map(n).join("")}var mj=R(()=>{"use strict";fj();zC();o(R7e,"preprocessMarkdown");o(dj,"markdownToLines");o(pj,"markdownToHTML")});function N7e(t){return Intl.Segmenter?[...new Intl.Segmenter().segment(t)].map(e=>e.segment):[...t]}function M7e(t,e){let r=N7e(e.content);return gj(t,[],r,e.type)}function gj(t,e,r,n){if(r.length===0)return[{content:e.join(""),type:n},{content:"",type:n}];let[i,...a]=r,s=[...e,i];return t([{content:s.join(""),type:n}])?gj(t,s,a,n):(e.length===0&&i&&(e.push(i),r.shift()),[{content:e.join(""),type:n},{content:r.join(""),type:n}])}function yj(t,e){if(t.some(({content:r})=>r.includes(` +`)))throw new Error("splitLineToFitWidth does not support newlines in the line");return w9(t,e)}function w9(t,e,r=[],n=[]){if(t.length===0)return n.length>0&&r.push(n),r.length>0?r:[];let i="";t[0].content===" "&&(i=" ",t.shift());let a=t.shift()??{content:" ",type:"normal"},s=[...n];if(i!==""&&s.push({content:i,type:"normal"}),s.push(a),e(s))return w9(t,e,r,s);if(n.length>0)r.push(n),t.unshift(a);else if(a.content){let[l,u]=M7e(e,a);r.push([l]),u.content&&t.unshift(u)}return w9(t,e,r)}var vj=R(()=>{"use strict";o(N7e,"splitTextToChars");o(M7e,"splitWordToFitWidth");o(gj,"splitWordToFitWidthRecursion");o(yj,"splitLineToFitWidth");o(w9,"splitLineToFitWidthRecursion")});function xj(t,e){e&&t.attr("style",e)}async function I7e(t,e,r,n,i=!1){let a=t.append("foreignObject"),s=a.append("xhtml:div"),l=e.label;e.label&&Ni(e.label)&&(l=await yh(e.label.replace(We.lineBreakRegex,` +`),de()));let u=e.isNode?"nodeLabel":"edgeLabel",h=s.append("span");h.html(l),xj(h,e.labelStyle),h.attr("class",`${u} ${n}`),xj(s,e.labelStyle),s.style("display","table-cell"),s.style("white-space","nowrap"),s.style("line-height","1.5"),s.style("max-width",r+"px"),s.style("text-align","center"),s.attr("xmlns","http://www.w3.org/1999/xhtml"),i&&s.attr("class","labelBkg");let f=s.node().getBoundingClientRect();return f.width===r&&(s.style("display","table"),s.style("white-space","break-spaces"),s.style("width",r+"px"),f=s.node().getBoundingClientRect()),a.node()}function T9(t,e,r){return t.append("tspan").attr("class","text-outer-tspan").attr("x",0).attr("y",e*r-.1+"em").attr("dy",r+"em")}function O7e(t,e,r){let n=t.append("text"),i=T9(n,1,e);k9(i,r);let a=i.node().getComputedTextLength();return n.remove(),a}function bj(t,e,r){let n=t.append("text"),i=T9(n,1,e);k9(i,[{content:r,type:"normal"}]);let a=i.node()?.getBoundingClientRect();return a&&n.remove(),a}function P7e(t,e,r,n=!1){let a=e.append("g"),s=a.insert("rect").attr("class","background").attr("style","stroke: none"),l=a.append("text").attr("y","-10.1"),u=0;for(let h of r){let f=o(p=>O7e(a,1.1,p)<=t,"checkWidth"),d=f(h)?[h]:yj(h,f);for(let p of d){let m=T9(l,u,1.1);k9(m,p),u++}}if(n){let h=l.node().getBBox(),f=2;return s.attr("x",-f).attr("y",-f).attr("width",h.width+2*f).attr("height",h.height+2*f),a.node()}else return l.node()}function k9(t,e){t.text(""),e.forEach((r,n)=>{let i=t.append("tspan").attr("font-style",r.type==="em"?"italic":"normal").attr("class","text-inner-tspan").attr("font-weight",r.type==="strong"?"bold":"normal");n===0?i.text(r.content):i.text(" "+r.content)})}function E9(t){return t.replace(/fa[bklrs]?:fa-[\w-]+/g,e=>``)}var ta,Al=R(()=>{"use strict";_t();rr();Zt();ut();mj();xr();vj();o(xj,"applyStyle");o(I7e,"addHtmlSpan");o(T9,"createTspan");o(O7e,"computeWidthOfText");o(bj,"computeDimensionOfText");o(P7e,"createFormattedText");o(k9,"updateTextContentAndStyles");o(E9,"replaceIconSubstring");ta=o(async(t,e="",{style:r="",isTitle:n=!1,classes:i="",useHtmlLabels:a=!0,isNode:s=!0,width:l=200,addSvgBackground:u=!1}={},h)=>{if(V.info("XYZ createText",e,r,n,i,a,s,"addSvgBackground: ",u),a){let f=pj(e,h),d=E9(to(f)),p=e.replace(/\\\\/g,"\\"),m={isNode:s,label:Ni(e)?p:d,labelStyle:r.replace("fill:","color:")};return await I7e(t,m,l,i,u)}else{let f=e.replace(//g,"
    "),d=dj(f.replace("
    ","
    "),h),p=P7e(l,t,d,e?u:!1);if(s){/stroke:/.exec(r)&&(r=r.replace("stroke:","lineColor:"));let m=r.replace(/stroke:[^;]+;?/g,"").replace(/stroke-width:[^;]+;?/g,"").replace(/fill:[^;]+;?/g,"").replace(/color:/g,"fill:");$e(p).attr("style",m)}else{let m=r.replace(/stroke:[^;]+;?/g,"").replace(/stroke-width:[^;]+;?/g,"").replace(/fill:[^;]+;?/g,"").replace(/background:/g,"fill:");$e(p).select("rect").attr("style",m.replace(/background:/g,"fill:"));let g=r.replace(/stroke:[^;]+;?/g,"").replace(/stroke-width:[^;]+;?/g,"").replace(/fill:[^;]+;?/g,"").replace(/color:/g,"fill:");$e(p).select("text").attr("style",g)}return p}},"createText")});function wj(t,e){e&&t.attr("style",e)}function B7e(t){let e=$e(document.createElementNS("http://www.w3.org/2000/svg","foreignObject")),r=e.append("xhtml:div"),n=t.label,i=t.isNode?"nodeLabel":"edgeLabel",a=r.append("span");return a.html(n),wj(a,t.labelStyle),a.attr("class",i),wj(r,t.labelStyle),r.style("display","inline-block"),r.style("white-space","nowrap"),r.attr("xmlns","http://www.w3.org/1999/xhtml"),e.node()}var F7e,ra,bv=R(()=>{"use strict";Zt();ut();_t();rr();xr();Al();o(wj,"applyStyle");o(B7e,"addHtmlLabel");F7e=o((t,e,r,n)=>{let i=t||"";if(typeof i=="object"&&(i=i[0]),yr(de().flowchart.htmlLabels)){i=i.replace(/\\n|\n/g,"
    "),V.debug("vertexText"+i);let a={isNode:n,label:E9(to(i)),labelStyle:e.replace("fill:","color:")};return B7e(a)}else{let a=document.createElementNS("http://www.w3.org/2000/svg","text");a.setAttribute("style",e.replace("color:","fill:"));let s=[];typeof i=="string"?s=i.split(/\\n|\n|/gi):Array.isArray(i)?s=i:s=[];for(let l of s){let u=document.createElementNS("http://www.w3.org/2000/svg","tspan");u.setAttributeNS("http://www.w3.org/XML/1998/namespace","xml:space","preserve"),u.setAttribute("dy","1em"),u.setAttribute("x","0"),r?u.setAttribute("class","title-row"):u.setAttribute("class","row"),u.textContent=l.trim(),a.appendChild(u)}return a}},"createLabel"),ra=F7e});function z7e(t,e){return t.intersect(e)}var Tj,kj=R(()=>{"use strict";o(z7e,"intersectNode");Tj=z7e});function G7e(t,e,r,n){var i=t.x,a=t.y,s=i-n.x,l=a-n.y,u=Math.sqrt(e*e*l*l+r*r*s*s),h=Math.abs(e*r*s/u);n.x{"use strict";o(G7e,"intersectEllipse");R5=G7e});function $7e(t,e,r){return R5(t,e,e,r)}var Ej,Cj=R(()=>{"use strict";C9();o($7e,"intersectCircle");Ej=$7e});function V7e(t,e,r,n){var i,a,s,l,u,h,f,d,p,m,g,y,v,x,b;if(i=e.y-t.y,s=t.x-e.x,u=e.x*t.y-t.x*e.y,p=i*r.x+s*r.y+u,m=i*n.x+s*n.y+u,!(p!==0&&m!==0&&Sj(p,m))&&(a=n.y-r.y,l=r.x-n.x,h=n.x*r.y-r.x*n.y,f=a*t.x+l*t.y+h,d=a*e.x+l*e.y+h,!(f!==0&&d!==0&&Sj(f,d))&&(g=i*l-a*s,g!==0)))return y=Math.abs(g/2),v=s*h-l*u,x=v<0?(v-y)/g:(v+y)/g,v=a*u-i*h,b=v<0?(v-y)/g:(v+y)/g,{x,y:b}}function Sj(t,e){return t*e>0}var Aj,_j=R(()=>{"use strict";o(V7e,"intersectLine");o(Sj,"sameSign");Aj=V7e});function U7e(t,e,r){var n=t.x,i=t.y,a=[],s=Number.POSITIVE_INFINITY,l=Number.POSITIVE_INFINITY;typeof e.forEach=="function"?e.forEach(function(g){s=Math.min(s,g.x),l=Math.min(l,g.y)}):(s=Math.min(s,e.x),l=Math.min(l,e.y));for(var u=n-t.width/2-s,h=i-t.height/2-l,f=0;f1&&a.sort(function(g,y){var v=g.x-r.x,x=g.y-r.y,b=Math.sqrt(v*v+x*x),w=y.x-r.x,S=y.y-r.y,T=Math.sqrt(w*w+S*S);return b{"use strict";_j();Lj=U7e;o(U7e,"intersectPolygon")});var H7e,Ad,S9=R(()=>{"use strict";H7e=o((t,e)=>{var r=t.x,n=t.y,i=e.x-r,a=e.y-n,s=t.width/2,l=t.height/2,u,h;return Math.abs(a)*s>Math.abs(i)*l?(a<0&&(l=-l),u=a===0?0:l*i/a,h=l):(i<0&&(s=-s),u=s,h=i===0?0:s*a/i),{x:r+u,y:n+h}},"intersectRect"),Ad=H7e});var Tn,A9=R(()=>{"use strict";kj();Cj();C9();Dj();S9();Tn={node:Tj,circle:Ej,ellipse:R5,polygon:Lj,rect:Ad}});function _l(t,e,r,n){return t.insert("polygon",":first-child").attr("points",n.map(function(i){return i.x+","+i.y}).join(" ")).attr("class","label-container").attr("transform","translate("+-e/2+","+r/2+")")}var Ti,kn,N5=R(()=>{"use strict";bv();Al();_t();Zt();rr();xr();Ti=o(async(t,e,r,n)=>{let i=de(),a,s=e.useHtmlLabels||yr(i.flowchart.htmlLabels);r?a=r:a="node default";let l=t.insert("g").attr("class",a).attr("id",e.domId||e.id),u=l.insert("g").attr("class","label").attr("style",e.labelStyle),h;e.labelText===void 0?h="":h=typeof e.labelText=="string"?e.labelText:e.labelText[0];let f=u.node(),d;e.labelType==="markdown"?d=ta(u,qr(to(h),i),{useHtmlLabels:s,width:e.width||i.flowchart.wrappingWidth,classes:"markdown-node-label"},i):d=f.appendChild(ra(qr(to(h),i),e.labelStyle,!1,n));let p=d.getBBox(),m=e.padding/2;if(yr(i.flowchart.htmlLabels)){let g=d.children[0],y=$e(d),v=g.getElementsByTagName("img");if(v){let x=h.replace(/]*>/g,"").trim()==="";await Promise.all([...v].map(b=>new Promise(w=>{function S(){if(b.style.display="flex",b.style.flexDirection="column",x){let T=i.fontSize?i.fontSize:window.getComputedStyle(document.body).fontSize,_=parseInt(T,10)*5+"px";b.style.minWidth=_,b.style.maxWidth=_}else b.style.width="100%";w(b)}o(S,"setupImage"),setTimeout(()=>{b.complete&&S()}),b.addEventListener("error",S),b.addEventListener("load",S)})))}p=g.getBoundingClientRect(),y.attr("width",p.width),y.attr("height",p.height)}return s?u.attr("transform","translate("+-p.width/2+", "+-p.height/2+")"):u.attr("transform","translate(0, "+-p.height/2+")"),e.centerLabel&&u.attr("transform","translate("+-p.width/2+", "+-p.height/2+")"),u.insert("rect",":first-child"),{shapeSvg:l,bbox:p,halfPadding:m,label:u}},"labelHelper"),kn=o((t,e)=>{let r=e.node().getBBox();t.width=r.width,t.height=r.height},"updateNodeBounds");o(_l,"insertPolygonShape")});var Y7e,Rj,Nj=R(()=>{"use strict";N5();ut();_t();A9();Y7e=o(async(t,e)=>{e.useHtmlLabels||de().flowchart.htmlLabels||(e.centerLabel=!0);let{shapeSvg:n,bbox:i,halfPadding:a}=await Ti(t,e,"node "+e.classes,!0);V.info("Classes = ",e.classes);let s=n.insert("rect",":first-child");return s.attr("rx",e.rx).attr("ry",e.ry).attr("x",-i.width/2-a).attr("y",-i.height/2-a).attr("width",i.width+e.padding).attr("height",i.height+e.padding),kn(e,s),e.intersect=function(l){return Tn.rect(e,l)},n},"note"),Rj=Y7e});function _9(t,e,r,n){let i=[],a=o(l=>{i.push(l,0)},"addBorder"),s=o(l=>{i.push(0,l)},"skipBorder");e.includes("t")?(V.debug("add top border"),a(r)):s(r),e.includes("r")?(V.debug("add right border"),a(n)):s(n),e.includes("b")?(V.debug("add bottom border"),a(r)):s(r),e.includes("l")?(V.debug("add left border"),a(n)):s(n),t.attr("stroke-dasharray",i.join(" "))}var Mj,no,Ij,W7e,q7e,X7e,j7e,K7e,Q7e,Z7e,J7e,eSe,tSe,rSe,nSe,iSe,aSe,sSe,oSe,lSe,cSe,uSe,Oj,hSe,fSe,Pj,dm,pm,Bj,Fj,wv,M5=R(()=>{"use strict";Zt();_t();rr();ut();KX();bv();A9();Nj();N5();Mj=o(t=>t?" "+t:"","formatClass"),no=o((t,e)=>`${e||"node default"}${Mj(t.classes)} ${Mj(t.class)}`,"getClassesFromNode"),Ij=o(async(t,e)=>{let{shapeSvg:r,bbox:n}=await Ti(t,e,no(e,void 0),!0),i=n.width+e.padding,a=n.height+e.padding,s=i+a,l=[{x:s/2,y:0},{x:s,y:-s/2},{x:s/2,y:-s},{x:0,y:-s/2}];V.info("Question main (Circle)");let u=_l(r,s,s,l);return u.attr("style",e.style),kn(e,u),e.intersect=function(h){return V.warn("Intersect called"),Tn.polygon(e,l,h)},r},"question"),W7e=o((t,e)=>{let r=t.insert("g").attr("class","node default").attr("id",e.domId||e.id),n=28,i=[{x:0,y:n/2},{x:n/2,y:0},{x:0,y:-n/2},{x:-n/2,y:0}];return r.insert("polygon",":first-child").attr("points",i.map(function(s){return s.x+","+s.y}).join(" ")).attr("class","state-start").attr("r",7).attr("width",28).attr("height",28),e.width=28,e.height=28,e.intersect=function(s){return Tn.circle(e,14,s)},r},"choice"),q7e=o(async(t,e)=>{let{shapeSvg:r,bbox:n}=await Ti(t,e,no(e,void 0),!0),i=4,a=n.height+e.padding,s=a/i,l=n.width+2*s+e.padding,u=[{x:s,y:0},{x:l-s,y:0},{x:l,y:-a/2},{x:l-s,y:-a},{x:s,y:-a},{x:0,y:-a/2}],h=_l(r,l,a,u);return h.attr("style",e.style),kn(e,h),e.intersect=function(f){return Tn.polygon(e,u,f)},r},"hexagon"),X7e=o(async(t,e)=>{let{shapeSvg:r,bbox:n}=await Ti(t,e,void 0,!0),i=2,a=n.height+2*e.padding,s=a/i,l=n.width+2*s+e.padding,u=jX(e.directions,n,e),h=_l(r,l,a,u);return h.attr("style",e.style),kn(e,h),e.intersect=function(f){return Tn.polygon(e,u,f)},r},"block_arrow"),j7e=o(async(t,e)=>{let{shapeSvg:r,bbox:n}=await Ti(t,e,no(e,void 0),!0),i=n.width+e.padding,a=n.height+e.padding,s=[{x:-a/2,y:0},{x:i,y:0},{x:i,y:-a},{x:-a/2,y:-a},{x:0,y:-a/2}];return _l(r,i,a,s).attr("style",e.style),e.width=i+a,e.height=a,e.intersect=function(u){return Tn.polygon(e,s,u)},r},"rect_left_inv_arrow"),K7e=o(async(t,e)=>{let{shapeSvg:r,bbox:n}=await Ti(t,e,no(e),!0),i=n.width+e.padding,a=n.height+e.padding,s=[{x:-2*a/6,y:0},{x:i-a/6,y:0},{x:i+2*a/6,y:-a},{x:a/6,y:-a}],l=_l(r,i,a,s);return l.attr("style",e.style),kn(e,l),e.intersect=function(u){return Tn.polygon(e,s,u)},r},"lean_right"),Q7e=o(async(t,e)=>{let{shapeSvg:r,bbox:n}=await Ti(t,e,no(e,void 0),!0),i=n.width+e.padding,a=n.height+e.padding,s=[{x:2*a/6,y:0},{x:i+a/6,y:0},{x:i-2*a/6,y:-a},{x:-a/6,y:-a}],l=_l(r,i,a,s);return l.attr("style",e.style),kn(e,l),e.intersect=function(u){return Tn.polygon(e,s,u)},r},"lean_left"),Z7e=o(async(t,e)=>{let{shapeSvg:r,bbox:n}=await Ti(t,e,no(e,void 0),!0),i=n.width+e.padding,a=n.height+e.padding,s=[{x:-2*a/6,y:0},{x:i+2*a/6,y:0},{x:i-a/6,y:-a},{x:a/6,y:-a}],l=_l(r,i,a,s);return l.attr("style",e.style),kn(e,l),e.intersect=function(u){return Tn.polygon(e,s,u)},r},"trapezoid"),J7e=o(async(t,e)=>{let{shapeSvg:r,bbox:n}=await Ti(t,e,no(e,void 0),!0),i=n.width+e.padding,a=n.height+e.padding,s=[{x:a/6,y:0},{x:i-a/6,y:0},{x:i+2*a/6,y:-a},{x:-2*a/6,y:-a}],l=_l(r,i,a,s);return l.attr("style",e.style),kn(e,l),e.intersect=function(u){return Tn.polygon(e,s,u)},r},"inv_trapezoid"),eSe=o(async(t,e)=>{let{shapeSvg:r,bbox:n}=await Ti(t,e,no(e,void 0),!0),i=n.width+e.padding,a=n.height+e.padding,s=[{x:0,y:0},{x:i+a/2,y:0},{x:i,y:-a/2},{x:i+a/2,y:-a},{x:0,y:-a}],l=_l(r,i,a,s);return l.attr("style",e.style),kn(e,l),e.intersect=function(u){return Tn.polygon(e,s,u)},r},"rect_right_inv_arrow"),tSe=o(async(t,e)=>{let{shapeSvg:r,bbox:n}=await Ti(t,e,no(e,void 0),!0),i=n.width+e.padding,a=i/2,s=a/(2.5+i/50),l=n.height+s+e.padding,u="M 0,"+s+" a "+a+","+s+" 0,0,0 "+i+" 0 a "+a+","+s+" 0,0,0 "+-i+" 0 l 0,"+l+" a "+a+","+s+" 0,0,0 "+i+" 0 l 0,"+-l,h=r.attr("label-offset-y",s).insert("path",":first-child").attr("style",e.style).attr("d",u).attr("transform","translate("+-i/2+","+-(l/2+s)+")");return kn(e,h),e.intersect=function(f){let d=Tn.rect(e,f),p=d.x-e.x;if(a!=0&&(Math.abs(p)e.height/2-s)){let m=s*s*(1-p*p/(a*a));m!=0&&(m=Math.sqrt(m)),m=s-m,f.y-e.y>0&&(m=-m),d.y+=m}return d},r},"cylinder"),rSe=o(async(t,e)=>{let{shapeSvg:r,bbox:n,halfPadding:i}=await Ti(t,e,"node "+e.classes+" "+e.class,!0),a=r.insert("rect",":first-child"),s=e.positioned?e.width:n.width+e.padding,l=e.positioned?e.height:n.height+e.padding,u=e.positioned?-s/2:-n.width/2-i,h=e.positioned?-l/2:-n.height/2-i;if(a.attr("class","basic label-container").attr("style",e.style).attr("rx",e.rx).attr("ry",e.ry).attr("x",u).attr("y",h).attr("width",s).attr("height",l),e.props){let f=new Set(Object.keys(e.props));e.props.borders&&(_9(a,e.props.borders,s,l),f.delete("borders")),f.forEach(d=>{V.warn(`Unknown node property ${d}`)})}return kn(e,a),e.intersect=function(f){return Tn.rect(e,f)},r},"rect"),nSe=o(async(t,e)=>{let{shapeSvg:r,bbox:n,halfPadding:i}=await Ti(t,e,"node "+e.classes,!0),a=r.insert("rect",":first-child"),s=e.positioned?e.width:n.width+e.padding,l=e.positioned?e.height:n.height+e.padding,u=e.positioned?-s/2:-n.width/2-i,h=e.positioned?-l/2:-n.height/2-i;if(a.attr("class","basic cluster composite label-container").attr("style",e.style).attr("rx",e.rx).attr("ry",e.ry).attr("x",u).attr("y",h).attr("width",s).attr("height",l),e.props){let f=new Set(Object.keys(e.props));e.props.borders&&(_9(a,e.props.borders,s,l),f.delete("borders")),f.forEach(d=>{V.warn(`Unknown node property ${d}`)})}return kn(e,a),e.intersect=function(f){return Tn.rect(e,f)},r},"composite"),iSe=o(async(t,e)=>{let{shapeSvg:r}=await Ti(t,e,"label",!0);V.trace("Classes = ",e.class);let n=r.insert("rect",":first-child"),i=0,a=0;if(n.attr("width",i).attr("height",a),r.attr("class","label edgeLabel"),e.props){let s=new Set(Object.keys(e.props));e.props.borders&&(_9(n,e.props.borders,i,a),s.delete("borders")),s.forEach(l=>{V.warn(`Unknown node property ${l}`)})}return kn(e,n),e.intersect=function(s){return Tn.rect(e,s)},r},"labelRect");o(_9,"applyNodePropertyBorders");aSe=o((t,e)=>{let r;e.classes?r="node "+e.classes:r="node default";let n=t.insert("g").attr("class",r).attr("id",e.domId||e.id),i=n.insert("rect",":first-child"),a=n.insert("line"),s=n.insert("g").attr("class","label"),l=e.labelText.flat?e.labelText.flat():e.labelText,u="";typeof l=="object"?u=l[0]:u=l,V.info("Label text abc79",u,l,typeof l=="object");let h=s.node().appendChild(ra(u,e.labelStyle,!0,!0)),f={width:0,height:0};if(yr(de().flowchart.htmlLabels)){let y=h.children[0],v=$e(h);f=y.getBoundingClientRect(),v.attr("width",f.width),v.attr("height",f.height)}V.info("Text 2",l);let d=l.slice(1,l.length),p=h.getBBox(),m=s.node().appendChild(ra(d.join?d.join("
    "):d,e.labelStyle,!0,!0));if(yr(de().flowchart.htmlLabels)){let y=m.children[0],v=$e(m);f=y.getBoundingClientRect(),v.attr("width",f.width),v.attr("height",f.height)}let g=e.padding/2;return $e(m).attr("transform","translate( "+(f.width>p.width?0:(p.width-f.width)/2)+", "+(p.height+g+5)+")"),$e(h).attr("transform","translate( "+(f.width{let{shapeSvg:r,bbox:n}=await Ti(t,e,no(e,void 0),!0),i=n.height+e.padding,a=n.width+i/4+e.padding,s=r.insert("rect",":first-child").attr("style",e.style).attr("rx",i/2).attr("ry",i/2).attr("x",-a/2).attr("y",-i/2).attr("width",a).attr("height",i);return kn(e,s),e.intersect=function(l){return Tn.rect(e,l)},r},"stadium"),oSe=o(async(t,e)=>{let{shapeSvg:r,bbox:n,halfPadding:i}=await Ti(t,e,no(e,void 0),!0),a=r.insert("circle",":first-child");return a.attr("style",e.style).attr("rx",e.rx).attr("ry",e.ry).attr("r",n.width/2+i).attr("width",n.width+e.padding).attr("height",n.height+e.padding),V.info("Circle main"),kn(e,a),e.intersect=function(s){return V.info("Circle intersect",e,n.width/2+i,s),Tn.circle(e,n.width/2+i,s)},r},"circle"),lSe=o(async(t,e)=>{let{shapeSvg:r,bbox:n,halfPadding:i}=await Ti(t,e,no(e,void 0),!0),a=5,s=r.insert("g",":first-child"),l=s.insert("circle"),u=s.insert("circle");return s.attr("class",e.class),l.attr("style",e.style).attr("rx",e.rx).attr("ry",e.ry).attr("r",n.width/2+i+a).attr("width",n.width+e.padding+a*2).attr("height",n.height+e.padding+a*2),u.attr("style",e.style).attr("rx",e.rx).attr("ry",e.ry).attr("r",n.width/2+i).attr("width",n.width+e.padding).attr("height",n.height+e.padding),V.info("DoubleCircle main"),kn(e,l),e.intersect=function(h){return V.info("DoubleCircle intersect",e,n.width/2+i+a,h),Tn.circle(e,n.width/2+i+a,h)},r},"doublecircle"),cSe=o(async(t,e)=>{let{shapeSvg:r,bbox:n}=await Ti(t,e,no(e,void 0),!0),i=n.width+e.padding,a=n.height+e.padding,s=[{x:0,y:0},{x:i,y:0},{x:i,y:-a},{x:0,y:-a},{x:0,y:0},{x:-8,y:0},{x:i+8,y:0},{x:i+8,y:-a},{x:-8,y:-a},{x:-8,y:0}],l=_l(r,i,a,s);return l.attr("style",e.style),kn(e,l),e.intersect=function(u){return Tn.polygon(e,s,u)},r},"subroutine"),uSe=o((t,e)=>{let r=t.insert("g").attr("class","node default").attr("id",e.domId||e.id),n=r.insert("circle",":first-child");return n.attr("class","state-start").attr("r",7).attr("width",14).attr("height",14),kn(e,n),e.intersect=function(i){return Tn.circle(e,7,i)},r},"start"),Oj=o((t,e,r)=>{let n=t.insert("g").attr("class","node default").attr("id",e.domId||e.id),i=70,a=10;r==="LR"&&(i=10,a=70);let s=n.append("rect").attr("x",-1*i/2).attr("y",-1*a/2).attr("width",i).attr("height",a).attr("class","fork-join");return kn(e,s),e.height=e.height+e.padding/2,e.width=e.width+e.padding/2,e.intersect=function(l){return Tn.rect(e,l)},n},"forkJoin"),hSe=o((t,e)=>{let r=t.insert("g").attr("class","node default").attr("id",e.domId||e.id),n=r.insert("circle",":first-child"),i=r.insert("circle",":first-child");return i.attr("class","state-start").attr("r",7).attr("width",14).attr("height",14),n.attr("class","state-end").attr("r",5).attr("width",10).attr("height",10),kn(e,i),e.intersect=function(a){return Tn.circle(e,7,a)},r},"end"),fSe=o((t,e)=>{let r=e.padding/2,n=4,i=8,a;e.classes?a="node "+e.classes:a="node default";let s=t.insert("g").attr("class",a).attr("id",e.domId||e.id),l=s.insert("rect",":first-child"),u=s.insert("line"),h=s.insert("line"),f=0,d=n,p=s.insert("g").attr("class","label"),m=0,g=e.classData.annotations?.[0],y=e.classData.annotations[0]?"\xAB"+e.classData.annotations[0]+"\xBB":"",v=p.node().appendChild(ra(y,e.labelStyle,!0,!0)),x=v.getBBox();if(yr(de().flowchart.htmlLabels)){let A=v.children[0],L=$e(v);x=A.getBoundingClientRect(),L.attr("width",x.width),L.attr("height",x.height)}e.classData.annotations[0]&&(d+=x.height+n,f+=x.width);let b=e.classData.label;e.classData.type!==void 0&&e.classData.type!==""&&(de().flowchart.htmlLabels?b+="<"+e.classData.type+">":b+="<"+e.classData.type+">");let w=p.node().appendChild(ra(b,e.labelStyle,!0,!0));$e(w).attr("class","classTitle");let S=w.getBBox();if(yr(de().flowchart.htmlLabels)){let A=w.children[0],L=$e(w);S=A.getBoundingClientRect(),L.attr("width",S.width),L.attr("height",S.height)}d+=S.height+n,S.width>f&&(f=S.width);let T=[];e.classData.members.forEach(A=>{let L=A.getDisplayDetails(),M=L.displayText;de().flowchart.htmlLabels&&(M=M.replace(//g,">"));let N=p.node().appendChild(ra(M,L.cssStyle?L.cssStyle:e.labelStyle,!0,!0)),k=N.getBBox();if(yr(de().flowchart.htmlLabels)){let I=N.children[0],C=$e(N);k=I.getBoundingClientRect(),C.attr("width",k.width),C.attr("height",k.height)}k.width>f&&(f=k.width),d+=k.height+n,T.push(N)}),d+=i;let E=[];if(e.classData.methods.forEach(A=>{let L=A.getDisplayDetails(),M=L.displayText;de().flowchart.htmlLabels&&(M=M.replace(//g,">"));let N=p.node().appendChild(ra(M,L.cssStyle?L.cssStyle:e.labelStyle,!0,!0)),k=N.getBBox();if(yr(de().flowchart.htmlLabels)){let I=N.children[0],C=$e(N);k=I.getBoundingClientRect(),C.attr("width",k.width),C.attr("height",k.height)}k.width>f&&(f=k.width),d+=k.height+n,E.push(N)}),d+=i,g){let A=(f-x.width)/2;$e(v).attr("transform","translate( "+(-1*f/2+A)+", "+-1*d/2+")"),m=x.height+n}let _=(f-S.width)/2;return $e(w).attr("transform","translate( "+(-1*f/2+_)+", "+(-1*d/2+m)+")"),m+=S.height+n,u.attr("class","divider").attr("x1",-f/2-r).attr("x2",f/2+r).attr("y1",-d/2-r+i+m).attr("y2",-d/2-r+i+m),m+=i,T.forEach(A=>{$e(A).attr("transform","translate( "+-f/2+", "+(-1*d/2+m+i/2)+")");let L=A?.getBBox();m+=(L?.height??0)+n}),m+=i,h.attr("class","divider").attr("x1",-f/2-r).attr("x2",f/2+r).attr("y1",-d/2-r+i+m).attr("y2",-d/2-r+i+m),m+=i,E.forEach(A=>{$e(A).attr("transform","translate( "+-f/2+", "+(-1*d/2+m)+")");let L=A?.getBBox();m+=(L?.height??0)+n}),l.attr("style",e.style).attr("class","outer title-state").attr("x",-f/2-r).attr("y",-(d/2)-r).attr("width",f+e.padding).attr("height",d+e.padding),kn(e,l),e.intersect=function(A){return Tn.rect(e,A)},s},"class_box"),Pj={rhombus:Ij,composite:nSe,question:Ij,rect:rSe,labelRect:iSe,rectWithTitle:aSe,choice:W7e,circle:oSe,doublecircle:lSe,stadium:sSe,hexagon:q7e,block_arrow:X7e,rect_left_inv_arrow:j7e,lean_right:K7e,lean_left:Q7e,trapezoid:Z7e,inv_trapezoid:J7e,rect_right_inv_arrow:eSe,cylinder:tSe,start:uSe,end:hSe,note:Rj,subroutine:cSe,fork:Oj,join:Oj,class_box:fSe},dm={},pm=o(async(t,e,r)=>{let n,i;if(e.link){let a;de().securityLevel==="sandbox"?a="_top":e.linkTarget&&(a=e.linkTarget||"_blank"),n=t.insert("svg:a").attr("xlink:href",e.link).attr("target",a),i=await Pj[e.shape](n,e,r)}else i=await Pj[e.shape](t,e,r),n=i;return e.tooltip&&i.attr("title",e.tooltip),e.class&&i.attr("class","node default "+e.class),dm[e.id]=n,e.haveCallback&&dm[e.id].attr("class",dm[e.id].attr("class")+" clickable"),n},"insertNode"),Bj=o((t,e)=>{dm[e.id]=t},"setNodeElem"),Fj=o(()=>{dm={}},"clear"),wv=o(t=>{let e=dm[t.id];V.trace("Transforming node",t.diff,t,"translate("+(t.x-t.width/2-5)+", "+t.width/2+")");let r=8,n=t.diff||0;return t.clusterNode?e.attr("transform","translate("+(t.x+n-t.width/2)+", "+(t.y-t.height/2-r)+")"):e.attr("transform","translate("+t.x+", "+t.y+")"),n},"positionNode")});var I5,L9=R(()=>{"use strict";Zt();M5();I5=o((t,e)=>{let r;return e==="sandbox"&&(r=$e("#i"+t)),(e==="sandbox"?$e(r.nodes()[0].contentDocument.body):$e("body")).select(`[id="${t}"]`)},"getDiagramElement")});var io,_d=R(()=>{"use strict";io=o(({flowchart:t})=>{let e=t?.subGraphTitleMargin?.top??0,r=t?.subGraphTitleMargin?.bottom??0,n=e+r;return{subGraphTitleTopMargin:e,subGraphTitleBottomMargin:r,subGraphTitleTotalMargin:n}},"getSubGraphTitleMargins")});function D9(t,e,r){if(t&&t.length){let[n,i]=e,a=Math.PI/180*r,s=Math.cos(a),l=Math.sin(a);for(let u of t){let[h,f]=u;u[0]=(h-n)*s-(f-i)*l+n,u[1]=(h-n)*l+(f-i)*s+i}}}function dSe(t,e){return t[0]===e[0]&&t[1]===e[1]}function pSe(t,e,r,n=1){let i=r,a=Math.max(e,.1),s=t[0]&&t[0][0]&&typeof t[0][0]=="number"?[t]:t,l=[0,0];if(i)for(let h of s)D9(h,l,i);let u=function(h,f,d){let p=[];for(let b of h){let w=[...b];dSe(w[0],w[w.length-1])||w.push([w[0][0],w[0][1]]),w.length>2&&p.push(w)}let m=[];f=Math.max(f,.1);let g=[];for(let b of p)for(let w=0;wb.yminw.ymin?1:b.xw.x?1:b.ymax===w.ymax?0:(b.ymax-w.ymax)/Math.abs(b.ymax-w.ymax)),!g.length)return m;let y=[],v=g[0].ymin,x=0;for(;y.length||g.length;){if(g.length){let b=-1;for(let w=0;wv);w++)b=w;g.splice(0,b+1).forEach(w=>{y.push({s:v,edge:w})})}if(y=y.filter(b=>!(b.edge.ymax<=v)),y.sort((b,w)=>b.edge.x===w.edge.x?0:(b.edge.x-w.edge.x)/Math.abs(b.edge.x-w.edge.x)),(d!==1||x%f==0)&&y.length>1)for(let b=0;b=y.length)break;let S=y[b].edge,T=y[w].edge;m.push([[Math.round(S.x),v],[Math.round(T.x),v]])}v+=d,y.forEach(b=>{b.edge.x=b.edge.x+d*b.edge.islope}),x++}return m}(s,a,n);if(i){for(let h of s)D9(h,l,-i);(function(h,f,d){let p=[];h.forEach(m=>p.push(...m)),D9(p,f,d)})(u,l,-i)}return u}function Cv(t,e){var r;let n=e.hachureAngle+90,i=e.hachureGap;i<0&&(i=4*e.strokeWidth),i=Math.round(Math.max(i,.1));let a=1;return e.roughness>=1&&(((r=e.randomizer)===null||r===void 0?void 0:r.next())||Math.random())>.7&&(a=i),pSe(t,i,n,a||1)}function U5(t){let e=t[0],r=t[1];return Math.sqrt(Math.pow(e[0]-r[0],2)+Math.pow(e[1]-r[1],2))}function N9(t,e){return t.type===e}function W9(t){let e=[],r=function(s){let l=new Array;for(;s!=="";)if(s.match(/^([ \t\r\n,]+)/))s=s.substr(RegExp.$1.length);else if(s.match(/^([aAcChHlLmMqQsStTvVzZ])/))l[l.length]={type:mSe,text:RegExp.$1},s=s.substr(RegExp.$1.length);else{if(!s.match(/^(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)/))return[];l[l.length]={type:R9,text:`${parseFloat(RegExp.$1)}`},s=s.substr(RegExp.$1.length)}return l[l.length]={type:zj,text:""},l}(t),n="BOD",i=0,a=r[i];for(;!N9(a,zj);){let s=0,l=[];if(n==="BOD"){if(a.text!=="M"&&a.text!=="m")return W9("M0,0"+t);i++,s=O5[a.text],n=a.text}else N9(a,R9)?s=O5[n]:(i++,s=O5[a.text],n=a.text);if(!(i+sf%2?h+r:h+e);a.push({key:"C",data:u}),e=u[4],r=u[5];break}case"Q":a.push({key:"Q",data:[...l]}),e=l[2],r=l[3];break;case"q":{let u=l.map((h,f)=>f%2?h+r:h+e);a.push({key:"Q",data:u}),e=u[2],r=u[3];break}case"A":a.push({key:"A",data:[...l]}),e=l[5],r=l[6];break;case"a":e+=l[5],r+=l[6],a.push({key:"A",data:[l[0],l[1],l[2],l[3],l[4],e,r]});break;case"H":a.push({key:"H",data:[...l]}),e=l[0];break;case"h":e+=l[0],a.push({key:"H",data:[e]});break;case"V":a.push({key:"V",data:[...l]}),r=l[0];break;case"v":r+=l[0],a.push({key:"V",data:[r]});break;case"S":a.push({key:"S",data:[...l]}),e=l[2],r=l[3];break;case"s":{let u=l.map((h,f)=>f%2?h+r:h+e);a.push({key:"S",data:u}),e=u[2],r=u[3];break}case"T":a.push({key:"T",data:[...l]}),e=l[0],r=l[1];break;case"t":e+=l[0],r+=l[1],a.push({key:"T",data:[e,r]});break;case"Z":case"z":a.push({key:"Z",data:[]}),e=n,r=i}return a}function Xj(t){let e=[],r="",n=0,i=0,a=0,s=0,l=0,u=0;for(let{key:h,data:f}of t){switch(h){case"M":e.push({key:"M",data:[...f]}),[n,i]=f,[a,s]=f;break;case"C":e.push({key:"C",data:[...f]}),n=f[4],i=f[5],l=f[2],u=f[3];break;case"L":e.push({key:"L",data:[...f]}),[n,i]=f;break;case"H":n=f[0],e.push({key:"L",data:[n,i]});break;case"V":i=f[0],e.push({key:"L",data:[n,i]});break;case"S":{let d=0,p=0;r==="C"||r==="S"?(d=n+(n-l),p=i+(i-u)):(d=n,p=i),e.push({key:"C",data:[d,p,...f]}),l=f[0],u=f[1],n=f[2],i=f[3];break}case"T":{let[d,p]=f,m=0,g=0;r==="Q"||r==="T"?(m=n+(n-l),g=i+(i-u)):(m=n,g=i);let y=n+2*(m-n)/3,v=i+2*(g-i)/3,x=d+2*(m-d)/3,b=p+2*(g-p)/3;e.push({key:"C",data:[y,v,x,b,d,p]}),l=m,u=g,n=d,i=p;break}case"Q":{let[d,p,m,g]=f,y=n+2*(d-n)/3,v=i+2*(p-i)/3,x=m+2*(d-m)/3,b=g+2*(p-g)/3;e.push({key:"C",data:[y,v,x,b,m,g]}),l=d,u=p,n=m,i=g;break}case"A":{let d=Math.abs(f[0]),p=Math.abs(f[1]),m=f[2],g=f[3],y=f[4],v=f[5],x=f[6];d===0||p===0?(e.push({key:"C",data:[n,i,v,x,v,x]}),n=v,i=x):(n!==v||i!==x)&&(jj(n,i,v,x,d,p,m,g,y).forEach(function(b){e.push({key:"C",data:b})}),n=v,i=x);break}case"Z":e.push({key:"Z",data:[]}),n=a,i=s}r=h}return e}function Tv(t,e,r){return[t*Math.cos(r)-e*Math.sin(r),t*Math.sin(r)+e*Math.cos(r)]}function jj(t,e,r,n,i,a,s,l,u,h){let f=(d=s,Math.PI*d/180);var d;let p=[],m=0,g=0,y=0,v=0;if(h)[m,g,y,v]=h;else{[t,e]=Tv(t,e,-f),[r,n]=Tv(r,n,-f);let I=(t-r)/2,C=(e-n)/2,O=I*I/(i*i)+C*C/(a*a);O>1&&(O=Math.sqrt(O),i*=O,a*=O);let D=i*i,P=a*a,F=D*P-D*C*C-P*I*I,B=D*C*C+P*I*I,$=(l===u?-1:1)*Math.sqrt(Math.abs(F/B));y=$*i*C/a+(t+r)/2,v=$*-a*I/i+(e+n)/2,m=Math.asin(parseFloat(((e-v)/a).toFixed(9))),g=Math.asin(parseFloat(((n-v)/a).toFixed(9))),tg&&(m-=2*Math.PI),!u&&g>m&&(g-=2*Math.PI)}let x=g-m;if(Math.abs(x)>120*Math.PI/180){let I=g,C=r,O=n;g=u&&g>m?m+120*Math.PI/180*1:m+120*Math.PI/180*-1,p=jj(r=y+i*Math.cos(g),n=v+a*Math.sin(g),C,O,i,a,s,0,u,[g,I,y,v])}x=g-m;let b=Math.cos(m),w=Math.sin(m),S=Math.cos(g),T=Math.sin(g),E=Math.tan(x/4),_=4/3*i*E,A=4/3*a*E,L=[t,e],M=[t+_*w,e-A*b],N=[r+_*T,n-A*S],k=[r,n];if(M[0]=2*L[0]-M[0],M[1]=2*L[1]-M[1],h)return[M,N,k].concat(p);{p=[M,N,k].concat(p);let I=[];for(let C=0;C2){let i=[];for(let a=0;a2*Math.PI&&(m=0,g=2*Math.PI);let y=2*Math.PI/u.curveStepCount,v=Math.min(y/2,(g-m)/2),x=Yj(v,h,f,d,p,m,g,1,u);if(!u.disableMultiStroke){let b=Yj(v,h,f,d,p,m,g,1.5,u);x.push(...b)}return s&&(l?x.push(...Vh(h,f,h+d*Math.cos(m),f+p*Math.sin(m),u),...Vh(h,f,h+d*Math.cos(g),f+p*Math.sin(g),u)):x.push({op:"lineTo",data:[h,f]},{op:"lineTo",data:[h+d*Math.cos(m),f+p*Math.sin(m)]})),{type:"path",ops:x}}function Vj(t,e){let r=Xj(qj(W9(t))),n=[],i=[0,0],a=[0,0];for(let{key:s,data:l}of r)switch(s){case"M":a=[l[0],l[1]],i=[l[0],l[1]];break;case"L":n.push(...Vh(a[0],a[1],l[0],l[1],e)),a=[l[0],l[1]];break;case"C":{let[u,h,f,d,p,m]=l;n.push(...vSe(u,h,f,d,p,m,a,e)),a=[p,m];break}case"Z":n.push(...Vh(a[0],a[1],i[0],i[1],e)),a=[i[0],i[1]]}return{type:"path",ops:n}}function M9(t,e){let r=[];for(let n of t)if(n.length){let i=e.maxRandomnessOffset||0,a=n.length;if(a>2){r.push({op:"move",data:[n[0][0]+Yt(i,e),n[0][1]+Yt(i,e)]});for(let s=1;s500?.4:-.0016668*u+1.233334;let f=i.maxRandomnessOffset||0;f*f*100>l&&(f=u/10);let d=f/2,p=.2+.2*Zj(i),m=i.bowing*i.maxRandomnessOffset*(n-e)/200,g=i.bowing*i.maxRandomnessOffset*(t-r)/200;m=Yt(m,i,h),g=Yt(g,i,h);let y=[],v=o(()=>Yt(d,i,h),"M"),x=o(()=>Yt(f,i,h),"k"),b=i.preserveVertices;return a&&(s?y.push({op:"move",data:[t+(b?0:v()),e+(b?0:v())]}):y.push({op:"move",data:[t+(b?0:Yt(f,i,h)),e+(b?0:Yt(f,i,h))]})),s?y.push({op:"bcurveTo",data:[m+t+(r-t)*p+v(),g+e+(n-e)*p+v(),m+t+2*(r-t)*p+v(),g+e+2*(n-e)*p+v(),r+(b?0:v()),n+(b?0:v())]}):y.push({op:"bcurveTo",data:[m+t+(r-t)*p+x(),g+e+(n-e)*p+x(),m+t+2*(r-t)*p+x(),g+e+2*(n-e)*p+x(),r+(b?0:x()),n+(b?0:x())]}),y}function P5(t,e,r){if(!t.length)return[];let n=[];n.push([t[0][0]+Yt(e,r),t[0][1]+Yt(e,r)]),n.push([t[0][0]+Yt(e,r),t[0][1]+Yt(e,r)]);for(let i=1;i3){let a=[],s=1-r.curveTightness;i.push({op:"move",data:[t[1][0],t[1][1]]});for(let l=1;l+21&&i.push(l)):i.push(l),i.push(t[e+3])}else{let u=t[e+0],h=t[e+1],f=t[e+2],d=t[e+3],p=Ld(u,h,.5),m=Ld(h,f,.5),g=Ld(f,d,.5),y=Ld(p,m,.5),v=Ld(m,g,.5),x=Ld(y,v,.5);U9([u,p,y,x],0,r,i),U9([x,v,g,d],0,r,i)}var a,s;return i}function bSe(t,e){return V5(t,0,t.length,e)}function V5(t,e,r,n,i){let a=i||[],s=t[e],l=t[r-1],u=0,h=1;for(let f=e+1;fu&&(u=d,h=f)}return Math.sqrt(u)>n?(V5(t,e,h+1,n,a),V5(t,h,r,n,a)):(a.length||a.push(s),a.push(l)),a}function I9(t,e=.15,r){let n=[],i=(t.length-1)/3;for(let a=0;a0?V5(n,0,n.length,r):n}var Ev,O9,P9,B9,F9,z9,Cs,G9,mSe,R9,zj,O5,gSe,ao,gm,H9,B5,Y9,Jt,ti=R(()=>{"use strict";o(D9,"t");o(dSe,"e");o(pSe,"s");o(Cv,"n");Ev=class{static{o(this,"o")}constructor(e){this.helper=e}fillPolygons(e,r){return this._fillPolygons(e,r)}_fillPolygons(e,r){let n=Cv(e,r);return{type:"fillSketch",ops:this.renderLines(n,r)}}renderLines(e,r){let n=[];for(let i of e)n.push(...this.helper.doubleLineOps(i[0][0],i[0][1],i[1][0],i[1][1],r));return n}};o(U5,"a");O9=class extends Ev{static{o(this,"h")}fillPolygons(e,r){let n=r.hachureGap;n<0&&(n=4*r.strokeWidth),n=Math.max(n,.1);let i=Cv(e,Object.assign({},r,{hachureGap:n})),a=Math.PI/180*r.hachureAngle,s=[],l=.5*n*Math.cos(a),u=.5*n*Math.sin(a);for(let[h,f]of i)U5([h,f])&&s.push([[h[0]-l,h[1]+u],[...f]],[[h[0]+l,h[1]-u],[...f]]);return{type:"fillSketch",ops:this.renderLines(s,r)}}},P9=class extends Ev{static{o(this,"r")}fillPolygons(e,r){let n=this._fillPolygons(e,r),i=Object.assign({},r,{hachureAngle:r.hachureAngle+90}),a=this._fillPolygons(e,i);return n.ops=n.ops.concat(a.ops),n}},B9=class{static{o(this,"i")}constructor(e){this.helper=e}fillPolygons(e,r){let n=Cv(e,r=Object.assign({},r,{hachureAngle:0}));return this.dotsOnLines(n,r)}dotsOnLines(e,r){let n=[],i=r.hachureGap;i<0&&(i=4*r.strokeWidth),i=Math.max(i,.1);let a=r.fillWeight;a<0&&(a=r.strokeWidth/2);let s=i/4;for(let l of e){let u=U5(l),h=u/i,f=Math.ceil(h)-1,d=u-f*i,p=(l[0][0]+l[1][0])/2-i/4,m=Math.min(l[0][1],l[1][1]);for(let g=0;g{let l=U5(s),u=Math.floor(l/(n+i)),h=(l+i-u*(n+i))/2,f=s[0],d=s[1];f[0]>d[0]&&(f=s[1],d=s[0]);let p=Math.atan((d[1]-f[1])/(d[0]-f[0]));for(let m=0;m{let s=U5(a),l=Math.round(s/(2*r)),u=a[0],h=a[1];u[0]>h[0]&&(u=a[1],h=a[0]);let f=Math.atan((h[1]-u[1])/(h[0]-u[0]));for(let d=0;d2*Math.PI&&(_=0,A=2*Math.PI);let L=(A-_)/b.curveStepCount,M=[];for(let N=_;N<=A;N+=L)M.push([w+T*Math.cos(N),S+E*Math.sin(N)]);return M.push([w+T*Math.cos(A),S+E*Math.sin(A)]),M.push([w,S]),mm([M],b)}(e,r,n,i,a,s,h));return h.stroke!==ao&&f.push(d),this._d("arc",f,h)}curve(e,r){let n=this._o(r),i=[],a=Gj(e,n);if(n.fill&&n.fill!==ao)if(n.fillStyle==="solid"){let s=Gj(e,Object.assign(Object.assign({},n),{disableMultiStroke:!0,roughness:n.roughness?n.roughness+n.fillShapeRoughnessGain:0}));i.push({type:"fillPath",ops:this._mergedShape(s.ops)})}else{let s=[],l=e;if(l.length){let u=typeof l[0][0]=="number"?[l]:l;for(let h of u)h.length<3?s.push(...h):h.length===3?s.push(...I9(Wj([h[0],h[0],h[1],h[2]]),10,(1+n.roughness)/2)):s.push(...I9(Wj(h),10,(1+n.roughness)/2))}s.length&&i.push(mm([s],n))}return n.stroke!==ao&&i.push(a),this._d("curve",i,n)}polygon(e,r){let n=this._o(r),i=[],a=F5(e,!0,n);return n.fill&&(n.fillStyle==="solid"?i.push(M9([e],n)):i.push(mm([e],n))),n.stroke!==ao&&i.push(a),this._d("polygon",i,n)}path(e,r){let n=this._o(r),i=[];if(!e)return this._d("path",i,n);e=(e||"").replace(/\n/g," ").replace(/(-\s)/g,"-").replace("/(ss)/g"," ");let a=n.fill&&n.fill!=="transparent"&&n.fill!==ao,s=n.stroke!==ao,l=!!(n.simplification&&n.simplification<1),u=function(f,d,p){let m=Xj(qj(W9(f))),g=[],y=[],v=[0,0],x=[],b=o(()=>{x.length>=4&&y.push(...I9(x,d)),x=[]},"i"),w=o(()=>{b(),y.length&&(g.push(y),y=[])},"c");for(let{key:T,data:E}of m)switch(T){case"M":w(),v=[E[0],E[1]],y.push(v);break;case"L":b(),y.push([E[0],E[1]]);break;case"C":if(!x.length){let _=y.length?y[y.length-1]:v;x.push([_[0],_[1]])}x.push([E[0],E[1]]),x.push([E[2],E[3]]),x.push([E[4],E[5]]);break;case"Z":b(),y.push([v[0],v[1]])}if(w(),!p)return g;let S=[];for(let T of g){let E=bSe(T,p);E.length&&S.push(E)}return S}(e,1,l?4-4*(n.simplification||1):(1+n.roughness)/2),h=Vj(e,n);if(a)if(n.fillStyle==="solid")if(u.length===1){let f=Vj(e,Object.assign(Object.assign({},n),{disableMultiStroke:!0,roughness:n.roughness?n.roughness+n.fillShapeRoughnessGain:0}));i.push({type:"fillPath",ops:this._mergedShape(f.ops)})}else i.push(M9(u,n));else i.push(mm(u,n));return s&&(l?u.forEach(f=>{i.push(F5(f,!1,n))}):i.push(h)),this._d("path",i,n)}opsToPath(e,r){let n="";for(let i of e.ops){let a=typeof r=="number"&&r>=0?i.data.map(s=>+s.toFixed(r)):i.data;switch(i.op){case"move":n+=`M${a[0]} ${a[1]} `;break;case"bcurveTo":n+=`C${a[0]} ${a[1]}, ${a[2]} ${a[3]}, ${a[4]} ${a[5]} `;break;case"lineTo":n+=`L${a[0]} ${a[1]} `}}return n.trim()}toPaths(e){let r=e.sets||[],n=e.options||this.defaultOptions,i=[];for(let a of r){let s=null;switch(a.type){case"path":s={d:this.opsToPath(a),stroke:n.stroke,strokeWidth:n.strokeWidth,fill:ao};break;case"fillPath":s={d:this.opsToPath(a),stroke:ao,strokeWidth:0,fill:n.fill||ao};break;case"fillSketch":s=this.fillSketch(a,n)}s&&i.push(s)}return i}fillSketch(e,r){let n=r.fillWeight;return n<0&&(n=r.strokeWidth/2),{d:this.opsToPath(e),stroke:r.fill||ao,strokeWidth:n,fill:ao}}_mergedShape(e){return e.filter((r,n)=>n===0||r.op!=="move")}},H9=class{static{o(this,"st")}constructor(e,r){this.canvas=e,this.ctx=this.canvas.getContext("2d"),this.gen=new gm(r)}draw(e){let r=e.sets||[],n=e.options||this.getDefaultOptions(),i=this.ctx,a=e.options.fixedDecimalPlaceDigits;for(let s of r)switch(s.type){case"path":i.save(),i.strokeStyle=n.stroke==="none"?"transparent":n.stroke,i.lineWidth=n.strokeWidth,n.strokeLineDash&&i.setLineDash(n.strokeLineDash),n.strokeLineDashOffset&&(i.lineDashOffset=n.strokeLineDashOffset),this._drawToContext(i,s,a),i.restore();break;case"fillPath":{i.save(),i.fillStyle=n.fill||"";let l=e.shape==="curve"||e.shape==="polygon"||e.shape==="path"?"evenodd":"nonzero";this._drawToContext(i,s,a,l),i.restore();break}case"fillSketch":this.fillSketch(i,s,n)}}fillSketch(e,r,n){let i=n.fillWeight;i<0&&(i=n.strokeWidth/2),e.save(),n.fillLineDash&&e.setLineDash(n.fillLineDash),n.fillLineDashOffset&&(e.lineDashOffset=n.fillLineDashOffset),e.strokeStyle=n.fill||"",e.lineWidth=i,this._drawToContext(e,r,n.fixedDecimalPlaceDigits),e.restore()}_drawToContext(e,r,n,i="nonzero"){e.beginPath();for(let a of r.ops){let s=typeof n=="number"&&n>=0?a.data.map(l=>+l.toFixed(n)):a.data;switch(a.op){case"move":e.moveTo(s[0],s[1]);break;case"bcurveTo":e.bezierCurveTo(s[0],s[1],s[2],s[3],s[4],s[5]);break;case"lineTo":e.lineTo(s[0],s[1])}}r.type==="fillPath"?e.fill(i):e.stroke()}get generator(){return this.gen}getDefaultOptions(){return this.gen.defaultOptions}line(e,r,n,i,a){let s=this.gen.line(e,r,n,i,a);return this.draw(s),s}rectangle(e,r,n,i,a){let s=this.gen.rectangle(e,r,n,i,a);return this.draw(s),s}ellipse(e,r,n,i,a){let s=this.gen.ellipse(e,r,n,i,a);return this.draw(s),s}circle(e,r,n,i){let a=this.gen.circle(e,r,n,i);return this.draw(a),a}linearPath(e,r){let n=this.gen.linearPath(e,r);return this.draw(n),n}polygon(e,r){let n=this.gen.polygon(e,r);return this.draw(n),n}arc(e,r,n,i,a,s,l=!1,u){let h=this.gen.arc(e,r,n,i,a,s,l,u);return this.draw(h),h}curve(e,r){let n=this.gen.curve(e,r);return this.draw(n),n}path(e,r){let n=this.gen.path(e,r);return this.draw(n),n}},B5="http://www.w3.org/2000/svg",Y9=class{static{o(this,"ot")}constructor(e,r){this.svg=e,this.gen=new gm(r)}draw(e){let r=e.sets||[],n=e.options||this.getDefaultOptions(),i=this.svg.ownerDocument||window.document,a=i.createElementNS(B5,"g"),s=e.options.fixedDecimalPlaceDigits;for(let l of r){let u=null;switch(l.type){case"path":u=i.createElementNS(B5,"path"),u.setAttribute("d",this.opsToPath(l,s)),u.setAttribute("stroke",n.stroke),u.setAttribute("stroke-width",n.strokeWidth+""),u.setAttribute("fill","none"),n.strokeLineDash&&u.setAttribute("stroke-dasharray",n.strokeLineDash.join(" ").trim()),n.strokeLineDashOffset&&u.setAttribute("stroke-dashoffset",`${n.strokeLineDashOffset}`);break;case"fillPath":u=i.createElementNS(B5,"path"),u.setAttribute("d",this.opsToPath(l,s)),u.setAttribute("stroke","none"),u.setAttribute("stroke-width","0"),u.setAttribute("fill",n.fill||""),e.shape!=="curve"&&e.shape!=="polygon"||u.setAttribute("fill-rule","evenodd");break;case"fillSketch":u=this.fillSketch(i,l,n)}u&&a.appendChild(u)}return a}fillSketch(e,r,n){let i=n.fillWeight;i<0&&(i=n.strokeWidth/2);let a=e.createElementNS(B5,"path");return a.setAttribute("d",this.opsToPath(r,n.fixedDecimalPlaceDigits)),a.setAttribute("stroke",n.fill||""),a.setAttribute("stroke-width",i+""),a.setAttribute("fill","none"),n.fillLineDash&&a.setAttribute("stroke-dasharray",n.fillLineDash.join(" ").trim()),n.fillLineDashOffset&&a.setAttribute("stroke-dashoffset",`${n.fillLineDashOffset}`),a}get generator(){return this.gen}getDefaultOptions(){return this.gen.defaultOptions}opsToPath(e,r){return this.gen.opsToPath(e,r)}line(e,r,n,i,a){let s=this.gen.line(e,r,n,i,a);return this.draw(s)}rectangle(e,r,n,i,a){let s=this.gen.rectangle(e,r,n,i,a);return this.draw(s)}ellipse(e,r,n,i,a){let s=this.gen.ellipse(e,r,n,i,a);return this.draw(s)}circle(e,r,n,i){let a=this.gen.circle(e,r,n,i);return this.draw(a)}linearPath(e,r){let n=this.gen.linearPath(e,r);return this.draw(n)}polygon(e,r){let n=this.gen.polygon(e,r);return this.draw(n)}arc(e,r,n,i,a,s,l=!1,u){let h=this.gen.arc(e,r,n,i,a,s,l,u);return this.draw(h)}curve(e,r){let n=this.gen.curve(e,r);return this.draw(n)}path(e,r){let n=this.gen.path(e,r);return this.draw(n)}},Jt={canvas:o((t,e)=>new H9(t,e),"canvas"),svg:o((t,e)=>new Y9(t,e),"svg"),generator:o(t=>new gm(t),"generator"),newSeed:o(()=>gm.newSeed(),"newSeed")}});var wSe,Dd,q9=R(()=>{"use strict";wSe=o((t,e)=>{var r=t.x,n=t.y,i=e.x-r,a=e.y-n,s=t.width/2,l=t.height/2,u,h;return Math.abs(a)*s>Math.abs(i)*l?(a<0&&(l=-l),u=a===0?0:l*i/a,h=l):(i<0&&(s=-s),u=s,h=i===0?0:s*a/i),{x:r+u,y:n+h}},"intersectRect"),Dd=wSe});function TSe(t,e){e&&t.attr("style",e)}async function kSe(t){let e=$e(document.createElementNS("http://www.w3.org/2000/svg","foreignObject")),r=e.append("xhtml:div"),n=t.label;t.label&&Ni(t.label)&&(n=await yh(t.label.replace(We.lineBreakRegex,` +`),de()));let i=t.isNode?"nodeLabel":"edgeLabel";return r.html('"+n+""),TSe(r,t.labelStyle),r.style("display","inline-block"),r.style("padding-right","1px"),r.style("white-space","nowrap"),r.attr("xmlns","http://www.w3.org/1999/xhtml"),e.node()}var ESe,gc,H5=R(()=>{"use strict";Zt();ut();_t();rr();xr();o(TSe,"applyStyle");o(kSe,"addHtmlLabel");ESe=o(async(t,e,r,n)=>{let i=t||"";if(typeof i=="object"&&(i=i[0]),yr(de().flowchart.htmlLabels)){i=i.replace(/\\n|\n/g,"
    "),V.info("vertexText"+i);let a={isNode:n,label:to(i).replace(/fa[blrs]?:fa-[\w-]+/g,l=>``),labelStyle:e&&e.replace("fill:","color:")};return await kSe(a)}else{let a=document.createElementNS("http://www.w3.org/2000/svg","text");a.setAttribute("style",e.replace("color:","fill:"));let s=[];typeof i=="string"?s=i.split(/\\n|\n|/gi):Array.isArray(i)?s=i:s=[];for(let l of s){let u=document.createElementNS("http://www.w3.org/2000/svg","tspan");u.setAttributeNS("http://www.w3.org/XML/1998/namespace","xml:space","preserve"),u.setAttribute("dy","1em"),u.setAttribute("x","0"),r?u.setAttribute("class","title-row"):u.setAttribute("class","row"),u.textContent=l.trim(),a.appendChild(u)}return a}},"createLabel"),gc=ESe});var _u,Sv=R(()=>{"use strict";_u=o((t,e,r,n,i)=>["M",t+i,e,"H",t+r-i,"A",i,i,0,0,1,t+r,e+i,"V",e+n-i,"A",i,i,0,0,1,t+r-i,e+n,"H",t+i,"A",i,i,0,0,1,t,e+n-i,"V",e+i,"A",i,i,0,0,1,t+i,e,"Z"].join(" "),"createRoundedRectPathD")});var Lu,Jj,CSe,Br,Fr,ki=R(()=>{"use strict";_t();Lu=o(t=>{let{handDrawnSeed:e}=de();return{fill:t,hachureAngle:120,hachureGap:4,fillWeight:2,roughness:.7,stroke:t,seed:e}},"solidStateFill"),Jj=o(t=>{let e=CSe([...t.cssCompiledStyles||[],...t.cssStyles||[]]);return{stylesMap:e,stylesArray:[...e]}},"compileStyles"),CSe=o(t=>{let e=new Map;return t.forEach(r=>{let[n,i]=r.split(":");e.set(n.trim(),i?.trim())}),e},"styles2Map"),Br=o(t=>{let{stylesArray:e}=Jj(t),r=[],n=[],i=[],a=[];return e.forEach(s=>{let l=s[0];l==="color"||l==="font-size"||l==="font-family"||l==="font-weight"||l==="font-style"||l==="text-decoration"||l==="text-align"||l==="text-transform"||l==="line-height"||l==="letter-spacing"||l==="word-spacing"||l==="text-shadow"||l==="text-overflow"||l==="white-space"||l==="word-wrap"||l==="word-break"||l==="overflow-wrap"||l==="hyphens"?r.push(s.join(":")+" !important"):(n.push(s.join(":")+" !important"),l.includes("stroke")&&i.push(s.join(":")+" !important"),l==="fill"&&a.push(s.join(":")+" !important"))}),{labelStyles:r.join(";"),nodeStyles:n.join(";"),stylesArray:e,borderStyles:i,backgroundStyles:a}},"styles2String"),Fr=o((t,e)=>{let{themeVariables:r,handDrawnSeed:n}=de(),{nodeBorder:i,mainBkg:a}=r,{stylesMap:s}=Jj(t);return Object.assign({roughness:.7,fill:s.get("fill")||a,fillStyle:"hachure",fillWeight:4,stroke:s.get("stroke")||i,seed:n,strokeWidth:1.3},e)},"userNodeOverrides")});var eK,SSe,ASe,_Se,LSe,DSe,tK,Y5,rK,X9=R(()=>{"use strict";_t();rr();ut();_d();Zt();ti();Al();q9();H5();Sv();ki();eK=o(async(t,e)=>{V.info("Creating subgraph rect for ",e.id,e);let r=de(),{themeVariables:n,handDrawnSeed:i}=r,{clusterBkg:a,clusterBorder:s}=n,{labelStyles:l,nodeStyles:u,borderStyles:h,backgroundStyles:f}=Br(e),d=t.insert("g").attr("class","cluster "+e.cssClasses).attr("id",e.id).attr("data-look",e.look),p=yr(r.flowchart.htmlLabels),m=d.insert("g").attr("class","cluster-label "),g=await ta(m,e.label,{style:e.labelStyle,useHtmlLabels:p,isNode:!0}),y=g.getBBox();if(yr(r.flowchart.htmlLabels)){let _=g.children[0],A=$e(g);y=_.getBoundingClientRect(),A.attr("width",y.width),A.attr("height",y.height)}let v=e.width<=y.width+e.padding?y.width+e.padding:e.width;e.width<=y.width+e.padding?e.diff=(v-e.width)/2-e.padding:e.diff=-e.padding;let x=e.height,b=e.x-v/2,w=e.y-x/2;V.trace("Data ",e,JSON.stringify(e));let S;if(e.look==="handDrawn"){let _=Jt.svg(d),A=Fr(e,{roughness:.7,fill:a,stroke:s,fillWeight:3,seed:i}),L=_.path(_u(b,w,v,x,0),A);S=d.insert(()=>(V.debug("Rough node insert CXC",L),L),":first-child"),S.select("path:nth-child(2)").attr("style",h.join(";")),S.select("path").attr("style",f.join(";").replace("fill","stroke"))}else S=d.insert("rect",":first-child"),S.attr("style",u).attr("rx",e.rx).attr("ry",e.ry).attr("x",b).attr("y",w).attr("width",v).attr("height",x);let{subGraphTitleTopMargin:T}=io(r);if(m.attr("transform",`translate(${e.x-y.width/2}, ${e.y-e.height/2+T})`),l){let _=m.select("span");_&&_.attr("style",l)}let E=S.node().getBBox();return e.offsetX=0,e.width=E.width,e.height=E.height,e.offsetY=y.height-e.padding/2,e.intersect=function(_){return Dd(e,_)},{cluster:d,labelBBox:y}},"rect"),SSe=o((t,e)=>{let r=t.insert("g").attr("class","note-cluster").attr("id",e.id),n=r.insert("rect",":first-child"),i=0*e.padding,a=i/2;n.attr("rx",e.rx).attr("ry",e.ry).attr("x",e.x-e.width/2-a).attr("y",e.y-e.height/2-a).attr("width",e.width+i).attr("height",e.height+i).attr("fill","none");let s=n.node().getBBox();return e.width=s.width,e.height=s.height,e.intersect=function(l){return Dd(e,l)},{cluster:r,labelBBox:{width:0,height:0}}},"noteGroup"),ASe=o(async(t,e)=>{let r=de(),{themeVariables:n,handDrawnSeed:i}=r,{altBackground:a,compositeBackground:s,compositeTitleBackground:l,nodeBorder:u}=n,h=t.insert("g").attr("class",e.cssClasses).attr("id",e.id).attr("data-id",e.id).attr("data-look",e.look),f=h.insert("g",":first-child"),d=h.insert("g").attr("class","cluster-label"),p=h.append("rect"),m=d.node().appendChild(await gc(e.label,e.labelStyle,void 0,!0)),g=m.getBBox();if(yr(r.flowchart.htmlLabels)){let L=m.children[0],M=$e(m);g=L.getBoundingClientRect(),M.attr("width",g.width),M.attr("height",g.height)}let y=0*e.padding,v=y/2,x=(e.width<=g.width+e.padding?g.width+e.padding:e.width)+y;e.width<=g.width+e.padding?e.diff=(x-e.width)/2-e.padding:e.diff=-e.padding;let b=e.height+y,w=e.height+y-g.height-6,S=e.x-x/2,T=e.y-b/2;e.width=x;let E=e.y-e.height/2-v+g.height+2,_;if(e.look==="handDrawn"){let L=e.cssClasses.includes("statediagram-cluster-alt"),M=Jt.svg(h),N=e.rx||e.ry?M.path(_u(S,T,x,b,10),{roughness:.7,fill:l,fillStyle:"solid",stroke:u,seed:i}):M.rectangle(S,T,x,b,{seed:i});_=h.insert(()=>N,":first-child");let k=M.rectangle(S,E,x,w,{fill:L?a:s,fillStyle:L?"hachure":"solid",stroke:u,seed:i});_=h.insert(()=>N,":first-child"),p=h.insert(()=>k)}else _=f.insert("rect",":first-child"),_.attr("class","outer").attr("x",S).attr("y",T).attr("width",x).attr("height",b).attr("data-look",e.look),p.attr("class","inner").attr("x",S).attr("y",E).attr("width",x).attr("height",w);d.attr("transform",`translate(${e.x-g.width/2}, ${T+1-(yr(r.flowchart.htmlLabels)?0:3)})`);let A=_.node().getBBox();return e.height=A.height,e.offsetX=0,e.offsetY=g.height-e.padding/2,e.labelBBox=g,e.intersect=function(L){return Dd(e,L)},{cluster:h,labelBBox:g}},"roundedWithTitle"),_Se=o((t,e)=>{let r=de(),{themeVariables:n,handDrawnSeed:i}=r,{nodeBorder:a}=n,s=t.insert("g").attr("class",e.cssClasses).attr("id",e.id).attr("data-look",e.look),l=s.insert("g",":first-child"),u=0*e.padding,h=e.width+u;e.diff=-e.padding;let f=e.height+u,d=e.x-h/2,p=e.y-f/2;e.width=h;let m;if(e.look==="handDrawn"){let v=Jt.svg(s).rectangle(d,p,h,f,{fill:"lightgrey",roughness:.5,strokeLineDash:[5],stroke:a,seed:i});m=s.insert(()=>v,":first-child")}else m=l.insert("rect",":first-child"),m.attr("class","divider").attr("x",d).attr("y",p).attr("width",h).attr("height",f).attr("data-look",e.look);let g=m.node().getBBox();return e.height=g.height,e.offsetX=0,e.offsetY=0,e.intersect=function(y){return Dd(e,y)},{cluster:s,labelBBox:{}}},"divider"),LSe=eK,DSe={rect:eK,squareRect:LSe,roundedWithTitle:ASe,noteGroup:SSe,divider:_Se},tK=new Map,Y5=o(async(t,e)=>{let r=e.shape||"rect",n=await DSe[r](t,e);return tK.set(e.id,n),n},"insertCluster"),rK=o(()=>{tK=new Map},"clear")});function W5(t,e){if(t===void 0||e===void 0)return{angle:0,deltaX:0,deltaY:0};t=q5(t),e=q5(e);let[r,n]=[t.x,t.y],[i,a]=[e.x,e.y],s=i-r,l=a-n;return{angle:Math.atan(l/s),deltaX:s,deltaY:l}}var Uh,q5,X5,j9=R(()=>{"use strict";Uh={aggregation:18,extension:18,composition:18,dependency:6,lollipop:13.5,arrow_point:4};o(W5,"calculateDeltaAndAngle");q5=o(t=>Array.isArray(t)?{x:t[0],y:t[1]}:t,"pointTransformer"),X5=o(t=>({x:o(function(e,r,n){let i=0;if(r===0&&Object.hasOwn(Uh,t.arrowTypeStart)){let{angle:a,deltaX:s}=W5(n[0],n[1]);i=Uh[t.arrowTypeStart]*Math.cos(a)*(s>=0?1:-1)}else if(r===n.length-1&&Object.hasOwn(Uh,t.arrowTypeEnd)){let{angle:a,deltaX:s}=W5(n[n.length-1],n[n.length-2]);i=Uh[t.arrowTypeEnd]*Math.cos(a)*(s>=0?1:-1)}return q5(e).x+i},"x"),y:o(function(e,r,n){let i=0;if(r===0&&Object.hasOwn(Uh,t.arrowTypeStart)){let{angle:a,deltaY:s}=W5(n[0],n[1]);i=Uh[t.arrowTypeStart]*Math.abs(Math.sin(a))*(s>=0?1:-1)}else if(r===n.length-1&&Object.hasOwn(Uh,t.arrowTypeEnd)){let{angle:a,deltaY:s}=W5(n[n.length-1],n[n.length-2]);i=Uh[t.arrowTypeEnd]*Math.abs(Math.sin(a))*(s>=0?1:-1)}return q5(e).y+i},"y")}),"getLineFunctionsWithOffset")});var iK,RSe,nK,aK=R(()=>{"use strict";ut();iK=o((t,e,r,n,i)=>{e.arrowTypeStart&&nK(t,"start",e.arrowTypeStart,r,n,i),e.arrowTypeEnd&&nK(t,"end",e.arrowTypeEnd,r,n,i)},"addEdgeMarkers"),RSe={arrow_cross:"cross",arrow_point:"point",arrow_barb:"barb",arrow_circle:"circle",aggregation:"aggregation",extension:"extension",composition:"composition",dependency:"dependency",lollipop:"lollipop"},nK=o((t,e,r,n,i,a)=>{let s=RSe[r];if(!s){V.warn(`Unknown arrow type: ${r}`);return}let l=e==="start"?"Start":"End";t.attr(`marker-${e}`,`url(${n}#${i}_${a}-${s}${l})`)},"addEdgeMarker")});function j5(t,e){de().flowchart.htmlLabels&&t&&(t.style.width=e.length*9+"px",t.style.height="12px")}function ISe(t){let e=[],r=[];for(let n=1;n5&&Math.abs(a.y-i.y)>5||i.y===a.y&&a.x===s.x&&Math.abs(a.x-i.x)>5&&Math.abs(a.y-s.y)>5)&&(e.push(a),r.push(n))}return{cornerPoints:e,cornerPointPositions:r}}var K5,da,lK,Av,Q5,Z5,NSe,MSe,sK,oK,OSe,J5,K9=R(()=>{"use strict";_t();rr();ut();Al();xr();j9();_d();Zt();ti();H5();aK();K5=new Map,da=new Map,lK=o(()=>{K5.clear(),da.clear()},"clear"),Av=o(t=>t?t.reduce((r,n)=>r+";"+n,""):"","getLabelStyles"),Q5=o(async(t,e)=>{let r=yr(de().flowchart.htmlLabels),n=await ta(t,e.label,{style:Av(e.labelStyle),useHtmlLabels:r,addSvgBackground:!0,isNode:!1});V.info("abc82",e,e.labelType);let i=t.insert("g").attr("class","edgeLabel"),a=i.insert("g").attr("class","label");a.node().appendChild(n);let s=n.getBBox();if(r){let u=n.children[0],h=$e(n);s=u.getBoundingClientRect(),h.attr("width",s.width),h.attr("height",s.height)}a.attr("transform","translate("+-s.width/2+", "+-s.height/2+")"),K5.set(e.id,i),e.width=s.width,e.height=s.height;let l;if(e.startLabelLeft){let u=await gc(e.startLabelLeft,Av(e.labelStyle)),h=t.insert("g").attr("class","edgeTerminals"),f=h.insert("g").attr("class","inner");l=f.node().appendChild(u);let d=u.getBBox();f.attr("transform","translate("+-d.width/2+", "+-d.height/2+")"),da.get(e.id)||da.set(e.id,{}),da.get(e.id).startLeft=h,j5(l,e.startLabelLeft)}if(e.startLabelRight){let u=await gc(e.startLabelRight,Av(e.labelStyle)),h=t.insert("g").attr("class","edgeTerminals"),f=h.insert("g").attr("class","inner");l=h.node().appendChild(u),f.node().appendChild(u);let d=u.getBBox();f.attr("transform","translate("+-d.width/2+", "+-d.height/2+")"),da.get(e.id)||da.set(e.id,{}),da.get(e.id).startRight=h,j5(l,e.startLabelRight)}if(e.endLabelLeft){let u=await gc(e.endLabelLeft,Av(e.labelStyle)),h=t.insert("g").attr("class","edgeTerminals"),f=h.insert("g").attr("class","inner");l=f.node().appendChild(u);let d=u.getBBox();f.attr("transform","translate("+-d.width/2+", "+-d.height/2+")"),h.node().appendChild(u),da.get(e.id)||da.set(e.id,{}),da.get(e.id).endLeft=h,j5(l,e.endLabelLeft)}if(e.endLabelRight){let u=await gc(e.endLabelRight,Av(e.labelStyle)),h=t.insert("g").attr("class","edgeTerminals"),f=h.insert("g").attr("class","inner");l=f.node().appendChild(u);let d=u.getBBox();f.attr("transform","translate("+-d.width/2+", "+-d.height/2+")"),h.node().appendChild(u),da.get(e.id)||da.set(e.id,{}),da.get(e.id).endRight=h,j5(l,e.endLabelRight)}return n},"insertEdgeLabel");o(j5,"setTerminalWidth");Z5=o((t,e)=>{V.debug("Moving label abc88 ",t.id,t.label,K5.get(t.id),e);let r=e.updatedPath?e.updatedPath:e.originalPath,n=de(),{subGraphTitleTotalMargin:i}=io(n);if(t.label){let a=K5.get(t.id),s=t.x,l=t.y;if(r){let u=Lt.calcLabelPosition(r);V.debug("Moving label "+t.label+" from (",s,",",l,") to (",u.x,",",u.y,") abc88"),e.updatedPath&&(s=u.x,l=u.y)}a.attr("transform",`translate(${s}, ${l+i/2})`)}if(t.startLabelLeft){let a=da.get(t.id).startLeft,s=t.x,l=t.y;if(r){let u=Lt.calcTerminalLabelPosition(t.arrowTypeStart?10:0,"start_left",r);s=u.x,l=u.y}a.attr("transform",`translate(${s}, ${l})`)}if(t.startLabelRight){let a=da.get(t.id).startRight,s=t.x,l=t.y;if(r){let u=Lt.calcTerminalLabelPosition(t.arrowTypeStart?10:0,"start_right",r);s=u.x,l=u.y}a.attr("transform",`translate(${s}, ${l})`)}if(t.endLabelLeft){let a=da.get(t.id).endLeft,s=t.x,l=t.y;if(r){let u=Lt.calcTerminalLabelPosition(t.arrowTypeEnd?10:0,"end_left",r);s=u.x,l=u.y}a.attr("transform",`translate(${s}, ${l})`)}if(t.endLabelRight){let a=da.get(t.id).endRight,s=t.x,l=t.y;if(r){let u=Lt.calcTerminalLabelPosition(t.arrowTypeEnd?10:0,"end_right",r);s=u.x,l=u.y}a.attr("transform",`translate(${s}, ${l})`)}},"positionEdgeLabel"),NSe=o((t,e)=>{let r=t.x,n=t.y,i=Math.abs(e.x-r),a=Math.abs(e.y-n),s=t.width/2,l=t.height/2;return i>=s||a>=l},"outsideNode"),MSe=o((t,e,r)=>{V.debug(`intersection calc abc89: + outsidePoint: ${JSON.stringify(e)} + insidePoint : ${JSON.stringify(r)} + node : x:${t.x} y:${t.y} w:${t.width} h:${t.height}`);let n=t.x,i=t.y,a=Math.abs(n-r.x),s=t.width/2,l=r.xMath.abs(n-e.x)*u){let d=r.y{V.warn("abc88 cutPathAtIntersect",t,e);let r=[],n=t[0],i=!1;return t.forEach(a=>{if(V.info("abc88 checking point",a,e),!NSe(e,a)&&!i){let s=MSe(e,n,a);V.debug("abc88 inside",a,n,s),V.debug("abc88 intersection",s,e);let l=!1;r.forEach(u=>{l=l||u.x===s.x&&u.y===s.y}),r.some(u=>u.x===s.x&&u.y===s.y)?V.warn("abc88 no intersect",s,r):r.push(s),i=!0}else V.warn("abc88 outside",a,n),n=a,i||r.push(a)}),V.debug("returning points",r),r},"cutPathAtIntersect");o(ISe,"extractCornerPoints");oK=o(function(t,e,r){let n=e.x-t.x,i=e.y-t.y,a=Math.sqrt(n*n+i*i),s=r/a;return{x:e.x-s*n,y:e.y-s*i}},"findAdjacentPoint"),OSe=o(function(t){let{cornerPointPositions:e}=ISe(t),r=[];for(let n=0;n10&&Math.abs(a.y-i.y)>=10){V.debug("Corner point fixing",Math.abs(a.x-i.x),Math.abs(a.y-i.y));let m=5;s.x===l.x?p={x:h<0?l.x-m+d:l.x+m-d,y:f<0?l.y-d:l.y+d}:p={x:h<0?l.x-d:l.x+d,y:f<0?l.y-m+d:l.y+m-d}}else V.debug("Corner point skipping fixing",Math.abs(a.x-i.x),Math.abs(a.y-i.y));r.push(p,u)}else r.push(t[n]);return r},"fixCorners"),J5=o(function(t,e,r,n,i,a,s){let{handDrawnSeed:l}=de(),u=e.points,h=!1,f=i;var d=a;d.intersect&&f.intersect&&(u=u.slice(1,e.points.length-1),u.unshift(f.intersect(u[0])),V.debug("Last point APA12",e.start,"-->",e.end,u[u.length-1],d,d.intersect(u[u.length-1])),u.push(d.intersect(u[u.length-1]))),e.toCluster&&(V.info("to cluster abc88",r.get(e.toCluster)),u=sK(e.points,r.get(e.toCluster).node),h=!0),e.fromCluster&&(V.debug("from cluster abc88",r.get(e.fromCluster),JSON.stringify(u,null,2)),u=sK(u.reverse(),r.get(e.fromCluster).node).reverse(),h=!0);let p=u.filter(A=>!Number.isNaN(A.y));p=OSe(p);let m=p[p.length-1];if(p.length>1){m=p[p.length-1];let A=p[p.length-2],L=(m.x-A.x)/2,M=(m.y-A.y)/2,N={x:A.x+L,y:A.y+M};p.splice(-1,0,N)}let g=vs;e.curve&&(g=e.curve);let{x:y,y:v}=X5(e),x=ha().x(y).y(v).curve(g),b;switch(e.thickness){case"normal":b="edge-thickness-normal";break;case"thick":b="edge-thickness-thick";break;case"invisible":b="edge-thickness-invisible";break;default:b="edge-thickness-normal"}switch(e.pattern){case"solid":b+=" edge-pattern-solid";break;case"dotted":b+=" edge-pattern-dotted";break;case"dashed":b+=" edge-pattern-dashed";break;default:b+=" edge-pattern-solid"}let w,S=x(p),T=Array.isArray(e.style)?e.style:[e.style];if(e.look==="handDrawn"){let A=Jt.svg(t);Object.assign([],p);let L=A.path(S,{roughness:.3,seed:l});b+=" transition",w=$e(L).select("path").attr("id",e.id).attr("class"," "+b+(e.classes?" "+e.classes:"")).attr("style",T?T.reduce((N,k)=>N+";"+k,""):"");let M=w.attr("d");w.attr("d",M),t.node().appendChild(w.node())}else w=t.append("path").attr("d",S).attr("id",e.id).attr("class"," "+b+(e.classes?" "+e.classes:"")).attr("style",T?T.reduce((A,L)=>A+";"+L,""):"");let E="";(de().flowchart.arrowMarkerAbsolute||de().state.arrowMarkerAbsolute)&&(E=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search,E=E.replace(/\(/g,"\\(").replace(/\)/g,"\\)")),V.info("arrowTypeStart",e.arrowTypeStart),V.info("arrowTypeEnd",e.arrowTypeEnd),iK(w,e,E,s,n);let _={};return h&&(_.updatedPath=u),_.originalPath=e.points,_},"insertEdge")});var PSe,BSe,FSe,zSe,GSe,$Se,VSe,USe,HSe,YSe,WSe,ew,Q9=R(()=>{"use strict";ut();PSe=o((t,e,r,n)=>{e.forEach(i=>{WSe[i](t,r,n)})},"insertMarkers"),BSe=o((t,e,r)=>{V.trace("Making markers for ",r),t.append("defs").append("marker").attr("id",r+"_"+e+"-extensionStart").attr("class","marker extension "+e).attr("refX",18).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 1,7 L18,13 V 1 Z"),t.append("defs").append("marker").attr("id",r+"_"+e+"-extensionEnd").attr("class","marker extension "+e).attr("refX",1).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 1,1 V 13 L18,7 Z")},"extension"),FSe=o((t,e,r)=>{t.append("defs").append("marker").attr("id",r+"_"+e+"-compositionStart").attr("class","marker composition "+e).attr("refX",18).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),t.append("defs").append("marker").attr("id",r+"_"+e+"-compositionEnd").attr("class","marker composition "+e).attr("refX",1).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z")},"composition"),zSe=o((t,e,r)=>{t.append("defs").append("marker").attr("id",r+"_"+e+"-aggregationStart").attr("class","marker aggregation "+e).attr("refX",18).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),t.append("defs").append("marker").attr("id",r+"_"+e+"-aggregationEnd").attr("class","marker aggregation "+e).attr("refX",1).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z")},"aggregation"),GSe=o((t,e,r)=>{t.append("defs").append("marker").attr("id",r+"_"+e+"-dependencyStart").attr("class","marker dependency "+e).attr("refX",6).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 5,7 L9,13 L1,7 L9,1 Z"),t.append("defs").append("marker").attr("id",r+"_"+e+"-dependencyEnd").attr("class","marker dependency "+e).attr("refX",13).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L14,7 L9,1 Z")},"dependency"),$Se=o((t,e,r)=>{t.append("defs").append("marker").attr("id",r+"_"+e+"-lollipopStart").attr("class","marker lollipop "+e).attr("refX",13).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("circle").attr("stroke","black").attr("fill","transparent").attr("cx",7).attr("cy",7).attr("r",6),t.append("defs").append("marker").attr("id",r+"_"+e+"-lollipopEnd").attr("class","marker lollipop "+e).attr("refX",1).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("circle").attr("stroke","black").attr("fill","transparent").attr("cx",7).attr("cy",7).attr("r",6)},"lollipop"),VSe=o((t,e,r)=>{t.append("marker").attr("id",r+"_"+e+"-pointEnd").attr("class","marker "+e).attr("viewBox","0 0 10 10").attr("refX",5).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",8).attr("markerHeight",8).attr("orient","auto").append("path").attr("d","M 0 0 L 10 5 L 0 10 z").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0"),t.append("marker").attr("id",r+"_"+e+"-pointStart").attr("class","marker "+e).attr("viewBox","0 0 10 10").attr("refX",4.5).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",8).attr("markerHeight",8).attr("orient","auto").append("path").attr("d","M 0 5 L 10 10 L 10 0 z").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0")},"point"),USe=o((t,e,r)=>{t.append("marker").attr("id",r+"_"+e+"-circleEnd").attr("class","marker "+e).attr("viewBox","0 0 10 10").attr("refX",11).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("circle").attr("cx","5").attr("cy","5").attr("r","5").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0"),t.append("marker").attr("id",r+"_"+e+"-circleStart").attr("class","marker "+e).attr("viewBox","0 0 10 10").attr("refX",-1).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("circle").attr("cx","5").attr("cy","5").attr("r","5").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0")},"circle"),HSe=o((t,e,r)=>{t.append("marker").attr("id",r+"_"+e+"-crossEnd").attr("class","marker cross "+e).attr("viewBox","0 0 11 11").attr("refX",12).attr("refY",5.2).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("path").attr("d","M 1,1 l 9,9 M 10,1 l -9,9").attr("class","arrowMarkerPath").style("stroke-width",2).style("stroke-dasharray","1,0"),t.append("marker").attr("id",r+"_"+e+"-crossStart").attr("class","marker cross "+e).attr("viewBox","0 0 11 11").attr("refX",-1).attr("refY",5.2).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("path").attr("d","M 1,1 l 9,9 M 10,1 l -9,9").attr("class","arrowMarkerPath").style("stroke-width",2).style("stroke-dasharray","1,0")},"cross"),YSe=o((t,e,r)=>{t.append("defs").append("marker").attr("id",r+"_"+e+"-barbEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",14).attr("markerUnits","userSpaceOnUse").attr("orient","auto").append("path").attr("d","M 19,7 L9,13 L14,7 L9,1 Z")},"barb"),WSe={extension:BSe,composition:FSe,aggregation:zSe,dependency:GSe,lollipop:$Se,point:VSe,circle:USe,cross:HSe,barb:YSe},ew=PSe});var zr,ar,En,ri=R(()=>{"use strict";Al();_t();Zt();rr();xr();zr=o(async(t,e,r)=>{let n,i=e.useHtmlLabels||yr(de().flowchart.htmlLabels);r?n=r:n="node default";let a=t.insert("g").attr("class",n).attr("id",e.domId||e.id),s=a.insert("g").attr("class","label").attr("style",e.labelStyle),l;e.label===void 0?l="":l=typeof e.label=="string"?e.label:e.label[0];let u;u=await ta(s,qr(to(l),de()),{useHtmlLabels:i,width:e.width||de().flowchart.wrappingWidth,cssClasses:"markdown-node-label",style:e.labelStyle});let h=u.getBBox(),f=e.padding/2;if(yr(de().flowchart.htmlLabels)){let d=u.children[0],p=$e(u),m=d.getElementsByTagName("img");if(m){let g=l.replace(/]*>/g,"").trim()==="";await Promise.all([...m].map(y=>new Promise(v=>{function x(){if(y.style.display="flex",y.style.flexDirection="column",g){let b=de().fontSize?de().fontSize:window.getComputedStyle(document.body).fontSize,S=parseInt(b,10)*5+"px";y.style.minWidth=S,y.style.maxWidth=S}else y.style.width="100%";v(y)}o(x,"setupImage"),setTimeout(()=>{y.complete&&x()}),y.addEventListener("error",x),y.addEventListener("load",x)})))}h=d.getBoundingClientRect(),p.attr("width",h.width),p.attr("height",h.height)}return i?s.attr("transform","translate("+-h.width/2+", "+-h.height/2+")"):s.attr("transform","translate(0, "+-h.height/2+")"),e.centerLabel&&s.attr("transform","translate("+-h.width/2+", "+-h.height/2+")"),s.insert("rect",":first-child"),{shapeSvg:a,bbox:h,halfPadding:f,label:s}},"labelHelper"),ar=o((t,e)=>{let r=e.node().getBBox();t.width=r.width,t.height=r.height},"updateNodeBounds"),En=o((t,e)=>(t.look==="handDrawn"?"rough-node":"node")+" "+t.cssClasses+" "+(e||""),"getNodeClasses")});function qSe(t,e){return t.intersect(e)}var cK,uK=R(()=>{"use strict";o(qSe,"intersectNode");cK=qSe});function XSe(t,e,r,n){var i=t.x,a=t.y,s=i-n.x,l=a-n.y,u=Math.sqrt(e*e*l*l+r*r*s*s),h=Math.abs(e*r*s/u);n.x{"use strict";o(XSe,"intersectEllipse");tw=XSe});function jSe(t,e,r){return tw(t,e,e,r)}var hK,fK=R(()=>{"use strict";Z9();o(jSe,"intersectCircle");hK=jSe});function KSe(t,e,r,n){var i,a,s,l,u,h,f,d,p,m,g,y,v,x,b;if(i=e.y-t.y,s=t.x-e.x,u=e.x*t.y-t.x*e.y,p=i*r.x+s*r.y+u,m=i*n.x+s*n.y+u,!(p!==0&&m!==0&&dK(p,m))&&(a=n.y-r.y,l=r.x-n.x,h=n.x*r.y-r.x*n.y,f=a*t.x+l*t.y+h,d=a*e.x+l*e.y+h,!(f!==0&&d!==0&&dK(f,d))&&(g=i*l-a*s,g!==0)))return y=Math.abs(g/2),v=s*h-l*u,x=v<0?(v-y)/g:(v+y)/g,v=a*u-i*h,b=v<0?(v-y)/g:(v+y)/g,{x,y:b}}function dK(t,e){return t*e>0}var pK,mK=R(()=>{"use strict";o(KSe,"intersectLine");o(dK,"sameSign");pK=KSe});function QSe(t,e,r){let n=t.x,i=t.y,a=[],s=Number.POSITIVE_INFINITY,l=Number.POSITIVE_INFINITY;typeof e.forEach=="function"?e.forEach(function(f){s=Math.min(s,f.x),l=Math.min(l,f.y)}):(s=Math.min(s,e.x),l=Math.min(l,e.y));let u=n-t.width/2-s,h=i-t.height/2-l;for(let f=0;f1&&a.sort(function(f,d){let p=f.x-r.x,m=f.y-r.y,g=Math.sqrt(p*p+m*m),y=d.x-r.x,v=d.y-r.y,x=Math.sqrt(y*y+v*v);return g{"use strict";mK();o(QSe,"intersectPolygon");gK=QSe});var sr,hi=R(()=>{"use strict";uK();fK();Z9();yK();q9();sr={node:cK,circle:hK,ellipse:tw,polygon:gK,rect:Dd}});var Rd,_v=R(()=>{"use strict";ri();hi();Sv();ki();ti();Rd=o(async(t,e,r)=>{let{labelStyles:n,nodeStyles:i}=Br(e);e.labelStyle=n;let{shapeSvg:a,bbox:s}=await zr(t,e,En(e)),l=Math.max(s.width+r.labelPaddingX*2,e?.width||0),u=Math.max(s.height+r.labelPaddingY*2,e?.height||0),h=-l/2,f=-u/2,d,{rx:p,ry:m}=e,{cssStyles:g}=e;if(r?.rx&&r.ry&&(p=r.rx,m=r.ry),e.look==="handDrawn"){let y=Jt.svg(a),v=Fr(e,{}),x=p||m?y.path(_u(h,f,l,u,p||0),v):y.rectangle(h,f,l,u,v);d=a.insert(()=>x,":first-child"),d.attr("class","basic label-container").attr("style",g)}else d=a.insert("rect",":first-child"),d.attr("class","basic label-container").attr("style",i).attr("rx",p).attr("data-id","abc").attr("data-et","node").attr("ry",m).attr("x",h).attr("y",f).attr("width",l).attr("height",u);return ar(e,d),e.intersect=function(y){return sr.rect(e,y)},a},"drawRect")});var vK,xK=R(()=>{"use strict";_v();vK=o(async(t,e)=>Rd(t,e,{rx:5,ry:5,classes:"flowchart-node"}),"state")});var bK,wK=R(()=>{"use strict";_v();bK=o(async(t,e)=>{let r={rx:5,ry:5,classes:"",labelPaddingX:(e?.padding||0)*1,labelPaddingY:(e?.padding||0)*1};return Rd(t,e,r)},"roundedRect")});var TK,kK=R(()=>{"use strict";_v();TK=o(async(t,e)=>{let r={rx:0,ry:0,classes:"",labelPaddingX:(e?.padding||0)*2,labelPaddingY:(e?.padding||0)*1};return Rd(t,e,r)},"squareRect")});var EK,CK=R(()=>{"use strict";ri();hi();ti();ki();_t();EK=o((t,e)=>{let{themeVariables:r}=de(),{lineColor:n}=r,i=t.insert("g").attr("class","node default").attr("id",e.domId||e.id),a;if(e.look==="handDrawn"){let l=Jt.svg(i).circle(0,0,14,Lu(n));a=i.insert(()=>l)}else a=i.insert("circle",":first-child");return a.attr("class","state-start").attr("r",7).attr("width",14).attr("height",14),ar(e,a),e.intersect=function(s){return sr.circle(e,7,s)},i},"stateStart")});var SK,AK=R(()=>{"use strict";ri();hi();ti();ki();_t();SK=o((t,e)=>{let{themeVariables:r}=de(),{lineColor:n}=r,i=t.insert("g").attr("class","node default").attr("id",e.domId||e.id),a,s;if(e.look==="handDrawn"){let l=Jt.svg(i),u=l.circle(0,0,14,{...Lu(n),roughness:.5}),h=l.circle(0,0,5,{...Lu(n),fillStyle:"solid"});a=i.insert(()=>u),s=i.insert(()=>h)}else s=i.insert("circle",":first-child"),a=i.insert("circle",":first-child"),a.attr("class","state-start").attr("r",7).attr("width",14).attr("height",14),s.attr("class","state-end").attr("r",5).attr("width",10).attr("height",10);return ar(e,a),e.intersect=function(l){return sr.circle(e,7,l)},i},"stateEnd")});var J9,_K=R(()=>{"use strict";ri();hi();ti();ki();_t();J9=o((t,e,r)=>{let{themeVariables:n}=de(),{lineColor:i}=n,a=t.insert("g").attr("class","node default").attr("id",e.domId||e.id),s=70,l=10;r==="LR"&&(s=10,l=70);let u=-1*s/2,h=-1*l/2,f;if(e.look==="handDrawn"){let y=Jt.svg(a).rectangle(u,h,s,l,Lu(i));f=a.insert(()=>y)}else f=a.append("rect").attr("x",u).attr("y",h).attr("width",s).attr("height",l).attr("class","fork-join");ar(e,f);let d=0,p=0,m=10;return e.height&&(d=e.height),e.width&&(p=e.width),e.padding&&(m=e.padding),e.height=d+m/2,e.width=p+m/2,e.intersect=function(g){return sr.rect(e,g)},a},"forkJoin")});var LK,DK=R(()=>{"use strict";hi();ti();ki();_t();LK=o((t,e)=>{let{labelStyles:r,nodeStyles:n}=Br(e);e.labelStyle=r;let{themeVariables:i}=de(),{lineColor:a}=i,s=t.insert("g").attr("class","node default").attr("id",e.domId||e.id),l=28,u=[{x:0,y:l/2},{x:l/2,y:0},{x:0,y:-l/2},{x:-l/2,y:0}],h;if(e.look==="handDrawn"){let f=Jt.svg(s),d=u.map(function(m){return[m.x,m.y]}),p=f.polygon(d,Lu(a));h=s.insert(()=>p)}else h=s.insert("polygon",":first-child").attr("points",u.map(function(f){return f.x+","+f.y}).join(" "));return h.attr("class","state-start").attr("r",7).attr("width",28).attr("height",28).attr("style",n),e.width=28,e.height=28,e.intersect=function(f){return sr.circle(e,14,f)},s},"choice")});var RK,NK=R(()=>{"use strict";ut();ri();hi();_t();ti();RK=o(async(t,e)=>{let{themeVariables:r,handDrawnSeed:n}=de(),{noteBorderColor:i,noteBkgColor:a}=r;e.useHtmlLabels||(e.centerLabel=!0);let{shapeSvg:l,bbox:u}=await zr(t,e,"node "+e.cssClasses);V.info("Classes = ",e.cssClasses);let{cssStyles:h}=e,f,d=u.width+e.padding,p=u.height+e.padding,m=-d/2,g=-p/2;if(e.look==="handDrawn"){let v=Jt.svg(l).rectangle(m,g,d,p,{roughness:.7,fill:a,fillWeight:3,seed:n,stroke:i});f=l.insert(()=>v,":first-child"),f.attr("class","basic label-container").attr("style",h)}else f=l.insert("rect",":first-child"),f.attr("rx",e.rx).attr("ry",e.ry).attr("x",m).attr("y",g).attr("width",d).attr("height",p);return ar(e,f),e.intersect=function(y){return sr.rect(e,y)},l},"note")});var MK,IK=R(()=>{"use strict";ri();hi();ki();ti();Sv();MK=o(async(t,e)=>{let{labelStyles:r,nodeStyles:n}=Br(e);e.labelStyle=r;let{shapeSvg:i,bbox:a}=await zr(t,e,En(e)),s=a.height+e.padding,l=a.width+s/4+e.padding,u,{cssStyles:h}=e;if(e.look==="handDrawn"){let f=Jt.svg(i),d=Fr(e,{}),p=_u(-l/2,-s/2,l,s,s/2),m=f.path(p,d);u=i.insert(()=>m,":first-child"),u.attr("class","basic label-container").attr("style",h)}else u=i.insert("rect",":first-child"),u.attr("class","basic label-container").attr("style",n).attr("rx",s/2).attr("ry",s/2).attr("x",-l/2).attr("y",-s/2).attr("width",l).attr("height",s);return ar(e,u),e.intersect=function(f){return sr.rect(e,f)},i},"stadium")});var OK,PK=R(()=>{"use strict";Zt();rr();ri();H5();hi();ki();ti();_t();Sv();ut();OK=o(async(t,e)=>{let{labelStyles:r,nodeStyles:n}=Br(e);e.labelStyle=r;let i;e.cssClasses?i="node "+e.cssClasses:i="node default";let a=t.insert("g").attr("class",i).attr("id",e.domId||e.id),s=a.insert("g"),l=a.insert("g").attr("class","label").attr("style",n),u=e.description,h=e.label,f=l.node().appendChild(await gc(h,e.labelStyle,!0,!0)),d={width:0,height:0};if(yr(de()?.flowchart?.htmlLabels)){let A=f.children[0],L=$e(f);d=A.getBoundingClientRect(),L.attr("width",d.width),L.attr("height",d.height)}V.info("Text 2",u);let p=u||[],m=f.getBBox(),g=l.node().appendChild(await gc(p.join?p.join("
    "):p,e.labelStyle,!0,!0)),y=g.children[0],v=$e(g);d=y.getBoundingClientRect(),v.attr("width",d.width),v.attr("height",d.height);let x=(e.padding||0)/2;$e(g).attr("transform","translate( "+(d.width>m.width?0:(m.width-d.width)/2)+", "+(m.height+x+5)+")"),$e(f).attr("transform","translate( "+(d.width(V.debug("Rough node insert CXC",M),N),":first-child"),E=a.insert(()=>(V.debug("Rough node insert CXC",M),M),":first-child")}else E=s.insert("rect",":first-child"),_=s.insert("line"),E.attr("class","outer title-state").attr("style",n).attr("x",-d.width/2-x).attr("y",-d.height/2-x).attr("width",d.width+(e.padding||0)).attr("height",d.height+(e.padding||0)),_.attr("class","divider").attr("x1",-d.width/2-x).attr("x2",d.width/2+x).attr("y1",-d.height/2-x+m.height+x).attr("y2",-d.height/2-x+m.height+x);return ar(e,E),e.intersect=function(A){return sr.rect(e,A)},a},"rectWithTitle")});function Ma(t,e,r,n){return t.insert("polygon",":first-child").attr("points",n.map(function(i){return i.x+","+i.y}).join(" ")).attr("class","label-container").attr("transform","translate("+-e/2+","+r/2+")")}var Du=R(()=>{"use strict";o(Ma,"insertPolygonShape")});var BK,FK=R(()=>{"use strict";ri();hi();ki();ti();Du();BK=o(async(t,e)=>{let{labelStyles:r,nodeStyles:n}=Br(e);e.labelStyle=r;let{shapeSvg:i,bbox:a}=await zr(t,e,En(e)),s=(e?.padding||0)/2,l=a.width+e.padding,u=a.height+e.padding,h=-a.width/2-s,f=-a.height/2-s,d=[{x:0,y:0},{x:l,y:0},{x:l,y:-u},{x:0,y:-u},{x:0,y:0},{x:-8,y:0},{x:l+8,y:0},{x:l+8,y:-u},{x:-8,y:-u},{x:-8,y:0}];if(e.look==="handDrawn"){let p=Jt.svg(i),m=Fr(e,{}),g=p.rectangle(h-8,f,l+16,u,m),y=p.line(h,f,h,f+u,m),v=p.line(h+l,f,h+l,f+u,m);i.insert(()=>y,":first-child"),i.insert(()=>v,":first-child");let x=i.insert(()=>g,":first-child"),{cssStyles:b}=e;x.attr("class","basic label-container").attr("style",b),ar(e,x)}else{let p=Ma(i,l,u,d);n&&p.attr("style",n),ar(e,p)}return e.intersect=function(p){return sr.polygon(e,d,p)},i},"subroutine")});var ZSe,JSe,eAe,zK,GK=R(()=>{"use strict";ri();hi();ki();ti();ZSe=o((t,e,r,n,i,a)=>[`M${t},${e+a}`,`a${i},${a} 0,0,0 ${r},0`,`a${i},${a} 0,0,0 ${-r},0`,`l0,${n}`,`a${i},${a} 0,0,0 ${r},0`,`l0,${-n}`].join(" "),"createCylinderPathD"),JSe=o((t,e,r,n,i,a)=>[`M${t},${e+a}`,`M${t+r},${e+a}`,`a${i},${a} 0,0,0 ${-r},0`,`l0,${n}`,`a${i},${a} 0,0,0 ${r},0`,`l0,${-n}`].join(" "),"createOuterCylinderPathD"),eAe=o((t,e,r,n,i,a)=>[`M${t-r/2},${-n/2}`,`a${i},${a} 0,0,0 ${r},0`].join(" "),"createInnerCylinderPathD"),zK=o(async(t,e)=>{let{labelStyles:r,nodeStyles:n}=Br(e);e.labelStyle=r;let{shapeSvg:i,bbox:a}=await zr(t,e,En(e)),s=a.width+e.padding,l=s/2,u=l/(2.5+s/50),h=a.height+u+e.padding,f,{cssStyles:d}=e;if(e.look==="handDrawn"){let p=Jt.svg(i),m=JSe(0,0,s,h,l,u),g=eAe(0,u,s,h,l,u),y=p.path(m,Fr(e,{})),v=p.path(g,Fr(e,{fill:"none"}));f=i.insert(()=>v,":first-child"),f=i.insert(()=>y,":first-child"),f.attr("class","basic label-container"),d&&f.attr("style",d)}else{let p=ZSe(0,0,s,h,l,u);f=i.insert("path",":first-child").attr("d",p).attr("class","basic label-container").attr("style",d).attr("style",n)}return f.attr("label-offset-y",u),f.attr("transform",`translate(${-s/2}, ${-(h/2+u)})`),ar(e,f),e.intersect=function(p){let m=sr.rect(e,p),g=m.x-(e.x??0);if(l!=0&&(Math.abs(g)<(e.width??0)/2||Math.abs(g)==(e.width??0)/2&&Math.abs(m.y-(e.y??0))>(e.height??0)/2-u)){let y=u*u*(1-g*g/(l*l));y>0&&(y=Math.sqrt(y)),y=u-y,p.y-(e.y??0)>0&&(y=-y),m.y+=y}return m},i},"cylinder")});var $K,VK=R(()=>{"use strict";ut();ri();hi();ki();ti();$K=o(async(t,e)=>{let{labelStyles:r,nodeStyles:n}=Br(e);e.labelStyle=r;let{shapeSvg:i,bbox:a,halfPadding:s}=await zr(t,e,En(e)),l=a.width/2+s,u,{cssStyles:h}=e;if(e.look==="handDrawn"){let f=Jt.svg(i),d=Fr(e,{}),p=f.circle(0,0,l*2,d);u=i.insert(()=>p,":first-child"),u.attr("class","basic label-container").attr("style",h)}else u=i.insert("circle",":first-child").attr("class","basic label-container").attr("style",n).attr("r",l).attr("cx",0).attr("cy",0);return ar(e,u),e.intersect=function(f){return V.info("Circle intersect",e,l,f),sr.circle(e,l,f)},i},"circle")});var UK,HK=R(()=>{"use strict";ut();ri();hi();ki();ti();UK=o(async(t,e)=>{let{labelStyles:r,nodeStyles:n}=Br(e);e.labelStyle=r;let{shapeSvg:i,bbox:a,halfPadding:s}=await zr(t,e,En(e)),u=a.width/2+s+5,h=a.width/2+s,f,{cssStyles:d}=e;if(e.look==="handDrawn"){let p=Jt.svg(i),m=Fr(e,{roughness:.2,strokeWidth:2.5}),g=Fr(e,{roughness:.2,strokeWidth:1.5}),y=p.circle(0,0,u*2,m),v=p.circle(0,0,h*2,g);f=i.insert("g",":first-child"),f.attr("class",e.cssClasses).attr("style",d),f.node()?.appendChild(y),f.node()?.appendChild(v)}else{f=i.insert("g",":first-child");let p=f.insert("circle",":first-child"),m=f.insert("circle");f.attr("class","basic label-container").attr("style",n),p.attr("class","outer-circle").attr("style",n).attr("r",u).attr("cx",0).attr("cy",0),m.attr("class","inner-circle").attr("style",n).attr("r",h).attr("cx",0).attr("cy",0)}return ar(e,f),e.intersect=function(p){return V.info("DoubleCircle intersect",e,u,p),sr.circle(e,u,p)},i},"doublecircle")});var tAe,YK,WK=R(()=>{"use strict";ri();hi();ki();ti();Du();tAe=o((t,e,r,n)=>[`M${t-n/2},${e}`,`L${t+r},${e}`,`L${t+r},${e-n}`,`L${t-n/2},${e-n}`,`L${t},${e-n/2}`,"Z"].join(" "),"createPolygonPathD"),YK=o(async(t,e)=>{let{labelStyles:r,nodeStyles:n}=Br(e);e.labelStyle=r;let{shapeSvg:i,bbox:a}=await zr(t,e,En(e)),s=a.width+e.padding,l=a.height+e.padding,u=[{x:-l/2,y:0},{x:s,y:0},{x:s,y:-l},{x:-l/2,y:-l},{x:0,y:-l/2}],h,{cssStyles:f}=e;if(e.look==="handDrawn"){let d=Jt.svg(i),p=Fr(e,{}),m=tAe(0,0,s,l),g=d.path(m,p);h=i.insert(()=>g,":first-child").attr("transform",`translate(${-s/2}, ${l/2})`),f&&h.attr("style",f)}else h=Ma(i,s,l,u);return n&&h.attr("style",n),e.width=s+l,e.height=l,ar(e,h),e.intersect=function(d){return sr.polygon(e,u,d)},i},"rect_left_inv_arrow")});var rAe,qK,XK=R(()=>{"use strict";ut();ri();hi();ki();ti();Du();rAe=o((t,e,r)=>[`M${t+r/2},${e}`,`L${t+r},${e-r/2}`,`L${t+r/2},${e-r}`,`L${t},${e-r/2}`,"Z"].join(" "),"createDecisionBoxPathD"),qK=o(async(t,e)=>{let{labelStyles:r,nodeStyles:n}=Br(e);e.labelStyle=r;let{shapeSvg:i,bbox:a}=await zr(t,e,En(e)),s=a.width+e.padding,l=a.height+e.padding,u=s+l,h=[{x:u/2,y:0},{x:u,y:-u/2},{x:u/2,y:-u},{x:0,y:-u/2}],f,{cssStyles:d}=e;if(e.look==="handDrawn"){let p=Jt.svg(i),m=Fr(e,{}),g=rAe(0,0,u),y=p.path(g,m);f=i.insert(()=>y,":first-child").attr("transform",`translate(${-u/2}, ${u/2})`),d&&f.attr("style",d)}else f=Ma(i,u,u,h);return n&&f.attr("style",n),ar(e,f),e.intersect=function(p){return V.debug(`APA12 Intersect called SPLIT +point:`,p,` +node: +`,e,` +res:`,sr.polygon(e,h,p)),sr.polygon(e,h,p)},i},"question")});var nAe,jK,KK=R(()=>{"use strict";ri();hi();ki();ti();Du();nAe=o((t,e,r,n,i)=>[`M${t+i},${e}`,`L${t+r-i},${e}`,`L${t+r},${e-n/2}`,`L${t+r-i},${e-n}`,`L${t+i},${e-n}`,`L${t},${e-n/2}`,"Z"].join(" "),"createHexagonPathD"),jK=o(async(t,e)=>{let{labelStyles:r,nodeStyles:n}=Br(e);e.labelStyle=r;let{shapeSvg:i,bbox:a}=await zr(t,e,En(e)),s=4,l=a.height+e.padding,u=l/s,h=a.width+2*u+e.padding,f=[{x:u,y:0},{x:h-u,y:0},{x:h,y:-l/2},{x:h-u,y:-l},{x:u,y:-l},{x:0,y:-l/2}],d,{cssStyles:p}=e;if(e.look==="handDrawn"){let m=Jt.svg(i),g=Fr(e,{}),y=nAe(0,0,h,l,u),v=m.path(y,g);d=i.insert(()=>v,":first-child").attr("transform",`translate(${-h/2}, ${l/2})`),p&&d.attr("style",p)}else d=Ma(i,h,l,f);return n&&d.attr("style",n),e.width=h,e.height=l,ar(e,d),e.intersect=function(m){return sr.polygon(e,f,m)},i},"hexagon")});var iAe,QK,ZK=R(()=>{"use strict";ri();hi();ki();ti();Du();iAe=o((t,e,r,n)=>[`M${t-2*n/6},${e}`,`L${t+r-n/6},${e}`,`L${t+r+2*n/6},${e-n}`,`L${t+n/6},${e-n}`,"Z"].join(" "),"createLeanRightPathD"),QK=o(async(t,e)=>{let{labelStyles:r,nodeStyles:n}=Br(e);e.labelStyle=r;let{shapeSvg:i,bbox:a}=await zr(t,e,En(e)),s=a.width+e.padding,l=a.height+e.padding,u=[{x:-2*l/6,y:0},{x:s-l/6,y:0},{x:s+2*l/6,y:-l},{x:l/6,y:-l}],h,{cssStyles:f}=e;if(e.look==="handDrawn"){let d=Jt.svg(i),p=Fr(e,{}),m=iAe(0,0,s,l),g=d.path(m,p);h=i.insert(()=>g,":first-child").attr("transform",`translate(${-s/2}, ${l/2})`),f&&h.attr("style",f)}else h=Ma(i,s,l,u);return n&&h.attr("style",n),e.width=s,e.height=l,ar(e,h),e.intersect=function(d){return sr.polygon(e,u,d)},i},"lean_right")});var aAe,JK,eQ=R(()=>{"use strict";ri();hi();ki();ti();Du();aAe=o((t,e,r,n)=>[`M${t+2*n/6},${e}`,`L${t+r+n/6},${e}`,`L${t+r-2*n/6},${e-n}`,`L${t-n/6},${e-n}`,"Z"].join(" "),"createLeanLeftPathD"),JK=o(async(t,e)=>{let{labelStyles:r,nodeStyles:n}=Br(e);e.labelStyle=r;let{shapeSvg:i,bbox:a}=await zr(t,e,En(e)),s=a.width+e.padding,l=a.height+e.padding,u=[{x:2*l/6,y:0},{x:s+l/6,y:0},{x:s-2*l/6,y:-l},{x:-l/6,y:-l}],h,{cssStyles:f}=e;if(e.look==="handDrawn"){let d=Jt.svg(i),p=Fr(e,{}),m=aAe(0,0,s,l),g=d.path(m,p);h=i.insert(()=>g,":first-child").attr("transform",`translate(${-s/2}, ${l/2})`),f&&h.attr("style",f)}else h=Ma(i,s,l,u);return n&&h.attr("style",n),e.width=s,e.height=l,ar(e,h),e.intersect=function(d){return sr.polygon(e,u,d)},i},"lean_left")});var sAe,tQ,rQ=R(()=>{"use strict";ri();hi();ki();ti();Du();sAe=o((t,e,r,n)=>[`M${t-2*n/6},${e}`,`L${t+r+2*n/6},${e}`,`L${t+r-n/6},${e-n}`,`L${t+n/6},${e-n}`,"Z"].join(" "),"createTrapezoidPathD"),tQ=o(async(t,e)=>{let{labelStyles:r,nodeStyles:n}=Br(e);e.labelStyle=r;let{shapeSvg:i,bbox:a}=await zr(t,e,En(e)),s=a.width+e.padding,l=a.height+e.padding,u=[{x:-2*l/6,y:0},{x:s+2*l/6,y:0},{x:s-l/6,y:-l},{x:l/6,y:-l}],h,{cssStyles:f}=e;if(e.look==="handDrawn"){let d=Jt.svg(i),p=Fr(e,{}),m=sAe(0,0,s,l),g=d.path(m,p);h=i.insert(()=>g,":first-child").attr("transform",`translate(${-s/2}, ${l/2})`),f&&h.attr("style",f)}else h=Ma(i,s,l,u);return n&&h.attr("style",n),e.width=s,e.height=l,ar(e,h),e.intersect=function(d){return sr.polygon(e,u,d)},i},"trapezoid")});var oAe,nQ,iQ=R(()=>{"use strict";ri();hi();ki();ti();Du();oAe=o((t,e,r,n)=>[`M${t+n/6},${e}`,`L${t+r-n/6},${e}`,`L${t+r+2*n/6},${e-n}`,`L${t-2*n/6},${e-n}`,"Z"].join(" "),"createInvertedTrapezoidPathD"),nQ=o(async(t,e)=>{let{labelStyles:r,nodeStyles:n}=Br(e);e.labelStyle=r;let{shapeSvg:i,bbox:a}=await zr(t,e,En(e)),s=a.width+e.padding,l=a.height+e.padding,u=[{x:l/6,y:0},{x:s-l/6,y:0},{x:s+2*l/6,y:-l},{x:-2*l/6,y:-l}],h,{cssStyles:f}=e;if(e.look==="handDrawn"){let d=Jt.svg(i),p=Fr(e,{}),m=oAe(0,0,s,l),g=d.path(m,p);h=i.insert(()=>g,":first-child").attr("transform",`translate(${-s/2}, ${l/2})`),f&&h.attr("style",f)}else h=Ma(i,s,l,u);return n&&h.attr("style",n),e.width=s,e.height=l,ar(e,h),e.intersect=function(d){return sr.polygon(e,u,d)},i},"inv_trapezoid")});var aQ,sQ=R(()=>{"use strict";_v();ri();hi();aQ=o(async(t,e)=>{let{shapeSvg:r}=await zr(t,e,"label"),n=r.insert("rect",":first-child");return n.attr("width",.1).attr("height",.1),r.attr("class","label edgeLabel"),ar(e,n),e.intersect=function(s){return sr.rect(e,s)},r},"labelRect")});var oQ,ym,rw,lQ,cQ,eL,tL=R(()=>{"use strict";ut();xK();wK();kK();CK();AK();_K();DK();NK();IK();PK();_t();FK();GK();VK();HK();WK();XK();KK();ZK();eQ();rQ();iQ();sQ();oQ={state:vK,stateStart:EK,stateEnd:SK,fork:J9,join:J9,choice:LK,note:RK,roundedRect:bK,rectWithTitle:OK,squareRect:TK,stadium:MK,subroutine:BK,cylinder:zK,circle:$K,doublecircle:UK,odd:YK,diamond:qK,hexagon:jK,lean_right:QK,lean_left:JK,trapezoid:tQ,inv_trapezoid:nQ,labelRect:aQ},ym=new Map,rw=o(async(t,e,r)=>{let n,i;if(e.shape==="rect"&&(e.rx&&e.ry?e.shape="roundedRect":e.shape="squareRect"),e.link){let a;de().securityLevel==="sandbox"?a="_top":e.linkTarget&&(a=e.linkTarget||"_blank"),n=t.insert("svg:a").attr("xlink:href",e.link).attr("target",a),i=await oQ[e.shape](n,e,r)}else i=await oQ[e.shape](t,e,r),n=i;return e.tooltip&&i.attr("title",e.tooltip),ym.set(e.id,n),e.haveCallback&&ym.get(e.id).attr("class",ym.get(e.id).attr("class")+" clickable"),n},"insertNode"),lQ=o((t,e)=>{ym.set(e.id,t)},"setNodeElem"),cQ=o(()=>{ym.clear()},"clear"),eL=o(t=>{let e=ym.get(t.id);V.trace("Transforming node",t.diff,t,"translate("+(t.x-t.width/2-5)+", "+t.width/2+")");let r=8,n=t.diff||0;return t.clusterNode?e.attr("transform","translate("+(t.x+n-t.width/2)+", "+(t.y-t.height/2-r)+")"):e.attr("transform","translate("+t.x+", "+t.y+")"),n},"positionNode")});var uQ,hQ=R(()=>{"use strict";qs();rr();ut();X9();K9();Q9();tL();ri();xr();uQ={common:We,getConfig:Or,insertCluster:Y5,insertEdge:J5,insertEdgeLabel:Q5,insertMarkers:ew,insertNode:rw,interpolateToCurve:om,labelHelper:zr,log:V,positionEdgeLabel:Z5}});function cAe(t){return typeof t=="symbol"||Wn(t)&&fa(t)==lAe}var lAe,so,Nd=R(()=>{"use strict";wu();Mo();lAe="[object Symbol]";o(cAe,"isSymbol");so=cAe});function uAe(t,e){for(var r=-1,n=t==null?0:t.length,i=Array(n);++r{"use strict";o(uAe,"arrayMap");Ss=uAe});function pQ(t){if(typeof t=="string")return t;if(wt(t))return Ss(t,pQ)+"";if(so(t))return dQ?dQ.call(t):"";var e=t+"";return e=="0"&&1/t==-hAe?"-0":e}var hAe,fQ,dQ,mQ,gQ=R(()=>{"use strict";vd();Md();Bn();Nd();hAe=1/0,fQ=Ji?Ji.prototype:void 0,dQ=fQ?fQ.toString:void 0;o(pQ,"baseToString");mQ=pQ});function dAe(t){for(var e=t.length;e--&&fAe.test(t.charAt(e)););return e}var fAe,yQ,vQ=R(()=>{"use strict";fAe=/\s/;o(dAe,"trimmedEndIndex");yQ=dAe});function mAe(t){return t&&t.slice(0,yQ(t)+1).replace(pAe,"")}var pAe,xQ,bQ=R(()=>{"use strict";vQ();pAe=/^\s+/;o(mAe,"baseTrim");xQ=mAe});function bAe(t){if(typeof t=="number")return t;if(so(t))return wQ;if(pn(t)){var e=typeof t.valueOf=="function"?t.valueOf():t;t=pn(e)?e+"":e}if(typeof t!="string")return t===0?t:+t;t=xQ(t);var r=yAe.test(t);return r||vAe.test(t)?xAe(t.slice(2),r?2:8):gAe.test(t)?wQ:+t}var wQ,gAe,yAe,vAe,xAe,TQ,kQ=R(()=>{"use strict";bQ();Js();Nd();wQ=NaN,gAe=/^[-+]0x[0-9a-f]+$/i,yAe=/^0b[01]+$/i,vAe=/^0o[0-7]+$/i,xAe=parseInt;o(bAe,"toNumber");TQ=bAe});function TAe(t){if(!t)return t===0?t:0;if(t=TQ(t),t===EQ||t===-EQ){var e=t<0?-1:1;return e*wAe}return t===t?t:0}var EQ,wAe,vm,rL=R(()=>{"use strict";kQ();EQ=1/0,wAe=17976931348623157e292;o(TAe,"toFinite");vm=TAe});function kAe(t){var e=vm(t),r=e%1;return e===e?r?e-r:e:0}var yc,xm=R(()=>{"use strict";rL();o(kAe,"toInteger");yc=kAe});var EAe,nw,CQ=R(()=>{"use strict";Nh();Ro();EAe=xs(Jn,"WeakMap"),nw=EAe});function CAe(){}var qn,nL=R(()=>{"use strict";o(CAe,"noop");qn=CAe});function SAe(t,e){for(var r=-1,n=t==null?0:t.length;++r{"use strict";o(SAe,"arrayEach");iw=SAe});function AAe(t,e,r,n){for(var i=t.length,a=r+(n?1:-1);n?a--:++a{"use strict";o(AAe,"baseFindIndex");aw=AAe});function _Ae(t){return t!==t}var SQ,AQ=R(()=>{"use strict";o(_Ae,"baseIsNaN");SQ=_Ae});function LAe(t,e,r){for(var n=r-1,i=t.length;++n{"use strict";o(LAe,"strictIndexOf");_Q=LAe});function DAe(t,e,r){return e===e?_Q(t,e,r):aw(t,SQ,r)}var bm,sw=R(()=>{"use strict";aL();AQ();LQ();o(DAe,"baseIndexOf");bm=DAe});function RAe(t,e){var r=t==null?0:t.length;return!!r&&bm(t,e,0)>-1}var ow,sL=R(()=>{"use strict";sw();o(RAe,"arrayIncludes");ow=RAe});var NAe,DQ,RQ=R(()=>{"use strict";F_();NAe=s5(Object.keys,Object),DQ=NAe});function OAe(t){if(!fc(t))return DQ(t);var e=[];for(var r in Object(t))IAe.call(t,r)&&r!="constructor"&&e.push(r);return e}var MAe,IAe,wm,lw=R(()=>{"use strict";tm();RQ();MAe=Object.prototype,IAe=MAe.hasOwnProperty;o(OAe,"baseKeys");wm=OAe});function PAe(t){return ei(t)?h5(t):wm(t)}var Dr,vc=R(()=>{"use strict";U_();lw();Io();o(PAe,"keys");Dr=PAe});var BAe,FAe,zAe,pa,NQ=R(()=>{"use strict";am();kd();q_();Io();tm();vc();BAe=Object.prototype,FAe=BAe.hasOwnProperty,zAe=p5(function(t,e){if(fc(e)||ei(e)){Bo(e,Dr(e),t);return}for(var r in e)FAe.call(e,r)&&dc(t,r,e[r])}),pa=zAe});function VAe(t,e){if(wt(t))return!1;var r=typeof t;return r=="number"||r=="symbol"||r=="boolean"||t==null||so(t)?!0:$Ae.test(t)||!GAe.test(t)||e!=null&&t in Object(e)}var GAe,$Ae,Tm,cw=R(()=>{"use strict";Bn();Nd();GAe=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,$Ae=/^\w*$/;o(VAe,"isKey");Tm=VAe});function HAe(t){var e=qp(t,function(n){return r.size===UAe&&r.clear(),n}),r=e.cache;return e}var UAe,MQ,IQ=R(()=>{"use strict";R_();UAe=500;o(HAe,"memoizeCapped");MQ=HAe});var YAe,WAe,qAe,OQ,PQ=R(()=>{"use strict";IQ();YAe=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,WAe=/\\(\\)?/g,qAe=MQ(function(t){var e=[];return t.charCodeAt(0)===46&&e.push(""),t.replace(YAe,function(r,n,i,a){e.push(i?a.replace(WAe,"$1"):n||r)}),e}),OQ=qAe});function XAe(t){return t==null?"":mQ(t)}var uw,oL=R(()=>{"use strict";gQ();o(XAe,"toString");uw=XAe});function jAe(t,e){return wt(t)?t:Tm(t,e)?[t]:OQ(uw(t))}var Hh,Lv=R(()=>{"use strict";Bn();cw();PQ();oL();o(jAe,"castPath");Hh=jAe});function QAe(t){if(typeof t=="string"||so(t))return t;var e=t+"";return e=="0"&&1/t==-KAe?"-0":e}var KAe,xc,km=R(()=>{"use strict";Nd();KAe=1/0;o(QAe,"toKey");xc=QAe});function ZAe(t,e){e=Hh(e,t);for(var r=0,n=e.length;t!=null&&r{"use strict";Lv();km();o(ZAe,"baseGet");Yh=ZAe});function JAe(t,e,r){var n=t==null?void 0:Yh(t,e);return n===void 0?r:n}var BQ,FQ=R(()=>{"use strict";Dv();o(JAe,"get");BQ=JAe});function e8e(t,e){for(var r=-1,n=e.length,i=t.length;++r{"use strict";o(e8e,"arrayPush");Em=e8e});function t8e(t){return wt(t)||kl(t)||!!(zQ&&t&&t[zQ])}var zQ,GQ,$Q=R(()=>{"use strict";vd();rm();Bn();zQ=Ji?Ji.isConcatSpreadable:void 0;o(t8e,"isFlattenable");GQ=t8e});function VQ(t,e,r,n,i){var a=-1,s=t.length;for(r||(r=GQ),i||(i=[]);++a0&&r(l)?e>1?VQ(l,e-1,r,n,i):Em(i,l):n||(i[i.length]=l)}return i}var bc,Cm=R(()=>{"use strict";hw();$Q();o(VQ,"baseFlatten");bc=VQ});function r8e(t){var e=t==null?0:t.length;return e?bc(t,1):[]}var Gr,fw=R(()=>{"use strict";Cm();o(r8e,"flatten");Gr=r8e});function n8e(t){return d5(f5(t,void 0,Gr),t+"")}var UQ,HQ=R(()=>{"use strict";fw();H_();W_();o(n8e,"flatRest");UQ=n8e});function i8e(t,e,r){var n=-1,i=t.length;e<0&&(e=-e>i?0:i+e),r=r>i?i:r,r<0&&(r+=i),i=e>r?0:r-e>>>0,e>>>=0;for(var a=Array(i);++n{"use strict";o(i8e,"baseSlice");dw=i8e});function d8e(t){return f8e.test(t)}var a8e,s8e,o8e,l8e,c8e,u8e,h8e,f8e,YQ,WQ=R(()=>{"use strict";a8e="\\ud800-\\udfff",s8e="\\u0300-\\u036f",o8e="\\ufe20-\\ufe2f",l8e="\\u20d0-\\u20ff",c8e=s8e+o8e+l8e,u8e="\\ufe0e\\ufe0f",h8e="\\u200d",f8e=RegExp("["+h8e+a8e+c8e+u8e+"]");o(d8e,"hasUnicode");YQ=d8e});function p8e(t,e,r,n){var i=-1,a=t==null?0:t.length;for(n&&a&&(r=t[++i]);++i{"use strict";o(p8e,"arrayReduce");qQ=p8e});function m8e(t,e){return t&&Bo(e,Dr(e),t)}var jQ,KQ=R(()=>{"use strict";kd();vc();o(m8e,"baseAssign");jQ=m8e});function g8e(t,e){return t&&Bo(e,bs(e),t)}var QQ,ZQ=R(()=>{"use strict";kd();zh();o(g8e,"baseAssignIn");QQ=g8e});function y8e(t,e){for(var r=-1,n=t==null?0:t.length,i=0,a=[];++r{"use strict";o(y8e,"arrayFilter");Sm=y8e});function v8e(){return[]}var mw,cL=R(()=>{"use strict";o(v8e,"stubArray");mw=v8e});var x8e,b8e,JQ,w8e,Am,gw=R(()=>{"use strict";pw();cL();x8e=Object.prototype,b8e=x8e.propertyIsEnumerable,JQ=Object.getOwnPropertySymbols,w8e=JQ?function(t){return t==null?[]:(t=Object(t),Sm(JQ(t),function(e){return b8e.call(t,e)}))}:mw,Am=w8e});function T8e(t,e){return Bo(t,Am(t),e)}var eZ,tZ=R(()=>{"use strict";kd();gw();o(T8e,"copySymbols");eZ=T8e});var k8e,E8e,yw,uL=R(()=>{"use strict";hw();o5();gw();cL();k8e=Object.getOwnPropertySymbols,E8e=k8e?function(t){for(var e=[];t;)Em(e,Am(t)),t=em(t);return e}:mw,yw=E8e});function C8e(t,e){return Bo(t,yw(t),e)}var rZ,nZ=R(()=>{"use strict";kd();uL();o(C8e,"copySymbolsIn");rZ=C8e});function S8e(t,e,r){var n=e(t);return wt(t)?n:Em(n,r(t))}var vw,hL=R(()=>{"use strict";hw();Bn();o(S8e,"baseGetAllKeys");vw=S8e});function A8e(t){return vw(t,Dr,Am)}var Rv,fL=R(()=>{"use strict";hL();gw();vc();o(A8e,"getAllKeys");Rv=A8e});function _8e(t){return vw(t,bs,yw)}var xw,dL=R(()=>{"use strict";hL();uL();zh();o(_8e,"getAllKeysIn");xw=_8e});var L8e,bw,iZ=R(()=>{"use strict";Nh();Ro();L8e=xs(Jn,"DataView"),bw=L8e});var D8e,ww,aZ=R(()=>{"use strict";Nh();Ro();D8e=xs(Jn,"Promise"),ww=D8e});var R8e,Wh,pL=R(()=>{"use strict";Nh();Ro();R8e=xs(Jn,"Set"),Wh=R8e});var sZ,N8e,oZ,lZ,cZ,uZ,M8e,I8e,O8e,P8e,B8e,Id,oo,Od=R(()=>{"use strict";iZ();J3();aZ();pL();CQ();wu();__();sZ="[object Map]",N8e="[object Object]",oZ="[object Promise]",lZ="[object Set]",cZ="[object WeakMap]",uZ="[object DataView]",M8e=Tu(bw),I8e=Tu(Oh),O8e=Tu(ww),P8e=Tu(Wh),B8e=Tu(nw),Id=fa;(bw&&Id(new bw(new ArrayBuffer(1)))!=uZ||Oh&&Id(new Oh)!=sZ||ww&&Id(ww.resolve())!=oZ||Wh&&Id(new Wh)!=lZ||nw&&Id(new nw)!=cZ)&&(Id=o(function(t){var e=fa(t),r=e==N8e?t.constructor:void 0,n=r?Tu(r):"";if(n)switch(n){case M8e:return uZ;case I8e:return sZ;case O8e:return oZ;case P8e:return lZ;case B8e:return cZ}return e},"getTag"));oo=Id});function G8e(t){var e=t.length,r=new t.constructor(e);return e&&typeof t[0]=="string"&&z8e.call(t,"index")&&(r.index=t.index,r.input=t.input),r}var F8e,z8e,hZ,fZ=R(()=>{"use strict";F8e=Object.prototype,z8e=F8e.hasOwnProperty;o(G8e,"initCloneArray");hZ=G8e});function $8e(t,e){var r=e?Jp(t.buffer):t.buffer;return new t.constructor(r,t.byteOffset,t.byteLength)}var dZ,pZ=R(()=>{"use strict";n5();o($8e,"cloneDataView");dZ=$8e});function U8e(t){var e=new t.constructor(t.source,V8e.exec(t));return e.lastIndex=t.lastIndex,e}var V8e,mZ,gZ=R(()=>{"use strict";V8e=/\w*$/;o(U8e,"cloneRegExp");mZ=U8e});function H8e(t){return vZ?Object(vZ.call(t)):{}}var yZ,vZ,xZ,bZ=R(()=>{"use strict";vd();yZ=Ji?Ji.prototype:void 0,vZ=yZ?yZ.valueOf:void 0;o(H8e,"cloneSymbol");xZ=H8e});function u_e(t,e,r){var n=t.constructor;switch(e){case J8e:return Jp(t);case Y8e:case W8e:return new n(+t);case e_e:return dZ(t,r);case t_e:case r_e:case n_e:case i_e:case a_e:case s_e:case o_e:case l_e:case c_e:return i5(t,r);case q8e:return new n;case X8e:case Q8e:return new n(t);case j8e:return mZ(t);case K8e:return new n;case Z8e:return xZ(t)}}var Y8e,W8e,q8e,X8e,j8e,K8e,Q8e,Z8e,J8e,e_e,t_e,r_e,n_e,i_e,a_e,s_e,o_e,l_e,c_e,wZ,TZ=R(()=>{"use strict";n5();pZ();gZ();bZ();P_();Y8e="[object Boolean]",W8e="[object Date]",q8e="[object Map]",X8e="[object Number]",j8e="[object RegExp]",K8e="[object Set]",Q8e="[object String]",Z8e="[object Symbol]",J8e="[object ArrayBuffer]",e_e="[object DataView]",t_e="[object Float32Array]",r_e="[object Float64Array]",n_e="[object Int8Array]",i_e="[object Int16Array]",a_e="[object Int32Array]",s_e="[object Uint8Array]",o_e="[object Uint8ClampedArray]",l_e="[object Uint16Array]",c_e="[object Uint32Array]";o(u_e,"initCloneByTag");wZ=u_e});function f_e(t){return Wn(t)&&oo(t)==h_e}var h_e,kZ,EZ=R(()=>{"use strict";Od();Mo();h_e="[object Map]";o(f_e,"baseIsMap");kZ=f_e});var CZ,d_e,SZ,AZ=R(()=>{"use strict";EZ();Td();ov();CZ=Po&&Po.isMap,d_e=CZ?Oo(CZ):kZ,SZ=d_e});function m_e(t){return Wn(t)&&oo(t)==p_e}var p_e,_Z,LZ=R(()=>{"use strict";Od();Mo();p_e="[object Set]";o(m_e,"baseIsSet");_Z=m_e});var DZ,g_e,RZ,NZ=R(()=>{"use strict";LZ();Td();ov();DZ=Po&&Po.isSet,g_e=DZ?Oo(DZ):_Z,RZ=g_e});function Tw(t,e,r,n,i,a){var s,l=e&y_e,u=e&v_e,h=e&x_e;if(r&&(s=i?r(t,n,i,a):r(t)),s!==void 0)return s;if(!pn(t))return t;var f=wt(t);if(f){if(s=hZ(t),!l)return a5(t,s)}else{var d=oo(t),p=d==IZ||d==E_e;if(El(t))return r5(t,l);if(d==OZ||d==MZ||p&&!i){if(s=u||p?{}:l5(t),!l)return u?rZ(t,QQ(s,t)):eZ(t,jQ(s,t))}else{if(!Cn[d])return i?t:{};s=wZ(t,d,l)}}a||(a=new uc);var m=a.get(t);if(m)return m;a.set(t,s),RZ(t)?t.forEach(function(v){s.add(Tw(v,e,r,v,t,a))}):SZ(t)&&t.forEach(function(v,x){s.set(x,Tw(v,e,r,x,t,a))});var g=h?u?xw:Rv:u?bs:Dr,y=f?void 0:g(t);return iw(y||t,function(v,x){y&&(x=v,v=t[x]),dc(s,x,Tw(v,e,r,x,t,a))}),s}var y_e,v_e,x_e,MZ,b_e,w_e,T_e,k_e,IZ,E_e,C_e,S_e,OZ,A_e,__e,L_e,D_e,R_e,N_e,M_e,I_e,O_e,P_e,B_e,F_e,z_e,G_e,$_e,V_e,Cn,kw,mL=R(()=>{"use strict";iv();iL();am();KQ();ZQ();I_();B_();tZ();nZ();fL();dL();Od();fZ();TZ();z_();Bn();im();AZ();Js();NZ();vc();zh();y_e=1,v_e=2,x_e=4,MZ="[object Arguments]",b_e="[object Array]",w_e="[object Boolean]",T_e="[object Date]",k_e="[object Error]",IZ="[object Function]",E_e="[object GeneratorFunction]",C_e="[object Map]",S_e="[object Number]",OZ="[object Object]",A_e="[object RegExp]",__e="[object Set]",L_e="[object String]",D_e="[object Symbol]",R_e="[object WeakMap]",N_e="[object ArrayBuffer]",M_e="[object DataView]",I_e="[object Float32Array]",O_e="[object Float64Array]",P_e="[object Int8Array]",B_e="[object Int16Array]",F_e="[object Int32Array]",z_e="[object Uint8Array]",G_e="[object Uint8ClampedArray]",$_e="[object Uint16Array]",V_e="[object Uint32Array]",Cn={};Cn[MZ]=Cn[b_e]=Cn[N_e]=Cn[M_e]=Cn[w_e]=Cn[T_e]=Cn[I_e]=Cn[O_e]=Cn[P_e]=Cn[B_e]=Cn[F_e]=Cn[C_e]=Cn[S_e]=Cn[OZ]=Cn[A_e]=Cn[__e]=Cn[L_e]=Cn[D_e]=Cn[z_e]=Cn[G_e]=Cn[$_e]=Cn[V_e]=!0;Cn[k_e]=Cn[IZ]=Cn[R_e]=!1;o(Tw,"baseClone");kw=Tw});function H_e(t){return kw(t,U_e)}var U_e,Qr,gL=R(()=>{"use strict";mL();U_e=4;o(H_e,"clone");Qr=H_e});function q_e(t){return kw(t,Y_e|W_e)}var Y_e,W_e,yL,PZ=R(()=>{"use strict";mL();Y_e=1,W_e=4;o(q_e,"cloneDeep");yL=q_e});function X_e(t){for(var e=-1,r=t==null?0:t.length,n=0,i=[];++e{"use strict";o(X_e,"compact");wc=X_e});function K_e(t){return this.__data__.set(t,j_e),this}var j_e,FZ,zZ=R(()=>{"use strict";j_e="__lodash_hash_undefined__";o(K_e,"setCacheAdd");FZ=K_e});function Q_e(t){return this.__data__.has(t)}var GZ,$Z=R(()=>{"use strict";o(Q_e,"setCacheHas");GZ=Q_e});function Ew(t){var e=-1,r=t==null?0:t.length;for(this.__data__=new bd;++e{"use strict";e5();zZ();$Z();o(Ew,"SetCache");Ew.prototype.add=Ew.prototype.push=FZ;Ew.prototype.has=GZ;_m=Ew});function Z_e(t,e){for(var r=-1,n=t==null?0:t.length;++r{"use strict";o(Z_e,"arraySome");Sw=Z_e});function J_e(t,e){return t.has(e)}var Lm,Aw=R(()=>{"use strict";o(J_e,"cacheHas");Lm=J_e});function r9e(t,e,r,n,i,a){var s=r&e9e,l=t.length,u=e.length;if(l!=u&&!(s&&u>l))return!1;var h=a.get(t),f=a.get(e);if(h&&f)return h==e&&f==t;var d=-1,p=!0,m=r&t9e?new _m:void 0;for(a.set(t,e),a.set(e,t);++d{"use strict";Cw();vL();Aw();e9e=1,t9e=2;o(r9e,"equalArrays");_w=r9e});function n9e(t){var e=-1,r=Array(t.size);return t.forEach(function(n,i){r[++e]=[i,n]}),r}var VZ,UZ=R(()=>{"use strict";o(n9e,"mapToArray");VZ=n9e});function i9e(t){var e=-1,r=Array(t.size);return t.forEach(function(n){r[++e]=n}),r}var Dm,Lw=R(()=>{"use strict";o(i9e,"setToArray");Dm=i9e});function v9e(t,e,r,n,i,a,s){switch(r){case y9e:if(t.byteLength!=e.byteLength||t.byteOffset!=e.byteOffset)return!1;t=t.buffer,e=e.buffer;case g9e:return!(t.byteLength!=e.byteLength||!a(new Zp(t),new Zp(e)));case o9e:case l9e:case h9e:return No(+t,+e);case c9e:return t.name==e.name&&t.message==e.message;case f9e:case p9e:return t==e+"";case u9e:var l=VZ;case d9e:var u=n&a9e;if(l||(l=Dm),t.size!=e.size&&!u)return!1;var h=s.get(t);if(h)return h==e;n|=s9e,s.set(t,e);var f=_w(l(t),l(e),n,i,a,s);return s.delete(t),f;case m9e:if(bL)return bL.call(t)==bL.call(e)}return!1}var a9e,s9e,o9e,l9e,c9e,u9e,h9e,f9e,d9e,p9e,m9e,g9e,y9e,HZ,bL,YZ,WZ=R(()=>{"use strict";vd();O_();xd();xL();UZ();Lw();a9e=1,s9e=2,o9e="[object Boolean]",l9e="[object Date]",c9e="[object Error]",u9e="[object Map]",h9e="[object Number]",f9e="[object RegExp]",d9e="[object Set]",p9e="[object String]",m9e="[object Symbol]",g9e="[object ArrayBuffer]",y9e="[object DataView]",HZ=Ji?Ji.prototype:void 0,bL=HZ?HZ.valueOf:void 0;o(v9e,"equalByTag");YZ=v9e});function T9e(t,e,r,n,i,a){var s=r&x9e,l=Rv(t),u=l.length,h=Rv(e),f=h.length;if(u!=f&&!s)return!1;for(var d=u;d--;){var p=l[d];if(!(s?p in e:w9e.call(e,p)))return!1}var m=a.get(t),g=a.get(e);if(m&&g)return m==e&&g==t;var y=!0;a.set(t,e),a.set(e,t);for(var v=s;++d{"use strict";fL();x9e=1,b9e=Object.prototype,w9e=b9e.hasOwnProperty;o(T9e,"equalObjects");qZ=T9e});function C9e(t,e,r,n,i,a){var s=wt(t),l=wt(e),u=s?KZ:oo(t),h=l?KZ:oo(e);u=u==jZ?Dw:u,h=h==jZ?Dw:h;var f=u==Dw,d=h==Dw,p=u==h;if(p&&El(t)){if(!El(e))return!1;s=!0,f=!1}if(p&&!f)return a||(a=new uc),s||Bh(t)?_w(t,e,r,n,i,a):YZ(t,e,u,r,n,i,a);if(!(r&k9e)){var m=f&&QZ.call(t,"__wrapped__"),g=d&&QZ.call(e,"__wrapped__");if(m||g){var y=m?t.value():t,v=g?e.value():e;return a||(a=new uc),i(y,v,r,n,a)}}return p?(a||(a=new uc),qZ(t,e,r,n,i,a)):!1}var k9e,jZ,KZ,Dw,E9e,QZ,ZZ,JZ=R(()=>{"use strict";iv();xL();WZ();XZ();Od();Bn();im();lv();k9e=1,jZ="[object Arguments]",KZ="[object Array]",Dw="[object Object]",E9e=Object.prototype,QZ=E9e.hasOwnProperty;o(C9e,"baseIsEqualDeep");ZZ=C9e});function eJ(t,e,r,n,i){return t===e?!0:t==null||e==null||!Wn(t)&&!Wn(e)?t!==t&&e!==e:ZZ(t,e,r,n,eJ,i)}var Rw,wL=R(()=>{"use strict";JZ();Mo();o(eJ,"baseIsEqual");Rw=eJ});function _9e(t,e,r,n){var i=r.length,a=i,s=!n;if(t==null)return!a;for(t=Object(t);i--;){var l=r[i];if(s&&l[2]?l[1]!==t[l[0]]:!(l[0]in t))return!1}for(;++i{"use strict";iv();wL();S9e=1,A9e=2;o(_9e,"baseIsMatch");tJ=_9e});function L9e(t){return t===t&&!pn(t)}var Nw,TL=R(()=>{"use strict";Js();o(L9e,"isStrictComparable");Nw=L9e});function D9e(t){for(var e=Dr(t),r=e.length;r--;){var n=e[r],i=t[n];e[r]=[n,i,Nw(i)]}return e}var nJ,iJ=R(()=>{"use strict";TL();vc();o(D9e,"getMatchData");nJ=D9e});function R9e(t,e){return function(r){return r==null?!1:r[t]===e&&(e!==void 0||t in Object(r))}}var Mw,kL=R(()=>{"use strict";o(R9e,"matchesStrictComparable");Mw=R9e});function N9e(t){var e=nJ(t);return e.length==1&&e[0][2]?Mw(e[0][0],e[0][1]):function(r){return r===t||tJ(r,t,e)}}var aJ,sJ=R(()=>{"use strict";rJ();iJ();kL();o(N9e,"baseMatches");aJ=N9e});function M9e(t,e){return t!=null&&e in Object(t)}var oJ,lJ=R(()=>{"use strict";o(M9e,"baseHasIn");oJ=M9e});function I9e(t,e,r){e=Hh(e,t);for(var n=-1,i=e.length,a=!1;++n{"use strict";Lv();rm();Bn();uv();c5();km();o(I9e,"hasPath");Iw=I9e});function O9e(t,e){return t!=null&&Iw(t,e,oJ)}var Ow,CL=R(()=>{"use strict";lJ();EL();o(O9e,"hasIn");Ow=O9e});function F9e(t,e){return Tm(t)&&Nw(e)?Mw(xc(t),e):function(r){var n=BQ(r,t);return n===void 0&&n===e?Ow(r,t):Rw(e,n,P9e|B9e)}}var P9e,B9e,cJ,uJ=R(()=>{"use strict";wL();FQ();CL();cw();TL();kL();km();P9e=1,B9e=2;o(F9e,"baseMatchesProperty");cJ=F9e});function z9e(t){return function(e){return e?.[t]}}var Pw,SL=R(()=>{"use strict";o(z9e,"baseProperty");Pw=z9e});function G9e(t){return function(e){return Yh(e,t)}}var hJ,fJ=R(()=>{"use strict";Dv();o(G9e,"basePropertyDeep");hJ=G9e});function $9e(t){return Tm(t)?Pw(xc(t)):hJ(t)}var dJ,pJ=R(()=>{"use strict";SL();fJ();cw();km();o($9e,"property");dJ=$9e});function V9e(t){return typeof t=="function"?t:t==null?ea:typeof t=="object"?wt(t)?cJ(t[0],t[1]):aJ(t):dJ(t)}var cn,Qa=R(()=>{"use strict";sJ();uJ();Eu();Bn();pJ();o(V9e,"baseIteratee");cn=V9e});function U9e(t,e,r,n){for(var i=-1,a=t==null?0:t.length;++i{"use strict";o(U9e,"arrayAggregator");mJ=U9e});function H9e(t,e){return t&&Qp(t,e,Dr)}var Rm,Bw=R(()=>{"use strict";t5();vc();o(H9e,"baseForOwn");Rm=H9e});function Y9e(t,e){return function(r,n){if(r==null)return r;if(!ei(r))return t(r,n);for(var i=r.length,a=e?i:-1,s=Object(r);(e?a--:++a{"use strict";Io();o(Y9e,"createBaseEach");yJ=Y9e});var W9e,As,qh=R(()=>{"use strict";Bw();vJ();W9e=yJ(Rm),As=W9e});function q9e(t,e,r,n){return As(t,function(i,a,s){e(n,i,r(i),s)}),n}var xJ,bJ=R(()=>{"use strict";qh();o(q9e,"baseAggregator");xJ=q9e});function X9e(t,e){return function(r,n){var i=wt(r)?mJ:xJ,a=e?e():{};return i(r,t,cn(n,2),a)}}var wJ,TJ=R(()=>{"use strict";gJ();bJ();Qa();Bn();o(X9e,"createAggregator");wJ=X9e});var j9e,Fw,kJ=R(()=>{"use strict";Ro();j9e=o(function(){return Jn.Date.now()},"now"),Fw=j9e});var EJ,K9e,Q9e,Xh,CJ=R(()=>{"use strict";sm();xd();Ed();zh();EJ=Object.prototype,K9e=EJ.hasOwnProperty,Q9e=pc(function(t,e){t=Object(t);var r=-1,n=e.length,i=n>2?e[2]:void 0;for(i&&eo(e[0],e[1],i)&&(n=1);++r{"use strict";o(Z9e,"arrayIncludesWith");zw=Z9e});function eLe(t,e,r,n){var i=-1,a=ow,s=!0,l=t.length,u=[],h=e.length;if(!l)return u;r&&(e=Ss(e,Oo(r))),n?(a=zw,s=!1):e.length>=J9e&&(a=Lm,s=!1,e=new _m(e));e:for(;++i{"use strict";Cw();sL();AL();Md();Td();Aw();J9e=200;o(eLe,"baseDifference");SJ=eLe});var tLe,jh,_J=R(()=>{"use strict";AJ();Cm();sm();u5();tLe=pc(function(t,e){return wd(t)?SJ(t,bc(e,1,wd,!0)):[]}),jh=tLe});function rLe(t){var e=t==null?0:t.length;return e?t[e-1]:void 0}var ma,LJ=R(()=>{"use strict";o(rLe,"last");ma=rLe});function nLe(t,e,r){var n=t==null?0:t.length;return n?(e=r||e===void 0?1:yc(e),dw(t,e<0?0:e,n)):[]}var fi,DJ=R(()=>{"use strict";lL();xm();o(nLe,"drop");fi=nLe});function iLe(t,e,r){var n=t==null?0:t.length;return n?(e=r||e===void 0?1:yc(e),e=n-e,dw(t,0,e<0?0:e)):[]}var Ru,RJ=R(()=>{"use strict";lL();xm();o(iLe,"dropRight");Ru=iLe});function aLe(t){return typeof t=="function"?t:ea}var Nm,Gw=R(()=>{"use strict";Eu();o(aLe,"castFunction");Nm=aLe});function sLe(t,e){var r=wt(t)?iw:As;return r(t,Nm(e))}var Ee,$w=R(()=>{"use strict";iL();qh();Gw();Bn();o(sLe,"forEach");Ee=sLe});var NJ=R(()=>{"use strict";$w()});function oLe(t,e){for(var r=-1,n=t==null?0:t.length;++r{"use strict";o(oLe,"arrayEvery");MJ=oLe});function lLe(t,e){var r=!0;return As(t,function(n,i,a){return r=!!e(n,i,a),r}),r}var OJ,PJ=R(()=>{"use strict";qh();o(lLe,"baseEvery");OJ=lLe});function cLe(t,e,r){var n=wt(t)?MJ:OJ;return r&&eo(t,e,r)&&(e=void 0),n(t,cn(e,3))}var Ia,BJ=R(()=>{"use strict";IJ();PJ();Qa();Bn();Ed();o(cLe,"every");Ia=cLe});function uLe(t,e){var r=[];return As(t,function(n,i,a){e(n,i,a)&&r.push(n)}),r}var Vw,_L=R(()=>{"use strict";qh();o(uLe,"baseFilter");Vw=uLe});function hLe(t,e){var r=wt(t)?Sm:Vw;return r(t,cn(e,3))}var $r,LL=R(()=>{"use strict";pw();_L();Qa();Bn();o(hLe,"filter");$r=hLe});function fLe(t){return function(e,r,n){var i=Object(e);if(!ei(e)){var a=cn(r,3);e=Dr(e),r=o(function(l){return a(i[l],l,i)},"predicate")}var s=t(e,r,n);return s>-1?i[a?e[s]:s]:void 0}}var FJ,zJ=R(()=>{"use strict";Qa();Io();vc();o(fLe,"createFind");FJ=fLe});function pLe(t,e,r){var n=t==null?0:t.length;if(!n)return-1;var i=r==null?0:yc(r);return i<0&&(i=dLe(n+i,0)),aw(t,cn(e,3),i)}var dLe,GJ,$J=R(()=>{"use strict";aL();Qa();xm();dLe=Math.max;o(pLe,"findIndex");GJ=pLe});var mLe,Za,VJ=R(()=>{"use strict";zJ();$J();mLe=FJ(GJ),Za=mLe});function gLe(t){return t&&t.length?t[0]:void 0}var na,UJ=R(()=>{"use strict";o(gLe,"head");na=gLe});var HJ=R(()=>{"use strict";UJ()});function yLe(t,e){var r=-1,n=ei(t)?Array(t.length):[];return As(t,function(i,a,s){n[++r]=e(i,a,s)}),n}var Uw,DL=R(()=>{"use strict";qh();Io();o(yLe,"baseMap");Uw=yLe});function vLe(t,e){var r=wt(t)?Ss:Uw;return r(t,cn(e,3))}var qe,Mm=R(()=>{"use strict";Md();Qa();DL();Bn();o(vLe,"map");qe=vLe});function xLe(t,e){return bc(qe(t,e),1)}var ga,RL=R(()=>{"use strict";Cm();Mm();o(xLe,"flatMap");ga=xLe});function bLe(t,e){return t==null?t:Qp(t,Nm(e),bs)}var NL,YJ=R(()=>{"use strict";t5();Gw();zh();o(bLe,"forIn");NL=bLe});function wLe(t,e){return t&&Rm(t,Nm(e))}var ML,WJ=R(()=>{"use strict";Bw();Gw();o(wLe,"forOwn");ML=wLe});var TLe,kLe,ELe,IL,qJ=R(()=>{"use strict";Kp();TJ();TLe=Object.prototype,kLe=TLe.hasOwnProperty,ELe=wJ(function(t,e,r){kLe.call(t,r)?t[r].push(e):hc(t,r,[e])}),IL=ELe});function CLe(t,e){return t>e}var XJ,jJ=R(()=>{"use strict";o(CLe,"baseGt");XJ=CLe});function _Le(t,e){return t!=null&&ALe.call(t,e)}var SLe,ALe,KJ,QJ=R(()=>{"use strict";SLe=Object.prototype,ALe=SLe.hasOwnProperty;o(_Le,"baseHas");KJ=_Le});function LLe(t,e){return t!=null&&Iw(t,e,KJ)}var Xe,ZJ=R(()=>{"use strict";QJ();EL();o(LLe,"has");Xe=LLe});function RLe(t){return typeof t=="string"||!wt(t)&&Wn(t)&&fa(t)==DLe}var DLe,di,Hw=R(()=>{"use strict";wu();Bn();Mo();DLe="[object String]";o(RLe,"isString");di=RLe});function NLe(t,e){return Ss(e,function(r){return t[r]})}var JJ,eee=R(()=>{"use strict";Md();o(NLe,"baseValues");JJ=NLe});function MLe(t){return t==null?[]:JJ(t,Dr(t))}var or,OL=R(()=>{"use strict";eee();vc();o(MLe,"values");or=MLe});function OLe(t,e,r,n){t=ei(t)?t:or(t),r=r&&!n?yc(r):0;var i=t.length;return r<0&&(r=ILe(i+r,0)),di(t)?r<=i&&t.indexOf(e,r)>-1:!!i&&bm(t,e,r)>-1}var ILe,Fn,tee=R(()=>{"use strict";sw();Io();Hw();xm();OL();ILe=Math.max;o(OLe,"includes");Fn=OLe});function BLe(t,e,r){var n=t==null?0:t.length;if(!n)return-1;var i=r==null?0:yc(r);return i<0&&(i=PLe(n+i,0)),bm(t,e,i)}var PLe,Yw,ree=R(()=>{"use strict";sw();xm();PLe=Math.max;o(BLe,"indexOf");Yw=BLe});function VLe(t){if(t==null)return!0;if(ei(t)&&(wt(t)||typeof t=="string"||typeof t.splice=="function"||El(t)||Bh(t)||kl(t)))return!t.length;var e=oo(t);if(e==FLe||e==zLe)return!t.size;if(fc(t))return!wm(t).length;for(var r in t)if($Le.call(t,r))return!1;return!0}var FLe,zLe,GLe,$Le,Qt,Ww=R(()=>{"use strict";lw();Od();rm();Bn();Io();im();tm();lv();FLe="[object Map]",zLe="[object Set]",GLe=Object.prototype,$Le=GLe.hasOwnProperty;o(VLe,"isEmpty");Qt=VLe});function HLe(t){return Wn(t)&&fa(t)==ULe}var ULe,nee,iee=R(()=>{"use strict";wu();Mo();ULe="[object RegExp]";o(HLe,"baseIsRegExp");nee=HLe});var aee,YLe,zo,see=R(()=>{"use strict";iee();Td();ov();aee=Po&&Po.isRegExp,YLe=aee?Oo(aee):nee,zo=YLe});function WLe(t){return t===void 0}var er,oee=R(()=>{"use strict";o(WLe,"isUndefined");er=WLe});function qLe(t,e){return t{"use strict";o(qLe,"baseLt");qw=qLe});function XLe(t,e){var r={};return e=cn(e,3),Rm(t,function(n,i,a){hc(r,i,e(n,i,a))}),r}var Pd,lee=R(()=>{"use strict";Kp();Bw();Qa();o(XLe,"mapValues");Pd=XLe});function jLe(t,e,r){for(var n=-1,i=t.length;++n{"use strict";Nd();o(jLe,"baseExtremum");Im=jLe});function KLe(t){return t&&t.length?Im(t,ea,XJ):void 0}var _s,cee=R(()=>{"use strict";Xw();jJ();Eu();o(KLe,"max");_s=KLe});function QLe(t){return t&&t.length?Im(t,ea,qw):void 0}var Ll,BL=R(()=>{"use strict";Xw();PL();Eu();o(QLe,"min");Ll=QLe});function ZLe(t,e){return t&&t.length?Im(t,cn(e,2),qw):void 0}var Bd,uee=R(()=>{"use strict";Xw();Qa();PL();o(ZLe,"minBy");Bd=ZLe});function eDe(t){if(typeof t!="function")throw new TypeError(JLe);return function(){var e=arguments;switch(e.length){case 0:return!t.call(this);case 1:return!t.call(this,e[0]);case 2:return!t.call(this,e[0],e[1]);case 3:return!t.call(this,e[0],e[1],e[2])}return!t.apply(this,e)}}var JLe,hee,fee=R(()=>{"use strict";JLe="Expected a function";o(eDe,"negate");hee=eDe});function tDe(t,e,r,n){if(!pn(t))return t;e=Hh(e,t);for(var i=-1,a=e.length,s=a-1,l=t;l!=null&&++i{"use strict";am();Lv();uv();Js();km();o(tDe,"baseSet");dee=tDe});function rDe(t,e,r){for(var n=-1,i=e.length,a={};++n{"use strict";Dv();pee();Lv();o(rDe,"basePickBy");jw=rDe});function nDe(t,e){if(t==null)return{};var r=Ss(xw(t),function(n){return[n]});return e=cn(e),jw(t,r,function(n,i){return e(n,i[0])})}var Ls,mee=R(()=>{"use strict";Md();Qa();FL();dL();o(nDe,"pickBy");Ls=nDe});function iDe(t,e){var r=t.length;for(t.sort(e);r--;)t[r]=t[r].value;return t}var gee,yee=R(()=>{"use strict";o(iDe,"baseSortBy");gee=iDe});function aDe(t,e){if(t!==e){var r=t!==void 0,n=t===null,i=t===t,a=so(t),s=e!==void 0,l=e===null,u=e===e,h=so(e);if(!l&&!h&&!a&&t>e||a&&s&&u&&!l&&!h||n&&s&&u||!r&&u||!i)return 1;if(!n&&!a&&!h&&t{"use strict";Nd();o(aDe,"compareAscending");vee=aDe});function sDe(t,e,r){for(var n=-1,i=t.criteria,a=e.criteria,s=i.length,l=r.length;++n=l)return u;var h=r[n];return u*(h=="desc"?-1:1)}}return t.index-e.index}var bee,wee=R(()=>{"use strict";xee();o(sDe,"compareMultiple");bee=sDe});function oDe(t,e,r){e.length?e=Ss(e,function(a){return wt(a)?function(s){return Yh(s,a.length===1?a[0]:a)}:a}):e=[ea];var n=-1;e=Ss(e,Oo(cn));var i=Uw(t,function(a,s,l){var u=Ss(e,function(h){return h(a)});return{criteria:u,index:++n,value:a}});return gee(i,function(a,s){return bee(a,s,r)})}var Tee,kee=R(()=>{"use strict";Md();Dv();Qa();DL();yee();Td();wee();Eu();Bn();o(oDe,"baseOrderBy");Tee=oDe});var lDe,Eee,Cee=R(()=>{"use strict";SL();lDe=Pw("length"),Eee=lDe});function bDe(t){for(var e=See.lastIndex=0;See.test(t);)++e;return e}var Aee,cDe,uDe,hDe,fDe,dDe,pDe,zL,GL,mDe,_ee,Lee,Dee,gDe,Ree,Nee,yDe,vDe,xDe,See,Mee,Iee=R(()=>{"use strict";Aee="\\ud800-\\udfff",cDe="\\u0300-\\u036f",uDe="\\ufe20-\\ufe2f",hDe="\\u20d0-\\u20ff",fDe=cDe+uDe+hDe,dDe="\\ufe0e\\ufe0f",pDe="["+Aee+"]",zL="["+fDe+"]",GL="\\ud83c[\\udffb-\\udfff]",mDe="(?:"+zL+"|"+GL+")",_ee="[^"+Aee+"]",Lee="(?:\\ud83c[\\udde6-\\uddff]){2}",Dee="[\\ud800-\\udbff][\\udc00-\\udfff]",gDe="\\u200d",Ree=mDe+"?",Nee="["+dDe+"]?",yDe="(?:"+gDe+"(?:"+[_ee,Lee,Dee].join("|")+")"+Nee+Ree+")*",vDe=Nee+Ree+yDe,xDe="(?:"+[_ee+zL+"?",zL,Lee,Dee,pDe].join("|")+")",See=RegExp(GL+"(?="+GL+")|"+xDe+vDe,"g");o(bDe,"unicodeSize");Mee=bDe});function wDe(t){return YQ(t)?Mee(t):Eee(t)}var Oee,Pee=R(()=>{"use strict";Cee();WQ();Iee();o(wDe,"stringSize");Oee=wDe});function TDe(t,e){return jw(t,e,function(r,n){return Ow(t,n)})}var Bee,Fee=R(()=>{"use strict";FL();CL();o(TDe,"basePick");Bee=TDe});var kDe,Fd,zee=R(()=>{"use strict";Fee();HQ();kDe=UQ(function(t,e){return t==null?{}:Bee(t,e)}),Fd=kDe});function SDe(t,e,r,n){for(var i=-1,a=CDe(EDe((e-t)/(r||1)),0),s=Array(a);a--;)s[n?a:++i]=t,t+=r;return s}var EDe,CDe,Gee,$ee=R(()=>{"use strict";EDe=Math.ceil,CDe=Math.max;o(SDe,"baseRange");Gee=SDe});function ADe(t){return function(e,r,n){return n&&typeof n!="number"&&eo(e,r,n)&&(r=n=void 0),e=vm(e),r===void 0?(r=e,e=0):r=vm(r),n=n===void 0?e{"use strict";$ee();Ed();rL();o(ADe,"createRange");Vee=ADe});var _De,Go,Hee=R(()=>{"use strict";Uee();_De=Vee(),Go=_De});function LDe(t,e,r,n,i){return i(t,function(a,s,l){r=n?(n=!1,a):e(r,a,s,l)}),r}var Yee,Wee=R(()=>{"use strict";o(LDe,"baseReduce");Yee=LDe});function DDe(t,e,r){var n=wt(t)?qQ:Yee,i=arguments.length<3;return n(t,cn(e,4),r,i,As)}var Vr,$L=R(()=>{"use strict";XQ();qh();Qa();Wee();Bn();o(DDe,"reduce");Vr=DDe});function RDe(t,e){var r=wt(t)?Sm:Vw;return r(t,hee(cn(e,3)))}var Kh,qee=R(()=>{"use strict";pw();_L();Qa();Bn();fee();o(RDe,"reject");Kh=RDe});function IDe(t){if(t==null)return 0;if(ei(t))return di(t)?Oee(t):t.length;var e=oo(t);return e==NDe||e==MDe?t.size:wm(t).length}var NDe,MDe,VL,Xee=R(()=>{"use strict";lw();Od();Io();Hw();Pee();NDe="[object Map]",MDe="[object Set]";o(IDe,"size");VL=IDe});function ODe(t,e){var r;return As(t,function(n,i,a){return r=e(n,i,a),!r}),!!r}var jee,Kee=R(()=>{"use strict";qh();o(ODe,"baseSome");jee=ODe});function PDe(t,e,r){var n=wt(t)?Sw:jee;return r&&eo(t,e,r)&&(e=void 0),n(t,cn(e,3))}var Nv,Qee=R(()=>{"use strict";vL();Qa();Kee();Bn();Ed();o(PDe,"some");Nv=PDe});var BDe,Tc,Zee=R(()=>{"use strict";Cm();kee();sm();Ed();BDe=pc(function(t,e){if(t==null)return[];var r=e.length;return r>1&&eo(t,e[0],e[1])?e=[]:r>2&&eo(e[0],e[1],e[2])&&(e=[e[0]]),Tee(t,bc(e,1),[])}),Tc=BDe});var FDe,zDe,Jee,ete=R(()=>{"use strict";pL();nL();Lw();FDe=1/0,zDe=Wh&&1/Dm(new Wh([,-0]))[1]==FDe?function(t){return new Wh(t)}:qn,Jee=zDe});function $De(t,e,r){var n=-1,i=ow,a=t.length,s=!0,l=[],u=l;if(r)s=!1,i=zw;else if(a>=GDe){var h=e?null:Jee(t);if(h)return Dm(h);s=!1,i=Lm,u=new _m}else u=e?[]:l;e:for(;++n{"use strict";Cw();sL();AL();Aw();ete();Lw();GDe=200;o($De,"baseUniq");Om=$De});var VDe,UL,tte=R(()=>{"use strict";Cm();sm();Kw();u5();VDe=pc(function(t){return Om(bc(t,1,wd,!0))}),UL=VDe});function UDe(t){return t&&t.length?Om(t):[]}var Pm,rte=R(()=>{"use strict";Kw();o(UDe,"uniq");Pm=UDe});function HDe(t,e){return t&&t.length?Om(t,cn(e,2)):[]}var nte,ite=R(()=>{"use strict";Qa();Kw();o(HDe,"uniqBy");nte=HDe});function WDe(t){var e=++YDe;return uw(t)+e}var YDe,zd,ate=R(()=>{"use strict";oL();YDe=0;o(WDe,"uniqueId");zd=WDe});function qDe(t,e,r){for(var n=-1,i=t.length,a=e.length,s={};++n{"use strict";o(qDe,"baseZipObject");ste=qDe});function XDe(t,e){return ste(t||[],e||[],dc)}var Qw,lte=R(()=>{"use strict";am();ote();o(XDe,"zipObject");Qw=XDe});var Pt=R(()=>{"use strict";NQ();gL();PZ();BZ();Y_();CJ();_J();DJ();RJ();NJ();BJ();LL();VJ();HJ();RL();fw();$w();YJ();WJ();qJ();ZJ();Eu();tee();ree();Bn();Ww();Jy();Js();see();Hw();oee();vc();LJ();Mm();lee();cee();X_();BL();uee();nL();kJ();zee();mee();Hee();$L();qee();Xee();Qee();Zee();tte();rte();ate();OL();lte();});function ute(t,e){t[e]?t[e]++:t[e]=1}function hte(t,e){--t[e]||delete t[e]}function Mv(t,e,r,n){var i=""+e,a=""+r;if(!t&&i>a){var s=i;i=a,a=s}return i+cte+a+cte+(er(n)?jDe:n)}function KDe(t,e,r,n){var i=""+e,a=""+r;if(!t&&i>a){var s=i;i=a,a=s}var l={v:i,w:a};return n&&(l.name=n),l}function HL(t,e){return Mv(t,e.v,e.w,e.name)}var jDe,Gd,cte,lr,Zw=R(()=>{"use strict";Pt();jDe="\0",Gd="\0",cte="",lr=class{static{o(this,"Graph")}constructor(e={}){this._isDirected=Xe(e,"directed")?e.directed:!0,this._isMultigraph=Xe(e,"multigraph")?e.multigraph:!1,this._isCompound=Xe(e,"compound")?e.compound:!1,this._label=void 0,this._defaultNodeLabelFn=ws(void 0),this._defaultEdgeLabelFn=ws(void 0),this._nodes={},this._isCompound&&(this._parent={},this._children={},this._children[Gd]={}),this._in={},this._preds={},this._out={},this._sucs={},this._edgeObjs={},this._edgeLabels={}}isDirected(){return this._isDirected}isMultigraph(){return this._isMultigraph}isCompound(){return this._isCompound}setGraph(e){return this._label=e,this}graph(){return this._label}setDefaultNodeLabel(e){return wi(e)||(e=ws(e)),this._defaultNodeLabelFn=e,this}nodeCount(){return this._nodeCount}nodes(){return Dr(this._nodes)}sources(){var e=this;return $r(this.nodes(),function(r){return Qt(e._in[r])})}sinks(){var e=this;return $r(this.nodes(),function(r){return Qt(e._out[r])})}setNodes(e,r){var n=arguments,i=this;return Ee(e,function(a){n.length>1?i.setNode(a,r):i.setNode(a)}),this}setNode(e,r){return Xe(this._nodes,e)?(arguments.length>1&&(this._nodes[e]=r),this):(this._nodes[e]=arguments.length>1?r:this._defaultNodeLabelFn(e),this._isCompound&&(this._parent[e]=Gd,this._children[e]={},this._children[Gd][e]=!0),this._in[e]={},this._preds[e]={},this._out[e]={},this._sucs[e]={},++this._nodeCount,this)}node(e){return this._nodes[e]}hasNode(e){return Xe(this._nodes,e)}removeNode(e){var r=this;if(Xe(this._nodes,e)){var n=o(function(i){r.removeEdge(r._edgeObjs[i])},"removeEdge");delete this._nodes[e],this._isCompound&&(this._removeFromParentsChildList(e),delete this._parent[e],Ee(this.children(e),function(i){r.setParent(i)}),delete this._children[e]),Ee(Dr(this._in[e]),n),delete this._in[e],delete this._preds[e],Ee(Dr(this._out[e]),n),delete this._out[e],delete this._sucs[e],--this._nodeCount}return this}setParent(e,r){if(!this._isCompound)throw new Error("Cannot set parent in a non-compound graph");if(er(r))r=Gd;else{r+="";for(var n=r;!er(n);n=this.parent(n))if(n===e)throw new Error("Setting "+r+" as parent of "+e+" would create a cycle");this.setNode(r)}return this.setNode(e),this._removeFromParentsChildList(e),this._parent[e]=r,this._children[r][e]=!0,this}_removeFromParentsChildList(e){delete this._children[this._parent[e]][e]}parent(e){if(this._isCompound){var r=this._parent[e];if(r!==Gd)return r}}children(e){if(er(e)&&(e=Gd),this._isCompound){var r=this._children[e];if(r)return Dr(r)}else{if(e===Gd)return this.nodes();if(this.hasNode(e))return[]}}predecessors(e){var r=this._preds[e];if(r)return Dr(r)}successors(e){var r=this._sucs[e];if(r)return Dr(r)}neighbors(e){var r=this.predecessors(e);if(r)return UL(r,this.successors(e))}isLeaf(e){var r;return this.isDirected()?r=this.successors(e):r=this.neighbors(e),r.length===0}filterNodes(e){var r=new this.constructor({directed:this._isDirected,multigraph:this._isMultigraph,compound:this._isCompound});r.setGraph(this.graph());var n=this;Ee(this._nodes,function(s,l){e(l)&&r.setNode(l,s)}),Ee(this._edgeObjs,function(s){r.hasNode(s.v)&&r.hasNode(s.w)&&r.setEdge(s,n.edge(s))});var i={};function a(s){var l=n.parent(s);return l===void 0||r.hasNode(l)?(i[s]=l,l):l in i?i[l]:a(l)}return o(a,"findParent"),this._isCompound&&Ee(r.nodes(),function(s){r.setParent(s,a(s))}),r}setDefaultEdgeLabel(e){return wi(e)||(e=ws(e)),this._defaultEdgeLabelFn=e,this}edgeCount(){return this._edgeCount}edges(){return or(this._edgeObjs)}setPath(e,r){var n=this,i=arguments;return Vr(e,function(a,s){return i.length>1?n.setEdge(a,s,r):n.setEdge(a,s),s}),this}setEdge(){var e,r,n,i,a=!1,s=arguments[0];typeof s=="object"&&s!==null&&"v"in s?(e=s.v,r=s.w,n=s.name,arguments.length===2&&(i=arguments[1],a=!0)):(e=s,r=arguments[1],n=arguments[3],arguments.length>2&&(i=arguments[2],a=!0)),e=""+e,r=""+r,er(n)||(n=""+n);var l=Mv(this._isDirected,e,r,n);if(Xe(this._edgeLabels,l))return a&&(this._edgeLabels[l]=i),this;if(!er(n)&&!this._isMultigraph)throw new Error("Cannot set a named edge when isMultigraph = false");this.setNode(e),this.setNode(r),this._edgeLabels[l]=a?i:this._defaultEdgeLabelFn(e,r,n);var u=KDe(this._isDirected,e,r,n);return e=u.v,r=u.w,Object.freeze(u),this._edgeObjs[l]=u,ute(this._preds[r],e),ute(this._sucs[e],r),this._in[r][l]=u,this._out[e][l]=u,this._edgeCount++,this}edge(e,r,n){var i=arguments.length===1?HL(this._isDirected,arguments[0]):Mv(this._isDirected,e,r,n);return this._edgeLabels[i]}hasEdge(e,r,n){var i=arguments.length===1?HL(this._isDirected,arguments[0]):Mv(this._isDirected,e,r,n);return Xe(this._edgeLabels,i)}removeEdge(e,r,n){var i=arguments.length===1?HL(this._isDirected,arguments[0]):Mv(this._isDirected,e,r,n),a=this._edgeObjs[i];return a&&(e=a.v,r=a.w,delete this._edgeLabels[i],delete this._edgeObjs[i],hte(this._preds[r],e),hte(this._sucs[e],r),delete this._in[r][i],delete this._out[e][i],this._edgeCount--),this}inEdges(e,r){var n=this._in[e];if(n){var i=or(n);return r?$r(i,function(a){return a.v===r}):i}}outEdges(e,r){var n=this._out[e];if(n){var i=or(n);return r?$r(i,function(a){return a.w===r}):i}}nodeEdges(e,r){var n=this.inEdges(e,r);if(n)return n.concat(this.outEdges(e,r))}};lr.prototype._nodeCount=0;lr.prototype._edgeCount=0;o(ute,"incrementOrInitEntry");o(hte,"decrementOrRemoveEntry");o(Mv,"edgeArgsToId");o(KDe,"edgeArgsToObj");o(HL,"edgeObjToId")});var ya=R(()=>{"use strict";Zw()});function fte(t){t._prev._next=t._next,t._next._prev=t._prev,delete t._next,delete t._prev}function QDe(t,e){if(t!=="_next"&&t!=="_prev")return e}var Jw,dte=R(()=>{"use strict";Jw=class{static{o(this,"List")}constructor(){var e={};e._next=e._prev=e,this._sentinel=e}dequeue(){var e=this._sentinel,r=e._prev;if(r!==e)return fte(r),r}enqueue(e){var r=this._sentinel;e._prev&&e._next&&fte(e),e._next=r._next,r._next._prev=e,r._next=e,e._prev=r}toString(){for(var e=[],r=this._sentinel,n=r._prev;n!==r;)e.push(JSON.stringify(n,QDe)),n=n._prev;return"["+e.join(", ")+"]"}};o(fte,"unlink");o(QDe,"filterOutLinks")});function pte(t,e){if(t.nodeCount()<=1)return[];var r=eRe(t,e||ZDe),n=JDe(r.graph,r.buckets,r.zeroIdx);return Gr(qe(n,function(i){return t.outEdges(i.v,i.w)}))}function JDe(t,e,r){for(var n=[],i=e[e.length-1],a=e[0],s;t.nodeCount();){for(;s=a.dequeue();)YL(t,e,r,s);for(;s=i.dequeue();)YL(t,e,r,s);if(t.nodeCount()){for(var l=e.length-2;l>0;--l)if(s=e[l].dequeue(),s){n=n.concat(YL(t,e,r,s,!0));break}}}return n}function YL(t,e,r,n,i){var a=i?[]:void 0;return Ee(t.inEdges(n.v),function(s){var l=t.edge(s),u=t.node(s.v);i&&a.push({v:s.v,w:s.w}),u.out-=l,WL(e,r,u)}),Ee(t.outEdges(n.v),function(s){var l=t.edge(s),u=s.w,h=t.node(u);h.in-=l,WL(e,r,h)}),t.removeNode(n.v),a}function eRe(t,e){var r=new lr,n=0,i=0;Ee(t.nodes(),function(l){r.setNode(l,{v:l,in:0,out:0})}),Ee(t.edges(),function(l){var u=r.edge(l.v,l.w)||0,h=e(l),f=u+h;r.setEdge(l.v,l.w,f),i=Math.max(i,r.node(l.v).out+=h),n=Math.max(n,r.node(l.w).in+=h)});var a=Go(i+n+3).map(function(){return new Jw}),s=n+1;return Ee(r.nodes(),function(l){WL(a,s,r.node(l))}),{graph:r,buckets:a,zeroIdx:s}}function WL(t,e,r){r.out?r.in?t[r.out-r.in+e].enqueue(r):t[t.length-1].enqueue(r):t[0].enqueue(r)}var ZDe,mte=R(()=>{"use strict";Pt();ya();dte();ZDe=ws(1);o(pte,"greedyFAS");o(JDe,"doGreedyFAS");o(YL,"removeNode");o(eRe,"buildState");o(WL,"assignBucket")});function gte(t){var e=t.graph().acyclicer==="greedy"?pte(t,r(t)):tRe(t);Ee(e,function(n){var i=t.edge(n);t.removeEdge(n),i.forwardName=n.name,i.reversed=!0,t.setEdge(n.w,n.v,i,zd("rev"))});function r(n){return function(i){return n.edge(i).weight}}o(r,"weightFn")}function tRe(t){var e=[],r={},n={};function i(a){Xe(n,a)||(n[a]=!0,r[a]=!0,Ee(t.outEdges(a),function(s){Xe(r,s.w)?e.push(s):i(s.w)}),delete r[a])}return o(i,"dfs"),Ee(t.nodes(),i),e}function yte(t){Ee(t.edges(),function(e){var r=t.edge(e);if(r.reversed){t.removeEdge(e);var n=r.forwardName;delete r.reversed,delete r.forwardName,t.setEdge(e.w,e.v,r,n)}})}var qL=R(()=>{"use strict";Pt();mte();o(gte,"run");o(tRe,"dfsFAS");o(yte,"undo")});function kc(t,e,r,n){var i;do i=zd(n);while(t.hasNode(i));return r.dummy=e,t.setNode(i,r),i}function xte(t){var e=new lr().setGraph(t.graph());return Ee(t.nodes(),function(r){e.setNode(r,t.node(r))}),Ee(t.edges(),function(r){var n=e.edge(r.v,r.w)||{weight:0,minlen:1},i=t.edge(r);e.setEdge(r.v,r.w,{weight:n.weight+i.weight,minlen:Math.max(n.minlen,i.minlen)})}),e}function eT(t){var e=new lr({multigraph:t.isMultigraph()}).setGraph(t.graph());return Ee(t.nodes(),function(r){t.children(r).length||e.setNode(r,t.node(r))}),Ee(t.edges(),function(r){e.setEdge(r,t.edge(r))}),e}function XL(t,e){var r=t.x,n=t.y,i=e.x-r,a=e.y-n,s=t.width/2,l=t.height/2;if(!i&&!a)throw new Error("Not possible to find intersection inside of the rectangle");var u,h;return Math.abs(a)*s>Math.abs(i)*l?(a<0&&(l=-l),u=l*i/a,h=l):(i<0&&(s=-s),u=s,h=s*a/i),{x:r+u,y:n+h}}function Qh(t){var e=qe(Go(KL(t)+1),function(){return[]});return Ee(t.nodes(),function(r){var n=t.node(r),i=n.rank;er(i)||(e[i][n.order]=r)}),e}function bte(t){var e=Ll(qe(t.nodes(),function(r){return t.node(r).rank}));Ee(t.nodes(),function(r){var n=t.node(r);Xe(n,"rank")&&(n.rank-=e)})}function wte(t){var e=Ll(qe(t.nodes(),function(a){return t.node(a).rank})),r=[];Ee(t.nodes(),function(a){var s=t.node(a).rank-e;r[s]||(r[s]=[]),r[s].push(a)});var n=0,i=t.graph().nodeRankFactor;Ee(r,function(a,s){er(a)&&s%i!==0?--n:n&&Ee(a,function(l){t.node(l).rank+=n})})}function jL(t,e,r,n){var i={width:0,height:0};return arguments.length>=4&&(i.rank=r,i.order=n),kc(t,"border",i,e)}function KL(t){return _s(qe(t.nodes(),function(e){var r=t.node(e).rank;if(!er(r))return r}))}function Tte(t,e){var r={lhs:[],rhs:[]};return Ee(t,function(n){e(n)?r.lhs.push(n):r.rhs.push(n)}),r}function kte(t,e){var r=Fw();try{return e()}finally{console.log(t+" time: "+(Fw()-r)+"ms")}}function Ete(t,e){return e()}var Ec=R(()=>{"use strict";Pt();ya();o(kc,"addDummyNode");o(xte,"simplify");o(eT,"asNonCompoundGraph");o(XL,"intersectRect");o(Qh,"buildLayerMatrix");o(bte,"normalizeRanks");o(wte,"removeEmptyRanks");o(jL,"addBorderNode");o(KL,"maxRank");o(Tte,"partition");o(kte,"time");o(Ete,"notime")});function Ste(t){function e(r){var n=t.children(r),i=t.node(r);if(n.length&&Ee(n,e),Xe(i,"minRank")){i.borderLeft=[],i.borderRight=[];for(var a=i.minRank,s=i.maxRank+1;a{"use strict";Pt();Ec();o(Ste,"addBorderSegments");o(Cte,"addBorderNode")});function Lte(t){var e=t.graph().rankdir.toLowerCase();(e==="lr"||e==="rl")&&Rte(t)}function Dte(t){var e=t.graph().rankdir.toLowerCase();(e==="bt"||e==="rl")&&rRe(t),(e==="lr"||e==="rl")&&(nRe(t),Rte(t))}function Rte(t){Ee(t.nodes(),function(e){_te(t.node(e))}),Ee(t.edges(),function(e){_te(t.edge(e))})}function _te(t){var e=t.width;t.width=t.height,t.height=e}function rRe(t){Ee(t.nodes(),function(e){QL(t.node(e))}),Ee(t.edges(),function(e){var r=t.edge(e);Ee(r.points,QL),Xe(r,"y")&&QL(r)})}function QL(t){t.y=-t.y}function nRe(t){Ee(t.nodes(),function(e){ZL(t.node(e))}),Ee(t.edges(),function(e){var r=t.edge(e);Ee(r.points,ZL),Xe(r,"x")&&ZL(r)})}function ZL(t){var e=t.x;t.x=t.y,t.y=e}var Nte=R(()=>{"use strict";Pt();o(Lte,"adjust");o(Dte,"undo");o(Rte,"swapWidthHeight");o(_te,"swapWidthHeightOne");o(rRe,"reverseY");o(QL,"reverseYOne");o(nRe,"swapXY");o(ZL,"swapXYOne")});function Mte(t){t.graph().dummyChains=[],Ee(t.edges(),function(e){aRe(t,e)})}function aRe(t,e){var r=e.v,n=t.node(r).rank,i=e.w,a=t.node(i).rank,s=e.name,l=t.edge(e),u=l.labelRank;if(a!==n+1){t.removeEdge(e);var h,f,d;for(d=0,++n;n{"use strict";Pt();Ec();o(Mte,"run");o(aRe,"normalizeEdge");o(Ite,"undo")});function Iv(t){var e={};function r(n){var i=t.node(n);if(Xe(e,n))return i.rank;e[n]=!0;var a=Ll(qe(t.outEdges(n),function(s){return r(s.w)-t.edge(s).minlen}));return(a===Number.POSITIVE_INFINITY||a===void 0||a===null)&&(a=0),i.rank=a}o(r,"dfs"),Ee(t.sources(),r)}function $d(t,e){return t.node(e.w).rank-t.node(e.v).rank-t.edge(e).minlen}var tT=R(()=>{"use strict";Pt();o(Iv,"longestPath");o($d,"slack")});function rT(t){var e=new lr({directed:!1}),r=t.nodes()[0],n=t.nodeCount();e.setNode(r,{});for(var i,a;sRe(e,t){"use strict";Pt();ya();tT();o(rT,"feasibleTree");o(sRe,"tightTree");o(oRe,"findMinSlackEdge");o(lRe,"shiftRanks")});var Pte=R(()=>{"use strict"});var tD=R(()=>{"use strict"});var tGt,rD=R(()=>{"use strict";Pt();tD();tGt=ws(1)});var Bte=R(()=>{"use strict";rD()});var nD=R(()=>{"use strict"});var Fte=R(()=>{"use strict";nD()});var fGt,zte=R(()=>{"use strict";Pt();fGt=ws(1)});function iD(t){var e={},r={},n=[];function i(a){if(Xe(r,a))throw new Ov;Xe(e,a)||(r[a]=!0,e[a]=!0,Ee(t.predecessors(a),i),delete r[a],n.push(a))}if(o(i,"visit"),Ee(t.sinks(),i),VL(e)!==t.nodeCount())throw new Ov;return n}function Ov(){}var aD=R(()=>{"use strict";Pt();iD.CycleException=Ov;o(iD,"topsort");o(Ov,"CycleException");Ov.prototype=new Error});var Gte=R(()=>{"use strict";aD()});function nT(t,e,r){wt(e)||(e=[e]);var n=(t.isDirected()?t.successors:t.neighbors).bind(t),i=[],a={};return Ee(e,function(s){if(!t.hasNode(s))throw new Error("Graph does not have node: "+s);$te(t,s,r==="post",a,n,i)}),i}function $te(t,e,r,n,i,a){Xe(n,e)||(n[e]=!0,r||a.push(e),Ee(i(e),function(s){$te(t,s,r,n,i,a)}),r&&a.push(e))}var sD=R(()=>{"use strict";Pt();o(nT,"dfs");o($te,"doDfs")});function oD(t,e){return nT(t,e,"post")}var Vte=R(()=>{"use strict";sD();o(oD,"postorder")});function lD(t,e){return nT(t,e,"pre")}var Ute=R(()=>{"use strict";sD();o(lD,"preorder")});var Hte=R(()=>{"use strict";tD();Zw()});var Yte=R(()=>{"use strict";Pte();rD();Bte();Fte();zte();Gte();Vte();Ute();Hte();nD();aD()});function Jh(t){t=xte(t),Iv(t);var e=rT(t);uD(e),cD(e,t);for(var r,n;r=jte(e);)n=Kte(e,t,r),Qte(e,t,r,n)}function cD(t,e){var r=oD(t,t.nodes());r=r.slice(0,r.length-1),Ee(r,function(n){dRe(t,e,n)})}function dRe(t,e,r){var n=t.node(r),i=n.parent;t.edge(r,i).cutvalue=qte(t,e,r)}function qte(t,e,r){var n=t.node(r),i=n.parent,a=!0,s=e.edge(r,i),l=0;return s||(a=!1,s=e.edge(i,r)),l=s.weight,Ee(e.nodeEdges(r),function(u){var h=u.v===r,f=h?u.w:u.v;if(f!==i){var d=h===a,p=e.edge(u).weight;if(l+=d?p:-p,mRe(t,r,f)){var m=t.edge(r,f).cutvalue;l+=d?-m:m}}}),l}function uD(t,e){arguments.length<2&&(e=t.nodes()[0]),Xte(t,{},1,e)}function Xte(t,e,r,n,i){var a=r,s=t.node(n);return e[n]=!0,Ee(t.neighbors(n),function(l){Xe(e,l)||(r=Xte(t,e,r,l,n))}),s.low=a,s.lim=r++,i?s.parent=i:delete s.parent,r}function jte(t){return Za(t.edges(),function(e){return t.edge(e).cutvalue<0})}function Kte(t,e,r){var n=r.v,i=r.w;e.hasEdge(n,i)||(n=r.w,i=r.v);var a=t.node(n),s=t.node(i),l=a,u=!1;a.lim>s.lim&&(l=s,u=!0);var h=$r(e.edges(),function(f){return u===Wte(t,t.node(f.v),l)&&u!==Wte(t,t.node(f.w),l)});return Bd(h,function(f){return $d(e,f)})}function Qte(t,e,r,n){var i=r.v,a=r.w;t.removeEdge(i,a),t.setEdge(n.v,n.w,{}),uD(t),cD(t,e),pRe(t,e)}function pRe(t,e){var r=Za(t.nodes(),function(i){return!e.node(i).parent}),n=lD(t,r);n=n.slice(1),Ee(n,function(i){var a=t.node(i).parent,s=e.edge(i,a),l=!1;s||(s=e.edge(a,i),l=!0),e.node(i).rank=e.node(a).rank+(l?s.minlen:-s.minlen)})}function mRe(t,e,r){return t.hasEdge(e,r)}function Wte(t,e,r){return r.low<=e.lim&&e.lim<=r.lim}var Zte=R(()=>{"use strict";Pt();Yte();Ec();eD();tT();Jh.initLowLimValues=uD;Jh.initCutValues=cD;Jh.calcCutValue=qte;Jh.leaveEdge=jte;Jh.enterEdge=Kte;Jh.exchangeEdges=Qte;o(Jh,"networkSimplex");o(cD,"initCutValues");o(dRe,"assignCutValue");o(qte,"calcCutValue");o(uD,"initLowLimValues");o(Xte,"dfsAssignLowLim");o(jte,"leaveEdge");o(Kte,"enterEdge");o(Qte,"exchangeEdges");o(pRe,"updateRanks");o(mRe,"isTreeEdge");o(Wte,"isDescendant")});function hD(t){switch(t.graph().ranker){case"network-simplex":Jte(t);break;case"tight-tree":yRe(t);break;case"longest-path":gRe(t);break;default:Jte(t)}}function yRe(t){Iv(t),rT(t)}function Jte(t){Jh(t)}var gRe,fD=R(()=>{"use strict";eD();Zte();tT();o(hD,"rank");gRe=Iv;o(yRe,"tightTreeRanker");o(Jte,"networkSimplexRanker")});function ere(t){var e=kc(t,"root",{},"_root"),r=vRe(t),n=_s(or(r))-1,i=2*n+1;t.graph().nestingRoot=e,Ee(t.edges(),function(s){t.edge(s).minlen*=i});var a=xRe(t)+1;Ee(t.children(),function(s){tre(t,e,i,a,n,r,s)}),t.graph().nodeRankFactor=i}function tre(t,e,r,n,i,a,s){var l=t.children(s);if(!l.length){s!==e&&t.setEdge(e,s,{weight:0,minlen:r});return}var u=jL(t,"_bt"),h=jL(t,"_bb"),f=t.node(s);t.setParent(u,s),f.borderTop=u,t.setParent(h,s),f.borderBottom=h,Ee(l,function(d){tre(t,e,r,n,i,a,d);var p=t.node(d),m=p.borderTop?p.borderTop:d,g=p.borderBottom?p.borderBottom:d,y=p.borderTop?n:2*n,v=m!==g?1:i-a[s]+1;t.setEdge(u,m,{weight:y,minlen:v,nestingEdge:!0}),t.setEdge(g,h,{weight:y,minlen:v,nestingEdge:!0})}),t.parent(s)||t.setEdge(e,u,{weight:0,minlen:i+a[s]})}function vRe(t){var e={};function r(n,i){var a=t.children(n);a&&a.length&&Ee(a,function(s){r(s,i+1)}),e[n]=i}return o(r,"dfs"),Ee(t.children(),function(n){r(n,1)}),e}function xRe(t){return Vr(t.edges(),function(e,r){return e+t.edge(r).weight},0)}function rre(t){var e=t.graph();t.removeNode(e.nestingRoot),delete e.nestingRoot,Ee(t.edges(),function(r){var n=t.edge(r);n.nestingEdge&&t.removeEdge(r)})}var nre=R(()=>{"use strict";Pt();Ec();o(ere,"run");o(tre,"dfs");o(vRe,"treeDepths");o(xRe,"sumWeights");o(rre,"cleanup")});function ire(t,e,r){var n={},i;Ee(r,function(a){for(var s=t.parent(a),l,u;s;){if(l=t.parent(s),l?(u=n[l],n[l]=s):(u=i,i=s),u&&u!==s){e.setEdge(u,s);return}s=l}})}var are=R(()=>{"use strict";Pt();o(ire,"addSubgraphConstraints")});function sre(t,e,r){var n=wRe(t),i=new lr({compound:!0}).setGraph({root:n}).setDefaultNodeLabel(function(a){return t.node(a)});return Ee(t.nodes(),function(a){var s=t.node(a),l=t.parent(a);(s.rank===e||s.minRank<=e&&e<=s.maxRank)&&(i.setNode(a),i.setParent(a,l||n),Ee(t[r](a),function(u){var h=u.v===a?u.w:u.v,f=i.edge(h,a),d=er(f)?0:f.weight;i.setEdge(h,a,{weight:t.edge(u).weight+d})}),Xe(s,"minRank")&&i.setNode(a,{borderLeft:s.borderLeft[e],borderRight:s.borderRight[e]}))}),i}function wRe(t){for(var e;t.hasNode(e=zd("_root")););return e}var ore=R(()=>{"use strict";Pt();ya();o(sre,"buildLayerGraph");o(wRe,"createRootNode")});function lre(t,e){for(var r=0,n=1;n0;)f%2&&(d+=l[f+1]),f=f-1>>1,l[f]+=h.weight;u+=h.weight*d})),u}var cre=R(()=>{"use strict";Pt();o(lre,"crossCount");o(TRe,"twoLayerCrossCount")});function ure(t){var e={},r=$r(t.nodes(),function(l){return!t.children(l).length}),n=_s(qe(r,function(l){return t.node(l).rank})),i=qe(Go(n+1),function(){return[]});function a(l){if(!Xe(e,l)){e[l]=!0;var u=t.node(l);i[u.rank].push(l),Ee(t.successors(l),a)}}o(a,"dfs");var s=Tc(r,function(l){return t.node(l).rank});return Ee(s,a),i}var hre=R(()=>{"use strict";Pt();o(ure,"initOrder")});function fre(t,e){return qe(e,function(r){var n=t.inEdges(r);if(n.length){var i=Vr(n,function(a,s){var l=t.edge(s),u=t.node(s.v);return{sum:a.sum+l.weight*u.order,weight:a.weight+l.weight}},{sum:0,weight:0});return{v:r,barycenter:i.sum/i.weight,weight:i.weight}}else return{v:r}})}var dre=R(()=>{"use strict";Pt();o(fre,"barycenter")});function pre(t,e){var r={};Ee(t,function(i,a){var s=r[i.v]={indegree:0,in:[],out:[],vs:[i.v],i:a};er(i.barycenter)||(s.barycenter=i.barycenter,s.weight=i.weight)}),Ee(e.edges(),function(i){var a=r[i.v],s=r[i.w];!er(a)&&!er(s)&&(s.indegree++,a.out.push(r[i.w]))});var n=$r(r,function(i){return!i.indegree});return kRe(n)}function kRe(t){var e=[];function r(a){return function(s){s.merged||(er(s.barycenter)||er(a.barycenter)||s.barycenter>=a.barycenter)&&ERe(a,s)}}o(r,"handleIn");function n(a){return function(s){s.in.push(a),--s.indegree===0&&t.push(s)}}for(o(n,"handleOut");t.length;){var i=t.pop();e.push(i),Ee(i.in.reverse(),r(i)),Ee(i.out,n(i))}return qe($r(e,function(a){return!a.merged}),function(a){return Fd(a,["vs","i","barycenter","weight"])})}function ERe(t,e){var r=0,n=0;t.weight&&(r+=t.barycenter*t.weight,n+=t.weight),e.weight&&(r+=e.barycenter*e.weight,n+=e.weight),t.vs=e.vs.concat(t.vs),t.barycenter=r/n,t.weight=n,t.i=Math.min(e.i,t.i),e.merged=!0}var mre=R(()=>{"use strict";Pt();o(pre,"resolveConflicts");o(kRe,"doResolveConflicts");o(ERe,"mergeEntries")});function yre(t,e){var r=Tte(t,function(f){return Xe(f,"barycenter")}),n=r.lhs,i=Tc(r.rhs,function(f){return-f.i}),a=[],s=0,l=0,u=0;n.sort(CRe(!!e)),u=gre(a,i,u),Ee(n,function(f){u+=f.vs.length,a.push(f.vs),s+=f.barycenter*f.weight,l+=f.weight,u=gre(a,i,u)});var h={vs:Gr(a)};return l&&(h.barycenter=s/l,h.weight=l),h}function gre(t,e,r){for(var n;e.length&&(n=ma(e)).i<=r;)e.pop(),t.push(n.vs),r++;return r}function CRe(t){return function(e,r){return e.barycenterr.barycenter?1:t?r.i-e.i:e.i-r.i}}var vre=R(()=>{"use strict";Pt();Ec();o(yre,"sort");o(gre,"consumeUnsortable");o(CRe,"compareWithBias")});function dD(t,e,r,n){var i=t.children(e),a=t.node(e),s=a?a.borderLeft:void 0,l=a?a.borderRight:void 0,u={};s&&(i=$r(i,function(g){return g!==s&&g!==l}));var h=fre(t,i);Ee(h,function(g){if(t.children(g.v).length){var y=dD(t,g.v,r,n);u[g.v]=y,Xe(y,"barycenter")&&ARe(g,y)}});var f=pre(h,r);SRe(f,u);var d=yre(f,n);if(s&&(d.vs=Gr([s,d.vs,l]),t.predecessors(s).length)){var p=t.node(t.predecessors(s)[0]),m=t.node(t.predecessors(l)[0]);Xe(d,"barycenter")||(d.barycenter=0,d.weight=0),d.barycenter=(d.barycenter*d.weight+p.order+m.order)/(d.weight+2),d.weight+=2}return d}function SRe(t,e){Ee(t,function(r){r.vs=Gr(r.vs.map(function(n){return e[n]?e[n].vs:n}))})}function ARe(t,e){er(t.barycenter)?(t.barycenter=e.barycenter,t.weight=e.weight):(t.barycenter=(t.barycenter*t.weight+e.barycenter*e.weight)/(t.weight+e.weight),t.weight+=e.weight)}var xre=R(()=>{"use strict";Pt();dre();mre();vre();o(dD,"sortSubgraph");o(SRe,"expandSubgraphs");o(ARe,"mergeBarycenters")});function Tre(t){var e=KL(t),r=bre(t,Go(1,e+1),"inEdges"),n=bre(t,Go(e-1,-1,-1),"outEdges"),i=ure(t);wre(t,i);for(var a=Number.POSITIVE_INFINITY,s,l=0,u=0;u<4;++l,++u){_Re(l%2?r:n,l%4>=2),i=Qh(t);var h=lre(t,i);h{"use strict";Pt();ya();Ec();are();ore();cre();hre();xre();o(Tre,"order");o(bre,"buildLayerGraphs");o(_Re,"sweepLayerGraphs");o(wre,"assignOrder")});function Ere(t){var e=DRe(t);Ee(t.graph().dummyChains,function(r){for(var n=t.node(r),i=n.edgeObj,a=LRe(t,e,i.v,i.w),s=a.path,l=a.lca,u=0,h=s[u],f=!0;r!==i.w;){if(n=t.node(r),f){for(;(h=s[u])!==l&&t.node(h).maxRanks||l>e[u].lim));for(h=u,u=n;(u=t.parent(u))!==h;)a.push(u);return{path:i.concat(a.reverse()),lca:h}}function DRe(t){var e={},r=0;function n(i){var a=r;Ee(t.children(i),n),e[i]={low:a,lim:r++}}return o(n,"dfs"),Ee(t.children(),n),e}var Cre=R(()=>{"use strict";Pt();o(Ere,"parentDummyChains");o(LRe,"findPath");o(DRe,"postorder")});function RRe(t,e){var r={};function n(i,a){var s=0,l=0,u=i.length,h=ma(a);return Ee(a,function(f,d){var p=MRe(t,f),m=p?t.node(p).order:u;(p||f===h)&&(Ee(a.slice(l,d+1),function(g){Ee(t.predecessors(g),function(y){var v=t.node(y),x=v.order;(xh)&&Sre(r,p,f)})})}o(n,"scan");function i(a,s){var l=-1,u,h=0;return Ee(s,function(f,d){if(t.node(f).dummy==="border"){var p=t.predecessors(f);p.length&&(u=t.node(p[0]).order,n(s,h,d,l,u),h=d,l=u)}n(s,h,s.length,u,a.length)}),s}return o(i,"visitLayer"),Vr(e,i),r}function MRe(t,e){if(t.node(e).dummy)return Za(t.predecessors(e),function(r){return t.node(r).dummy})}function Sre(t,e,r){if(e>r){var n=e;e=r,r=n}var i=t[e];i||(t[e]=i={}),i[r]=!0}function IRe(t,e,r){if(e>r){var n=e;e=r,r=n}return Xe(t[e],r)}function ORe(t,e,r,n){var i={},a={},s={};return Ee(e,function(l){Ee(l,function(u,h){i[u]=u,a[u]=u,s[u]=h})}),Ee(e,function(l){var u=-1;Ee(l,function(h){var f=n(h);if(f.length){f=Tc(f,function(y){return s[y]});for(var d=(f.length-1)/2,p=Math.floor(d),m=Math.ceil(d);p<=m;++p){var g=f[p];a[h]===h&&u{"use strict";Pt();ya();Ec();o(RRe,"findType1Conflicts");o(NRe,"findType2Conflicts");o(MRe,"findOtherInnerSegmentNode");o(Sre,"addConflict");o(IRe,"hasConflict");o(ORe,"verticalAlignment");o(PRe,"horizontalCompaction");o(BRe,"buildBlockGraph");o(FRe,"findSmallestWidthAlignment");o(zRe,"alignCoordinates");o(GRe,"balance");o(Are,"positionX");o($Re,"sep");o(VRe,"width")});function Lre(t){t=eT(t),URe(t),ML(Are(t),function(e,r){t.node(r).x=e})}function URe(t){var e=Qh(t),r=t.graph().ranksep,n=0;Ee(e,function(i){var a=_s(qe(i,function(s){return t.node(s).height}));Ee(i,function(s){t.node(s).y=n+a/2}),n+=a+r})}var Dre=R(()=>{"use strict";Pt();Ec();_re();o(Lre,"position");o(URe,"positionY")});function lo(t,e){var r=e&&e.debugTiming?kte:Ete;r("layout",function(){var n=r(" buildLayoutGraph",function(){return eNe(t)});r(" runLayout",function(){HRe(n,r)}),r(" updateInputGraph",function(){YRe(t,n)})})}function HRe(t,e){e(" makeSpaceForEdgeLabels",function(){tNe(t)}),e(" removeSelfEdges",function(){uNe(t)}),e(" acyclic",function(){gte(t)}),e(" nestingGraph.run",function(){ere(t)}),e(" rank",function(){hD(eT(t))}),e(" injectEdgeLabelProxies",function(){rNe(t)}),e(" removeEmptyRanks",function(){wte(t)}),e(" nestingGraph.cleanup",function(){rre(t)}),e(" normalizeRanks",function(){bte(t)}),e(" assignRankMinMax",function(){nNe(t)}),e(" removeEdgeLabelProxies",function(){iNe(t)}),e(" normalize.run",function(){Mte(t)}),e(" parentDummyChains",function(){Ere(t)}),e(" addBorderSegments",function(){Ste(t)}),e(" order",function(){Tre(t)}),e(" insertSelfEdges",function(){hNe(t)}),e(" adjustCoordinateSystem",function(){Lte(t)}),e(" position",function(){Lre(t)}),e(" positionSelfEdges",function(){fNe(t)}),e(" removeBorderNodes",function(){cNe(t)}),e(" normalize.undo",function(){Ite(t)}),e(" fixupEdgeLabelCoords",function(){oNe(t)}),e(" undoCoordinateSystem",function(){Dte(t)}),e(" translateGraph",function(){aNe(t)}),e(" assignNodeIntersects",function(){sNe(t)}),e(" reversePoints",function(){lNe(t)}),e(" acyclic.undo",function(){yte(t)})}function YRe(t,e){Ee(t.nodes(),function(r){var n=t.node(r),i=e.node(r);n&&(n.x=i.x,n.y=i.y,e.children(r).length&&(n.width=i.width,n.height=i.height))}),Ee(t.edges(),function(r){var n=t.edge(r),i=e.edge(r);n.points=i.points,Xe(i,"x")&&(n.x=i.x,n.y=i.y)}),t.graph().width=e.graph().width,t.graph().height=e.graph().height}function eNe(t){var e=new lr({multigraph:!0,compound:!0}),r=mD(t.graph());return e.setGraph(Gh({},qRe,pD(r,WRe),Fd(r,XRe))),Ee(t.nodes(),function(n){var i=mD(t.node(n));e.setNode(n,Xh(pD(i,jRe),KRe)),e.setParent(n,t.parent(n))}),Ee(t.edges(),function(n){var i=mD(t.edge(n));e.setEdge(n,Gh({},ZRe,pD(i,QRe),Fd(i,JRe)))}),e}function tNe(t){var e=t.graph();e.ranksep/=2,Ee(t.edges(),function(r){var n=t.edge(r);n.minlen*=2,n.labelpos.toLowerCase()!=="c"&&(e.rankdir==="TB"||e.rankdir==="BT"?n.width+=n.labeloffset:n.height+=n.labeloffset)})}function rNe(t){Ee(t.edges(),function(e){var r=t.edge(e);if(r.width&&r.height){var n=t.node(e.v),i=t.node(e.w),a={rank:(i.rank-n.rank)/2+n.rank,e};kc(t,"edge-proxy",a,"_ep")}})}function nNe(t){var e=0;Ee(t.nodes(),function(r){var n=t.node(r);n.borderTop&&(n.minRank=t.node(n.borderTop).rank,n.maxRank=t.node(n.borderBottom).rank,e=_s(e,n.maxRank))}),t.graph().maxRank=e}function iNe(t){Ee(t.nodes(),function(e){var r=t.node(e);r.dummy==="edge-proxy"&&(t.edge(r.e).labelRank=r.rank,t.removeNode(e))})}function aNe(t){var e=Number.POSITIVE_INFINITY,r=0,n=Number.POSITIVE_INFINITY,i=0,a=t.graph(),s=a.marginx||0,l=a.marginy||0;function u(h){var f=h.x,d=h.y,p=h.width,m=h.height;e=Math.min(e,f-p/2),r=Math.max(r,f+p/2),n=Math.min(n,d-m/2),i=Math.max(i,d+m/2)}o(u,"getExtremes"),Ee(t.nodes(),function(h){u(t.node(h))}),Ee(t.edges(),function(h){var f=t.edge(h);Xe(f,"x")&&u(f)}),e-=s,n-=l,Ee(t.nodes(),function(h){var f=t.node(h);f.x-=e,f.y-=n}),Ee(t.edges(),function(h){var f=t.edge(h);Ee(f.points,function(d){d.x-=e,d.y-=n}),Xe(f,"x")&&(f.x-=e),Xe(f,"y")&&(f.y-=n)}),a.width=r-e+s,a.height=i-n+l}function sNe(t){Ee(t.edges(),function(e){var r=t.edge(e),n=t.node(e.v),i=t.node(e.w),a,s;r.points?(a=r.points[0],s=r.points[r.points.length-1]):(r.points=[],a=i,s=n),r.points.unshift(XL(n,a)),r.points.push(XL(i,s))})}function oNe(t){Ee(t.edges(),function(e){var r=t.edge(e);if(Xe(r,"x"))switch((r.labelpos==="l"||r.labelpos==="r")&&(r.width-=r.labeloffset),r.labelpos){case"l":r.x-=r.width/2+r.labeloffset;break;case"r":r.x+=r.width/2+r.labeloffset;break}})}function lNe(t){Ee(t.edges(),function(e){var r=t.edge(e);r.reversed&&r.points.reverse()})}function cNe(t){Ee(t.nodes(),function(e){if(t.children(e).length){var r=t.node(e),n=t.node(r.borderTop),i=t.node(r.borderBottom),a=t.node(ma(r.borderLeft)),s=t.node(ma(r.borderRight));r.width=Math.abs(s.x-a.x),r.height=Math.abs(i.y-n.y),r.x=a.x+r.width/2,r.y=n.y+r.height/2}}),Ee(t.nodes(),function(e){t.node(e).dummy==="border"&&t.removeNode(e)})}function uNe(t){Ee(t.edges(),function(e){if(e.v===e.w){var r=t.node(e.v);r.selfEdges||(r.selfEdges=[]),r.selfEdges.push({e,label:t.edge(e)}),t.removeEdge(e)}})}function hNe(t){var e=Qh(t);Ee(e,function(r){var n=0;Ee(r,function(i,a){var s=t.node(i);s.order=a+n,Ee(s.selfEdges,function(l){kc(t,"selfedge",{width:l.label.width,height:l.label.height,rank:s.rank,order:a+ ++n,e:l.e,label:l.label},"_se")}),delete s.selfEdges})})}function fNe(t){Ee(t.nodes(),function(e){var r=t.node(e);if(r.dummy==="selfedge"){var n=t.node(r.e.v),i=n.x+n.width/2,a=n.y,s=r.x-i,l=n.height/2;t.setEdge(r.e,r.label),t.removeNode(e),r.label.points=[{x:i+2*s/3,y:a-l},{x:i+5*s/6,y:a-l},{x:i+s,y:a},{x:i+5*s/6,y:a+l},{x:i+2*s/3,y:a+l}],r.label.x=r.x,r.label.y=r.y}})}function pD(t,e){return Pd(Fd(t,e),Number)}function mD(t){var e={};return Ee(t,function(r,n){e[n.toLowerCase()]=r}),e}var WRe,qRe,XRe,jRe,KRe,QRe,ZRe,JRe,Rre=R(()=>{"use strict";Pt();ya();Ate();Nte();qL();JL();fD();nre();kre();Cre();Dre();Ec();o(lo,"layout");o(HRe,"runLayout");o(YRe,"updateInputGraph");WRe=["nodesep","edgesep","ranksep","marginx","marginy"],qRe={ranksep:50,edgesep:20,nodesep:50,rankdir:"tb"},XRe=["acyclicer","ranker","rankdir","align"],jRe=["width","height"],KRe={width:0,height:0},QRe=["minlen","weight","width","height","labeloffset"],ZRe={minlen:1,weight:1,width:0,height:0,labeloffset:10,labelpos:"r"},JRe=["labelpos"];o(eNe,"buildLayoutGraph");o(tNe,"makeSpaceForEdgeLabels");o(rNe,"injectEdgeLabelProxies");o(nNe,"assignRankMinMax");o(iNe,"removeEdgeLabelProxies");o(aNe,"translateGraph");o(sNe,"assignNodeIntersects");o(oNe,"fixupEdgeLabelCoords");o(lNe,"reversePointsForReversedEdges");o(cNe,"removeBorderNodes");o(uNe,"removeSelfEdges");o(hNe,"insertSelfEdges");o(fNe,"positionSelfEdges");o(pD,"selectNumberAttrs");o(mD,"canonicalize")});var Vd=R(()=>{"use strict";qL();Rre();JL();fD()});function zn(t){var e={options:{directed:t.isDirected(),multigraph:t.isMultigraph(),compound:t.isCompound()},nodes:dNe(t),edges:pNe(t)};return er(t.graph())||(e.value=Qr(t.graph())),e}function dNe(t){return qe(t.nodes(),function(e){var r=t.node(e),n=t.parent(e),i={v:e};return er(r)||(i.value=r),er(n)||(i.parent=n),i})}function pNe(t){return qe(t.edges(),function(e){var r=t.edge(e),n={v:e.v,w:e.w};return er(e.name)||(n.name=e.name),er(r)||(n.value=r),n})}var Pv=R(()=>{"use strict";Pt();Zw();o(zn,"write");o(dNe,"writeNodes");o(pNe,"writeEdges")});var cr,Ud,Mre,Ire,aT,mNe,Ore,Pre,gNe,Bm,Nre,Bre,Fre,zre,Gre,$re=R(()=>{"use strict";ut();ya();Pv();cr=new Map,Ud=new Map,Mre=new Map,Ire=o(()=>{Ud.clear(),Mre.clear(),cr.clear()},"clear"),aT=o((t,e)=>{let r=Ud.get(e)||[];return V.trace("In isDescendant",e," ",t," = ",r.includes(t)),r.includes(t)},"isDescendant"),mNe=o((t,e)=>{let r=Ud.get(e)||[];return V.info("Descendants of ",e," is ",r),V.info("Edge is ",t),t.v===e||t.w===e?!1:r?r.includes(t.v)||aT(t.v,e)||aT(t.w,e)||r.includes(t.w):(V.debug("Tilt, ",e,",not in descendants"),!1)},"edgeInCluster"),Ore=o((t,e,r,n)=>{V.warn("Copying children of ",t,"root",n,"data",e.node(t),n);let i=e.children(t)||[];t!==n&&i.push(t),V.warn("Copying (nodes) clusterId",t,"nodes",i),i.forEach(a=>{if(e.children(a).length>0)Ore(a,e,r,n);else{let s=e.node(a);V.info("cp ",a," to ",n," with parent ",t),r.setNode(a,s),n!==e.parent(a)&&(V.warn("Setting parent",a,e.parent(a)),r.setParent(a,e.parent(a))),t!==n&&a!==t?(V.debug("Setting parent",a,t),r.setParent(a,t)):(V.info("In copy ",t,"root",n,"data",e.node(t),n),V.debug("Not Setting parent for node=",a,"cluster!==rootId",t!==n,"node!==clusterId",a!==t));let l=e.edges(a);V.debug("Copying Edges",l),l.forEach(u=>{V.info("Edge",u);let h=e.edge(u.v,u.w,u.name);V.info("Edge data",h,n);try{mNe(u,n)?(V.info("Copying as ",u.v,u.w,h,u.name),r.setEdge(u.v,u.w,h,u.name),V.info("newGraph edges ",r.edges(),r.edge(r.edges()[0]))):V.info("Skipping copy of edge ",u.v,"-->",u.w," rootId: ",n," clusterId:",t)}catch(f){V.error(f)}})}V.debug("Removing node",a),e.removeNode(a)})},"copy"),Pre=o((t,e)=>{let r=e.children(t),n=[...r];for(let i of r)Mre.set(i,t),n=[...n,...Pre(i,e)];return n},"extractDescendants"),gNe=o((t,e,r)=>{let n=t.edges().filter(u=>u.v===e||u.w===e),i=t.edges().filter(u=>u.v===r||u.w===r),a=n.map(u=>({v:u.v===e?r:u.v,w:u.w===e?e:u.w})),s=i.map(u=>({v:u.v,w:u.w}));return a.filter(u=>s.some(h=>u.v===h.v&&u.w===h.w))},"findCommonEdges"),Bm=o((t,e,r)=>{let n=e.children(t);if(V.trace("Searching children of id ",t,n),n.length<1)return t;let i;for(let a of n){let s=Bm(a,e,r),l=gNe(e,r,s);if(s)if(l.length>0)i=s;else return s}return i},"findNonClusterChild"),Nre=o(t=>!cr.has(t)||!cr.get(t).externalConnections?t:cr.has(t)?cr.get(t).id:t,"getAnchorId"),Bre=o((t,e)=>{if(!t||e>10){V.debug("Opting out, no graph ");return}else V.debug("Opting in, graph ");t.nodes().forEach(function(r){t.children(r).length>0&&(V.warn("Cluster identified",r," Replacement id in edges: ",Bm(r,t,r)),Ud.set(r,Pre(r,t)),cr.set(r,{id:Bm(r,t,r),clusterData:t.node(r)}))}),t.nodes().forEach(function(r){let n=t.children(r),i=t.edges();n.length>0?(V.debug("Cluster identified",r,Ud),i.forEach(a=>{let s=aT(a.v,r),l=aT(a.w,r);s^l&&(V.warn("Edge: ",a," leaves cluster ",r),V.warn("Descendants of XXX ",r,": ",Ud.get(r)),cr.get(r).externalConnections=!0)})):V.debug("Not a cluster ",r,Ud)});for(let r of cr.keys()){let n=cr.get(r).id,i=t.parent(n);i!==r&&cr.has(i)&&!cr.get(i).externalConnections&&(cr.get(r).id=i)}t.edges().forEach(function(r){let n=t.edge(r);V.warn("Edge "+r.v+" -> "+r.w+": "+JSON.stringify(r)),V.warn("Edge "+r.v+" -> "+r.w+": "+JSON.stringify(t.edge(r)));let i=r.v,a=r.w;if(V.warn("Fix XXX",cr,"ids:",r.v,r.w,"Translating: ",cr.get(r.v)," --- ",cr.get(r.w)),cr.get(r.v)||cr.get(r.w)){if(V.warn("Fixing and trying - removing XXX",r.v,r.w,r.name),i=Nre(r.v),a=Nre(r.w),t.removeEdge(r.v,r.w,r.name),i!==r.v){let s=t.parent(i);cr.get(s).externalConnections=!0,n.fromCluster=r.v}if(a!==r.w){let s=t.parent(a);cr.get(s).externalConnections=!0,n.toCluster=r.w}V.warn("Fix Replacing with XXX",i,a,r.name),t.setEdge(i,a,n,r.name)}}),V.warn("Adjusted Graph",zn(t)),Fre(t,0),V.trace(cr)},"adjustClustersAndEdges"),Fre=o((t,e)=>{if(V.warn("extractor - ",e,zn(t),t.children("D")),e>10){V.error("Bailing out");return}let r=t.nodes(),n=!1;for(let i of r){let a=t.children(i);n=n||a.length>0}if(!n){V.debug("Done, no node has children",t.nodes());return}V.debug("Nodes = ",r,e);for(let i of r)if(V.debug("Extracting node",i,cr,cr.has(i)&&!cr.get(i).externalConnections,!t.parent(i),t.node(i),t.children("D")," Depth ",e),!cr.has(i))V.debug("Not a cluster",i,e);else if(!cr.get(i).externalConnections&&t.children(i)&&t.children(i).length>0){V.warn("Cluster without external connections, without a parent and with children",i,e);let s=t.graph().rankdir==="TB"?"LR":"TB";cr.get(i)?.clusterData?.dir&&(s=cr.get(i).clusterData.dir,V.warn("Fixing dir",cr.get(i).clusterData.dir,s));let l=new lr({multigraph:!0,compound:!0}).setGraph({rankdir:s,nodesep:50,ranksep:50,marginx:8,marginy:8}).setDefaultEdgeLabel(function(){return{}});V.warn("Old graph before copy",zn(t)),Ore(i,t,l,i),t.setNode(i,{clusterNode:!0,id:i,clusterData:cr.get(i).clusterData,label:cr.get(i).label,graph:l}),V.warn("New graph after copy node: (",i,")",zn(l)),V.debug("Old graph after copy",zn(t))}else V.warn("Cluster ** ",i," **not meeting the criteria !externalConnections:",!cr.get(i).externalConnections," no parent: ",!t.parent(i)," children ",t.children(i)&&t.children(i).length>0,t.children("D"),e),V.debug(cr);r=t.nodes(),V.warn("New list of nodes",r);for(let i of r){let a=t.node(i);V.warn(" Now next level",i,a),a?.clusterNode&&Fre(a.graph,e+1)}},"extractor"),zre=o((t,e)=>{if(e.length===0)return[];let r=Object.assign([],e);return e.forEach(n=>{let i=t.children(n),a=zre(t,i);r=[...r,...a]}),r},"sorter"),Gre=o(t=>zre(t,t.children()),"sortNodesByHierarchy")});var Ure={};hr(Ure,{render:()=>yNe});var Vre,yNe,Hre=R(()=>{"use strict";Vd();Pv();ya();Q9();ri();$re();tL();X9();K9();ut();_d();_t();Vre=o(async(t,e,r,n,i,a)=>{V.warn("Graph in recursive render:XAX",zn(e),i);let s=e.graph().rankdir;V.trace("Dir in recursive render - dir:",s);let l=t.insert("g").attr("class","root");e.nodes()?V.info("Recursive render XXX",e.nodes()):V.info("No nodes found for",e),e.edges().length>0&&V.info("Recursive edges",e.edge(e.edges()[0]));let u=l.insert("g").attr("class","clusters"),h=l.insert("g").attr("class","edgePaths"),f=l.insert("g").attr("class","edgeLabels"),d=l.insert("g").attr("class","nodes");await Promise.all(e.nodes().map(async function(y){let v=e.node(y);if(i!==void 0){let x=JSON.parse(JSON.stringify(i.clusterData));V.trace(`Setting data for parent cluster XXX + Node.id = `,y,` + data=`,x.height,` +Parent cluster`,i.height),e.setNode(i.id,x),e.parent(y)||(V.trace("Setting parent",y,i.id),e.setParent(y,i.id,x))}if(V.info("(Insert) Node XXX"+y+": "+JSON.stringify(e.node(y))),v?.clusterNode){V.info("Cluster identified XBX",y,v.width,e.node(y));let{ranksep:x,nodesep:b}=e.graph();v.graph.setGraph({...v.graph.graph(),ranksep:x+25,nodesep:b});let w=await Vre(d,v.graph,r,n,e.node(y),a),S=w.elem;ar(v,S),v.diff=w.diff||0,V.info("New compound node after recursive render XAX",y,"width",v.width,"height",v.height),lQ(S,v)}else e.children(y).length>0?(V.trace("Cluster - the non recursive path XBX",y,v.id,v,v.width,"Graph:",e),V.trace(Bm(v.id,e)),cr.set(v.id,{id:Bm(v.id,e),node:v})):(V.trace("Node - the non recursive path XAX",y,d,e.node(y),s),await rw(d,e.node(y),s))})),await o(async()=>{let y=e.edges().map(async function(v){let x=e.edge(v.v,v.w,v.name);V.info("Edge "+v.v+" -> "+v.w+": "+JSON.stringify(v)),V.info("Edge "+v.v+" -> "+v.w+": ",v," ",JSON.stringify(e.edge(v))),V.info("Fix",cr,"ids:",v.v,v.w,"Translating: ",cr.get(v.v),cr.get(v.w)),await Q5(f,x)});await Promise.all(y)},"processEdges")(),V.info("Graph before layout:",JSON.stringify(zn(e))),V.info("############################################# XXX"),V.info("### Layout ### XXX"),V.info("############################################# XXX"),lo(e),V.info("Graph after layout:",JSON.stringify(zn(e)));let m=0,{subGraphTitleTotalMargin:g}=io(a);return await Promise.all(Gre(e).map(async function(y){let v=e.node(y);if(V.info("Position XBX => "+y+": ("+v.x,","+v.y,") width: ",v.width," height: ",v.height),v?.clusterNode)v.y+=g,V.info("A tainted cluster node XBX1",y,v.id,v.width,v.height,v.x,v.y,e.parent(y)),cr.get(v.id).node=v,eL(v);else if(e.children(y).length>0){V.info("A pure cluster node XBX1",y,v.id,v.x,v.y,v.width,v.height,e.parent(y)),v.height+=g,e.node(v.parentId);let x=v?.padding/2||0,b=v?.labelBBox?.height||0,w=b-x||0;V.debug("OffsetY",w,"labelHeight",b,"halfPadding",x),await Y5(u,v),cr.get(v.id).node=v}else{let x=e.node(v.parentId);v.y+=g/2,V.info("A regular node XBX1 - using the padding",v.id,"parent",v.parentId,v.width,v.height,v.x,v.y,"offsetY",v.offsetY,"parent",x,x?.offsetY,v),eL(v)}})),e.edges().forEach(function(y){let v=e.edge(y);V.info("Edge "+y.v+" -> "+y.w+": "+JSON.stringify(v),v),v.points.forEach(S=>S.y+=g/2);let x=e.node(y.v);var b=e.node(y.w);let w=J5(h,v,cr,r,x,b,n);Z5(v,w)}),e.nodes().forEach(function(y){let v=e.node(y);V.info(y,v.type,v.diff),v.isGroup&&(m=v.diff)}),V.warn("Returning from recursive render XAX",l,m),{elem:l,diff:m}},"recursiveRender"),yNe=o(async(t,e)=>{let r=new lr({multigraph:!0,compound:!0}).setGraph({rankdir:t.direction,nodesep:t.config?.nodeSpacing||t.config?.flowchart?.nodeSpacing||t.nodeSpacing,ranksep:t.config?.rankSpacing||t.config?.flowchart?.rankSpacing||t.rankSpacing,marginx:8,marginy:8}).setDefaultEdgeLabel(function(){return{}}),n=e.select("g");ew(n,t.markers,t.type,t.diagramId),cQ(),lK(),rK(),Ire(),t.nodes.forEach(a=>{r.setNode(a.id,{...a}),a.parentId&&r.setParent(a.id,a.parentId)}),V.debug("Edges:",t.edges),t.edges.forEach(a=>{if(a.start===a.end){let s=a.start,l=s+"---"+s+"---1",u=s+"---"+s+"---2",h=r.node(s);r.setNode(l,{domId:l,id:l,parentId:h.parentId,labelStyle:"",label:"",padding:0,shape:"labelRect",style:"",width:10,height:10}),r.setParent(l,h.parentId),r.setNode(u,{domId:u,id:u,parentId:h.parentId,labelStyle:"",padding:0,shape:"labelRect",label:"",style:"",width:10,height:10}),r.setParent(u,h.parentId);let f=structuredClone(a),d=structuredClone(a),p=structuredClone(a);f.label="",f.arrowTypeEnd="none",f.id=s+"-cyclic-special-1",d.arrowTypeEnd="none",d.id=s+"-cyclic-special-mid",p.label="",h.isGroup&&(f.fromCluster=s,p.toCluster=s),p.id=s+"-cyclic-special-2",r.setEdge(s,l,f,s+"-cyclic-special-0"),r.setEdge(l,u,d,s+"-cyclic-special-1"),r.setEdge(u,s,p,s+"-cyc{"use strict";hQ();ut();Bv={},gD=o(t=>{for(let e of t)Bv[e.name]=e},"registerLayoutLoaders"),vNe=o(()=>{gD([{name:"dagre",loader:o(async()=>await Promise.resolve().then(()=>(Hre(),Ure)),"loader")}])},"registerDefaultLayoutLoaders");vNe();sT=o(async(t,e)=>{if(!(t.layoutAlgorithm in Bv))throw new Error(`Unknown layout algorithm: ${t.layoutAlgorithm}`);let r=Bv[t.layoutAlgorithm];return(await r.loader()).render(t,e,uQ,{algorithm:r.algorithm})},"render"),Yre=o((t="",{fallback:e="dagre"}={})=>{if(t in Bv)return t;if(e in Bv)return V.warn(`Layout algorithm ${t} is not registered. Using ${e} as fallback.`),e;throw new Error(`Both layout algorithms ${t} and ${e} are not registered.`)},"getRegisteredLayoutAlgorithm")});var lT,xNe,bNe,yD=R(()=>{"use strict";Yn();ut();lT=o((t,e,r,n)=>{t.attr("class",r);let{width:i,height:a,x:s,y:l}=xNe(t,e);Sr(t,a,i,n);let u=bNe(s,l,i,a,e);t.attr("viewBox",u),V.debug(`viewBox configured: ${u} with padding: ${e}`)},"setupViewPortForSVG"),xNe=o((t,e)=>{let r=t.node()?.getBBox()||{width:0,height:0,x:0,y:0};return{width:r.width+e*2,height:r.height+e*2,x:r.x,y:r.y}},"calculateDimensionsWithPadding"),bNe=o((t,e,r,n,i)=>`${t-i} ${e-i} ${r} ${n}`,"createViewBox")});var wNe,TNe,Wre,qre=R(()=>{"use strict";Zt();_t();ut();L9();oT();yD();xr();f9();wNe=o(function(t,e){return e.db.getClasses()},"getClasses"),TNe=o(async function(t,e,r,n){V.info("REF0:"),V.info("Drawing state diagram (v2)",e);let{securityLevel:i,flowchart:a,layout:s}=de(),l;i==="sandbox"&&(l=$e("#i"+e));let u=i==="sandbox"?l.nodes()[0].contentDocument:document;V.debug("Before getData: ");let h=n.db.getData();V.debug("Data: ",h);let f=I5(e,i),d=h9();h.type=n.type,h.layoutAlgorithm=Yre(s),h.layoutAlgorithm==="dagre"&&s==="elk"&&V.warn("flowchart-elk was moved to an external package in Mermaid v11. Please refer [release notes](https://github.com/mermaid-js/mermaid/releases/tag/v11.0.0) for more details. This diagram will be rendered using `dagre` layout as a fallback."),h.direction=d,h.nodeSpacing=a?.nodeSpacing||50,h.rankSpacing=a?.rankSpacing||50,h.markers=["point","circle","cross"],h.diagramId=e,V.debug("REF1:",h),await sT(h,f);let p=h.config.flowchart?.diagramPadding??8;Lt.insertTitle(f,"flowchartTitleText",a?.titleTopMargin||0,n.db.getDiagramTitle()),lT(f,p,"flowchart",a?.useMaxWidth||!1);for(let m of h.nodes){let g=$e(`#${e} [id="${m.id}"]`);if(!g||!m.link)continue;let y=u.createElementNS("http://www.w3.org/2000/svg","a");y.setAttributeNS("http://www.w3.org/2000/svg","class",m.cssClasses),y.setAttributeNS("http://www.w3.org/2000/svg","rel","noopener"),i==="sandbox"?y.setAttributeNS("http://www.w3.org/2000/svg","target","_top"):m.linkTarget&&y.setAttributeNS("http://www.w3.org/2000/svg","target",m.linkTarget);let v=g.insert(function(){return y},":first-child"),x=g.select(".label-container");x&&v.append(function(){return x.node()});let b=g.select(".label");b&&v.append(function(){return b.node()})}},"draw"),Wre={getClasses:wNe,draw:TNe}});var vD,Xre,jre=R(()=>{"use strict";vD=function(){var t=o(function(qi,ht,At,$t){for(At=At||{},$t=qi.length;$t--;At[qi[$t]]=ht);return At},"o"),e=[1,4],r=[1,3],n=[1,5],i=[1,8,9,10,11,27,34,36,38,42,58,81,82,83,84,85,86,99,102,103,106,108,111,112,113,118,119,120,121],a=[2,2],s=[1,13],l=[1,14],u=[1,15],h=[1,16],f=[1,23],d=[1,25],p=[1,26],m=[1,27],g=[1,49],y=[1,48],v=[1,29],x=[1,30],b=[1,31],w=[1,32],S=[1,33],T=[1,44],E=[1,46],_=[1,42],A=[1,47],L=[1,43],M=[1,50],N=[1,45],k=[1,51],I=[1,52],C=[1,34],O=[1,35],D=[1,36],P=[1,37],F=[1,57],B=[1,8,9,10,11,27,32,34,36,38,42,58,81,82,83,84,85,86,99,102,103,106,108,111,112,113,118,119,120,121],$=[1,61],z=[1,60],Y=[1,62],Q=[8,9,11,73,75],X=[1,88],ie=[1,93],j=[1,92],J=[1,89],Z=[1,85],H=[1,91],q=[1,87],K=[1,94],se=[1,90],ce=[1,95],ue=[1,86],te=[8,9,10,11,73,75],De=[8,9,10,11,44,73,75],oe=[8,9,10,11,29,42,44,46,48,50,52,54,56,58,61,63,65,66,68,73,75,86,99,102,103,106,108,111,112,113],ke=[8,9,11,42,58,73,75,86,99,102,103,106,108,111,112,113],Ie=[42,58,86,99,102,103,106,108,111,112,113],Se=[1,121],Ue=[1,120],Pe=[1,128],_e=[1,142],me=[1,143],W=[1,144],fe=[1,145],ge=[1,130],re=[1,132],he=[1,136],ne=[1,137],ae=[1,138],we=[1,139],Te=[1,140],Ce=[1,141],Ae=[1,146],Ge=[1,147],Me=[1,126],ye=[1,127],He=[1,134],ze=[1,129],Ze=[1,133],gt=[1,131],yt=[8,9,10,11,27,32,34,36,38,42,58,81,82,83,84,85,86,99,102,103,106,108,111,112,113,118,119,120,121],tt=[1,149],Ye=[8,9,11],Je=[8,9,10,11,14,42,58,86,102,103,106,108,111,112,113],Ve=[1,169],je=[1,165],kt=[1,166],at=[1,170],xt=[1,167],it=[1,168],dt=[75,113,116],lt=[8,9,10,11,12,14,27,29,32,42,58,73,81,82,83,84,85,86,87,102,106,108,111,112,113],It=[10,103],mt=[31,47,49,51,53,55,60,62,64,65,67,69,113,114,115],St=[1,235],gr=[1,233],xn=[1,237],jt=[1,231],rn=[1,232],Er=[1,234],Kn=[1,236],hn=[1,238],Qn=[1,255],on=[8,9,11,103],Rn=[8,9,10,11,58,81,102,103,106,107,108,109],Ha={trace:o(function(){},"trace"),yy:{},symbols_:{error:2,start:3,graphConfig:4,document:5,line:6,statement:7,SEMI:8,NEWLINE:9,SPACE:10,EOF:11,GRAPH:12,NODIR:13,DIR:14,FirstStmtSeparator:15,ending:16,endToken:17,spaceList:18,spaceListNewline:19,vertexStatement:20,separator:21,styleStatement:22,linkStyleStatement:23,classDefStatement:24,classStatement:25,clickStatement:26,subgraph:27,textNoTags:28,SQS:29,text:30,SQE:31,end:32,direction:33,acc_title:34,acc_title_value:35,acc_descr:36,acc_descr_value:37,acc_descr_multiline_value:38,link:39,node:40,styledVertex:41,AMP:42,vertex:43,STYLE_SEPARATOR:44,idString:45,DOUBLECIRCLESTART:46,DOUBLECIRCLEEND:47,PS:48,PE:49,"(-":50,"-)":51,STADIUMSTART:52,STADIUMEND:53,SUBROUTINESTART:54,SUBROUTINEEND:55,VERTEX_WITH_PROPS_START:56,"NODE_STRING[field]":57,COLON:58,"NODE_STRING[value]":59,PIPE:60,CYLINDERSTART:61,CYLINDEREND:62,DIAMOND_START:63,DIAMOND_STOP:64,TAGEND:65,TRAPSTART:66,TRAPEND:67,INVTRAPSTART:68,INVTRAPEND:69,linkStatement:70,arrowText:71,TESTSTR:72,START_LINK:73,edgeText:74,LINK:75,edgeTextToken:76,STR:77,MD_STR:78,textToken:79,keywords:80,STYLE:81,LINKSTYLE:82,CLASSDEF:83,CLASS:84,CLICK:85,DOWN:86,UP:87,textNoTagsToken:88,stylesOpt:89,"idString[vertex]":90,"idString[class]":91,CALLBACKNAME:92,CALLBACKARGS:93,HREF:94,LINK_TARGET:95,"STR[link]":96,"STR[tooltip]":97,alphaNum:98,DEFAULT:99,numList:100,INTERPOLATE:101,NUM:102,COMMA:103,style:104,styleComponent:105,NODE_STRING:106,UNIT:107,BRKT:108,PCT:109,idStringToken:110,MINUS:111,MULT:112,UNICODE_TEXT:113,TEXT:114,TAGSTART:115,EDGE_TEXT:116,alphaNumToken:117,direction_tb:118,direction_bt:119,direction_rl:120,direction_lr:121,$accept:0,$end:1},terminals_:{2:"error",8:"SEMI",9:"NEWLINE",10:"SPACE",11:"EOF",12:"GRAPH",13:"NODIR",14:"DIR",27:"subgraph",29:"SQS",31:"SQE",32:"end",34:"acc_title",35:"acc_title_value",36:"acc_descr",37:"acc_descr_value",38:"acc_descr_multiline_value",42:"AMP",44:"STYLE_SEPARATOR",46:"DOUBLECIRCLESTART",47:"DOUBLECIRCLEEND",48:"PS",49:"PE",50:"(-",51:"-)",52:"STADIUMSTART",53:"STADIUMEND",54:"SUBROUTINESTART",55:"SUBROUTINEEND",56:"VERTEX_WITH_PROPS_START",57:"NODE_STRING[field]",58:"COLON",59:"NODE_STRING[value]",60:"PIPE",61:"CYLINDERSTART",62:"CYLINDEREND",63:"DIAMOND_START",64:"DIAMOND_STOP",65:"TAGEND",66:"TRAPSTART",67:"TRAPEND",68:"INVTRAPSTART",69:"INVTRAPEND",72:"TESTSTR",73:"START_LINK",75:"LINK",77:"STR",78:"MD_STR",81:"STYLE",82:"LINKSTYLE",83:"CLASSDEF",84:"CLASS",85:"CLICK",86:"DOWN",87:"UP",90:"idString[vertex]",91:"idString[class]",92:"CALLBACKNAME",93:"CALLBACKARGS",94:"HREF",95:"LINK_TARGET",96:"STR[link]",97:"STR[tooltip]",99:"DEFAULT",101:"INTERPOLATE",102:"NUM",103:"COMMA",106:"NODE_STRING",107:"UNIT",108:"BRKT",109:"PCT",111:"MINUS",112:"MULT",113:"UNICODE_TEXT",114:"TEXT",115:"TAGSTART",116:"EDGE_TEXT",118:"direction_tb",119:"direction_bt",120:"direction_rl",121:"direction_lr"},productions_:[0,[3,2],[5,0],[5,2],[6,1],[6,1],[6,1],[6,1],[6,1],[4,2],[4,2],[4,2],[4,3],[16,2],[16,1],[17,1],[17,1],[17,1],[15,1],[15,1],[15,2],[19,2],[19,2],[19,1],[19,1],[18,2],[18,1],[7,2],[7,2],[7,2],[7,2],[7,2],[7,2],[7,9],[7,6],[7,4],[7,1],[7,2],[7,2],[7,1],[21,1],[21,1],[21,1],[20,3],[20,4],[20,2],[20,1],[40,1],[40,5],[41,1],[41,3],[43,4],[43,4],[43,6],[43,4],[43,4],[43,4],[43,8],[43,4],[43,4],[43,4],[43,6],[43,4],[43,4],[43,4],[43,4],[43,4],[43,1],[39,2],[39,3],[39,3],[39,1],[39,3],[74,1],[74,2],[74,1],[74,1],[70,1],[71,3],[30,1],[30,2],[30,1],[30,1],[80,1],[80,1],[80,1],[80,1],[80,1],[80,1],[80,1],[80,1],[80,1],[80,1],[80,1],[28,1],[28,2],[28,1],[28,1],[24,5],[25,5],[26,2],[26,4],[26,3],[26,5],[26,3],[26,5],[26,5],[26,7],[26,2],[26,4],[26,2],[26,4],[26,4],[26,6],[22,5],[23,5],[23,5],[23,9],[23,9],[23,7],[23,7],[100,1],[100,3],[89,1],[89,3],[104,1],[104,2],[105,1],[105,1],[105,1],[105,1],[105,1],[105,1],[105,1],[105,1],[110,1],[110,1],[110,1],[110,1],[110,1],[110,1],[110,1],[110,1],[110,1],[110,1],[110,1],[79,1],[79,1],[79,1],[79,1],[88,1],[88,1],[88,1],[88,1],[88,1],[88,1],[88,1],[88,1],[88,1],[88,1],[88,1],[76,1],[76,1],[117,1],[117,1],[117,1],[117,1],[117,1],[117,1],[117,1],[117,1],[117,1],[117,1],[117,1],[45,1],[45,2],[98,1],[98,2],[33,1],[33,1],[33,1],[33,1]],performAction:o(function(ht,At,$t,rt,Ot,pe,ur){var be=pe.length-1;switch(Ot){case 2:this.$=[];break;case 3:(!Array.isArray(pe[be])||pe[be].length>0)&&pe[be-1].push(pe[be]),this.$=pe[be-1];break;case 4:case 176:this.$=pe[be];break;case 11:rt.setDirection("TB"),this.$="TB";break;case 12:rt.setDirection(pe[be-1]),this.$=pe[be-1];break;case 27:this.$=pe[be-1].nodes;break;case 28:case 29:case 30:case 31:case 32:this.$=[];break;case 33:this.$=rt.addSubGraph(pe[be-6],pe[be-1],pe[be-4]);break;case 34:this.$=rt.addSubGraph(pe[be-3],pe[be-1],pe[be-3]);break;case 35:this.$=rt.addSubGraph(void 0,pe[be-1],void 0);break;case 37:this.$=pe[be].trim(),rt.setAccTitle(this.$);break;case 38:case 39:this.$=pe[be].trim(),rt.setAccDescription(this.$);break;case 43:rt.addLink(pe[be-2].stmt,pe[be],pe[be-1]),this.$={stmt:pe[be],nodes:pe[be].concat(pe[be-2].nodes)};break;case 44:rt.addLink(pe[be-3].stmt,pe[be-1],pe[be-2]),this.$={stmt:pe[be-1],nodes:pe[be-1].concat(pe[be-3].nodes)};break;case 45:this.$={stmt:pe[be-1],nodes:pe[be-1]};break;case 46:this.$={stmt:pe[be],nodes:pe[be]};break;case 47:this.$=[pe[be]];break;case 48:this.$=pe[be-4].concat(pe[be]);break;case 49:this.$=pe[be];break;case 50:this.$=pe[be-2],rt.setClass(pe[be-2],pe[be]);break;case 51:this.$=pe[be-3],rt.addVertex(pe[be-3],pe[be-1],"square");break;case 52:this.$=pe[be-3],rt.addVertex(pe[be-3],pe[be-1],"doublecircle");break;case 53:this.$=pe[be-5],rt.addVertex(pe[be-5],pe[be-2],"circle");break;case 54:this.$=pe[be-3],rt.addVertex(pe[be-3],pe[be-1],"ellipse");break;case 55:this.$=pe[be-3],rt.addVertex(pe[be-3],pe[be-1],"stadium");break;case 56:this.$=pe[be-3],rt.addVertex(pe[be-3],pe[be-1],"subroutine");break;case 57:this.$=pe[be-7],rt.addVertex(pe[be-7],pe[be-1],"rect",void 0,void 0,void 0,Object.fromEntries([[pe[be-5],pe[be-3]]]));break;case 58:this.$=pe[be-3],rt.addVertex(pe[be-3],pe[be-1],"cylinder");break;case 59:this.$=pe[be-3],rt.addVertex(pe[be-3],pe[be-1],"round");break;case 60:this.$=pe[be-3],rt.addVertex(pe[be-3],pe[be-1],"diamond");break;case 61:this.$=pe[be-5],rt.addVertex(pe[be-5],pe[be-2],"hexagon");break;case 62:this.$=pe[be-3],rt.addVertex(pe[be-3],pe[be-1],"odd");break;case 63:this.$=pe[be-3],rt.addVertex(pe[be-3],pe[be-1],"trapezoid");break;case 64:this.$=pe[be-3],rt.addVertex(pe[be-3],pe[be-1],"inv_trapezoid");break;case 65:this.$=pe[be-3],rt.addVertex(pe[be-3],pe[be-1],"lean_right");break;case 66:this.$=pe[be-3],rt.addVertex(pe[be-3],pe[be-1],"lean_left");break;case 67:this.$=pe[be],rt.addVertex(pe[be]);break;case 68:pe[be-1].text=pe[be],this.$=pe[be-1];break;case 69:case 70:pe[be-2].text=pe[be-1],this.$=pe[be-2];break;case 71:this.$=pe[be];break;case 72:var Ir=rt.destructLink(pe[be],pe[be-2]);this.$={type:Ir.type,stroke:Ir.stroke,length:Ir.length,text:pe[be-1]};break;case 73:this.$={text:pe[be],type:"text"};break;case 74:this.$={text:pe[be-1].text+""+pe[be],type:pe[be-1].type};break;case 75:this.$={text:pe[be],type:"string"};break;case 76:this.$={text:pe[be],type:"markdown"};break;case 77:var Ir=rt.destructLink(pe[be]);this.$={type:Ir.type,stroke:Ir.stroke,length:Ir.length};break;case 78:this.$=pe[be-1];break;case 79:this.$={text:pe[be],type:"text"};break;case 80:this.$={text:pe[be-1].text+""+pe[be],type:pe[be-1].type};break;case 81:this.$={text:pe[be],type:"string"};break;case 82:case 97:this.$={text:pe[be],type:"markdown"};break;case 94:this.$={text:pe[be],type:"text"};break;case 95:this.$={text:pe[be-1].text+""+pe[be],type:pe[be-1].type};break;case 96:this.$={text:pe[be],type:"text"};break;case 98:this.$=pe[be-4],rt.addClass(pe[be-2],pe[be]);break;case 99:this.$=pe[be-4],rt.setClass(pe[be-2],pe[be]);break;case 100:case 108:this.$=pe[be-1],rt.setClickEvent(pe[be-1],pe[be]);break;case 101:case 109:this.$=pe[be-3],rt.setClickEvent(pe[be-3],pe[be-2]),rt.setTooltip(pe[be-3],pe[be]);break;case 102:this.$=pe[be-2],rt.setClickEvent(pe[be-2],pe[be-1],pe[be]);break;case 103:this.$=pe[be-4],rt.setClickEvent(pe[be-4],pe[be-3],pe[be-2]),rt.setTooltip(pe[be-4],pe[be]);break;case 104:this.$=pe[be-2],rt.setLink(pe[be-2],pe[be]);break;case 105:this.$=pe[be-4],rt.setLink(pe[be-4],pe[be-2]),rt.setTooltip(pe[be-4],pe[be]);break;case 106:this.$=pe[be-4],rt.setLink(pe[be-4],pe[be-2],pe[be]);break;case 107:this.$=pe[be-6],rt.setLink(pe[be-6],pe[be-4],pe[be]),rt.setTooltip(pe[be-6],pe[be-2]);break;case 110:this.$=pe[be-1],rt.setLink(pe[be-1],pe[be]);break;case 111:this.$=pe[be-3],rt.setLink(pe[be-3],pe[be-2]),rt.setTooltip(pe[be-3],pe[be]);break;case 112:this.$=pe[be-3],rt.setLink(pe[be-3],pe[be-2],pe[be]);break;case 113:this.$=pe[be-5],rt.setLink(pe[be-5],pe[be-4],pe[be]),rt.setTooltip(pe[be-5],pe[be-2]);break;case 114:this.$=pe[be-4],rt.addVertex(pe[be-2],void 0,void 0,pe[be]);break;case 115:this.$=pe[be-4],rt.updateLink([pe[be-2]],pe[be]);break;case 116:this.$=pe[be-4],rt.updateLink(pe[be-2],pe[be]);break;case 117:this.$=pe[be-8],rt.updateLinkInterpolate([pe[be-6]],pe[be-2]),rt.updateLink([pe[be-6]],pe[be]);break;case 118:this.$=pe[be-8],rt.updateLinkInterpolate(pe[be-6],pe[be-2]),rt.updateLink(pe[be-6],pe[be]);break;case 119:this.$=pe[be-6],rt.updateLinkInterpolate([pe[be-4]],pe[be]);break;case 120:this.$=pe[be-6],rt.updateLinkInterpolate(pe[be-4],pe[be]);break;case 121:case 123:this.$=[pe[be]];break;case 122:case 124:pe[be-2].push(pe[be]),this.$=pe[be-2];break;case 126:this.$=pe[be-1]+pe[be];break;case 174:this.$=pe[be];break;case 175:this.$=pe[be-1]+""+pe[be];break;case 177:this.$=pe[be-1]+""+pe[be];break;case 178:this.$={stmt:"dir",value:"TB"};break;case 179:this.$={stmt:"dir",value:"BT"};break;case 180:this.$={stmt:"dir",value:"RL"};break;case 181:this.$={stmt:"dir",value:"LR"};break}},"anonymous"),table:[{3:1,4:2,9:e,10:r,12:n},{1:[3]},t(i,a,{5:6}),{4:7,9:e,10:r,12:n},{4:8,9:e,10:r,12:n},{13:[1,9],14:[1,10]},{1:[2,1],6:11,7:12,8:s,9:l,10:u,11:h,20:17,22:18,23:19,24:20,25:21,26:22,27:f,33:24,34:d,36:p,38:m,40:28,41:38,42:g,43:39,45:40,58:y,81:v,82:x,83:b,84:w,85:S,86:T,99:E,102:_,103:A,106:L,108:M,110:41,111:N,112:k,113:I,118:C,119:O,120:D,121:P},t(i,[2,9]),t(i,[2,10]),t(i,[2,11]),{8:[1,54],9:[1,55],10:F,15:53,18:56},t(B,[2,3]),t(B,[2,4]),t(B,[2,5]),t(B,[2,6]),t(B,[2,7]),t(B,[2,8]),{8:$,9:z,11:Y,21:58,39:59,70:63,73:[1,64],75:[1,65]},{8:$,9:z,11:Y,21:66},{8:$,9:z,11:Y,21:67},{8:$,9:z,11:Y,21:68},{8:$,9:z,11:Y,21:69},{8:$,9:z,11:Y,21:70},{8:$,9:z,10:[1,71],11:Y,21:72},t(B,[2,36]),{35:[1,73]},{37:[1,74]},t(B,[2,39]),t(Q,[2,46],{18:75,10:F}),{10:[1,76]},{10:[1,77]},{10:[1,78]},{10:[1,79]},{14:X,42:ie,58:j,77:[1,83],86:J,92:[1,80],94:[1,81],98:82,102:Z,103:H,106:q,108:K,111:se,112:ce,113:ue,117:84},t(B,[2,178]),t(B,[2,179]),t(B,[2,180]),t(B,[2,181]),t(te,[2,47]),t(te,[2,49],{44:[1,96]}),t(De,[2,67],{110:109,29:[1,97],42:g,46:[1,98],48:[1,99],50:[1,100],52:[1,101],54:[1,102],56:[1,103],58:y,61:[1,104],63:[1,105],65:[1,106],66:[1,107],68:[1,108],86:T,99:E,102:_,103:A,106:L,108:M,111:N,112:k,113:I}),t(oe,[2,174]),t(oe,[2,135]),t(oe,[2,136]),t(oe,[2,137]),t(oe,[2,138]),t(oe,[2,139]),t(oe,[2,140]),t(oe,[2,141]),t(oe,[2,142]),t(oe,[2,143]),t(oe,[2,144]),t(oe,[2,145]),t(i,[2,12]),t(i,[2,18]),t(i,[2,19]),{9:[1,110]},t(ke,[2,26],{18:111,10:F}),t(B,[2,27]),{40:112,41:38,42:g,43:39,45:40,58:y,86:T,99:E,102:_,103:A,106:L,108:M,110:41,111:N,112:k,113:I},t(B,[2,40]),t(B,[2,41]),t(B,[2,42]),t(Ie,[2,71],{71:113,60:[1,115],72:[1,114]}),{74:116,76:117,77:[1,118],78:[1,119],113:Se,116:Ue},t([42,58,60,72,86,99,102,103,106,108,111,112,113],[2,77]),t(B,[2,28]),t(B,[2,29]),t(B,[2,30]),t(B,[2,31]),t(B,[2,32]),{10:Pe,12:_e,14:me,27:W,28:122,32:fe,42:ge,58:re,73:he,77:[1,124],78:[1,125],80:135,81:ne,82:ae,83:we,84:Te,85:Ce,86:Ae,87:Ge,88:123,102:Me,106:ye,108:He,111:ze,112:Ze,113:gt},t(yt,a,{5:148}),t(B,[2,37]),t(B,[2,38]),t(Q,[2,45],{42:tt}),{42:g,45:150,58:y,86:T,99:E,102:_,103:A,106:L,108:M,110:41,111:N,112:k,113:I},{99:[1,151],100:152,102:[1,153]},{42:g,45:154,58:y,86:T,99:E,102:_,103:A,106:L,108:M,110:41,111:N,112:k,113:I},{42:g,45:155,58:y,86:T,99:E,102:_,103:A,106:L,108:M,110:41,111:N,112:k,113:I},t(Ye,[2,100],{10:[1,156],93:[1,157]}),{77:[1,158]},t(Ye,[2,108],{117:160,10:[1,159],14:X,42:ie,58:j,86:J,102:Z,103:H,106:q,108:K,111:se,112:ce,113:ue}),t(Ye,[2,110],{10:[1,161]}),t(Je,[2,176]),t(Je,[2,163]),t(Je,[2,164]),t(Je,[2,165]),t(Je,[2,166]),t(Je,[2,167]),t(Je,[2,168]),t(Je,[2,169]),t(Je,[2,170]),t(Je,[2,171]),t(Je,[2,172]),t(Je,[2,173]),{42:g,45:162,58:y,86:T,99:E,102:_,103:A,106:L,108:M,110:41,111:N,112:k,113:I},{30:163,65:Ve,77:je,78:kt,79:164,113:at,114:xt,115:it},{30:171,65:Ve,77:je,78:kt,79:164,113:at,114:xt,115:it},{30:173,48:[1,172],65:Ve,77:je,78:kt,79:164,113:at,114:xt,115:it},{30:174,65:Ve,77:je,78:kt,79:164,113:at,114:xt,115:it},{30:175,65:Ve,77:je,78:kt,79:164,113:at,114:xt,115:it},{30:176,65:Ve,77:je,78:kt,79:164,113:at,114:xt,115:it},{106:[1,177]},{30:178,65:Ve,77:je,78:kt,79:164,113:at,114:xt,115:it},{30:179,63:[1,180],65:Ve,77:je,78:kt,79:164,113:at,114:xt,115:it},{30:181,65:Ve,77:je,78:kt,79:164,113:at,114:xt,115:it},{30:182,65:Ve,77:je,78:kt,79:164,113:at,114:xt,115:it},{30:183,65:Ve,77:je,78:kt,79:164,113:at,114:xt,115:it},t(oe,[2,175]),t(i,[2,20]),t(ke,[2,25]),t(Q,[2,43],{18:184,10:F}),t(Ie,[2,68],{10:[1,185]}),{10:[1,186]},{30:187,65:Ve,77:je,78:kt,79:164,113:at,114:xt,115:it},{75:[1,188],76:189,113:Se,116:Ue},t(dt,[2,73]),t(dt,[2,75]),t(dt,[2,76]),t(dt,[2,161]),t(dt,[2,162]),{8:$,9:z,10:Pe,11:Y,12:_e,14:me,21:191,27:W,29:[1,190],32:fe,42:ge,58:re,73:he,80:135,81:ne,82:ae,83:we,84:Te,85:Ce,86:Ae,87:Ge,88:192,102:Me,106:ye,108:He,111:ze,112:Ze,113:gt},t(lt,[2,94]),t(lt,[2,96]),t(lt,[2,97]),t(lt,[2,150]),t(lt,[2,151]),t(lt,[2,152]),t(lt,[2,153]),t(lt,[2,154]),t(lt,[2,155]),t(lt,[2,156]),t(lt,[2,157]),t(lt,[2,158]),t(lt,[2,159]),t(lt,[2,160]),t(lt,[2,83]),t(lt,[2,84]),t(lt,[2,85]),t(lt,[2,86]),t(lt,[2,87]),t(lt,[2,88]),t(lt,[2,89]),t(lt,[2,90]),t(lt,[2,91]),t(lt,[2,92]),t(lt,[2,93]),{6:11,7:12,8:s,9:l,10:u,11:h,20:17,22:18,23:19,24:20,25:21,26:22,27:f,32:[1,193],33:24,34:d,36:p,38:m,40:28,41:38,42:g,43:39,45:40,58:y,81:v,82:x,83:b,84:w,85:S,86:T,99:E,102:_,103:A,106:L,108:M,110:41,111:N,112:k,113:I,118:C,119:O,120:D,121:P},{10:F,18:194},{10:[1,195],42:g,58:y,86:T,99:E,102:_,103:A,106:L,108:M,110:109,111:N,112:k,113:I},{10:[1,196]},{10:[1,197],103:[1,198]},t(It,[2,121]),{10:[1,199],42:g,58:y,86:T,99:E,102:_,103:A,106:L,108:M,110:109,111:N,112:k,113:I},{10:[1,200],42:g,58:y,86:T,99:E,102:_,103:A,106:L,108:M,110:109,111:N,112:k,113:I},{77:[1,201]},t(Ye,[2,102],{10:[1,202]}),t(Ye,[2,104],{10:[1,203]}),{77:[1,204]},t(Je,[2,177]),{77:[1,205],95:[1,206]},t(te,[2,50],{110:109,42:g,58:y,86:T,99:E,102:_,103:A,106:L,108:M,111:N,112:k,113:I}),{31:[1,207],65:Ve,79:208,113:at,114:xt,115:it},t(mt,[2,79]),t(mt,[2,81]),t(mt,[2,82]),t(mt,[2,146]),t(mt,[2,147]),t(mt,[2,148]),t(mt,[2,149]),{47:[1,209],65:Ve,79:208,113:at,114:xt,115:it},{30:210,65:Ve,77:je,78:kt,79:164,113:at,114:xt,115:it},{49:[1,211],65:Ve,79:208,113:at,114:xt,115:it},{51:[1,212],65:Ve,79:208,113:at,114:xt,115:it},{53:[1,213],65:Ve,79:208,113:at,114:xt,115:it},{55:[1,214],65:Ve,79:208,113:at,114:xt,115:it},{58:[1,215]},{62:[1,216],65:Ve,79:208,113:at,114:xt,115:it},{64:[1,217],65:Ve,79:208,113:at,114:xt,115:it},{30:218,65:Ve,77:je,78:kt,79:164,113:at,114:xt,115:it},{31:[1,219],65:Ve,79:208,113:at,114:xt,115:it},{65:Ve,67:[1,220],69:[1,221],79:208,113:at,114:xt,115:it},{65:Ve,67:[1,223],69:[1,222],79:208,113:at,114:xt,115:it},t(Q,[2,44],{42:tt}),t(Ie,[2,70]),t(Ie,[2,69]),{60:[1,224],65:Ve,79:208,113:at,114:xt,115:it},t(Ie,[2,72]),t(dt,[2,74]),{30:225,65:Ve,77:je,78:kt,79:164,113:at,114:xt,115:it},t(yt,a,{5:226}),t(lt,[2,95]),t(B,[2,35]),{41:227,42:g,43:39,45:40,58:y,86:T,99:E,102:_,103:A,106:L,108:M,110:41,111:N,112:k,113:I},{10:St,58:gr,81:xn,89:228,102:jt,104:229,105:230,106:rn,107:Er,108:Kn,109:hn},{10:St,58:gr,81:xn,89:239,101:[1,240],102:jt,104:229,105:230,106:rn,107:Er,108:Kn,109:hn},{10:St,58:gr,81:xn,89:241,101:[1,242],102:jt,104:229,105:230,106:rn,107:Er,108:Kn,109:hn},{102:[1,243]},{10:St,58:gr,81:xn,89:244,102:jt,104:229,105:230,106:rn,107:Er,108:Kn,109:hn},{42:g,45:245,58:y,86:T,99:E,102:_,103:A,106:L,108:M,110:41,111:N,112:k,113:I},t(Ye,[2,101]),{77:[1,246]},{77:[1,247],95:[1,248]},t(Ye,[2,109]),t(Ye,[2,111],{10:[1,249]}),t(Ye,[2,112]),t(De,[2,51]),t(mt,[2,80]),t(De,[2,52]),{49:[1,250],65:Ve,79:208,113:at,114:xt,115:it},t(De,[2,59]),t(De,[2,54]),t(De,[2,55]),t(De,[2,56]),{106:[1,251]},t(De,[2,58]),t(De,[2,60]),{64:[1,252],65:Ve,79:208,113:at,114:xt,115:it},t(De,[2,62]),t(De,[2,63]),t(De,[2,65]),t(De,[2,64]),t(De,[2,66]),t([10,42,58,86,99,102,103,106,108,111,112,113],[2,78]),{31:[1,253],65:Ve,79:208,113:at,114:xt,115:it},{6:11,7:12,8:s,9:l,10:u,11:h,20:17,22:18,23:19,24:20,25:21,26:22,27:f,32:[1,254],33:24,34:d,36:p,38:m,40:28,41:38,42:g,43:39,45:40,58:y,81:v,82:x,83:b,84:w,85:S,86:T,99:E,102:_,103:A,106:L,108:M,110:41,111:N,112:k,113:I,118:C,119:O,120:D,121:P},t(te,[2,48]),t(Ye,[2,114],{103:Qn}),t(on,[2,123],{105:256,10:St,58:gr,81:xn,102:jt,106:rn,107:Er,108:Kn,109:hn}),t(Rn,[2,125]),t(Rn,[2,127]),t(Rn,[2,128]),t(Rn,[2,129]),t(Rn,[2,130]),t(Rn,[2,131]),t(Rn,[2,132]),t(Rn,[2,133]),t(Rn,[2,134]),t(Ye,[2,115],{103:Qn}),{10:[1,257]},t(Ye,[2,116],{103:Qn}),{10:[1,258]},t(It,[2,122]),t(Ye,[2,98],{103:Qn}),t(Ye,[2,99],{110:109,42:g,58:y,86:T,99:E,102:_,103:A,106:L,108:M,111:N,112:k,113:I}),t(Ye,[2,103]),t(Ye,[2,105],{10:[1,259]}),t(Ye,[2,106]),{95:[1,260]},{49:[1,261]},{60:[1,262]},{64:[1,263]},{8:$,9:z,11:Y,21:264},t(B,[2,34]),{10:St,58:gr,81:xn,102:jt,104:265,105:230,106:rn,107:Er,108:Kn,109:hn},t(Rn,[2,126]),{14:X,42:ie,58:j,86:J,98:266,102:Z,103:H,106:q,108:K,111:se,112:ce,113:ue,117:84},{14:X,42:ie,58:j,86:J,98:267,102:Z,103:H,106:q,108:K,111:se,112:ce,113:ue,117:84},{95:[1,268]},t(Ye,[2,113]),t(De,[2,53]),{30:269,65:Ve,77:je,78:kt,79:164,113:at,114:xt,115:it},t(De,[2,61]),t(yt,a,{5:270}),t(on,[2,124],{105:256,10:St,58:gr,81:xn,102:jt,106:rn,107:Er,108:Kn,109:hn}),t(Ye,[2,119],{117:160,10:[1,271],14:X,42:ie,58:j,86:J,102:Z,103:H,106:q,108:K,111:se,112:ce,113:ue}),t(Ye,[2,120],{117:160,10:[1,272],14:X,42:ie,58:j,86:J,102:Z,103:H,106:q,108:K,111:se,112:ce,113:ue}),t(Ye,[2,107]),{31:[1,273],65:Ve,79:208,113:at,114:xt,115:it},{6:11,7:12,8:s,9:l,10:u,11:h,20:17,22:18,23:19,24:20,25:21,26:22,27:f,32:[1,274],33:24,34:d,36:p,38:m,40:28,41:38,42:g,43:39,45:40,58:y,81:v,82:x,83:b,84:w,85:S,86:T,99:E,102:_,103:A,106:L,108:M,110:41,111:N,112:k,113:I,118:C,119:O,120:D,121:P},{10:St,58:gr,81:xn,89:275,102:jt,104:229,105:230,106:rn,107:Er,108:Kn,109:hn},{10:St,58:gr,81:xn,89:276,102:jt,104:229,105:230,106:rn,107:Er,108:Kn,109:hn},t(De,[2,57]),t(B,[2,33]),t(Ye,[2,117],{103:Qn}),t(Ye,[2,118],{103:Qn})],defaultActions:{},parseError:o(function(ht,At){if(At.recoverable)this.trace(ht);else{var $t=new Error(ht);throw $t.hash=At,$t}},"parseError"),parse:o(function(ht){var At=this,$t=[0],rt=[],Ot=[null],pe=[],ur=this.table,be="",Ir=0,Xc=0,M1=0,_b=2,I1=1,O1=pe.slice.call(arguments,1),ci=Object.create(this.lexer),ko={yy:{}};for(var ih in this.yy)Object.prototype.hasOwnProperty.call(this.yy,ih)&&(ko.yy[ih]=this.yy[ih]);ci.setInput(ht,ko.yy),ko.yy.lexer=ci,ko.yy.parser=this,typeof ci.yylloc>"u"&&(ci.yylloc={});var Us=ci.yylloc;pe.push(Us);var ah=ci.options&&ci.options.ranges;typeof ko.yy.parseError=="function"?this.parseError=ko.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function Lb(La){$t.length=$t.length-2*La,Ot.length=Ot.length-La,pe.length=pe.length-La}o(Lb,"popStack");function P1(){var La;return La=rt.pop()||ci.lex()||I1,typeof La!="number"&&(La instanceof Array&&(rt=La,La=rt.pop()),La=At.symbols_[La]||La),La}o(P1,"lex");for(var sa,jc,Kc,us,_i,Wl,sh={},zf,Hs,B1,Gf;;){if(Kc=$t[$t.length-1],this.defaultActions[Kc]?us=this.defaultActions[Kc]:((sa===null||typeof sa>"u")&&(sa=P1()),us=ur[Kc]&&ur[Kc][sa]),typeof us>"u"||!us.length||!us[0]){var F1="";Gf=[];for(zf in ur[Kc])this.terminals_[zf]&&zf>_b&&Gf.push("'"+this.terminals_[zf]+"'");ci.showPosition?F1="Parse error on line "+(Ir+1)+`: +`+ci.showPosition()+` +Expecting `+Gf.join(", ")+", got '"+(this.terminals_[sa]||sa)+"'":F1="Parse error on line "+(Ir+1)+": Unexpected "+(sa==I1?"end of input":"'"+(this.terminals_[sa]||sa)+"'"),this.parseError(F1,{text:ci.match,token:this.terminals_[sa]||sa,line:ci.yylineno,loc:Us,expected:Gf})}if(us[0]instanceof Array&&us.length>1)throw new Error("Parse Error: multiple actions possible at state: "+Kc+", token: "+sa);switch(us[0]){case 1:$t.push(sa),Ot.push(ci.yytext),pe.push(ci.yylloc),$t.push(us[1]),sa=null,jc?(sa=jc,jc=null):(Xc=ci.yyleng,be=ci.yytext,Ir=ci.yylineno,Us=ci.yylloc,M1>0&&M1--);break;case 2:if(Hs=this.productions_[us[1]][1],sh.$=Ot[Ot.length-Hs],sh._$={first_line:pe[pe.length-(Hs||1)].first_line,last_line:pe[pe.length-1].last_line,first_column:pe[pe.length-(Hs||1)].first_column,last_column:pe[pe.length-1].last_column},ah&&(sh._$.range=[pe[pe.length-(Hs||1)].range[0],pe[pe.length-1].range[1]]),Wl=this.performAction.apply(sh,[be,Xc,Ir,ko.yy,us[1],Ot,pe].concat(O1)),typeof Wl<"u")return Wl;Hs&&($t=$t.slice(0,-1*Hs*2),Ot=Ot.slice(0,-1*Hs),pe=pe.slice(0,-1*Hs)),$t.push(this.productions_[us[1]][0]),Ot.push(sh.$),pe.push(sh._$),B1=ur[$t[$t.length-2]][$t[$t.length-1]],$t.push(B1);break;case 3:return!0}}return!0},"parse")},_a=function(){var qi={EOF:1,parseError:o(function(At,$t){if(this.yy.parser)this.yy.parser.parseError(At,$t);else throw new Error(At)},"parseError"),setInput:o(function(ht,At){return this.yy=At||this.yy||{},this._input=ht,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:o(function(){var ht=this._input[0];this.yytext+=ht,this.yyleng++,this.offset++,this.match+=ht,this.matched+=ht;var At=ht.match(/(?:\r\n?|\n).*/g);return At?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),ht},"input"),unput:o(function(ht){var At=ht.length,$t=ht.split(/(?:\r\n?|\n)/g);this._input=ht+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-At),this.offset-=At;var rt=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),$t.length-1&&(this.yylineno-=$t.length-1);var Ot=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:$t?($t.length===rt.length?this.yylloc.first_column:0)+rt[rt.length-$t.length].length-$t[0].length:this.yylloc.first_column-At},this.options.ranges&&(this.yylloc.range=[Ot[0],Ot[0]+this.yyleng-At]),this.yyleng=this.yytext.length,this},"unput"),more:o(function(){return this._more=!0,this},"more"),reject:o(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:o(function(ht){this.unput(this.match.slice(ht))},"less"),pastInput:o(function(){var ht=this.matched.substr(0,this.matched.length-this.match.length);return(ht.length>20?"...":"")+ht.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:o(function(){var ht=this.match;return ht.length<20&&(ht+=this._input.substr(0,20-ht.length)),(ht.substr(0,20)+(ht.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:o(function(){var ht=this.pastInput(),At=new Array(ht.length+1).join("-");return ht+this.upcomingInput()+` +`+At+"^"},"showPosition"),test_match:o(function(ht,At){var $t,rt,Ot;if(this.options.backtrack_lexer&&(Ot={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(Ot.yylloc.range=this.yylloc.range.slice(0))),rt=ht[0].match(/(?:\r\n?|\n).*/g),rt&&(this.yylineno+=rt.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:rt?rt[rt.length-1].length-rt[rt.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+ht[0].length},this.yytext+=ht[0],this.match+=ht[0],this.matches=ht,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(ht[0].length),this.matched+=ht[0],$t=this.performAction.call(this,this.yy,this,At,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),$t)return $t;if(this._backtrack){for(var pe in Ot)this[pe]=Ot[pe];return!1}return!1},"test_match"),next:o(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var ht,At,$t,rt;this._more||(this.yytext="",this.match="");for(var Ot=this._currentRules(),pe=0;peAt[0].length)){if(At=$t,rt=pe,this.options.backtrack_lexer){if(ht=this.test_match($t,Ot[pe]),ht!==!1)return ht;if(this._backtrack){At=!1;continue}else return!1}else if(!this.options.flex)break}return At?(ht=this.test_match(At,Ot[rt]),ht!==!1?ht:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:o(function(){var At=this.next();return At||this.lex()},"lex"),begin:o(function(At){this.conditionStack.push(At)},"begin"),popState:o(function(){var At=this.conditionStack.length-1;return At>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:o(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:o(function(At){return At=this.conditionStack.length-1-Math.abs(At||0),At>=0?this.conditionStack[At]:"INITIAL"},"topState"),pushState:o(function(At){this.begin(At)},"pushState"),stateStackSize:o(function(){return this.conditionStack.length},"stateStackSize"),options:{},performAction:o(function(At,$t,rt,Ot){var pe=Ot;switch(rt){case 0:return this.begin("acc_title"),34;break;case 1:return this.popState(),"acc_title_value";break;case 2:return this.begin("acc_descr"),36;break;case 3:return this.popState(),"acc_descr_value";break;case 4:this.begin("acc_descr_multiline");break;case 5:this.popState();break;case 6:return"acc_descr_multiline_value";case 7:this.begin("callbackname");break;case 8:this.popState();break;case 9:this.popState(),this.begin("callbackargs");break;case 10:return 92;case 11:this.popState();break;case 12:return 93;case 13:return"MD_STR";case 14:this.popState();break;case 15:this.begin("md_string");break;case 16:return"STR";case 17:this.popState();break;case 18:this.pushState("string");break;case 19:return 81;case 20:return 99;case 21:return 82;case 22:return 101;case 23:return 83;case 24:return 84;case 25:return 94;case 26:this.begin("click");break;case 27:this.popState();break;case 28:return 85;case 29:return At.lex.firstGraph()&&this.begin("dir"),12;break;case 30:return At.lex.firstGraph()&&this.begin("dir"),12;break;case 31:return At.lex.firstGraph()&&this.begin("dir"),12;break;case 32:return 27;case 33:return 32;case 34:return 95;case 35:return 95;case 36:return 95;case 37:return 95;case 38:return this.popState(),13;break;case 39:return this.popState(),14;break;case 40:return this.popState(),14;break;case 41:return this.popState(),14;break;case 42:return this.popState(),14;break;case 43:return this.popState(),14;break;case 44:return this.popState(),14;break;case 45:return this.popState(),14;break;case 46:return this.popState(),14;break;case 47:return this.popState(),14;break;case 48:return this.popState(),14;break;case 49:return 118;case 50:return 119;case 51:return 120;case 52:return 121;case 53:return 102;case 54:return 108;case 55:return 44;case 56:return 58;case 57:return 42;case 58:return 8;case 59:return 103;case 60:return 112;case 61:return this.popState(),75;break;case 62:return this.pushState("edgeText"),73;break;case 63:return 116;case 64:return this.popState(),75;break;case 65:return this.pushState("thickEdgeText"),73;break;case 66:return 116;case 67:return this.popState(),75;break;case 68:return this.pushState("dottedEdgeText"),73;break;case 69:return 116;case 70:return 75;case 71:return this.popState(),51;break;case 72:return"TEXT";case 73:return this.pushState("ellipseText"),50;break;case 74:return this.popState(),53;break;case 75:return this.pushState("text"),52;break;case 76:return this.popState(),55;break;case 77:return this.pushState("text"),54;break;case 78:return 56;case 79:return this.pushState("text"),65;break;case 80:return this.popState(),62;break;case 81:return this.pushState("text"),61;break;case 82:return this.popState(),47;break;case 83:return this.pushState("text"),46;break;case 84:return this.popState(),67;break;case 85:return this.popState(),69;break;case 86:return 114;case 87:return this.pushState("trapText"),66;break;case 88:return this.pushState("trapText"),68;break;case 89:return 115;case 90:return 65;case 91:return 87;case 92:return"SEP";case 93:return 86;case 94:return 112;case 95:return 108;case 96:return 42;case 97:return 106;case 98:return 111;case 99:return 113;case 100:return this.popState(),60;break;case 101:return this.pushState("text"),60;break;case 102:return this.popState(),49;break;case 103:return this.pushState("text"),48;break;case 104:return this.popState(),31;break;case 105:return this.pushState("text"),29;break;case 106:return this.popState(),64;break;case 107:return this.pushState("text"),63;break;case 108:return"TEXT";case 109:return"QUOTE";case 110:return 9;case 111:return 10;case 112:return 11}},"anonymous"),rules:[/^(?:accTitle\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*\{\s*)/,/^(?:[\}])/,/^(?:[^\}]*)/,/^(?:call[\s]+)/,/^(?:\([\s]*\))/,/^(?:\()/,/^(?:[^(]*)/,/^(?:\))/,/^(?:[^)]*)/,/^(?:[^`"]+)/,/^(?:[`]["])/,/^(?:["][`])/,/^(?:[^"]+)/,/^(?:["])/,/^(?:["])/,/^(?:style\b)/,/^(?:default\b)/,/^(?:linkStyle\b)/,/^(?:interpolate\b)/,/^(?:classDef\b)/,/^(?:class\b)/,/^(?:href[\s])/,/^(?:click[\s]+)/,/^(?:[\s\n])/,/^(?:[^\s\n]*)/,/^(?:flowchart-elk\b)/,/^(?:graph\b)/,/^(?:flowchart\b)/,/^(?:subgraph\b)/,/^(?:end\b\s*)/,/^(?:_self\b)/,/^(?:_blank\b)/,/^(?:_parent\b)/,/^(?:_top\b)/,/^(?:(\r?\n)*\s*\n)/,/^(?:\s*LR\b)/,/^(?:\s*RL\b)/,/^(?:\s*TB\b)/,/^(?:\s*BT\b)/,/^(?:\s*TD\b)/,/^(?:\s*BR\b)/,/^(?:\s*<)/,/^(?:\s*>)/,/^(?:\s*\^)/,/^(?:\s*v\b)/,/^(?:.*direction\s+TB[^\n]*)/,/^(?:.*direction\s+BT[^\n]*)/,/^(?:.*direction\s+RL[^\n]*)/,/^(?:.*direction\s+LR[^\n]*)/,/^(?:[0-9]+)/,/^(?:#)/,/^(?::::)/,/^(?::)/,/^(?:&)/,/^(?:;)/,/^(?:,)/,/^(?:\*)/,/^(?:\s*[xo<]?--+[-xo>]\s*)/,/^(?:\s*[xo<]?--\s*)/,/^(?:[^-]|-(?!-)+)/,/^(?:\s*[xo<]?==+[=xo>]\s*)/,/^(?:\s*[xo<]?==\s*)/,/^(?:[^=]|=(?!))/,/^(?:\s*[xo<]?-?\.+-[xo>]?\s*)/,/^(?:\s*[xo<]?-\.\s*)/,/^(?:[^\.]|\.(?!))/,/^(?:\s*~~[\~]+\s*)/,/^(?:[-/\)][\)])/,/^(?:[^\(\)\[\]\{\}]|!\)+)/,/^(?:\(-)/,/^(?:\]\))/,/^(?:\(\[)/,/^(?:\]\])/,/^(?:\[\[)/,/^(?:\[\|)/,/^(?:>)/,/^(?:\)\])/,/^(?:\[\()/,/^(?:\)\)\))/,/^(?:\(\(\()/,/^(?:[\\(?=\])][\]])/,/^(?:\/(?=\])\])/,/^(?:\/(?!\])|\\(?!\])|[^\\\[\]\(\)\{\}\/]+)/,/^(?:\[\/)/,/^(?:\[\\)/,/^(?:<)/,/^(?:>)/,/^(?:\^)/,/^(?:\\\|)/,/^(?:v\b)/,/^(?:\*)/,/^(?:#)/,/^(?:&)/,/^(?:([A-Za-z0-9!"\#$%&'*+\.`?\\_\/]|-(?=[^\>\-\.])|(?!))+)/,/^(?:-)/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\|)/,/^(?:\|)/,/^(?:\))/,/^(?:\()/,/^(?:\])/,/^(?:\[)/,/^(?:(\}))/,/^(?:\{)/,/^(?:[^\[\]\(\)\{\}\|\"]+)/,/^(?:")/,/^(?:(\r?\n)+)/,/^(?:\s)/,/^(?:$)/],conditions:{callbackargs:{rules:[11,12,15,18,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},callbackname:{rules:[8,9,10,15,18,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},href:{rules:[15,18,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},click:{rules:[15,18,27,28,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},dottedEdgeText:{rules:[15,18,67,69,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},thickEdgeText:{rules:[15,18,64,66,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},edgeText:{rules:[15,18,61,63,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},trapText:{rules:[15,18,70,73,75,77,81,83,84,85,86,87,88,101,103,105,107],inclusive:!1},ellipseText:{rules:[15,18,70,71,72,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},text:{rules:[15,18,70,73,74,75,76,77,80,81,82,83,87,88,100,101,102,103,104,105,106,107,108],inclusive:!1},vertex:{rules:[15,18,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},dir:{rules:[15,18,38,39,40,41,42,43,44,45,46,47,48,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},acc_descr_multiline:{rules:[5,6,15,18,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},acc_descr:{rules:[3,15,18,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},acc_title:{rules:[1,15,18,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},md_string:{rules:[13,14,15,18,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},string:{rules:[15,16,17,18,70,73,75,77,81,83,87,88,101,103,105,107],inclusive:!1},INITIAL:{rules:[0,2,4,7,15,18,19,20,21,22,23,24,25,26,29,30,31,32,33,34,35,36,37,49,50,51,52,53,54,55,56,57,58,59,60,61,62,64,65,67,68,70,73,75,77,78,79,81,83,87,88,89,90,91,92,93,94,95,96,97,98,99,101,103,105,107,109,110,111,112],inclusive:!0}}};return qi}();Ha.lexer=_a;function To(){this.yy={}}return o(To,"Parser"),To.prototype=Ha,Ha.Parser=To,new To}();vD.parser=vD;Xre=vD});var kNe,ENe,Kre,Qre=R(()=>{"use strict";al();kNe=o((t,e)=>{let r=X1,n=r(t,"r"),i=r(t,"g"),a=r(t,"b");return Ws(n,i,a,e)},"fade"),ENe=o(t=>`.label { + font-family: ${t.fontFamily}; + color: ${t.nodeTextColor||t.textColor}; + } + .cluster-label text { + fill: ${t.titleColor}; + } + .cluster-label span { + color: ${t.titleColor}; + } + .cluster-label span p { + background-color: transparent; + } + + .label text,span { + fill: ${t.nodeTextColor||t.textColor}; + color: ${t.nodeTextColor||t.textColor}; + } + + .node rect, + .node circle, + .node ellipse, + .node polygon, + .node path { + fill: ${t.mainBkg}; + stroke: ${t.nodeBorder}; + stroke-width: 1px; + } + .rough-node .label text , .node .label text { + text-anchor: middle; + } + // .flowchart-label .text-outer-tspan { + // text-anchor: middle; + // } + // .flowchart-label .text-inner-tspan { + // text-anchor: start; + // } + + .node .katex path { + fill: #000; + stroke: #000; + stroke-width: 1px; + } + + .node .label { + text-align: center; + } + .node.clickable { + cursor: pointer; + } + + .arrowheadPath { + fill: ${t.arrowheadColor}; + } + + .edgePath .path { + stroke: ${t.lineColor}; + stroke-width: 2.0px; + } + + .flowchart-link { + stroke: ${t.lineColor}; + fill: none; + } + + .edgeLabel { + background-color: ${t.edgeLabelBackground}; + p { + background-color: ${t.edgeLabelBackground}; + } + rect { + opacity: 0.5; + background-color: ${t.edgeLabelBackground}; + fill: ${t.edgeLabelBackground}; + } + text-align: center; + } + + /* For html labels only */ + .labelBkg { + background-color: ${kNe(t.edgeLabelBackground,.5)}; + // background-color: + } + + .cluster rect { + fill: ${t.clusterBkg}; + stroke: ${t.clusterBorder}; + stroke-width: 1px; + } + + .cluster text { + fill: ${t.titleColor}; + } + + .cluster span { + color: ${t.titleColor}; + } + /* .cluster div { + color: ${t.titleColor}; + } */ + + div.mermaidTooltip { + position: absolute; + text-align: center; + max-width: 200px; + padding: 2px; + font-family: ${t.fontFamily}; + font-size: 12px; + background: ${t.tertiaryColor}; + border: 1px solid ${t.border2}; + border-radius: 2px; + pointer-events: none; + z-index: 100; + } + + .flowchartTitleText { + text-anchor: middle; + font-size: 18px; + fill: ${t.textColor}; + } +`,"getStyles"),Kre=ENe});var cT={};hr(cT,{diagram:()=>CNe});var CNe,uT=R(()=>{"use strict";_t();f9();qre();jre();Qre();CNe={parser:Xre,db:A5,renderer:Wre,styles:Kre,init:o(t=>{t.flowchart||(t.flowchart={}),t.layout&&iS({layout:t.layout}),t.flowchart.arrowMarkerAbsolute=t.arrowMarkerAbsolute,iS({flowchart:{arrowMarkerAbsolute:t.arrowMarkerAbsolute}}),A5.clear(),A5.setGen("gen-2")},"init")}});var xD,rne,nne=R(()=>{"use strict";xD=function(){var t=o(function(A,L,M,N){for(M=M||{},N=A.length;N--;M[A[N]]=L);return M},"o"),e=[6,8,10,20,22,24,26,27,28],r=[1,10],n=[1,11],i=[1,12],a=[1,13],s=[1,14],l=[1,15],u=[1,21],h=[1,22],f=[1,23],d=[1,24],p=[1,25],m=[6,8,10,13,15,18,19,20,22,24,26,27,28,41,42,43,44,45],g=[1,34],y=[27,28,46,47],v=[41,42,43,44,45],x=[17,34],b=[1,54],w=[1,53],S=[17,34,36,38],T={trace:o(function(){},"trace"),yy:{},symbols_:{error:2,start:3,ER_DIAGRAM:4,document:5,EOF:6,line:7,SPACE:8,statement:9,NEWLINE:10,entityName:11,relSpec:12,":":13,role:14,BLOCK_START:15,attributes:16,BLOCK_STOP:17,SQS:18,SQE:19,title:20,title_value:21,acc_title:22,acc_title_value:23,acc_descr:24,acc_descr_value:25,acc_descr_multiline_value:26,ALPHANUM:27,ENTITY_NAME:28,attribute:29,attributeType:30,attributeName:31,attributeKeyTypeList:32,attributeComment:33,ATTRIBUTE_WORD:34,attributeKeyType:35,COMMA:36,ATTRIBUTE_KEY:37,COMMENT:38,cardinality:39,relType:40,ZERO_OR_ONE:41,ZERO_OR_MORE:42,ONE_OR_MORE:43,ONLY_ONE:44,MD_PARENT:45,NON_IDENTIFYING:46,IDENTIFYING:47,WORD:48,$accept:0,$end:1},terminals_:{2:"error",4:"ER_DIAGRAM",6:"EOF",8:"SPACE",10:"NEWLINE",13:":",15:"BLOCK_START",17:"BLOCK_STOP",18:"SQS",19:"SQE",20:"title",21:"title_value",22:"acc_title",23:"acc_title_value",24:"acc_descr",25:"acc_descr_value",26:"acc_descr_multiline_value",27:"ALPHANUM",28:"ENTITY_NAME",34:"ATTRIBUTE_WORD",36:"COMMA",37:"ATTRIBUTE_KEY",38:"COMMENT",41:"ZERO_OR_ONE",42:"ZERO_OR_MORE",43:"ONE_OR_MORE",44:"ONLY_ONE",45:"MD_PARENT",46:"NON_IDENTIFYING",47:"IDENTIFYING",48:"WORD"},productions_:[0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[9,5],[9,4],[9,3],[9,1],[9,7],[9,6],[9,4],[9,2],[9,2],[9,2],[9,1],[11,1],[11,1],[16,1],[16,2],[29,2],[29,3],[29,3],[29,4],[30,1],[31,1],[32,1],[32,3],[35,1],[33,1],[12,3],[39,1],[39,1],[39,1],[39,1],[39,1],[40,1],[40,1],[14,1],[14,1],[14,1]],performAction:o(function(L,M,N,k,I,C,O){var D=C.length-1;switch(I){case 1:break;case 2:this.$=[];break;case 3:C[D-1].push(C[D]),this.$=C[D-1];break;case 4:case 5:this.$=C[D];break;case 6:case 7:this.$=[];break;case 8:k.addEntity(C[D-4]),k.addEntity(C[D-2]),k.addRelationship(C[D-4],C[D],C[D-2],C[D-3]);break;case 9:k.addEntity(C[D-3]),k.addAttributes(C[D-3],C[D-1]);break;case 10:k.addEntity(C[D-2]);break;case 11:k.addEntity(C[D]);break;case 12:k.addEntity(C[D-6],C[D-4]),k.addAttributes(C[D-6],C[D-1]);break;case 13:k.addEntity(C[D-5],C[D-3]);break;case 14:k.addEntity(C[D-3],C[D-1]);break;case 15:case 16:this.$=C[D].trim(),k.setAccTitle(this.$);break;case 17:case 18:this.$=C[D].trim(),k.setAccDescription(this.$);break;case 19:case 43:this.$=C[D];break;case 20:case 41:case 42:this.$=C[D].replace(/"/g,"");break;case 21:case 29:this.$=[C[D]];break;case 22:C[D].push(C[D-1]),this.$=C[D];break;case 23:this.$={attributeType:C[D-1],attributeName:C[D]};break;case 24:this.$={attributeType:C[D-2],attributeName:C[D-1],attributeKeyTypeList:C[D]};break;case 25:this.$={attributeType:C[D-2],attributeName:C[D-1],attributeComment:C[D]};break;case 26:this.$={attributeType:C[D-3],attributeName:C[D-2],attributeKeyTypeList:C[D-1],attributeComment:C[D]};break;case 27:case 28:case 31:this.$=C[D];break;case 30:C[D-2].push(C[D]),this.$=C[D-2];break;case 32:this.$=C[D].replace(/"/g,"");break;case 33:this.$={cardA:C[D],relType:C[D-1],cardB:C[D-2]};break;case 34:this.$=k.Cardinality.ZERO_OR_ONE;break;case 35:this.$=k.Cardinality.ZERO_OR_MORE;break;case 36:this.$=k.Cardinality.ONE_OR_MORE;break;case 37:this.$=k.Cardinality.ONLY_ONE;break;case 38:this.$=k.Cardinality.MD_PARENT;break;case 39:this.$=k.Identification.NON_IDENTIFYING;break;case 40:this.$=k.Identification.IDENTIFYING;break}},"anonymous"),table:[{3:1,4:[1,2]},{1:[3]},t(e,[2,2],{5:3}),{6:[1,4],7:5,8:[1,6],9:7,10:[1,8],11:9,20:r,22:n,24:i,26:a,27:s,28:l},t(e,[2,7],{1:[2,1]}),t(e,[2,3]),{9:16,11:9,20:r,22:n,24:i,26:a,27:s,28:l},t(e,[2,5]),t(e,[2,6]),t(e,[2,11],{12:17,39:20,15:[1,18],18:[1,19],41:u,42:h,43:f,44:d,45:p}),{21:[1,26]},{23:[1,27]},{25:[1,28]},t(e,[2,18]),t(m,[2,19]),t(m,[2,20]),t(e,[2,4]),{11:29,27:s,28:l},{16:30,17:[1,31],29:32,30:33,34:g},{11:35,27:s,28:l},{40:36,46:[1,37],47:[1,38]},t(y,[2,34]),t(y,[2,35]),t(y,[2,36]),t(y,[2,37]),t(y,[2,38]),t(e,[2,15]),t(e,[2,16]),t(e,[2,17]),{13:[1,39]},{17:[1,40]},t(e,[2,10]),{16:41,17:[2,21],29:32,30:33,34:g},{31:42,34:[1,43]},{34:[2,27]},{19:[1,44]},{39:45,41:u,42:h,43:f,44:d,45:p},t(v,[2,39]),t(v,[2,40]),{14:46,27:[1,49],28:[1,48],48:[1,47]},t(e,[2,9]),{17:[2,22]},t(x,[2,23],{32:50,33:51,35:52,37:b,38:w}),t([17,34,37,38],[2,28]),t(e,[2,14],{15:[1,55]}),t([27,28],[2,33]),t(e,[2,8]),t(e,[2,41]),t(e,[2,42]),t(e,[2,43]),t(x,[2,24],{33:56,36:[1,57],38:w}),t(x,[2,25]),t(S,[2,29]),t(x,[2,32]),t(S,[2,31]),{16:58,17:[1,59],29:32,30:33,34:g},t(x,[2,26]),{35:60,37:b},{17:[1,61]},t(e,[2,13]),t(S,[2,30]),t(e,[2,12])],defaultActions:{34:[2,27],41:[2,22]},parseError:o(function(L,M){if(M.recoverable)this.trace(L);else{var N=new Error(L);throw N.hash=M,N}},"parseError"),parse:o(function(L){var M=this,N=[0],k=[],I=[null],C=[],O=this.table,D="",P=0,F=0,B=0,$=2,z=1,Y=C.slice.call(arguments,1),Q=Object.create(this.lexer),X={yy:{}};for(var ie in this.yy)Object.prototype.hasOwnProperty.call(this.yy,ie)&&(X.yy[ie]=this.yy[ie]);Q.setInput(L,X.yy),X.yy.lexer=Q,X.yy.parser=this,typeof Q.yylloc>"u"&&(Q.yylloc={});var j=Q.yylloc;C.push(j);var J=Q.options&&Q.options.ranges;typeof X.yy.parseError=="function"?this.parseError=X.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function Z(Pe){N.length=N.length-2*Pe,I.length=I.length-Pe,C.length=C.length-Pe}o(Z,"popStack");function H(){var Pe;return Pe=k.pop()||Q.lex()||z,typeof Pe!="number"&&(Pe instanceof Array&&(k=Pe,Pe=k.pop()),Pe=M.symbols_[Pe]||Pe),Pe}o(H,"lex");for(var q,K,se,ce,ue,te,De={},oe,ke,Ie,Se;;){if(se=N[N.length-1],this.defaultActions[se]?ce=this.defaultActions[se]:((q===null||typeof q>"u")&&(q=H()),ce=O[se]&&O[se][q]),typeof ce>"u"||!ce.length||!ce[0]){var Ue="";Se=[];for(oe in O[se])this.terminals_[oe]&&oe>$&&Se.push("'"+this.terminals_[oe]+"'");Q.showPosition?Ue="Parse error on line "+(P+1)+`: +`+Q.showPosition()+` +Expecting `+Se.join(", ")+", got '"+(this.terminals_[q]||q)+"'":Ue="Parse error on line "+(P+1)+": Unexpected "+(q==z?"end of input":"'"+(this.terminals_[q]||q)+"'"),this.parseError(Ue,{text:Q.match,token:this.terminals_[q]||q,line:Q.yylineno,loc:j,expected:Se})}if(ce[0]instanceof Array&&ce.length>1)throw new Error("Parse Error: multiple actions possible at state: "+se+", token: "+q);switch(ce[0]){case 1:N.push(q),I.push(Q.yytext),C.push(Q.yylloc),N.push(ce[1]),q=null,K?(q=K,K=null):(F=Q.yyleng,D=Q.yytext,P=Q.yylineno,j=Q.yylloc,B>0&&B--);break;case 2:if(ke=this.productions_[ce[1]][1],De.$=I[I.length-ke],De._$={first_line:C[C.length-(ke||1)].first_line,last_line:C[C.length-1].last_line,first_column:C[C.length-(ke||1)].first_column,last_column:C[C.length-1].last_column},J&&(De._$.range=[C[C.length-(ke||1)].range[0],C[C.length-1].range[1]]),te=this.performAction.apply(De,[D,F,P,X.yy,ce[1],I,C].concat(Y)),typeof te<"u")return te;ke&&(N=N.slice(0,-1*ke*2),I=I.slice(0,-1*ke),C=C.slice(0,-1*ke)),N.push(this.productions_[ce[1]][0]),I.push(De.$),C.push(De._$),Ie=O[N[N.length-2]][N[N.length-1]],N.push(Ie);break;case 3:return!0}}return!0},"parse")},E=function(){var A={EOF:1,parseError:o(function(M,N){if(this.yy.parser)this.yy.parser.parseError(M,N);else throw new Error(M)},"parseError"),setInput:o(function(L,M){return this.yy=M||this.yy||{},this._input=L,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:o(function(){var L=this._input[0];this.yytext+=L,this.yyleng++,this.offset++,this.match+=L,this.matched+=L;var M=L.match(/(?:\r\n?|\n).*/g);return M?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),L},"input"),unput:o(function(L){var M=L.length,N=L.split(/(?:\r\n?|\n)/g);this._input=L+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-M),this.offset-=M;var k=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),N.length-1&&(this.yylineno-=N.length-1);var I=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:N?(N.length===k.length?this.yylloc.first_column:0)+k[k.length-N.length].length-N[0].length:this.yylloc.first_column-M},this.options.ranges&&(this.yylloc.range=[I[0],I[0]+this.yyleng-M]),this.yyleng=this.yytext.length,this},"unput"),more:o(function(){return this._more=!0,this},"more"),reject:o(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:o(function(L){this.unput(this.match.slice(L))},"less"),pastInput:o(function(){var L=this.matched.substr(0,this.matched.length-this.match.length);return(L.length>20?"...":"")+L.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:o(function(){var L=this.match;return L.length<20&&(L+=this._input.substr(0,20-L.length)),(L.substr(0,20)+(L.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:o(function(){var L=this.pastInput(),M=new Array(L.length+1).join("-");return L+this.upcomingInput()+` +`+M+"^"},"showPosition"),test_match:o(function(L,M){var N,k,I;if(this.options.backtrack_lexer&&(I={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(I.yylloc.range=this.yylloc.range.slice(0))),k=L[0].match(/(?:\r\n?|\n).*/g),k&&(this.yylineno+=k.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:k?k[k.length-1].length-k[k.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+L[0].length},this.yytext+=L[0],this.match+=L[0],this.matches=L,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(L[0].length),this.matched+=L[0],N=this.performAction.call(this,this.yy,this,M,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),N)return N;if(this._backtrack){for(var C in I)this[C]=I[C];return!1}return!1},"test_match"),next:o(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var L,M,N,k;this._more||(this.yytext="",this.match="");for(var I=this._currentRules(),C=0;CM[0].length)){if(M=N,k=C,this.options.backtrack_lexer){if(L=this.test_match(N,I[C]),L!==!1)return L;if(this._backtrack){M=!1;continue}else return!1}else if(!this.options.flex)break}return M?(L=this.test_match(M,I[k]),L!==!1?L:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:o(function(){var M=this.next();return M||this.lex()},"lex"),begin:o(function(M){this.conditionStack.push(M)},"begin"),popState:o(function(){var M=this.conditionStack.length-1;return M>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:o(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:o(function(M){return M=this.conditionStack.length-1-Math.abs(M||0),M>=0?this.conditionStack[M]:"INITIAL"},"topState"),pushState:o(function(M){this.begin(M)},"pushState"),stateStackSize:o(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:o(function(M,N,k,I){var C=I;switch(k){case 0:return this.begin("acc_title"),22;break;case 1:return this.popState(),"acc_title_value";break;case 2:return this.begin("acc_descr"),24;break;case 3:return this.popState(),"acc_descr_value";break;case 4:this.begin("acc_descr_multiline");break;case 5:this.popState();break;case 6:return"acc_descr_multiline_value";case 7:return 10;case 8:break;case 9:return 8;case 10:return 28;case 11:return 48;case 12:return 4;case 13:return this.begin("block"),15;break;case 14:return 36;case 15:break;case 16:return 37;case 17:return 34;case 18:return 34;case 19:return 38;case 20:break;case 21:return this.popState(),17;break;case 22:return N.yytext[0];case 23:return 18;case 24:return 19;case 25:return 41;case 26:return 43;case 27:return 43;case 28:return 43;case 29:return 41;case 30:return 41;case 31:return 42;case 32:return 42;case 33:return 42;case 34:return 42;case 35:return 42;case 36:return 43;case 37:return 42;case 38:return 43;case 39:return 44;case 40:return 44;case 41:return 44;case 42:return 44;case 43:return 41;case 44:return 42;case 45:return 43;case 46:return 45;case 47:return 46;case 48:return 47;case 49:return 47;case 50:return 46;case 51:return 46;case 52:return 46;case 53:return 27;case 54:return N.yytext[0];case 55:return 6}},"anonymous"),rules:[/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:[\s]+)/i,/^(?:"[^"%\r\n\v\b\\]+")/i,/^(?:"[^"]*")/i,/^(?:erDiagram\b)/i,/^(?:\{)/i,/^(?:,)/i,/^(?:\s+)/i,/^(?:\b((?:PK)|(?:FK)|(?:UK))\b)/i,/^(?:(.*?)[~](.*?)*[~])/i,/^(?:[\*A-Za-z_][A-Za-z0-9\-_\[\]\(\)]*)/i,/^(?:"[^"]*")/i,/^(?:[\n]+)/i,/^(?:\})/i,/^(?:.)/i,/^(?:\[)/i,/^(?:\])/i,/^(?:one or zero\b)/i,/^(?:one or more\b)/i,/^(?:one or many\b)/i,/^(?:1\+)/i,/^(?:\|o\b)/i,/^(?:zero or one\b)/i,/^(?:zero or more\b)/i,/^(?:zero or many\b)/i,/^(?:0\+)/i,/^(?:\}o\b)/i,/^(?:many\(0\))/i,/^(?:many\(1\))/i,/^(?:many\b)/i,/^(?:\}\|)/i,/^(?:one\b)/i,/^(?:only one\b)/i,/^(?:1\b)/i,/^(?:\|\|)/i,/^(?:o\|)/i,/^(?:o\{)/i,/^(?:\|\{)/i,/^(?:\s*u\b)/i,/^(?:\.\.)/i,/^(?:--)/i,/^(?:to\b)/i,/^(?:optionally to\b)/i,/^(?:\.-)/i,/^(?:-\.)/i,/^(?:[A-Za-z_][A-Za-z0-9\-_]*)/i,/^(?:.)/i,/^(?:$)/i],conditions:{acc_descr_multiline:{rules:[5,6],inclusive:!1},acc_descr:{rules:[3],inclusive:!1},acc_title:{rules:[1],inclusive:!1},block:{rules:[14,15,16,17,18,19,20,21,22],inclusive:!1},INITIAL:{rules:[0,2,4,7,8,9,10,11,12,13,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55],inclusive:!0}}};return A}();T.lexer=E;function _(){this.yy={}}return o(_,"Parser"),_.prototype=T,T.Parser=_,new _}();xD.parser=xD;rne=xD});var Hd,bD,NNe,MNe,ine,INe,ONe,PNe,BNe,FNe,ane,sne=R(()=>{"use strict";ut();_t();bi();Hd=new Map,bD=[],NNe={ZERO_OR_ONE:"ZERO_OR_ONE",ZERO_OR_MORE:"ZERO_OR_MORE",ONE_OR_MORE:"ONE_OR_MORE",ONLY_ONE:"ONLY_ONE",MD_PARENT:"MD_PARENT"},MNe={NON_IDENTIFYING:"NON_IDENTIFYING",IDENTIFYING:"IDENTIFYING"},ine=o(function(t,e=void 0){return Hd.has(t)?!Hd.get(t).alias&&e&&(Hd.get(t).alias=e,V.info(`Add alias '${e}' to entity '${t}'`)):(Hd.set(t,{attributes:[],alias:e}),V.info("Added new entity :",t)),Hd.get(t)},"addEntity"),INe=o(()=>Hd,"getEntities"),ONe=o(function(t,e){let r=ine(t),n;for(n=e.length-1;n>=0;n--)r.attributes.push(e[n]),V.debug("Added attribute ",e[n].attributeName)},"addAttributes"),PNe=o(function(t,e,r,n){let i={entityA:t,roleA:e,entityB:r,relSpec:n};bD.push(i),V.debug("Added new relationship :",i)},"addRelationship"),BNe=o(()=>bD,"getRelationships"),FNe=o(function(){Hd=new Map,bD=[],vr()},"clear"),ane={Cardinality:NNe,Identification:MNe,getConfig:o(()=>de().er,"getConfig"),addEntity:ine,addAttributes:ONe,getEntities:INe,addRelationship:PNe,getRelationships:BNe,clear:FNe,setAccTitle:kr,getAccTitle:Ar,setAccDescription:_r,getAccDescription:Lr,setDiagramTitle:nn,getDiagramTitle:Xr}});var Dl,zNe,$o,one=R(()=>{"use strict";Dl={ONLY_ONE_START:"ONLY_ONE_START",ONLY_ONE_END:"ONLY_ONE_END",ZERO_OR_ONE_START:"ZERO_OR_ONE_START",ZERO_OR_ONE_END:"ZERO_OR_ONE_END",ONE_OR_MORE_START:"ONE_OR_MORE_START",ONE_OR_MORE_END:"ONE_OR_MORE_END",ZERO_OR_MORE_START:"ZERO_OR_MORE_START",ZERO_OR_MORE_END:"ZERO_OR_MORE_END",MD_PARENT_END:"MD_PARENT_END",MD_PARENT_START:"MD_PARENT_START"},zNe=o(function(t,e){let r;t.append("defs").append("marker").attr("id",Dl.MD_PARENT_START).attr("refX",0).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),t.append("defs").append("marker").attr("id",Dl.MD_PARENT_END).attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),t.append("defs").append("marker").attr("id",Dl.ONLY_ONE_START).attr("refX",0).attr("refY",9).attr("markerWidth",18).attr("markerHeight",18).attr("orient","auto").append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M9,0 L9,18 M15,0 L15,18"),t.append("defs").append("marker").attr("id",Dl.ONLY_ONE_END).attr("refX",18).attr("refY",9).attr("markerWidth",18).attr("markerHeight",18).attr("orient","auto").append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M3,0 L3,18 M9,0 L9,18"),r=t.append("defs").append("marker").attr("id",Dl.ZERO_OR_ONE_START).attr("refX",0).attr("refY",9).attr("markerWidth",30).attr("markerHeight",18).attr("orient","auto"),r.append("circle").attr("stroke",e.stroke).attr("fill","white").attr("cx",21).attr("cy",9).attr("r",6),r.append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M9,0 L9,18"),r=t.append("defs").append("marker").attr("id",Dl.ZERO_OR_ONE_END).attr("refX",30).attr("refY",9).attr("markerWidth",30).attr("markerHeight",18).attr("orient","auto"),r.append("circle").attr("stroke",e.stroke).attr("fill","white").attr("cx",9).attr("cy",9).attr("r",6),r.append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M21,0 L21,18"),t.append("defs").append("marker").attr("id",Dl.ONE_OR_MORE_START).attr("refX",18).attr("refY",18).attr("markerWidth",45).attr("markerHeight",36).attr("orient","auto").append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M0,18 Q 18,0 36,18 Q 18,36 0,18 M42,9 L42,27"),t.append("defs").append("marker").attr("id",Dl.ONE_OR_MORE_END).attr("refX",27).attr("refY",18).attr("markerWidth",45).attr("markerHeight",36).attr("orient","auto").append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M3,9 L3,27 M9,18 Q27,0 45,18 Q27,36 9,18"),r=t.append("defs").append("marker").attr("id",Dl.ZERO_OR_MORE_START).attr("refX",18).attr("refY",18).attr("markerWidth",57).attr("markerHeight",36).attr("orient","auto"),r.append("circle").attr("stroke",e.stroke).attr("fill","white").attr("cx",48).attr("cy",18).attr("r",6),r.append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M0,18 Q18,0 36,18 Q18,36 0,18"),r=t.append("defs").append("marker").attr("id",Dl.ZERO_OR_MORE_END).attr("refX",39).attr("refY",18).attr("markerWidth",57).attr("markerHeight",36).attr("orient","auto"),r.append("circle").attr("stroke",e.stroke).attr("fill","white").attr("cx",9).attr("cy",18).attr("r",6),r.append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M21,18 Q39,0 57,18 Q39,36 21,18")},"insertMarkers"),$o={ERMarkers:Dl,insertMarkers:zNe}});var lne,cne=R(()=>{"use strict";lne=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i});function GNe(t){return typeof t=="string"&&lne.test(t)}var une,hne=R(()=>{"use strict";cne();o(GNe,"validate");une=GNe});function fne(t,e=0){return va[t[e+0]]+va[t[e+1]]+va[t[e+2]]+va[t[e+3]]+"-"+va[t[e+4]]+va[t[e+5]]+"-"+va[t[e+6]]+va[t[e+7]]+"-"+va[t[e+8]]+va[t[e+9]]+"-"+va[t[e+10]]+va[t[e+11]]+va[t[e+12]]+va[t[e+13]]+va[t[e+14]]+va[t[e+15]]}var va,dne=R(()=>{"use strict";va=[];for(let t=0;t<256;++t)va.push((t+256).toString(16).slice(1));o(fne,"unsafeStringify")});function $Ne(t){if(!une(t))throw TypeError("Invalid UUID");let e,r=new Uint8Array(16);return r[0]=(e=parseInt(t.slice(0,8),16))>>>24,r[1]=e>>>16&255,r[2]=e>>>8&255,r[3]=e&255,r[4]=(e=parseInt(t.slice(9,13),16))>>>8,r[5]=e&255,r[6]=(e=parseInt(t.slice(14,18),16))>>>8,r[7]=e&255,r[8]=(e=parseInt(t.slice(19,23),16))>>>8,r[9]=e&255,r[10]=(e=parseInt(t.slice(24,36),16))/1099511627776&255,r[11]=e/4294967296&255,r[12]=e>>>24&255,r[13]=e>>>16&255,r[14]=e>>>8&255,r[15]=e&255,r}var pne,mne=R(()=>{"use strict";hne();o($Ne,"parse");pne=$Ne});function VNe(t){t=unescape(encodeURIComponent(t));let e=[];for(let r=0;r{"use strict";dne();mne();o(VNe,"stringToBytes");UNe="6ba7b810-9dad-11d1-80b4-00c04fd430c8",HNe="6ba7b811-9dad-11d1-80b4-00c04fd430c8";o(wD,"v35")});function YNe(t,e,r,n){switch(t){case 0:return e&r^~e&n;case 1:return e^r^n;case 2:return e&r^e&n^r&n;case 3:return e^r^n}}function TD(t,e){return t<>>32-e}function WNe(t){let e=[1518500249,1859775393,2400959708,3395469782],r=[1732584193,4023233417,2562383102,271733878,3285377520];if(typeof t=="string"){let s=unescape(encodeURIComponent(t));t=[];for(let l=0;l>>0;p=d,d=f,f=TD(h,30)>>>0,h=u,u=y}r[0]=r[0]+u>>>0,r[1]=r[1]+h>>>0,r[2]=r[2]+f>>>0,r[3]=r[3]+d>>>0,r[4]=r[4]+p>>>0}return[r[0]>>24&255,r[0]>>16&255,r[0]>>8&255,r[0]&255,r[1]>>24&255,r[1]>>16&255,r[1]>>8&255,r[1]&255,r[2]>>24&255,r[2]>>16&255,r[2]>>8&255,r[2]&255,r[3]>>24&255,r[3]>>16&255,r[3]>>8&255,r[3]&255,r[4]>>24&255,r[4]>>16&255,r[4]>>8&255,r[4]&255]}var yne,vne=R(()=>{"use strict";o(YNe,"f");o(TD,"ROTL");o(WNe,"sha1");yne=WNe});var qNe,kD,xne=R(()=>{"use strict";gne();vne();qNe=wD("v5",80,yne),kD=qNe});var bne=R(()=>{"use strict";xne()});function nMe(t="",e=""){let r=t.replace(XNe,"");return`${Tne(e)}${Tne(r)}${kD(t,rMe)}`}function Tne(t=""){return t.length>0?`${t}-`:""}var XNe,Ii,Fv,jNe,KNe,QNe,ZNe,kne,JNe,wne,eMe,tMe,rMe,Ene,Cne=R(()=>{"use strict";ya();Zt();Vd();_t();ut();xr();one();Yn();rr();bne();XNe=/[^\dA-Za-z](\W)*/g,Ii={},Fv=new Map,jNe=o(function(t){let e=Object.keys(t);for(let r of e)Ii[r]=t[r]},"setConf"),KNe=o((t,e,r)=>{let n=Ii.entityPadding/3,i=Ii.entityPadding/3,a=Ii.fontSize*.85,s=e.node().getBBox(),l=[],u=!1,h=!1,f=0,d=0,p=0,m=0,g=s.height+n*2,y=1;r.forEach(w=>{w.attributeKeyTypeList!==void 0&&w.attributeKeyTypeList.length>0&&(u=!0),w.attributeComment!==void 0&&(h=!0)}),r.forEach(w=>{let S=`${e.node().id}-attr-${y}`,T=0,E=gh(w.attributeType),_=t.append("text").classed("er entityLabel",!0).attr("id",`${S}-type`).attr("x",0).attr("y",0).style("dominant-baseline","middle").style("text-anchor","left").style("font-family",de().fontFamily).style("font-size",a+"px").text(E),A=t.append("text").classed("er entityLabel",!0).attr("id",`${S}-name`).attr("x",0).attr("y",0).style("dominant-baseline","middle").style("text-anchor","left").style("font-family",de().fontFamily).style("font-size",a+"px").text(w.attributeName),L={};L.tn=_,L.nn=A;let M=_.node().getBBox(),N=A.node().getBBox();if(f=Math.max(f,M.width),d=Math.max(d,N.width),T=Math.max(M.height,N.height),u){let k=w.attributeKeyTypeList!==void 0?w.attributeKeyTypeList.join(","):"",I=t.append("text").classed("er entityLabel",!0).attr("id",`${S}-key`).attr("x",0).attr("y",0).style("dominant-baseline","middle").style("text-anchor","left").style("font-family",de().fontFamily).style("font-size",a+"px").text(k);L.kn=I;let C=I.node().getBBox();p=Math.max(p,C.width),T=Math.max(T,C.height)}if(h){let k=t.append("text").classed("er entityLabel",!0).attr("id",`${S}-comment`).attr("x",0).attr("y",0).style("dominant-baseline","middle").style("text-anchor","left").style("font-family",de().fontFamily).style("font-size",a+"px").text(w.attributeComment||"");L.cn=k;let I=k.node().getBBox();m=Math.max(m,I.width),T=Math.max(T,I.height)}L.height=T,l.push(L),g+=T+n*2,y+=1});let v=4;u&&(v+=2),h&&(v+=2);let x=f+d+p+m,b={width:Math.max(Ii.minEntityWidth,Math.max(s.width+Ii.entityPadding*2,x+i*v)),height:r.length>0?g:Math.max(Ii.minEntityHeight,s.height+Ii.entityPadding*2)};if(r.length>0){let w=Math.max(0,(b.width-x-i*v)/(v/2));e.attr("transform","translate("+b.width/2+","+(n+s.height/2)+")");let S=s.height+n*2,T="attributeBoxOdd";l.forEach(E=>{let _=S+n+E.height/2;E.tn.attr("transform","translate("+i+","+_+")");let A=t.insert("rect","#"+E.tn.node().id).classed(`er ${T}`,!0).attr("x",0).attr("y",S).attr("width",f+i*2+w).attr("height",E.height+n*2),L=parseFloat(A.attr("x"))+parseFloat(A.attr("width"));E.nn.attr("transform","translate("+(L+i)+","+_+")");let M=t.insert("rect","#"+E.nn.node().id).classed(`er ${T}`,!0).attr("x",L).attr("y",S).attr("width",d+i*2+w).attr("height",E.height+n*2),N=parseFloat(M.attr("x"))+parseFloat(M.attr("width"));if(u){E.kn.attr("transform","translate("+(N+i)+","+_+")");let k=t.insert("rect","#"+E.kn.node().id).classed(`er ${T}`,!0).attr("x",N).attr("y",S).attr("width",p+i*2+w).attr("height",E.height+n*2);N=parseFloat(k.attr("x"))+parseFloat(k.attr("width"))}h&&(E.cn.attr("transform","translate("+(N+i)+","+_+")"),t.insert("rect","#"+E.cn.node().id).classed(`er ${T}`,"true").attr("x",N).attr("y",S).attr("width",m+i*2+w).attr("height",E.height+n*2)),S+=E.height+n*2,T=T==="attributeBoxOdd"?"attributeBoxEven":"attributeBoxOdd"})}else b.height=Math.max(Ii.minEntityHeight,g),e.attr("transform","translate("+b.width/2+","+b.height/2+")");return b},"drawAttributes"),QNe=o(function(t,e,r){let n=[...e.keys()],i;return n.forEach(function(a){let s=nMe(a,"entity");Fv.set(a,s);let l=t.append("g").attr("id",s);i=i===void 0?s:i;let u="text-"+s,h=l.append("text").classed("er entityLabel",!0).attr("id",u).attr("x",0).attr("y",0).style("dominant-baseline","middle").style("text-anchor","middle").style("font-family",de().fontFamily).style("font-size",Ii.fontSize+"px").text(e.get(a).alias??a),{width:f,height:d}=KNe(l,h,e.get(a).attributes),m=l.insert("rect","#"+u).classed("er entityBox",!0).attr("x",0).attr("y",0).attr("width",f).attr("height",d).node().getBBox();r.setNode(s,{width:m.width,height:m.height,shape:"rect",id:s})}),i},"drawEntities"),ZNe=o(function(t,e){e.nodes().forEach(function(r){r!==void 0&&e.node(r)!==void 0&&t.select("#"+r).attr("transform","translate("+(e.node(r).x-e.node(r).width/2)+","+(e.node(r).y-e.node(r).height/2)+" )")})},"adjustEntities"),kne=o(function(t){return(t.entityA+t.roleA+t.entityB).replace(/\s/g,"")},"getEdgeName"),JNe=o(function(t,e){return t.forEach(function(r){e.setEdge(Fv.get(r.entityA),Fv.get(r.entityB),{relationship:r},kne(r))}),t},"addRelationships"),wne=0,eMe=o(function(t,e,r,n,i){wne++;let a=r.edge(Fv.get(e.entityA),Fv.get(e.entityB),kne(e)),s=ha().x(function(y){return y.x}).y(function(y){return y.y}).curve(vs),l=t.insert("path","#"+n).classed("er relationshipLine",!0).attr("d",s(a.points)).style("stroke",Ii.stroke).style("fill","none");e.relSpec.relType===i.db.Identification.NON_IDENTIFYING&&l.attr("stroke-dasharray","8,8");let u="";switch(Ii.arrowMarkerAbsolute&&(u=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search,u=u.replace(/\(/g,"\\("),u=u.replace(/\)/g,"\\)")),e.relSpec.cardA){case i.db.Cardinality.ZERO_OR_ONE:l.attr("marker-end","url("+u+"#"+$o.ERMarkers.ZERO_OR_ONE_END+")");break;case i.db.Cardinality.ZERO_OR_MORE:l.attr("marker-end","url("+u+"#"+$o.ERMarkers.ZERO_OR_MORE_END+")");break;case i.db.Cardinality.ONE_OR_MORE:l.attr("marker-end","url("+u+"#"+$o.ERMarkers.ONE_OR_MORE_END+")");break;case i.db.Cardinality.ONLY_ONE:l.attr("marker-end","url("+u+"#"+$o.ERMarkers.ONLY_ONE_END+")");break;case i.db.Cardinality.MD_PARENT:l.attr("marker-end","url("+u+"#"+$o.ERMarkers.MD_PARENT_END+")");break}switch(e.relSpec.cardB){case i.db.Cardinality.ZERO_OR_ONE:l.attr("marker-start","url("+u+"#"+$o.ERMarkers.ZERO_OR_ONE_START+")");break;case i.db.Cardinality.ZERO_OR_MORE:l.attr("marker-start","url("+u+"#"+$o.ERMarkers.ZERO_OR_MORE_START+")");break;case i.db.Cardinality.ONE_OR_MORE:l.attr("marker-start","url("+u+"#"+$o.ERMarkers.ONE_OR_MORE_START+")");break;case i.db.Cardinality.ONLY_ONE:l.attr("marker-start","url("+u+"#"+$o.ERMarkers.ONLY_ONE_START+")");break;case i.db.Cardinality.MD_PARENT:l.attr("marker-start","url("+u+"#"+$o.ERMarkers.MD_PARENT_START+")");break}let h=l.node().getTotalLength(),f=l.node().getPointAtLength(h*.5),d="rel"+wne,p=e.roleA.split(/
    /g),m=t.append("text").classed("er relationshipLabel",!0).attr("id",d).attr("x",f.x).attr("y",f.y).style("text-anchor","middle").style("dominant-baseline","middle").style("font-family",de().fontFamily).style("font-size",Ii.fontSize+"px");if(p.length==1)m.text(e.roleA);else{let y=-(p.length-1)*.5;p.forEach((v,x)=>{m.append("tspan").attr("x",f.x).attr("dy",`${x===0?y:1}em`).text(v)})}let g=m.node().getBBox();t.insert("rect","#"+d).classed("er relationshipLabelBox",!0).attr("x",f.x-g.width/2).attr("y",f.y-g.height/2).attr("width",g.width).attr("height",g.height)},"drawRelationshipFromLayout"),tMe=o(function(t,e,r,n){Ii=de().er,V.info("Drawing ER diagram");let i=de().securityLevel,a;i==="sandbox"&&(a=$e("#i"+e));let l=(i==="sandbox"?$e(a.nodes()[0].contentDocument.body):$e("body")).select(`[id='${e}']`);$o.insertMarkers(l,Ii);let u;u=new lr({multigraph:!0,directed:!0,compound:!1}).setGraph({rankdir:Ii.layoutDirection,marginx:20,marginy:20,nodesep:100,edgesep:100,ranksep:100}).setDefaultEdgeLabel(function(){return{}});let h=QNe(l,n.db.getEntities(),u),f=JNe(n.db.getRelationships(),u);lo(u),ZNe(l,u),f.forEach(function(y){eMe(l,y,u,h,n)});let d=Ii.diagramPadding;Lt.insertTitle(l,"entityTitleText",Ii.titleTopMargin,n.db.getDiagramTitle());let p=l.node().getBBox(),m=p.width+d*2,g=p.height+d*2;Sr(l,g,m,Ii.useMaxWidth),l.attr("viewBox",`${p.x-d} ${p.y-d} ${m} ${g}`)},"draw"),rMe="28e9f9db-3c8d-5aa5-9faf-44286ae5937c";o(nMe,"generateId");o(Tne,"strWithHyphen");Ene={setConf:jNe,draw:tMe}});var iMe,Sne,Ane=R(()=>{"use strict";iMe=o(t=>` + .entityBox { + fill: ${t.mainBkg}; + stroke: ${t.nodeBorder}; + } + + .attributeBoxOdd { + fill: ${t.attributeBackgroundColorOdd}; + stroke: ${t.nodeBorder}; + } + + .attributeBoxEven { + fill: ${t.attributeBackgroundColorEven}; + stroke: ${t.nodeBorder}; + } + + .relationshipLabelBox { + fill: ${t.tertiaryColor}; + opacity: 0.7; + background-color: ${t.tertiaryColor}; + rect { + opacity: 0.5; + } + } + + .relationshipLine { + stroke: ${t.lineColor}; + } + + .entityTitleText { + text-anchor: middle; + font-size: 18px; + fill: ${t.textColor}; + } + #MD_PARENT_START { + fill: #f5f5f5 !important; + stroke: ${t.lineColor} !important; + stroke-width: 1; + } + #MD_PARENT_END { + fill: #f5f5f5 !important; + stroke: ${t.lineColor} !important; + stroke-width: 1; + } + +`,"getStyles"),Sne=iMe});var _ne={};hr(_ne,{diagram:()=>aMe});var aMe,Lne=R(()=>{"use strict";nne();sne();Cne();Ane();aMe={parser:rne,db:ane,renderer:Ene,styles:Sne}});function Xn(t){return typeof t=="object"&&t!==null&&typeof t.$type=="string"}function xa(t){return typeof t=="object"&&t!==null&&typeof t.$refText=="string"}function ED(t){return typeof t=="object"&&t!==null&&typeof t.name=="string"&&typeof t.type=="string"&&typeof t.path=="string"}function Wd(t){return typeof t=="object"&&t!==null&&Xn(t.container)&&xa(t.reference)&&typeof t.message=="string"}function co(t){return typeof t=="object"&&t!==null&&Array.isArray(t.content)}function ef(t){return typeof t=="object"&&t!==null&&typeof t.tokenType=="object"}function zv(t){return co(t)&&typeof t.fullText=="string"}var Yd,Vo=R(()=>{"use strict";o(Xn,"isAstNode");o(xa,"isReference");o(ED,"isAstNodeDescription");o(Wd,"isLinkingError");Yd=class{static{o(this,"AbstractAstReflection")}constructor(){this.subtypes={},this.allSubtypes={}}isInstance(e,r){return Xn(e)&&this.isSubtype(e.$type,r)}isSubtype(e,r){if(e===r)return!0;let n=this.subtypes[e];n||(n=this.subtypes[e]={});let i=n[r];if(i!==void 0)return i;{let a=this.computeIsSubtype(e,r);return n[r]=a,a}}getAllSubTypes(e){let r=this.allSubtypes[e];if(r)return r;{let n=this.getAllTypes(),i=[];for(let a of n)this.isSubtype(a,e)&&i.push(a);return this.allSubtypes[e]=i,i}}};o(co,"isCompositeCstNode");o(ef,"isLeafCstNode");o(zv,"isRootCstNode")});function cMe(t){return typeof t=="string"?t:typeof t>"u"?"undefined":typeof t.toString=="function"?t.toString():Object.prototype.toString.call(t)}function hT(t){return!!t&&typeof t[Symbol.iterator]=="function"}function Kr(...t){if(t.length===1){let e=t[0];if(e instanceof uo)return e;if(hT(e))return new uo(()=>e[Symbol.iterator](),r=>r.next());if(typeof e.length=="number")return new uo(()=>({index:0}),r=>r.index1?new uo(()=>({collIndex:0,arrIndex:0}),e=>{do{if(e.iterator){let r=e.iterator.next();if(!r.done)return r;e.iterator=void 0}if(e.array){if(e.arrIndex{"use strict";uo=class t{static{o(this,"StreamImpl")}constructor(e,r){this.startFn=e,this.nextFn=r}iterator(){let e={state:this.startFn(),next:o(()=>this.nextFn(e.state),"next"),[Symbol.iterator]:()=>e};return e}[Symbol.iterator](){return this.iterator()}isEmpty(){return!!this.iterator().next().done}count(){let e=this.iterator(),r=0,n=e.next();for(;!n.done;)r++,n=e.next();return r}toArray(){let e=[],r=this.iterator(),n;do n=r.next(),n.value!==void 0&&e.push(n.value);while(!n.done);return e}toSet(){return new Set(this)}toMap(e,r){let n=this.map(i=>[e?e(i):i,r?r(i):i]);return new Map(n)}toString(){return this.join()}concat(e){let r=e[Symbol.iterator]();return new t(()=>({first:this.startFn(),firstDone:!1}),n=>{let i;if(!n.firstDone){do if(i=this.nextFn(n.first),!i.done)return i;while(!i.done);n.firstDone=!0}do if(i=r.next(),!i.done)return i;while(!i.done);return Ja})}join(e=","){let r=this.iterator(),n="",i,a=!1;do i=r.next(),i.done||(a&&(n+=e),n+=cMe(i.value)),a=!0;while(!i.done);return n}indexOf(e,r=0){let n=this.iterator(),i=0,a=n.next();for(;!a.done;){if(i>=r&&a.value===e)return i;a=n.next(),i++}return-1}every(e){let r=this.iterator(),n=r.next();for(;!n.done;){if(!e(n.value))return!1;n=r.next()}return!0}some(e){let r=this.iterator(),n=r.next();for(;!n.done;){if(e(n.value))return!0;n=r.next()}return!1}forEach(e){let r=this.iterator(),n=0,i=r.next();for(;!i.done;)e(i.value,n),i=r.next(),n++}map(e){return new t(this.startFn,r=>{let{done:n,value:i}=this.nextFn(r);return n?Ja:{done:!1,value:e(i)}})}filter(e){return new t(this.startFn,r=>{let n;do if(n=this.nextFn(r),!n.done&&e(n.value))return n;while(!n.done);return Ja})}nonNullable(){return this.filter(e=>e!=null)}reduce(e,r){let n=this.iterator(),i=r,a=n.next();for(;!a.done;)i===void 0?i=a.value:i=e(i,a.value),a=n.next();return i}reduceRight(e,r){return this.recursiveReduce(this.iterator(),e,r)}recursiveReduce(e,r,n){let i=e.next();if(i.done)return n;let a=this.recursiveReduce(e,r,n);return a===void 0?i.value:r(a,i.value)}find(e){let r=this.iterator(),n=r.next();for(;!n.done;){if(e(n.value))return n.value;n=r.next()}}findIndex(e){let r=this.iterator(),n=0,i=r.next();for(;!i.done;){if(e(i.value))return n;i=r.next(),n++}return-1}includes(e){let r=this.iterator(),n=r.next();for(;!n.done;){if(n.value===e)return!0;n=r.next()}return!1}flatMap(e){return new t(()=>({this:this.startFn()}),r=>{do{if(r.iterator){let a=r.iterator.next();if(a.done)r.iterator=void 0;else return a}let{done:n,value:i}=this.nextFn(r.this);if(!n){let a=e(i);if(hT(a))r.iterator=a[Symbol.iterator]();else return{done:!1,value:a}}}while(r.iterator);return Ja})}flat(e){if(e===void 0&&(e=1),e<=0)return this;let r=e>1?this.flat(e-1):this;return new t(()=>({this:r.startFn()}),n=>{do{if(n.iterator){let s=n.iterator.next();if(s.done)n.iterator=void 0;else return s}let{done:i,value:a}=r.nextFn(n.this);if(!i)if(hT(a))n.iterator=a[Symbol.iterator]();else return{done:!1,value:a}}while(n.iterator);return Ja})}head(){let r=this.iterator().next();if(!r.done)return r.value}tail(e=1){return new t(()=>{let r=this.startFn();for(let n=0;n({size:0,state:this.startFn()}),r=>(r.size++,r.size>e?Ja:this.nextFn(r.state)))}distinct(e){let r=new Set;return this.filter(n=>{let i=e?e(n):n;return r.has(i)?!1:(r.add(i),!0)})}exclude(e,r){let n=new Set;for(let i of e){let a=r?r(i):i;n.add(a)}return this.filter(i=>{let a=r?r(i):i;return!n.has(a)})}};o(cMe,"toString");o(hT,"isIterable");Gv=new uo(()=>{},()=>Ja),Ja=Object.freeze({done:!0,value:void 0});o(Kr,"stream");Cc=class extends uo{static{o(this,"TreeStreamImpl")}constructor(e,r,n){super(()=>({iterators:n?.includeRoot?[[e][Symbol.iterator]()]:[r(e)[Symbol.iterator]()],pruned:!1}),i=>{for(i.pruned&&(i.iterators.pop(),i.pruned=!1);i.iterators.length>0;){let s=i.iterators[i.iterators.length-1].next();if(s.done)i.iterators.pop();else return i.iterators.push(r(s.value)[Symbol.iterator]()),s}return Ja})}iterator(){let e={state:this.startFn(),next:o(()=>this.nextFn(e.state),"next"),prune:o(()=>{e.state.pruned=!0},"prune"),[Symbol.iterator]:()=>e};return e}};(function(t){function e(a){return a.reduce((s,l)=>s+l,0)}o(e,"sum"),t.sum=e;function r(a){return a.reduce((s,l)=>s*l,0)}o(r,"product"),t.product=r;function n(a){return a.reduce((s,l)=>Math.min(s,l))}o(n,"min"),t.min=n;function i(a){return a.reduce((s,l)=>Math.max(s,l))}o(i,"max"),t.max=i})(Fm||(Fm={}))});var dT={};hr(dT,{DefaultNameRegexp:()=>fT,RangeComparison:()=>Mu,compareRange:()=>Mne,findCommentNode:()=>_D,findDeclarationNodeAtOffset:()=>hMe,findLeafNodeAtOffset:()=>LD,findLeafNodeBeforeOffset:()=>Ine,flattenCst:()=>uMe,getInteriorNodes:()=>pMe,getNextNode:()=>fMe,getPreviousNode:()=>Pne,getStartlineNode:()=>dMe,inRange:()=>AD,isChildNode:()=>SD,isCommentNode:()=>CD,streamCst:()=>qd,toDocumentSegment:()=>Xd,tokenToRange:()=>zm});function qd(t){return new Cc(t,e=>co(e)?e.content:[],{includeRoot:!0})}function uMe(t){return qd(t).filter(ef)}function SD(t,e){for(;t.container;)if(t=t.container,t===e)return!0;return!1}function zm(t){return{start:{character:t.startColumn-1,line:t.startLine-1},end:{character:t.endColumn,line:t.endLine-1}}}function Xd(t){if(!t)return;let{offset:e,end:r,range:n}=t;return{range:n,offset:e,end:r,length:r-e}}function Mne(t,e){if(t.end.linee.end.line||t.start.line===e.end.line&&t.start.character>e.end.character)return Mu.After;let r=t.start.line>e.start.line||t.start.line===e.start.line&&t.start.character>=e.start.character,n=t.end.lineMu.After}function hMe(t,e,r=fT){if(t){if(e>0){let n=e-t.offset,i=t.text.charAt(n);r.test(i)||e--}return LD(t,e)}}function _D(t,e){if(t){let r=Pne(t,!0);if(r&&CD(r,e))return r;if(zv(t)){let n=t.content.findIndex(i=>!i.hidden);for(let i=n-1;i>=0;i--){let a=t.content[i];if(CD(a,e))return a}}}}function CD(t,e){return ef(t)&&e.includes(t.tokenType.name)}function LD(t,e){if(ef(t))return t;if(co(t)){let r=One(t,e,!1);if(r)return LD(r,e)}}function Ine(t,e){if(ef(t))return t;if(co(t)){let r=One(t,e,!0);if(r)return Ine(r,e)}}function One(t,e,r){let n=0,i=t.content.length-1,a;for(;n<=i;){let s=Math.floor((n+i)/2),l=t.content[s];if(l.offset<=e&&l.end>e)return l;l.end<=e?(a=r?l:void 0,n=s+1):i=s-1}return a}function Pne(t,e=!0){for(;t.container;){let r=t.container,n=r.content.indexOf(t);for(;n>0;){n--;let i=r.content[n];if(e||!i.hidden)return i}t=r}}function fMe(t,e=!0){for(;t.container;){let r=t.container,n=r.content.indexOf(t),i=r.content.length-1;for(;n{"use strict";Vo();Ds();o(qd,"streamCst");o(uMe,"flattenCst");o(SD,"isChildNode");o(zm,"tokenToRange");o(Xd,"toDocumentSegment");(function(t){t[t.Before=0]="Before",t[t.After=1]="After",t[t.OverlapFront=2]="OverlapFront",t[t.OverlapBack=3]="OverlapBack",t[t.Inside=4]="Inside"})(Mu||(Mu={}));o(Mne,"compareRange");o(AD,"inRange");fT=/^[\w\p{L}]$/u;o(hMe,"findDeclarationNodeAtOffset");o(_D,"findCommentNode");o(CD,"isCommentNode");o(LD,"findLeafNodeAtOffset");o(Ine,"findLeafNodeBeforeOffset");o(One,"binarySearch");o(Pne,"getPreviousNode");o(fMe,"getNextNode");o(dMe,"getStartlineNode");o(pMe,"getInteriorNodes");o(mMe,"getCommonParent");o(Nne,"getParentChain")});function tf(t){throw new Error("Error! The input value was not handled.")}var jd,pT=R(()=>{"use strict";jd=class extends Error{static{o(this,"ErrorWithLocation")}constructor(e,r){super(e?`${r} at ${e.range.start.line}:${e.range.start.character}`:r)}};o(tf,"assertUnreachable")});var Yv={};hr(Yv,{AbstractElement:()=>RD,AbstractRule:()=>$v,AbstractType:()=>Vv,Action:()=>aR,Alternatives:()=>sR,ArrayLiteral:()=>ND,ArrayType:()=>MD,Assignment:()=>oR,BooleanLiteral:()=>OD,CharacterRange:()=>lR,Condition:()=>mT,Conjunction:()=>BD,CrossReference:()=>uR,Disjunction:()=>zD,EndOfFile:()=>hR,Grammar:()=>$D,GrammarImport:()=>Fne,Group:()=>dR,InferredType:()=>VD,Interface:()=>UD,Keyword:()=>pR,LangiumGrammarAstReflection:()=>Gm,LangiumGrammarTerminals:()=>gMe,NamedArgument:()=>zne,NegatedToken:()=>mR,Negation:()=>HD,NumberLiteral:()=>WD,Parameter:()=>qD,ParameterReference:()=>XD,ParserRule:()=>KD,ReferenceType:()=>QD,RegexToken:()=>yR,ReturnType:()=>Gne,RuleCall:()=>xR,SimpleType:()=>eR,StringLiteral:()=>tR,TerminalAlternatives:()=>bR,TerminalGroup:()=>TR,TerminalRule:()=>yT,TerminalRuleCall:()=>ER,Type:()=>rR,TypeAttribute:()=>$ne,TypeDefinition:()=>DD,UnionType:()=>nR,UnorderedGroup:()=>CR,UntilToken:()=>SR,ValueLiteral:()=>gT,Wildcard:()=>_R,isAbstractElement:()=>Uv,isAbstractRule:()=>yMe,isAbstractType:()=>vMe,isAction:()=>Iu,isAlternatives:()=>wT,isArrayLiteral:()=>kMe,isArrayType:()=>ID,isAssignment:()=>Nl,isBooleanLiteral:()=>PD,isCharacterRange:()=>cR,isCondition:()=>xMe,isConjunction:()=>FD,isCrossReference:()=>Kd,isDisjunction:()=>GD,isEndOfFile:()=>fR,isFeatureName:()=>bMe,isGrammar:()=>EMe,isGrammarImport:()=>CMe,isGroup:()=>rf,isInferredType:()=>vT,isInterface:()=>xT,isKeyword:()=>Ho,isNamedArgument:()=>SMe,isNegatedToken:()=>gR,isNegation:()=>YD,isNumberLiteral:()=>AMe,isParameter:()=>_Me,isParameterReference:()=>jD,isParserRule:()=>Oa,isPrimitiveType:()=>Bne,isReferenceType:()=>ZD,isRegexToken:()=>vR,isReturnType:()=>JD,isRuleCall:()=>Ml,isSimpleType:()=>bT,isStringLiteral:()=>LMe,isTerminalAlternatives:()=>wR,isTerminalGroup:()=>kR,isTerminalRule:()=>Uo,isTerminalRuleCall:()=>TT,isType:()=>Hv,isTypeAttribute:()=>DMe,isTypeDefinition:()=>wMe,isUnionType:()=>iR,isUnorderedGroup:()=>kT,isUntilToken:()=>AR,isValueLiteral:()=>TMe,isWildcard:()=>LR,reflection:()=>Kt});function yMe(t){return Kt.isInstance(t,$v)}function vMe(t){return Kt.isInstance(t,Vv)}function xMe(t){return Kt.isInstance(t,mT)}function bMe(t){return Bne(t)||t==="current"||t==="entry"||t==="extends"||t==="false"||t==="fragment"||t==="grammar"||t==="hidden"||t==="import"||t==="interface"||t==="returns"||t==="terminal"||t==="true"||t==="type"||t==="infer"||t==="infers"||t==="with"||typeof t=="string"&&/\^?[_a-zA-Z][\w_]*/.test(t)}function Bne(t){return t==="string"||t==="number"||t==="boolean"||t==="Date"||t==="bigint"}function wMe(t){return Kt.isInstance(t,DD)}function TMe(t){return Kt.isInstance(t,gT)}function Uv(t){return Kt.isInstance(t,RD)}function kMe(t){return Kt.isInstance(t,ND)}function ID(t){return Kt.isInstance(t,MD)}function PD(t){return Kt.isInstance(t,OD)}function FD(t){return Kt.isInstance(t,BD)}function GD(t){return Kt.isInstance(t,zD)}function EMe(t){return Kt.isInstance(t,$D)}function CMe(t){return Kt.isInstance(t,Fne)}function vT(t){return Kt.isInstance(t,VD)}function xT(t){return Kt.isInstance(t,UD)}function SMe(t){return Kt.isInstance(t,zne)}function YD(t){return Kt.isInstance(t,HD)}function AMe(t){return Kt.isInstance(t,WD)}function _Me(t){return Kt.isInstance(t,qD)}function jD(t){return Kt.isInstance(t,XD)}function Oa(t){return Kt.isInstance(t,KD)}function ZD(t){return Kt.isInstance(t,QD)}function JD(t){return Kt.isInstance(t,Gne)}function bT(t){return Kt.isInstance(t,eR)}function LMe(t){return Kt.isInstance(t,tR)}function Uo(t){return Kt.isInstance(t,yT)}function Hv(t){return Kt.isInstance(t,rR)}function DMe(t){return Kt.isInstance(t,$ne)}function iR(t){return Kt.isInstance(t,nR)}function Iu(t){return Kt.isInstance(t,aR)}function wT(t){return Kt.isInstance(t,sR)}function Nl(t){return Kt.isInstance(t,oR)}function cR(t){return Kt.isInstance(t,lR)}function Kd(t){return Kt.isInstance(t,uR)}function fR(t){return Kt.isInstance(t,hR)}function rf(t){return Kt.isInstance(t,dR)}function Ho(t){return Kt.isInstance(t,pR)}function gR(t){return Kt.isInstance(t,mR)}function vR(t){return Kt.isInstance(t,yR)}function Ml(t){return Kt.isInstance(t,xR)}function wR(t){return Kt.isInstance(t,bR)}function kR(t){return Kt.isInstance(t,TR)}function TT(t){return Kt.isInstance(t,ER)}function kT(t){return Kt.isInstance(t,CR)}function AR(t){return Kt.isInstance(t,SR)}function LR(t){return Kt.isInstance(t,_R)}var gMe,$v,Vv,mT,DD,gT,RD,ND,MD,OD,BD,zD,$D,Fne,VD,UD,zne,HD,WD,qD,XD,KD,QD,Gne,eR,tR,yT,rR,$ne,nR,aR,sR,oR,lR,uR,hR,dR,pR,mR,yR,xR,bR,TR,ER,CR,SR,_R,Gm,Kt,Sc=R(()=>{"use strict";Vo();gMe={ID:/\^?[_a-zA-Z][\w_]*/,STRING:/"(\\.|[^"\\])*"|'(\\.|[^'\\])*'/,NUMBER:/NaN|-?((\d*\.\d+|\d+)([Ee][+-]?\d+)?|Infinity)/,RegexLiteral:/\/(?![*+?])(?:[^\r\n\[/\\]|\\.|\[(?:[^\r\n\]\\]|\\.)*\])+\/[a-z]*/,WS:/\s+/,ML_COMMENT:/\/\*[\s\S]*?\*\//,SL_COMMENT:/\/\/[^\n\r]*/},$v="AbstractRule";o(yMe,"isAbstractRule");Vv="AbstractType";o(vMe,"isAbstractType");mT="Condition";o(xMe,"isCondition");o(bMe,"isFeatureName");o(Bne,"isPrimitiveType");DD="TypeDefinition";o(wMe,"isTypeDefinition");gT="ValueLiteral";o(TMe,"isValueLiteral");RD="AbstractElement";o(Uv,"isAbstractElement");ND="ArrayLiteral";o(kMe,"isArrayLiteral");MD="ArrayType";o(ID,"isArrayType");OD="BooleanLiteral";o(PD,"isBooleanLiteral");BD="Conjunction";o(FD,"isConjunction");zD="Disjunction";o(GD,"isDisjunction");$D="Grammar";o(EMe,"isGrammar");Fne="GrammarImport";o(CMe,"isGrammarImport");VD="InferredType";o(vT,"isInferredType");UD="Interface";o(xT,"isInterface");zne="NamedArgument";o(SMe,"isNamedArgument");HD="Negation";o(YD,"isNegation");WD="NumberLiteral";o(AMe,"isNumberLiteral");qD="Parameter";o(_Me,"isParameter");XD="ParameterReference";o(jD,"isParameterReference");KD="ParserRule";o(Oa,"isParserRule");QD="ReferenceType";o(ZD,"isReferenceType");Gne="ReturnType";o(JD,"isReturnType");eR="SimpleType";o(bT,"isSimpleType");tR="StringLiteral";o(LMe,"isStringLiteral");yT="TerminalRule";o(Uo,"isTerminalRule");rR="Type";o(Hv,"isType");$ne="TypeAttribute";o(DMe,"isTypeAttribute");nR="UnionType";o(iR,"isUnionType");aR="Action";o(Iu,"isAction");sR="Alternatives";o(wT,"isAlternatives");oR="Assignment";o(Nl,"isAssignment");lR="CharacterRange";o(cR,"isCharacterRange");uR="CrossReference";o(Kd,"isCrossReference");hR="EndOfFile";o(fR,"isEndOfFile");dR="Group";o(rf,"isGroup");pR="Keyword";o(Ho,"isKeyword");mR="NegatedToken";o(gR,"isNegatedToken");yR="RegexToken";o(vR,"isRegexToken");xR="RuleCall";o(Ml,"isRuleCall");bR="TerminalAlternatives";o(wR,"isTerminalAlternatives");TR="TerminalGroup";o(kR,"isTerminalGroup");ER="TerminalRuleCall";o(TT,"isTerminalRuleCall");CR="UnorderedGroup";o(kT,"isUnorderedGroup");SR="UntilToken";o(AR,"isUntilToken");_R="Wildcard";o(LR,"isWildcard");Gm=class extends Yd{static{o(this,"LangiumGrammarAstReflection")}getAllTypes(){return["AbstractElement","AbstractRule","AbstractType","Action","Alternatives","ArrayLiteral","ArrayType","Assignment","BooleanLiteral","CharacterRange","Condition","Conjunction","CrossReference","Disjunction","EndOfFile","Grammar","GrammarImport","Group","InferredType","Interface","Keyword","NamedArgument","NegatedToken","Negation","NumberLiteral","Parameter","ParameterReference","ParserRule","ReferenceType","RegexToken","ReturnType","RuleCall","SimpleType","StringLiteral","TerminalAlternatives","TerminalGroup","TerminalRule","TerminalRuleCall","Type","TypeAttribute","TypeDefinition","UnionType","UnorderedGroup","UntilToken","ValueLiteral","Wildcard"]}computeIsSubtype(e,r){switch(e){case aR:case sR:case oR:case lR:case uR:case hR:case dR:case pR:case mR:case yR:case xR:case bR:case TR:case ER:case CR:case SR:case _R:return this.isSubtype(RD,r);case ND:case WD:case tR:return this.isSubtype(gT,r);case MD:case QD:case eR:case nR:return this.isSubtype(DD,r);case OD:return this.isSubtype(mT,r)||this.isSubtype(gT,r);case BD:case zD:case HD:case XD:return this.isSubtype(mT,r);case VD:case UD:case rR:return this.isSubtype(Vv,r);case KD:return this.isSubtype($v,r)||this.isSubtype(Vv,r);case yT:return this.isSubtype($v,r);default:return!1}}getReferenceType(e){let r=`${e.container.$type}:${e.property}`;switch(r){case"Action:type":case"CrossReference:type":case"Interface:superTypes":case"ParserRule:returnType":case"SimpleType:typeRef":return Vv;case"Grammar:hiddenTokens":case"ParserRule:hiddenTokens":case"RuleCall:rule":return $v;case"Grammar:usedGrammars":return $D;case"NamedArgument:parameter":case"ParameterReference:parameter":return qD;case"TerminalRuleCall:rule":return yT;default:throw new Error(`${r} is not a valid reference id.`)}}getTypeMetaData(e){switch(e){case"AbstractElement":return{name:"AbstractElement",properties:[{name:"cardinality"},{name:"lookahead"}]};case"ArrayLiteral":return{name:"ArrayLiteral",properties:[{name:"elements",defaultValue:[]}]};case"ArrayType":return{name:"ArrayType",properties:[{name:"elementType"}]};case"BooleanLiteral":return{name:"BooleanLiteral",properties:[{name:"true",defaultValue:!1}]};case"Conjunction":return{name:"Conjunction",properties:[{name:"left"},{name:"right"}]};case"Disjunction":return{name:"Disjunction",properties:[{name:"left"},{name:"right"}]};case"Grammar":return{name:"Grammar",properties:[{name:"definesHiddenTokens",defaultValue:!1},{name:"hiddenTokens",defaultValue:[]},{name:"imports",defaultValue:[]},{name:"interfaces",defaultValue:[]},{name:"isDeclared",defaultValue:!1},{name:"name"},{name:"rules",defaultValue:[]},{name:"types",defaultValue:[]},{name:"usedGrammars",defaultValue:[]}]};case"GrammarImport":return{name:"GrammarImport",properties:[{name:"path"}]};case"InferredType":return{name:"InferredType",properties:[{name:"name"}]};case"Interface":return{name:"Interface",properties:[{name:"attributes",defaultValue:[]},{name:"name"},{name:"superTypes",defaultValue:[]}]};case"NamedArgument":return{name:"NamedArgument",properties:[{name:"calledByName",defaultValue:!1},{name:"parameter"},{name:"value"}]};case"Negation":return{name:"Negation",properties:[{name:"value"}]};case"NumberLiteral":return{name:"NumberLiteral",properties:[{name:"value"}]};case"Parameter":return{name:"Parameter",properties:[{name:"name"}]};case"ParameterReference":return{name:"ParameterReference",properties:[{name:"parameter"}]};case"ParserRule":return{name:"ParserRule",properties:[{name:"dataType"},{name:"definesHiddenTokens",defaultValue:!1},{name:"definition"},{name:"entry",defaultValue:!1},{name:"fragment",defaultValue:!1},{name:"hiddenTokens",defaultValue:[]},{name:"inferredType"},{name:"name"},{name:"parameters",defaultValue:[]},{name:"returnType"},{name:"wildcard",defaultValue:!1}]};case"ReferenceType":return{name:"ReferenceType",properties:[{name:"referenceType"}]};case"ReturnType":return{name:"ReturnType",properties:[{name:"name"}]};case"SimpleType":return{name:"SimpleType",properties:[{name:"primitiveType"},{name:"stringType"},{name:"typeRef"}]};case"StringLiteral":return{name:"StringLiteral",properties:[{name:"value"}]};case"TerminalRule":return{name:"TerminalRule",properties:[{name:"definition"},{name:"fragment",defaultValue:!1},{name:"hidden",defaultValue:!1},{name:"name"},{name:"type"}]};case"Type":return{name:"Type",properties:[{name:"name"},{name:"type"}]};case"TypeAttribute":return{name:"TypeAttribute",properties:[{name:"defaultValue"},{name:"isOptional",defaultValue:!1},{name:"name"},{name:"type"}]};case"UnionType":return{name:"UnionType",properties:[{name:"types",defaultValue:[]}]};case"Action":return{name:"Action",properties:[{name:"cardinality"},{name:"feature"},{name:"inferredType"},{name:"lookahead"},{name:"operator"},{name:"type"}]};case"Alternatives":return{name:"Alternatives",properties:[{name:"cardinality"},{name:"elements",defaultValue:[]},{name:"lookahead"}]};case"Assignment":return{name:"Assignment",properties:[{name:"cardinality"},{name:"feature"},{name:"lookahead"},{name:"operator"},{name:"terminal"}]};case"CharacterRange":return{name:"CharacterRange",properties:[{name:"cardinality"},{name:"left"},{name:"lookahead"},{name:"right"}]};case"CrossReference":return{name:"CrossReference",properties:[{name:"cardinality"},{name:"deprecatedSyntax",defaultValue:!1},{name:"lookahead"},{name:"terminal"},{name:"type"}]};case"EndOfFile":return{name:"EndOfFile",properties:[{name:"cardinality"},{name:"lookahead"}]};case"Group":return{name:"Group",properties:[{name:"cardinality"},{name:"elements",defaultValue:[]},{name:"guardCondition"},{name:"lookahead"}]};case"Keyword":return{name:"Keyword",properties:[{name:"cardinality"},{name:"lookahead"},{name:"value"}]};case"NegatedToken":return{name:"NegatedToken",properties:[{name:"cardinality"},{name:"lookahead"},{name:"terminal"}]};case"RegexToken":return{name:"RegexToken",properties:[{name:"cardinality"},{name:"lookahead"},{name:"regex"}]};case"RuleCall":return{name:"RuleCall",properties:[{name:"arguments",defaultValue:[]},{name:"cardinality"},{name:"lookahead"},{name:"rule"}]};case"TerminalAlternatives":return{name:"TerminalAlternatives",properties:[{name:"cardinality"},{name:"elements",defaultValue:[]},{name:"lookahead"}]};case"TerminalGroup":return{name:"TerminalGroup",properties:[{name:"cardinality"},{name:"elements",defaultValue:[]},{name:"lookahead"}]};case"TerminalRuleCall":return{name:"TerminalRuleCall",properties:[{name:"cardinality"},{name:"lookahead"},{name:"rule"}]};case"UnorderedGroup":return{name:"UnorderedGroup",properties:[{name:"cardinality"},{name:"elements",defaultValue:[]},{name:"lookahead"}]};case"UntilToken":return{name:"UntilToken",properties:[{name:"cardinality"},{name:"lookahead"},{name:"terminal"}]};case"Wildcard":return{name:"Wildcard",properties:[{name:"cardinality"},{name:"lookahead"}]};default:return{name:e,properties:[]}}}},Kt=new Gm});var CT={};hr(CT,{assignMandatoryProperties:()=>NR,copyAstNode:()=>RR,findLocalReferences:()=>NMe,findRootNode:()=>Vne,getContainerOfType:()=>Qd,getDocument:()=>Oi,hasContainerOfType:()=>RMe,linkContentToContainer:()=>ET,streamAllContents:()=>Ac,streamAst:()=>Yo,streamContents:()=>Wv,streamReferences:()=>$m});function ET(t){for(let[e,r]of Object.entries(t))e.startsWith("$")||(Array.isArray(r)?r.forEach((n,i)=>{Xn(n)&&(n.$container=t,n.$containerProperty=e,n.$containerIndex=i)}):Xn(r)&&(r.$container=t,r.$containerProperty=e))}function Qd(t,e){let r=t;for(;r;){if(e(r))return r;r=r.$container}}function RMe(t,e){let r=t;for(;r;){if(e(r))return!0;r=r.$container}return!1}function Oi(t){let r=Vne(t).$document;if(!r)throw new Error("AST node has no document.");return r}function Vne(t){for(;t.$container;)t=t.$container;return t}function Wv(t,e){if(!t)throw new Error("Node must be an AstNode.");let r=e?.range;return new uo(()=>({keys:Object.keys(t),keyIndex:0,arrayIndex:0}),n=>{for(;n.keyIndexWv(r,e))}function Yo(t,e){if(t){if(e?.range&&!DR(t,e.range))return new Cc(t,()=>[])}else throw new Error("Root node must be an AstNode.");return new Cc(t,r=>Wv(r,e),{includeRoot:!0})}function DR(t,e){var r;if(!e)return!0;let n=(r=t.$cstNode)===null||r===void 0?void 0:r.range;return n?AD(n,e):!1}function $m(t){return new uo(()=>({keys:Object.keys(t),keyIndex:0,arrayIndex:0}),e=>{for(;e.keyIndex{$m(n).forEach(i=>{i.reference.ref===t&&r.push(i.reference)})}),Kr(r)}function NR(t,e){let r=t.getTypeMetaData(e.$type),n=e;for(let i of r.properties)i.defaultValue!==void 0&&n[i.name]===void 0&&(n[i.name]=Une(i.defaultValue))}function Une(t){return Array.isArray(t)?[...t.map(Une)]:t}function RR(t,e){let r={$type:t.$type};for(let[n,i]of Object.entries(t))if(!n.startsWith("$"))if(Xn(i))r[n]=RR(i,e);else if(xa(i))r[n]=e(r,n,i.$refNode,i.$refText);else if(Array.isArray(i)){let a=[];for(let s of i)Xn(s)?a.push(RR(s,e)):xa(s)?a.push(e(r,n,s.$refNode,s.$refText)):a.push(s);r[n]=a}else r[n]=i;return ET(r),r}var es=R(()=>{"use strict";Vo();Ds();Rl();o(ET,"linkContentToContainer");o(Qd,"getContainerOfType");o(RMe,"hasContainerOfType");o(Oi,"getDocument");o(Vne,"findRootNode");o(Wv,"streamContents");o(Ac,"streamAllContents");o(Yo,"streamAst");o(DR,"isAstNodeInRange");o($m,"streamReferences");o(NMe,"findLocalReferences");o(NR,"assignMandatoryProperties");o(Une,"copyDefaultValue");o(RR,"copyAstNode")});function qt(t){return t.charCodeAt(0)}function ST(t,e){Array.isArray(t)?t.forEach(function(r){e.push(r)}):e.push(t)}function Vm(t,e){if(t[e]===!0)throw"duplicate flag "+e;let r=t[e];t[e]=!0}function Zd(t){if(t===void 0)throw Error("Internal Error - Should never get here!");return!0}function qv(){throw Error("Internal Error - Should never get here!")}function MR(t){return t.type==="Character"}var IR=R(()=>{"use strict";o(qt,"cc");o(ST,"insertToSet");o(Vm,"addFlag");o(Zd,"ASSERT_EXISTS");o(qv,"ASSERT_NEVER_REACH_HERE");o(MR,"isCharacter")});var Xv,jv,OR,Hne=R(()=>{"use strict";IR();Xv=[];for(let t=qt("0");t<=qt("9");t++)Xv.push(t);jv=[qt("_")].concat(Xv);for(let t=qt("a");t<=qt("z");t++)jv.push(t);for(let t=qt("A");t<=qt("Z");t++)jv.push(t);OR=[qt(" "),qt("\f"),qt(` +`),qt("\r"),qt(" "),qt("\v"),qt(" "),qt("\xA0"),qt("\u1680"),qt("\u2000"),qt("\u2001"),qt("\u2002"),qt("\u2003"),qt("\u2004"),qt("\u2005"),qt("\u2006"),qt("\u2007"),qt("\u2008"),qt("\u2009"),qt("\u200A"),qt("\u2028"),qt("\u2029"),qt("\u202F"),qt("\u205F"),qt("\u3000"),qt("\uFEFF")]});var MMe,AT,IMe,Jd,Yne=R(()=>{"use strict";IR();Hne();MMe=/[0-9a-fA-F]/,AT=/[0-9]/,IMe=/[1-9]/,Jd=class{static{o(this,"RegExpParser")}constructor(){this.idx=0,this.input="",this.groupIdx=0}saveState(){return{idx:this.idx,input:this.input,groupIdx:this.groupIdx}}restoreState(e){this.idx=e.idx,this.input=e.input,this.groupIdx=e.groupIdx}pattern(e){this.idx=0,this.input=e,this.groupIdx=0,this.consumeChar("/");let r=this.disjunction();this.consumeChar("/");let n={type:"Flags",loc:{begin:this.idx,end:e.length},global:!1,ignoreCase:!1,multiLine:!1,unicode:!1,sticky:!1};for(;this.isRegExpFlag();)switch(this.popChar()){case"g":Vm(n,"global");break;case"i":Vm(n,"ignoreCase");break;case"m":Vm(n,"multiLine");break;case"u":Vm(n,"unicode");break;case"y":Vm(n,"sticky");break}if(this.idx!==this.input.length)throw Error("Redundant input: "+this.input.substring(this.idx));return{type:"Pattern",flags:n,value:r,loc:this.loc(0)}}disjunction(){let e=[],r=this.idx;for(e.push(this.alternative());this.peekChar()==="|";)this.consumeChar("|"),e.push(this.alternative());return{type:"Disjunction",value:e,loc:this.loc(r)}}alternative(){let e=[],r=this.idx;for(;this.isTerm();)e.push(this.term());return{type:"Alternative",value:e,loc:this.loc(r)}}term(){return this.isAssertion()?this.assertion():this.atom()}assertion(){let e=this.idx;switch(this.popChar()){case"^":return{type:"StartAnchor",loc:this.loc(e)};case"$":return{type:"EndAnchor",loc:this.loc(e)};case"\\":switch(this.popChar()){case"b":return{type:"WordBoundary",loc:this.loc(e)};case"B":return{type:"NonWordBoundary",loc:this.loc(e)}}throw Error("Invalid Assertion Escape");case"(":this.consumeChar("?");let r;switch(this.popChar()){case"=":r="Lookahead";break;case"!":r="NegativeLookahead";break}Zd(r);let n=this.disjunction();return this.consumeChar(")"),{type:r,value:n,loc:this.loc(e)}}return qv()}quantifier(e=!1){let r,n=this.idx;switch(this.popChar()){case"*":r={atLeast:0,atMost:1/0};break;case"+":r={atLeast:1,atMost:1/0};break;case"?":r={atLeast:0,atMost:1};break;case"{":let i=this.integerIncludingZero();switch(this.popChar()){case"}":r={atLeast:i,atMost:i};break;case",":let a;this.isDigit()?(a=this.integerIncludingZero(),r={atLeast:i,atMost:a}):r={atLeast:i,atMost:1/0},this.consumeChar("}");break}if(e===!0&&r===void 0)return;Zd(r);break}if(!(e===!0&&r===void 0)&&Zd(r))return this.peekChar(0)==="?"?(this.consumeChar("?"),r.greedy=!1):r.greedy=!0,r.type="Quantifier",r.loc=this.loc(n),r}atom(){let e,r=this.idx;switch(this.peekChar()){case".":e=this.dotAll();break;case"\\":e=this.atomEscape();break;case"[":e=this.characterClass();break;case"(":e=this.group();break}return e===void 0&&this.isPatternCharacter()&&(e=this.patternCharacter()),Zd(e)?(e.loc=this.loc(r),this.isQuantifier()&&(e.quantifier=this.quantifier()),e):qv()}dotAll(){return this.consumeChar("."),{type:"Set",complement:!0,value:[qt(` +`),qt("\r"),qt("\u2028"),qt("\u2029")]}}atomEscape(){switch(this.consumeChar("\\"),this.peekChar()){case"1":case"2":case"3":case"4":case"5":case"6":case"7":case"8":case"9":return this.decimalEscapeAtom();case"d":case"D":case"s":case"S":case"w":case"W":return this.characterClassEscape();case"f":case"n":case"r":case"t":case"v":return this.controlEscapeAtom();case"c":return this.controlLetterEscapeAtom();case"0":return this.nulCharacterAtom();case"x":return this.hexEscapeSequenceAtom();case"u":return this.regExpUnicodeEscapeSequenceAtom();default:return this.identityEscapeAtom()}}decimalEscapeAtom(){return{type:"GroupBackReference",value:this.positiveInteger()}}characterClassEscape(){let e,r=!1;switch(this.popChar()){case"d":e=Xv;break;case"D":e=Xv,r=!0;break;case"s":e=OR;break;case"S":e=OR,r=!0;break;case"w":e=jv;break;case"W":e=jv,r=!0;break}return Zd(e)?{type:"Set",value:e,complement:r}:qv()}controlEscapeAtom(){let e;switch(this.popChar()){case"f":e=qt("\f");break;case"n":e=qt(` +`);break;case"r":e=qt("\r");break;case"t":e=qt(" ");break;case"v":e=qt("\v");break}return Zd(e)?{type:"Character",value:e}:qv()}controlLetterEscapeAtom(){this.consumeChar("c");let e=this.popChar();if(/[a-zA-Z]/.test(e)===!1)throw Error("Invalid ");return{type:"Character",value:e.toUpperCase().charCodeAt(0)-64}}nulCharacterAtom(){return this.consumeChar("0"),{type:"Character",value:qt("\0")}}hexEscapeSequenceAtom(){return this.consumeChar("x"),this.parseHexDigits(2)}regExpUnicodeEscapeSequenceAtom(){return this.consumeChar("u"),this.parseHexDigits(4)}identityEscapeAtom(){let e=this.popChar();return{type:"Character",value:qt(e)}}classPatternCharacterAtom(){switch(this.peekChar()){case` +`:case"\r":case"\u2028":case"\u2029":case"\\":case"]":throw Error("TBD");default:let e=this.popChar();return{type:"Character",value:qt(e)}}}characterClass(){let e=[],r=!1;for(this.consumeChar("["),this.peekChar(0)==="^"&&(this.consumeChar("^"),r=!0);this.isClassAtom();){let n=this.classAtom(),i=n.type==="Character";if(MR(n)&&this.isRangeDash()){this.consumeChar("-");let a=this.classAtom(),s=a.type==="Character";if(MR(a)){if(a.value=this.input.length)throw Error("Unexpected end of input");this.idx++}loc(e){return{begin:e,end:this.idx}}}});var _c,Wne=R(()=>{"use strict";_c=class{static{o(this,"BaseRegExpVisitor")}visitChildren(e){for(let r in e){let n=e[r];e.hasOwnProperty(r)&&(n.type!==void 0?this.visit(n):Array.isArray(n)&&n.forEach(i=>{this.visit(i)},this))}}visit(e){switch(e.type){case"Pattern":this.visitPattern(e);break;case"Flags":this.visitFlags(e);break;case"Disjunction":this.visitDisjunction(e);break;case"Alternative":this.visitAlternative(e);break;case"StartAnchor":this.visitStartAnchor(e);break;case"EndAnchor":this.visitEndAnchor(e);break;case"WordBoundary":this.visitWordBoundary(e);break;case"NonWordBoundary":this.visitNonWordBoundary(e);break;case"Lookahead":this.visitLookahead(e);break;case"NegativeLookahead":this.visitNegativeLookahead(e);break;case"Character":this.visitCharacter(e);break;case"Set":this.visitSet(e);break;case"Group":this.visitGroup(e);break;case"GroupBackReference":this.visitGroupBackReference(e);break;case"Quantifier":this.visitQuantifier(e);break}this.visitChildren(e)}visitPattern(e){}visitFlags(e){}visitDisjunction(e){}visitAlternative(e){}visitStartAnchor(e){}visitEndAnchor(e){}visitWordBoundary(e){}visitNonWordBoundary(e){}visitLookahead(e){}visitNegativeLookahead(e){}visitCharacter(e){}visitSet(e){}visitGroup(e){}visitGroupBackReference(e){}visitQuantifier(e){}}});var Kv=R(()=>{"use strict";Yne();Wne()});var LT={};hr(LT,{NEWLINE_REGEXP:()=>BR,escapeRegExp:()=>t0,getCaseInsensitivePattern:()=>zR,getTerminalParts:()=>OMe,isMultilineComment:()=>FR,isWhitespace:()=>_T,partialMatches:()=>GR,partialRegExp:()=>Xne});function OMe(t){try{typeof t!="string"&&(t=t.source),t=`/${t}/`;let e=qne.pattern(t),r=[];for(let n of e.value.value)e0.reset(t),e0.visit(n),r.push({start:e0.startRegexp,end:e0.endRegex});return r}catch{return[]}}function FR(t){try{return typeof t=="string"&&(t=new RegExp(t)),t=t.toString(),e0.reset(t),e0.visit(qne.pattern(t)),e0.multiline}catch{return!1}}function _T(t){return(typeof t=="string"?new RegExp(t):t).test(" ")}function t0(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function zR(t){return Array.prototype.map.call(t,e=>/\w/.test(e)?`[${e.toLowerCase()}${e.toUpperCase()}]`:t0(e)).join("")}function GR(t,e){let r=Xne(t),n=e.match(r);return!!n&&n[0].length>0}function Xne(t){typeof t=="string"&&(t=new RegExp(t));let e=t,r=t.source,n=0;function i(){let a="",s;function l(h){a+=r.substr(n,h),n+=h}o(l,"appendRaw");function u(h){a+="(?:"+r.substr(n,h)+"|$)",n+=h}for(o(u,"appendOptional");n",n)-n+1);break;default:u(2);break}break;case"[":s=/\[(?:\\.|.)*?\]/g,s.lastIndex=n,s=s.exec(r)||[],u(s[0].length);break;case"|":case"^":case"$":case"*":case"+":case"?":l(1);break;case"{":s=/\{\d+,?\d*\}/g,s.lastIndex=n,s=s.exec(r),s?l(s[0].length):u(1);break;case"(":if(r[n+1]==="?")switch(r[n+2]){case":":a+="(?:",n+=3,a+=i()+"|$)";break;case"=":a+="(?=",n+=3,a+=i()+")";break;case"!":s=n,n+=3,i(),a+=r.substr(s,n-s);break;case"<":switch(r[n+3]){case"=":case"!":s=n,n+=4,i(),a+=r.substr(s,n-s);break;default:l(r.indexOf(">",n)-n+1),a+=i()+"|$)";break}break}else l(1),a+=i()+"|$)";break;case")":return++n,a;default:u(1);break}return a}return o(i,"process"),new RegExp(i(),t.flags)}var BR,qne,PR,e0,Um=R(()=>{"use strict";Kv();BR=/\r?\n/gm,qne=new Jd,PR=class extends _c{static{o(this,"TerminalRegExpVisitor")}constructor(){super(...arguments),this.isStarting=!0,this.endRegexpStack=[],this.multiline=!1}get endRegex(){return this.endRegexpStack.join("")}reset(e){this.multiline=!1,this.regex=e,this.startRegexp="",this.isStarting=!0,this.endRegexpStack=[]}visitGroup(e){e.quantifier&&(this.isStarting=!1,this.endRegexpStack=[])}visitCharacter(e){let r=String.fromCharCode(e.value);if(!this.multiline&&r===` +`&&(this.multiline=!0),e.quantifier)this.isStarting=!1,this.endRegexpStack=[];else{let n=t0(r);this.endRegexpStack.push(n),this.isStarting&&(this.startRegexp+=n)}}visitSet(e){if(!this.multiline){let r=this.regex.substring(e.loc.begin,e.loc.end),n=new RegExp(r);this.multiline=!!` +`.match(n)}if(e.quantifier)this.isStarting=!1,this.endRegexpStack=[];else{let r=this.regex.substring(e.loc.begin,e.loc.end);this.endRegexpStack.push(r),this.isStarting&&(this.startRegexp+=r)}}visitChildren(e){e.type==="Group"&&e.quantifier||super.visitChildren(e)}},e0=new PR;o(OMe,"getTerminalParts");o(FR,"isMultilineComment");o(_T,"isWhitespace");o(t0,"escapeRegExp");o(zR,"getCaseInsensitivePattern");o(GR,"partialMatches");o(Xne,"partialRegExp")});var RT={};hr(RT,{findAssignment:()=>jR,findNameAssignment:()=>DT,findNodeForKeyword:()=>qR,findNodeForProperty:()=>Zv,findNodesForKeyword:()=>PMe,findNodesForKeywordInternal:()=>XR,findNodesForProperty:()=>YR,getActionAtElement:()=>Jne,getActionType:()=>tie,getAllReachableRules:()=>Qv,getCrossReferenceTerminal:()=>UR,getEntryRule:()=>jne,getExplicitRuleType:()=>KR,getHiddenRules:()=>Kne,getRuleType:()=>QR,getTypeName:()=>r0,isArrayCardinality:()=>FMe,isArrayOperator:()=>zMe,isCommentTerminal:()=>HR,isDataType:()=>GMe,isDataTypeRule:()=>Jv,isOptionalCardinality:()=>BMe,terminalRegex:()=>Hm});function jne(t){return t.rules.find(e=>Oa(e)&&e.entry)}function Kne(t){return t.rules.filter(e=>Uo(e)&&e.hidden)}function Qv(t,e){let r=new Set,n=jne(t);if(!n)return new Set(t.rules);let i=[n].concat(Kne(t));for(let s of i)Qne(s,r,e);let a=new Set;for(let s of t.rules)(r.has(s.name)||Uo(s)&&s.hidden)&&a.add(s);return a}function Qne(t,e,r){e.add(t.name),Ac(t).forEach(n=>{if(Ml(n)||r&&TT(n)){let i=n.rule.ref;i&&!e.has(i.name)&&Qne(i,e,r)}})}function UR(t){if(t.terminal)return t.terminal;if(t.type.ref){let e=DT(t.type.ref);return e?.terminal}}function HR(t){return t.hidden&&!Hm(t).test(" ")}function YR(t,e){return!t||!e?[]:WR(t,e,t.astNode,!0)}function Zv(t,e,r){if(!t||!e)return;let n=WR(t,e,t.astNode,!0);if(n.length!==0)return r!==void 0?r=Math.max(0,Math.min(r,n.length-1)):r=0,n[r]}function WR(t,e,r,n){if(!n){let i=Qd(t.grammarSource,Nl);if(i&&i.feature===e)return[t]}return co(t)&&t.astNode===r?t.content.flatMap(i=>WR(i,e,r,!1)):[]}function PMe(t,e){return t?XR(t,e,t?.astNode):[]}function qR(t,e,r){if(!t)return;let n=XR(t,e,t?.astNode);if(n.length!==0)return r!==void 0?r=Math.max(0,Math.min(r,n.length-1)):r=0,n[r]}function XR(t,e,r){if(t.astNode!==r)return[];if(Ho(t.grammarSource)&&t.grammarSource.value===e)return[t];let n=qd(t).iterator(),i,a=[];do if(i=n.next(),!i.done){let s=i.value;s.astNode===r?Ho(s.grammarSource)&&s.grammarSource.value===e&&a.push(s):n.prune()}while(!i.done);return a}function jR(t){var e;let r=t.astNode;for(;r===((e=t.container)===null||e===void 0?void 0:e.astNode);){let n=Qd(t.grammarSource,Nl);if(n)return n;t=t.container}}function DT(t){let e=t;return vT(e)&&(Iu(e.$container)?e=e.$container.$container:Oa(e.$container)?e=e.$container:tf(e.$container)),Zne(t,e,new Map)}function Zne(t,e,r){var n;function i(a,s){let l;return Qd(a,Nl)||(l=Zne(s,s,r)),r.set(t,l),l}if(o(i,"go"),r.has(t))return r.get(t);r.set(t,void 0);for(let a of Ac(e)){if(Nl(a)&&a.feature.toLowerCase()==="name")return r.set(t,a),a;if(Ml(a)&&Oa(a.rule.ref))return i(a,a.rule.ref);if(bT(a)&&(!((n=a.typeRef)===null||n===void 0)&&n.ref))return i(a,a.typeRef.ref)}}function Jne(t){let e=t.$container;if(rf(e)){let r=e.elements,n=r.indexOf(t);for(let i=n-1;i>=0;i--){let a=r[i];if(Iu(a))return a;{let s=Ac(r[i]).find(Iu);if(s)return s}}}if(Uv(e))return Jne(e)}function BMe(t,e){return t==="?"||t==="*"||rf(e)&&!!e.guardCondition}function FMe(t){return t==="*"||t==="+"}function zMe(t){return t==="+="}function Jv(t){return eie(t,new Set)}function eie(t,e){if(e.has(t))return!0;e.add(t);for(let r of Ac(t))if(Ml(r)){if(!r.rule.ref||Oa(r.rule.ref)&&!eie(r.rule.ref,e))return!1}else{if(Nl(r))return!1;if(Iu(r))return!1}return!!t.definition}function GMe(t){return VR(t.type,new Set)}function VR(t,e){if(e.has(t))return!0;if(e.add(t),ID(t))return!1;if(ZD(t))return!1;if(iR(t))return t.types.every(r=>VR(r,e));if(bT(t)){if(t.primitiveType!==void 0)return!0;if(t.stringType!==void 0)return!0;if(t.typeRef!==void 0){let r=t.typeRef.ref;return Hv(r)?VR(r.type,e):!1}else return!1}else return!1}function KR(t){if(t.inferredType)return t.inferredType.name;if(t.dataType)return t.dataType;if(t.returnType){let e=t.returnType.ref;if(e){if(Oa(e))return e.name;if(xT(e)||Hv(e))return e.name}}}function r0(t){var e;if(Oa(t))return Jv(t)?t.name:(e=KR(t))!==null&&e!==void 0?e:t.name;if(xT(t)||Hv(t)||JD(t))return t.name;if(Iu(t)){let r=tie(t);if(r)return r}else if(vT(t))return t.name;throw new Error("Cannot get name of Unknown Type")}function tie(t){var e;if(t.inferredType)return t.inferredType.name;if(!((e=t.type)===null||e===void 0)&&e.ref)return r0(t.type.ref)}function QR(t){var e,r,n;return Uo(t)?(r=(e=t.type)===null||e===void 0?void 0:e.name)!==null&&r!==void 0?r:"string":Jv(t)?t.name:(n=KR(t))!==null&&n!==void 0?n:t.name}function Hm(t){let e={s:!1,i:!1,u:!1},r=Ym(t.definition,e),n=Object.entries(e).filter(([,i])=>i).map(([i])=>i).join("");return new RegExp(r,n)}function Ym(t,e){if(wR(t))return $Me(t);if(kR(t))return VMe(t);if(cR(t))return YMe(t);if(TT(t)){let r=t.rule.ref;if(!r)throw new Error("Missing rule reference.");return Ou(Ym(r.definition),{cardinality:t.cardinality,lookahead:t.lookahead})}else{if(gR(t))return HMe(t);if(AR(t))return UMe(t);if(vR(t)){let r=t.regex.lastIndexOf("/"),n=t.regex.substring(1,r),i=t.regex.substring(r+1);return e&&(e.i=i.includes("i"),e.s=i.includes("s"),e.u=i.includes("u")),Ou(n,{cardinality:t.cardinality,lookahead:t.lookahead,wrap:!1})}else{if(LR(t))return Ou(ZR,{cardinality:t.cardinality,lookahead:t.lookahead});throw new Error(`Invalid terminal element: ${t?.$type}`)}}}function $Me(t){return Ou(t.elements.map(e=>Ym(e)).join("|"),{cardinality:t.cardinality,lookahead:t.lookahead})}function VMe(t){return Ou(t.elements.map(e=>Ym(e)).join(""),{cardinality:t.cardinality,lookahead:t.lookahead})}function UMe(t){return Ou(`${ZR}*?${Ym(t.terminal)}`,{cardinality:t.cardinality,lookahead:t.lookahead})}function HMe(t){return Ou(`(?!${Ym(t.terminal)})${ZR}*?`,{cardinality:t.cardinality,lookahead:t.lookahead})}function YMe(t){return t.right?Ou(`[${$R(t.left)}-${$R(t.right)}]`,{cardinality:t.cardinality,lookahead:t.lookahead,wrap:!1}):Ou($R(t.left),{cardinality:t.cardinality,lookahead:t.lookahead,wrap:!1})}function $R(t){return t0(t.value)}function Ou(t,e){var r;return(e.wrap!==!1||e.lookahead)&&(t=`(${(r=e.lookahead)!==null&&r!==void 0?r:""}${t})`),e.cardinality?`${t}${e.cardinality}`:t}var ZR,Il=R(()=>{"use strict";pT();Sc();Vo();es();Rl();Um();o(jne,"getEntryRule");o(Kne,"getHiddenRules");o(Qv,"getAllReachableRules");o(Qne,"ruleDfs");o(UR,"getCrossReferenceTerminal");o(HR,"isCommentTerminal");o(YR,"findNodesForProperty");o(Zv,"findNodeForProperty");o(WR,"findNodesForPropertyInternal");o(PMe,"findNodesForKeyword");o(qR,"findNodeForKeyword");o(XR,"findNodesForKeywordInternal");o(jR,"findAssignment");o(DT,"findNameAssignment");o(Zne,"findNameAssignmentInternal");o(Jne,"getActionAtElement");o(BMe,"isOptionalCardinality");o(FMe,"isArrayCardinality");o(zMe,"isArrayOperator");o(Jv,"isDataTypeRule");o(eie,"isDataTypeRuleInternal");o(GMe,"isDataType");o(VR,"isDataTypeInternal");o(KR,"getExplicitRuleType");o(r0,"getTypeName");o(tie,"getActionType");o(QR,"getRuleType");o(Hm,"terminalRegex");ZR=/[\s\S]/.source;o(Ym,"abstractElementToRegex");o($Me,"terminalAlternativesToRegex");o(VMe,"terminalGroupToRegex");o(UMe,"untilTokenToRegex");o(HMe,"negateTokenToRegex");o(YMe,"characterRangeToRegex");o($R,"keywordToRegex");o(Ou,"withCardinality")});function JR(t){let e=[],r=t.Grammar;for(let n of r.rules)Uo(n)&&HR(n)&&FR(Hm(n))&&e.push(n.name);return{multilineCommentRules:e,nameRegexp:fT}}var eN=R(()=>{"use strict";Rl();Il();Um();Sc();o(JR,"createGrammarConfig")});var tN=R(()=>{"use strict"});function Wm(t){console&&console.error&&console.error(`Error: ${t}`)}function e2(t){console&&console.warn&&console.warn(`Warning: ${t}`)}var rie=R(()=>{"use strict";o(Wm,"PRINT_ERROR");o(e2,"PRINT_WARNING")});function t2(t){let e=new Date().getTime(),r=t();return{time:new Date().getTime()-e,value:r}}var nie=R(()=>{"use strict";o(t2,"timer")});function r2(t){function e(){}o(e,"FakeConstructor"),e.prototype=t;let r=new e;function n(){return typeof r.bar}return o(n,"fakeAccess"),n(),n(),t;(0,eval)(t)}var iie=R(()=>{"use strict";o(r2,"toFastProperties")});var qm=R(()=>{"use strict";rie();nie();iie()});function WMe(t){return qMe(t)?t.LABEL:t.name}function qMe(t){return di(t.LABEL)&&t.LABEL!==""}function NT(t){return qe(t,Xm)}function Xm(t){function e(r){return qe(r,Xm)}if(o(e,"convertDefinition"),t instanceof Zr){let r={type:"NonTerminal",name:t.nonTerminalName,idx:t.idx};return di(t.label)&&(r.label=t.label),r}else{if(t instanceof Sn)return{type:"Alternative",definition:e(t.definition)};if(t instanceof Jr)return{type:"Option",idx:t.idx,definition:e(t.definition)};if(t instanceof An)return{type:"RepetitionMandatory",idx:t.idx,definition:e(t.definition)};if(t instanceof _n)return{type:"RepetitionMandatoryWithSeparator",idx:t.idx,separator:Xm(new fr({terminalType:t.separator})),definition:e(t.definition)};if(t instanceof mn)return{type:"RepetitionWithSeparator",idx:t.idx,separator:Xm(new fr({terminalType:t.separator})),definition:e(t.definition)};if(t instanceof br)return{type:"Repetition",idx:t.idx,definition:e(t.definition)};if(t instanceof gn)return{type:"Alternation",idx:t.idx,definition:e(t.definition)};if(t instanceof fr){let r={type:"Terminal",name:t.terminalType.name,label:WMe(t.terminalType),idx:t.idx};di(t.label)&&(r.terminalLabel=t.label);let n=t.terminalType.PATTERN;return t.terminalType.PATTERN&&(r.pattern=zo(n)?n.source:n),r}else{if(t instanceof ts)return{type:"Rule",name:t.name,orgText:t.orgText,definition:e(t.definition)};throw Error("non exhaustive match")}}}var ho,Zr,ts,Sn,Jr,An,_n,br,mn,gn,fr,MT=R(()=>{"use strict";Pt();o(WMe,"tokenLabel");o(qMe,"hasTokenLabel");ho=class{static{o(this,"AbstractProduction")}get definition(){return this._definition}set definition(e){this._definition=e}constructor(e){this._definition=e}accept(e){e.visit(this),Ee(this.definition,r=>{r.accept(e)})}},Zr=class extends ho{static{o(this,"NonTerminal")}constructor(e){super([]),this.idx=1,pa(this,Ls(e,r=>r!==void 0))}set definition(e){}get definition(){return this.referencedRule!==void 0?this.referencedRule.definition:[]}accept(e){e.visit(this)}},ts=class extends ho{static{o(this,"Rule")}constructor(e){super(e.definition),this.orgText="",pa(this,Ls(e,r=>r!==void 0))}},Sn=class extends ho{static{o(this,"Alternative")}constructor(e){super(e.definition),this.ignoreAmbiguities=!1,pa(this,Ls(e,r=>r!==void 0))}},Jr=class extends ho{static{o(this,"Option")}constructor(e){super(e.definition),this.idx=1,pa(this,Ls(e,r=>r!==void 0))}},An=class extends ho{static{o(this,"RepetitionMandatory")}constructor(e){super(e.definition),this.idx=1,pa(this,Ls(e,r=>r!==void 0))}},_n=class extends ho{static{o(this,"RepetitionMandatoryWithSeparator")}constructor(e){super(e.definition),this.idx=1,pa(this,Ls(e,r=>r!==void 0))}},br=class extends ho{static{o(this,"Repetition")}constructor(e){super(e.definition),this.idx=1,pa(this,Ls(e,r=>r!==void 0))}},mn=class extends ho{static{o(this,"RepetitionWithSeparator")}constructor(e){super(e.definition),this.idx=1,pa(this,Ls(e,r=>r!==void 0))}},gn=class extends ho{static{o(this,"Alternation")}get definition(){return this._definition}set definition(e){this._definition=e}constructor(e){super(e.definition),this.idx=1,this.ignoreAmbiguities=!1,this.hasPredicates=!1,pa(this,Ls(e,r=>r!==void 0))}},fr=class{static{o(this,"Terminal")}constructor(e){this.idx=1,pa(this,Ls(e,r=>r!==void 0))}accept(e){e.visit(this)}};o(NT,"serializeGrammar");o(Xm,"serializeProduction")});var rs,aie=R(()=>{"use strict";MT();rs=class{static{o(this,"GAstVisitor")}visit(e){let r=e;switch(r.constructor){case Zr:return this.visitNonTerminal(r);case Sn:return this.visitAlternative(r);case Jr:return this.visitOption(r);case An:return this.visitRepetitionMandatory(r);case _n:return this.visitRepetitionMandatoryWithSeparator(r);case mn:return this.visitRepetitionWithSeparator(r);case br:return this.visitRepetition(r);case gn:return this.visitAlternation(r);case fr:return this.visitTerminal(r);case ts:return this.visitRule(r);default:throw Error("non exhaustive match")}}visitNonTerminal(e){}visitAlternative(e){}visitOption(e){}visitRepetition(e){}visitRepetitionMandatory(e){}visitRepetitionMandatoryWithSeparator(e){}visitRepetitionWithSeparator(e){}visitAlternation(e){}visitTerminal(e){}visitRule(e){}}});function rN(t){return t instanceof Sn||t instanceof Jr||t instanceof br||t instanceof An||t instanceof _n||t instanceof mn||t instanceof fr||t instanceof ts}function n0(t,e=[]){return t instanceof Jr||t instanceof br||t instanceof mn?!0:t instanceof gn?Nv(t.definition,n=>n0(n,e)):t instanceof Zr&&Fn(e,t)?!1:t instanceof ho?(t instanceof Zr&&e.push(t),Ia(t.definition,n=>n0(n,e))):!1}function nN(t){return t instanceof gn}function Rs(t){if(t instanceof Zr)return"SUBRULE";if(t instanceof Jr)return"OPTION";if(t instanceof gn)return"OR";if(t instanceof An)return"AT_LEAST_ONE";if(t instanceof _n)return"AT_LEAST_ONE_SEP";if(t instanceof mn)return"MANY_SEP";if(t instanceof br)return"MANY";if(t instanceof fr)return"CONSUME";throw Error("non exhaustive match")}var sie=R(()=>{"use strict";Pt();MT();o(rN,"isSequenceProd");o(n0,"isOptionalProd");o(nN,"isBranchingProd");o(Rs,"getProductionDslName")});var ns=R(()=>{"use strict";MT();aie();sie()});function oie(t,e,r){return[new Jr({definition:[new fr({terminalType:t.separator})].concat(t.definition)})].concat(e,r)}var Pu,IT=R(()=>{"use strict";Pt();ns();Pu=class{static{o(this,"RestWalker")}walk(e,r=[]){Ee(e.definition,(n,i)=>{let a=fi(e.definition,i+1);if(n instanceof Zr)this.walkProdRef(n,a,r);else if(n instanceof fr)this.walkTerminal(n,a,r);else if(n instanceof Sn)this.walkFlat(n,a,r);else if(n instanceof Jr)this.walkOption(n,a,r);else if(n instanceof An)this.walkAtLeastOne(n,a,r);else if(n instanceof _n)this.walkAtLeastOneSep(n,a,r);else if(n instanceof mn)this.walkManySep(n,a,r);else if(n instanceof br)this.walkMany(n,a,r);else if(n instanceof gn)this.walkOr(n,a,r);else throw Error("non exhaustive match")})}walkTerminal(e,r,n){}walkProdRef(e,r,n){}walkFlat(e,r,n){let i=r.concat(n);this.walk(e,i)}walkOption(e,r,n){let i=r.concat(n);this.walk(e,i)}walkAtLeastOne(e,r,n){let i=[new Jr({definition:e.definition})].concat(r,n);this.walk(e,i)}walkAtLeastOneSep(e,r,n){let i=oie(e,r,n);this.walk(e,i)}walkMany(e,r,n){let i=[new Jr({definition:e.definition})].concat(r,n);this.walk(e,i)}walkManySep(e,r,n){let i=oie(e,r,n);this.walk(e,i)}walkOr(e,r,n){let i=r.concat(n);Ee(e.definition,a=>{let s=new Sn({definition:[a]});this.walk(s,i)})}};o(oie,"restForRepetitionWithSeparator")});function i0(t){if(t instanceof Zr)return i0(t.referencedRule);if(t instanceof fr)return KMe(t);if(rN(t))return XMe(t);if(nN(t))return jMe(t);throw Error("non exhaustive match")}function XMe(t){let e=[],r=t.definition,n=0,i=r.length>n,a,s=!0;for(;i&&s;)a=r[n],s=n0(a),e=e.concat(i0(a)),n=n+1,i=r.length>n;return Pm(e)}function jMe(t){let e=qe(t.definition,r=>i0(r));return Pm(Gr(e))}function KMe(t){return[t.terminalType]}var iN=R(()=>{"use strict";Pt();ns();o(i0,"first");o(XMe,"firstForSequence");o(jMe,"firstForBranching");o(KMe,"firstForTerminal")});var OT,aN=R(()=>{"use strict";OT="_~IN~_"});function lie(t){let e={};return Ee(t,r=>{let n=new sN(r).startWalking();pa(e,n)}),e}function QMe(t,e){return t.name+e+OT}var sN,cie=R(()=>{"use strict";IT();iN();Pt();aN();ns();sN=class extends Pu{static{o(this,"ResyncFollowsWalker")}constructor(e){super(),this.topProd=e,this.follows={}}startWalking(){return this.walk(this.topProd),this.follows}walkTerminal(e,r,n){}walkProdRef(e,r,n){let i=QMe(e.referencedRule,e.idx)+this.topProd.name,a=r.concat(n),s=new Sn({definition:a}),l=i0(s);this.follows[i]=l}};o(lie,"computeAllProdsFollows");o(QMe,"buildBetweenProdsFollowPrefix")});function jm(t){let e=t.toString();if(PT.hasOwnProperty(e))return PT[e];{let r=ZMe.pattern(e);return PT[e]=r,r}}function uie(){PT={}}var PT,ZMe,BT=R(()=>{"use strict";Kv();PT={},ZMe=new Jd;o(jm,"getRegExpAst");o(uie,"clearRegExpParserCache")});function die(t,e=!1){try{let r=jm(t);return oN(r.value,{},r.flags.ignoreCase)}catch(r){if(r.message===fie)e&&e2(`${n2} Unable to optimize: < ${t.toString()} > + Complement Sets cannot be automatically optimized. + This will disable the lexer's first char optimizations. + See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#COMPLEMENT for details.`);else{let n="";e&&(n=` + This will disable the lexer's first char optimizations. + See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#REGEXP_PARSING for details.`),Wm(`${n2} + Failed parsing: < ${t.toString()} > + Using the @chevrotain/regexp-to-ast library + Please open an issue at: https://github.com/chevrotain/chevrotain/issues`+n)}}return[]}function oN(t,e,r){switch(t.type){case"Disjunction":for(let i=0;i{if(typeof u=="number")FT(u,e,r);else{let h=u;if(r===!0)for(let f=h.from;f<=h.to;f++)FT(f,e,r);else{for(let f=h.from;f<=h.to&&f=Km){let f=h.from>=Km?h.from:Km,d=h.to,p=Lc(f),m=Lc(d);for(let g=p;g<=m;g++)e[g]=g}}}});break;case"Group":oN(s.value,e,r);break;default:throw Error("Non Exhaustive Match")}let l=s.quantifier!==void 0&&s.quantifier.atLeast===0;if(s.type==="Group"&&lN(s)===!1||s.type!=="Group"&&l===!1)break}break;default:throw Error("non exhaustive match!")}return or(e)}function FT(t,e,r){let n=Lc(t);e[n]=n,r===!0&&JMe(t,e)}function JMe(t,e){let r=String.fromCharCode(t),n=r.toUpperCase();if(n!==r){let i=Lc(n.charCodeAt(0));e[i]=i}else{let i=r.toLowerCase();if(i!==r){let a=Lc(i.charCodeAt(0));e[a]=a}}}function hie(t,e){return Za(t.value,r=>{if(typeof r=="number")return Fn(e,r);{let n=r;return Za(e,i=>n.from<=i&&i<=n.to)!==void 0}})}function lN(t){let e=t.quantifier;return e&&e.atLeast===0?!0:t.value?wt(t.value)?Ia(t.value,lN):lN(t.value):!1}function zT(t,e){if(e instanceof RegExp){let r=jm(e),n=new cN(t);return n.visit(r),n.found}else return Za(e,r=>Fn(t,r.charCodeAt(0)))!==void 0}var fie,n2,cN,pie=R(()=>{"use strict";Kv();Pt();qm();BT();uN();fie="Complement Sets are not supported for first char optimization",n2=`Unable to use "first char" lexer optimizations: +`;o(die,"getOptimizedStartCodesIndices");o(oN,"firstCharOptimizedIndices");o(FT,"addOptimizedIdxToResult");o(JMe,"handleIgnoreCase");o(hie,"findCode");o(lN,"isWholeOptional");cN=class extends _c{static{o(this,"CharCodeFinder")}constructor(e){super(),this.targetCharCodes=e,this.found=!1}visitChildren(e){if(this.found!==!0){switch(e.type){case"Lookahead":this.visitLookahead(e);return;case"NegativeLookahead":this.visitNegativeLookahead(e);return}super.visitChildren(e)}}visitCharacter(e){Fn(this.targetCharCodes,e.value)&&(this.found=!0)}visitSet(e){e.complement?hie(e,this.targetCharCodes)===void 0&&(this.found=!0):hie(e,this.targetCharCodes)!==void 0&&(this.found=!0)}};o(zT,"canMatchCharCode")});function yie(t,e){e=Xh(e,{useSticky:fN,debug:!1,safeMode:!1,positionTracking:"full",lineTerminatorCharacters:["\r",` +`],tracer:o((b,w)=>w(),"tracer")});let r=e.tracer;r("initCharCodeToOptimizedIndexMap",()=>{yIe()});let n;r("Reject Lexer.NA",()=>{n=Kh(t,b=>b[a0]===ni.NA)});let i=!1,a;r("Transform Patterns",()=>{i=!1,a=qe(n,b=>{let w=b[a0];if(zo(w)){let S=w.source;return S.length===1&&S!=="^"&&S!=="$"&&S!=="."&&!w.ignoreCase?S:S.length===2&&S[0]==="\\"&&!Fn(["d","D","s","S","t","r","n","t","0","c","b","B","f","v","w","W"],S[1])?S[1]:e.useSticky?gie(w):mie(w)}else{if(wi(w))return i=!0,{exec:w};if(typeof w=="object")return i=!0,w;if(typeof w=="string"){if(w.length===1)return w;{let S=w.replace(/[\\^$.*+?()[\]{}|]/g,"\\$&"),T=new RegExp(S);return e.useSticky?gie(T):mie(T)}}else throw Error("non exhaustive match")}})});let s,l,u,h,f;r("misc mapping",()=>{s=qe(n,b=>b.tokenTypeIdx),l=qe(n,b=>{let w=b.GROUP;if(w!==ni.SKIPPED){if(di(w))return w;if(er(w))return!1;throw Error("non exhaustive match")}}),u=qe(n,b=>{let w=b.LONGER_ALT;if(w)return wt(w)?qe(w,T=>Yw(n,T)):[Yw(n,w)]}),h=qe(n,b=>b.PUSH_MODE),f=qe(n,b=>Xe(b,"POP_MODE"))});let d;r("Line Terminator Handling",()=>{let b=Cie(e.lineTerminatorCharacters);d=qe(n,w=>!1),e.positionTracking!=="onlyOffset"&&(d=qe(n,w=>Xe(w,"LINE_BREAKS")?!!w.LINE_BREAKS:Eie(w,b)===!1&&zT(b,w.PATTERN)))});let p,m,g,y;r("Misc Mapping #2",()=>{p=qe(n,Tie),m=qe(a,mIe),g=Vr(n,(b,w)=>{let S=w.GROUP;return di(S)&&S!==ni.SKIPPED&&(b[S]=[]),b},{}),y=qe(a,(b,w)=>({pattern:a[w],longerAlt:u[w],canLineTerminator:d[w],isCustom:p[w],short:m[w],group:l[w],push:h[w],pop:f[w],tokenTypeIdx:s[w],tokenType:n[w]}))});let v=!0,x=[];return e.safeMode||r("First Char Optimization",()=>{x=Vr(n,(b,w,S)=>{if(typeof w.PATTERN=="string"){let T=w.PATTERN.charCodeAt(0),E=Lc(T);hN(b,E,y[S])}else if(wt(w.START_CHARS_HINT)){let T;Ee(w.START_CHARS_HINT,E=>{let _=typeof E=="string"?E.charCodeAt(0):E,A=Lc(_);T!==A&&(T=A,hN(b,A,y[S]))})}else if(zo(w.PATTERN))if(w.PATTERN.unicode)v=!1,e.ensureOptimizations&&Wm(`${n2} Unable to analyze < ${w.PATTERN.toString()} > pattern. + The regexp unicode flag is not currently supported by the regexp-to-ast library. + This will disable the lexer's first char optimizations. + For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#UNICODE_OPTIMIZE`);else{let T=die(w.PATTERN,e.ensureOptimizations);Qt(T)&&(v=!1),Ee(T,E=>{hN(b,E,y[S])})}else e.ensureOptimizations&&Wm(`${n2} TokenType: <${w.name}> is using a custom token pattern without providing parameter. + This will disable the lexer's first char optimizations. + For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#CUSTOM_OPTIMIZE`),v=!1;return b},[])}),{emptyGroups:g,patternIdxToConfig:y,charCodeToPatternIdxToConfig:x,hasCustom:i,canBeOptimized:v}}function vie(t,e){let r=[],n=tIe(t);r=r.concat(n.errors);let i=rIe(n.valid),a=i.valid;return r=r.concat(i.errors),r=r.concat(eIe(a)),r=r.concat(uIe(a)),r=r.concat(hIe(a,e)),r=r.concat(fIe(a)),r}function eIe(t){let e=[],r=$r(t,n=>zo(n[a0]));return e=e.concat(iIe(r)),e=e.concat(oIe(r)),e=e.concat(lIe(r)),e=e.concat(cIe(r)),e=e.concat(aIe(r)),e}function tIe(t){let e=$r(t,i=>!Xe(i,a0)),r=qe(e,i=>({message:"Token Type: ->"+i.name+"<- missing static 'PATTERN' property",type:Gn.MISSING_PATTERN,tokenTypes:[i]})),n=jh(t,e);return{errors:r,valid:n}}function rIe(t){let e=$r(t,i=>{let a=i[a0];return!zo(a)&&!wi(a)&&!Xe(a,"exec")&&!di(a)}),r=qe(e,i=>({message:"Token Type: ->"+i.name+"<- static 'PATTERN' can only be a RegExp, a Function matching the {CustomPatternMatcherFunc} type or an Object matching the {ICustomPattern} interface.",type:Gn.INVALID_PATTERN,tokenTypes:[i]})),n=jh(t,e);return{errors:r,valid:n}}function iIe(t){class e extends _c{static{o(this,"EndAnchorFinder")}constructor(){super(...arguments),this.found=!1}visitEndAnchor(a){this.found=!0}}let r=$r(t,i=>{let a=i.PATTERN;try{let s=jm(a),l=new e;return l.visit(s),l.found}catch{return nIe.test(a.source)}});return qe(r,i=>({message:`Unexpected RegExp Anchor Error: + Token Type: ->`+i.name+`<- static 'PATTERN' cannot contain end of input anchor '$' + See chevrotain.io/docs/guide/resolving_lexer_errors.html#ANCHORS for details.`,type:Gn.EOI_ANCHOR_FOUND,tokenTypes:[i]}))}function aIe(t){let e=$r(t,n=>n.PATTERN.test(""));return qe(e,n=>({message:"Token Type: ->"+n.name+"<- static 'PATTERN' must not match an empty string",type:Gn.EMPTY_MATCH_PATTERN,tokenTypes:[n]}))}function oIe(t){class e extends _c{static{o(this,"StartAnchorFinder")}constructor(){super(...arguments),this.found=!1}visitStartAnchor(a){this.found=!0}}let r=$r(t,i=>{let a=i.PATTERN;try{let s=jm(a),l=new e;return l.visit(s),l.found}catch{return sIe.test(a.source)}});return qe(r,i=>({message:`Unexpected RegExp Anchor Error: + Token Type: ->`+i.name+`<- static 'PATTERN' cannot contain start of input anchor '^' + See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#ANCHORS for details.`,type:Gn.SOI_ANCHOR_FOUND,tokenTypes:[i]}))}function lIe(t){let e=$r(t,n=>{let i=n[a0];return i instanceof RegExp&&(i.multiline||i.global)});return qe(e,n=>({message:"Token Type: ->"+n.name+"<- static 'PATTERN' may NOT contain global('g') or multiline('m')",type:Gn.UNSUPPORTED_FLAGS_FOUND,tokenTypes:[n]}))}function cIe(t){let e=[],r=qe(t,a=>Vr(t,(s,l)=>(a.PATTERN.source===l.PATTERN.source&&!Fn(e,l)&&l.PATTERN!==ni.NA&&(e.push(l),s.push(l)),s),[]));r=wc(r);let n=$r(r,a=>a.length>1);return qe(n,a=>{let s=qe(a,u=>u.name);return{message:`The same RegExp pattern ->${na(a).PATTERN}<-has been used in all of the following Token Types: ${s.join(", ")} <-`,type:Gn.DUPLICATE_PATTERNS_FOUND,tokenTypes:a}})}function uIe(t){let e=$r(t,n=>{if(!Xe(n,"GROUP"))return!1;let i=n.GROUP;return i!==ni.SKIPPED&&i!==ni.NA&&!di(i)});return qe(e,n=>({message:"Token Type: ->"+n.name+"<- static 'GROUP' can only be Lexer.SKIPPED/Lexer.NA/A String",type:Gn.INVALID_GROUP_TYPE_FOUND,tokenTypes:[n]}))}function hIe(t,e){let r=$r(t,i=>i.PUSH_MODE!==void 0&&!Fn(e,i.PUSH_MODE));return qe(r,i=>({message:`Token Type: ->${i.name}<- static 'PUSH_MODE' value cannot refer to a Lexer Mode ->${i.PUSH_MODE}<-which does not exist`,type:Gn.PUSH_MODE_DOES_NOT_EXIST,tokenTypes:[i]}))}function fIe(t){let e=[],r=Vr(t,(n,i,a)=>{let s=i.PATTERN;return s===ni.NA||(di(s)?n.push({str:s,idx:a,tokenType:i}):zo(s)&&pIe(s)&&n.push({str:s.source,idx:a,tokenType:i})),n},[]);return Ee(t,(n,i)=>{Ee(r,({str:a,idx:s,tokenType:l})=>{if(i${l.name}<- can never be matched. +Because it appears AFTER the Token Type ->${n.name}<-in the lexer's definition. +See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#UNREACHABLE`;e.push({message:u,type:Gn.UNREACHABLE_PATTERN,tokenTypes:[n,l]})}})}),e}function dIe(t,e){if(zo(e)){let r=e.exec(t);return r!==null&&r.index===0}else{if(wi(e))return e(t,0,[],{});if(Xe(e,"exec"))return e.exec(t,0,[],{});if(typeof e=="string")return e===t;throw Error("non exhaustive match")}}function pIe(t){return Za([".","\\","[","]","|","^","$","(",")","?","*","+","{"],r=>t.source.indexOf(r)!==-1)===void 0}function mie(t){let e=t.ignoreCase?"i":"";return new RegExp(`^(?:${t.source})`,e)}function gie(t){let e=t.ignoreCase?"iy":"y";return new RegExp(`${t.source}`,e)}function xie(t,e,r){let n=[];return Xe(t,Qm)||n.push({message:"A MultiMode Lexer cannot be initialized without a <"+Qm+`> property in its definition +`,type:Gn.MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE}),Xe(t,GT)||n.push({message:"A MultiMode Lexer cannot be initialized without a <"+GT+`> property in its definition +`,type:Gn.MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY}),Xe(t,GT)&&Xe(t,Qm)&&!Xe(t.modes,t.defaultMode)&&n.push({message:`A MultiMode Lexer cannot be initialized with a ${Qm}: <${t.defaultMode}>which does not exist +`,type:Gn.MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST}),Xe(t,GT)&&Ee(t.modes,(i,a)=>{Ee(i,(s,l)=>{if(er(s))n.push({message:`A Lexer cannot be initialized using an undefined Token Type. Mode:<${a}> at index: <${l}> +`,type:Gn.LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED});else if(Xe(s,"LONGER_ALT")){let u=wt(s.LONGER_ALT)?s.LONGER_ALT:[s.LONGER_ALT];Ee(u,h=>{!er(h)&&!Fn(i,h)&&n.push({message:`A MultiMode Lexer cannot be initialized with a longer_alt <${h.name}> on token <${s.name}> outside of mode <${a}> +`,type:Gn.MULTI_MODE_LEXER_LONGER_ALT_NOT_IN_CURRENT_MODE})})}})}),n}function bie(t,e,r){let n=[],i=!1,a=wc(Gr(or(t.modes))),s=Kh(a,u=>u[a0]===ni.NA),l=Cie(r);return e&&Ee(s,u=>{let h=Eie(u,l);if(h!==!1){let d={message:gIe(u,h),type:h.issue,tokenType:u};n.push(d)}else Xe(u,"LINE_BREAKS")?u.LINE_BREAKS===!0&&(i=!0):zT(l,u.PATTERN)&&(i=!0)}),e&&!i&&n.push({message:`Warning: No LINE_BREAKS Found. + This Lexer has been defined to track line and column information, + But none of the Token Types can be identified as matching a line terminator. + See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#LINE_BREAKS + for details.`,type:Gn.NO_LINE_BREAKS_FLAGS}),n}function wie(t){let e={},r=Dr(t);return Ee(r,n=>{let i=t[n];if(wt(i))e[n]=[];else throw Error("non exhaustive match")}),e}function Tie(t){let e=t.PATTERN;if(zo(e))return!1;if(wi(e))return!0;if(Xe(e,"exec"))return!0;if(di(e))return!1;throw Error("non exhaustive match")}function mIe(t){return di(t)&&t.length===1?t.charCodeAt(0):!1}function Eie(t,e){if(Xe(t,"LINE_BREAKS"))return!1;if(zo(t.PATTERN)){try{zT(e,t.PATTERN)}catch(r){return{issue:Gn.IDENTIFY_TERMINATOR,errMsg:r.message}}return!1}else{if(di(t.PATTERN))return!1;if(Tie(t))return{issue:Gn.CUSTOM_LINE_BREAK};throw Error("non exhaustive match")}}function gIe(t,e){if(e.issue===Gn.IDENTIFY_TERMINATOR)return`Warning: unable to identify line terminator usage in pattern. + The problem is in the <${t.name}> Token Type + Root cause: ${e.errMsg}. + For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#IDENTIFY_TERMINATOR`;if(e.issue===Gn.CUSTOM_LINE_BREAK)return`Warning: A Custom Token Pattern should specify the option. + The problem is in the <${t.name}> Token Type + For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#CUSTOM_LINE_BREAK`;throw Error("non exhaustive match")}function Cie(t){return qe(t,r=>di(r)?r.charCodeAt(0):r)}function hN(t,e,r){t[e]===void 0?t[e]=[r]:t[e].push(r)}function Lc(t){return t255?255+~~(t/255):t}}var a0,Qm,GT,fN,nIe,sIe,kie,Km,$T,uN=R(()=>{"use strict";Kv();i2();Pt();qm();pie();BT();a0="PATTERN",Qm="defaultMode",GT="modes",fN=typeof new RegExp("(?:)").sticky=="boolean";o(yie,"analyzeTokenTypes");o(vie,"validatePatterns");o(eIe,"validateRegExpPattern");o(tIe,"findMissingPatterns");o(rIe,"findInvalidPatterns");nIe=/[^\\][$]/;o(iIe,"findEndOfInputAnchor");o(aIe,"findEmptyMatchRegExps");sIe=/[^\\[][\^]|^\^/;o(oIe,"findStartOfInputAnchor");o(lIe,"findUnsupportedFlags");o(cIe,"findDuplicatePatterns");o(uIe,"findInvalidGroupType");o(hIe,"findModesThatDoNotExist");o(fIe,"findUnreachablePatterns");o(dIe,"testTokenType");o(pIe,"noMetaChar");o(mie,"addStartOfInput");o(gie,"addStickyFlag");o(xie,"performRuntimeChecks");o(bie,"performWarningRuntimeChecks");o(wie,"cloneEmptyGroups");o(Tie,"isCustomPattern");o(mIe,"isShortPattern");kie={test:o(function(t){let e=t.length;for(let r=this.lastIndex;r{r.isParent=r.categoryMatches.length>0})}function vIe(t){let e=Qr(t),r=t,n=!0;for(;n;){r=wc(Gr(qe(r,a=>a.CATEGORIES)));let i=jh(r,e);e=e.concat(i),Qt(i)?n=!1:r=i}return e}function xIe(t){Ee(t,e=>{dN(e)||(_ie[Sie]=e,e.tokenTypeIdx=Sie++),Aie(e)&&!wt(e.CATEGORIES)&&(e.CATEGORIES=[e.CATEGORIES]),Aie(e)||(e.CATEGORIES=[]),TIe(e)||(e.categoryMatches=[]),kIe(e)||(e.categoryMatchesMap={})})}function bIe(t){Ee(t,e=>{e.categoryMatches=[],Ee(e.categoryMatchesMap,(r,n)=>{e.categoryMatches.push(_ie[n].tokenTypeIdx)})})}function wIe(t){Ee(t,e=>{Lie([],e)})}function Lie(t,e){Ee(t,r=>{e.categoryMatchesMap[r.tokenTypeIdx]=!0}),Ee(e.CATEGORIES,r=>{let n=t.concat(e);Fn(n,r)||Lie(n,r)})}function dN(t){return Xe(t,"tokenTypeIdx")}function Aie(t){return Xe(t,"CATEGORIES")}function TIe(t){return Xe(t,"categoryMatches")}function kIe(t){return Xe(t,"categoryMatchesMap")}function Die(t){return Xe(t,"tokenTypeIdx")}var Sie,_ie,s0=R(()=>{"use strict";Pt();o(Bu,"tokenStructuredMatcher");o(Zm,"tokenStructuredMatcherNoCategories");Sie=1,_ie={};o(Fu,"augmentTokenTypes");o(vIe,"expandCategories");o(xIe,"assignTokenDefaultProps");o(bIe,"assignCategoriesTokensProp");o(wIe,"assignCategoriesMapProp");o(Lie,"singleAssignCategoriesToksMap");o(dN,"hasShortKeyProperty");o(Aie,"hasCategoriesProperty");o(TIe,"hasExtendingTokensTypesProperty");o(kIe,"hasExtendingTokensTypesMapProperty");o(Die,"isTokenType")});var pN,mN=R(()=>{"use strict";pN={buildUnableToPopLexerModeMessage(t){return`Unable to pop Lexer Mode after encountering Token ->${t.image}<- The Mode Stack is empty`},buildUnexpectedCharactersMessage(t,e,r,n,i){return`unexpected character: ->${t.charAt(e)}<- at offset: ${e}, skipped ${r} characters.`}}});var Gn,a2,ni,i2=R(()=>{"use strict";uN();Pt();qm();s0();mN();BT();(function(t){t[t.MISSING_PATTERN=0]="MISSING_PATTERN",t[t.INVALID_PATTERN=1]="INVALID_PATTERN",t[t.EOI_ANCHOR_FOUND=2]="EOI_ANCHOR_FOUND",t[t.UNSUPPORTED_FLAGS_FOUND=3]="UNSUPPORTED_FLAGS_FOUND",t[t.DUPLICATE_PATTERNS_FOUND=4]="DUPLICATE_PATTERNS_FOUND",t[t.INVALID_GROUP_TYPE_FOUND=5]="INVALID_GROUP_TYPE_FOUND",t[t.PUSH_MODE_DOES_NOT_EXIST=6]="PUSH_MODE_DOES_NOT_EXIST",t[t.MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE=7]="MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE",t[t.MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY=8]="MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY",t[t.MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST=9]="MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST",t[t.LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED=10]="LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED",t[t.SOI_ANCHOR_FOUND=11]="SOI_ANCHOR_FOUND",t[t.EMPTY_MATCH_PATTERN=12]="EMPTY_MATCH_PATTERN",t[t.NO_LINE_BREAKS_FLAGS=13]="NO_LINE_BREAKS_FLAGS",t[t.UNREACHABLE_PATTERN=14]="UNREACHABLE_PATTERN",t[t.IDENTIFY_TERMINATOR=15]="IDENTIFY_TERMINATOR",t[t.CUSTOM_LINE_BREAK=16]="CUSTOM_LINE_BREAK",t[t.MULTI_MODE_LEXER_LONGER_ALT_NOT_IN_CURRENT_MODE=17]="MULTI_MODE_LEXER_LONGER_ALT_NOT_IN_CURRENT_MODE"})(Gn||(Gn={}));a2={deferDefinitionErrorsHandling:!1,positionTracking:"full",lineTerminatorsPattern:/\n|\r\n?/g,lineTerminatorCharacters:[` +`,"\r"],ensureOptimizations:!1,safeMode:!1,errorMessageProvider:pN,traceInitPerf:!1,skipValidations:!1,recoveryEnabled:!0};Object.freeze(a2);ni=class{static{o(this,"Lexer")}constructor(e,r=a2){if(this.lexerDefinition=e,this.lexerDefinitionErrors=[],this.lexerDefinitionWarning=[],this.patternIdxToConfig={},this.charCodeToPatternIdxToConfig={},this.modes=[],this.emptyGroups={},this.trackStartLines=!0,this.trackEndLines=!0,this.hasCustom=!1,this.canModeBeOptimized={},this.TRACE_INIT=(i,a)=>{if(this.traceInitPerf===!0){this.traceInitIndent++;let s=new Array(this.traceInitIndent+1).join(" ");this.traceInitIndent <${i}>`);let{time:l,value:u}=t2(a),h=l>10?console.warn:console.log;return this.traceInitIndent time: ${l}ms`),this.traceInitIndent--,u}else return a()},typeof r=="boolean")throw Error(`The second argument to the Lexer constructor is now an ILexerConfig Object. +a boolean 2nd argument is no longer supported`);this.config=pa({},a2,r);let n=this.config.traceInitPerf;n===!0?(this.traceInitMaxIdent=1/0,this.traceInitPerf=!0):typeof n=="number"&&(this.traceInitMaxIdent=n,this.traceInitPerf=!0),this.traceInitIndent=-1,this.TRACE_INIT("Lexer Constructor",()=>{let i,a=!0;this.TRACE_INIT("Lexer Config handling",()=>{if(this.config.lineTerminatorsPattern===a2.lineTerminatorsPattern)this.config.lineTerminatorsPattern=kie;else if(this.config.lineTerminatorCharacters===a2.lineTerminatorCharacters)throw Error(`Error: Missing property on the Lexer config. + For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#MISSING_LINE_TERM_CHARS`);if(r.safeMode&&r.ensureOptimizations)throw Error('"safeMode" and "ensureOptimizations" flags are mutually exclusive.');this.trackStartLines=/full|onlyStart/i.test(this.config.positionTracking),this.trackEndLines=/full/i.test(this.config.positionTracking),wt(e)?i={modes:{defaultMode:Qr(e)},defaultMode:Qm}:(a=!1,i=Qr(e))}),this.config.skipValidations===!1&&(this.TRACE_INIT("performRuntimeChecks",()=>{this.lexerDefinitionErrors=this.lexerDefinitionErrors.concat(xie(i,this.trackStartLines,this.config.lineTerminatorCharacters))}),this.TRACE_INIT("performWarningRuntimeChecks",()=>{this.lexerDefinitionWarning=this.lexerDefinitionWarning.concat(bie(i,this.trackStartLines,this.config.lineTerminatorCharacters))})),i.modes=i.modes?i.modes:{},Ee(i.modes,(l,u)=>{i.modes[u]=Kh(l,h=>er(h))});let s=Dr(i.modes);if(Ee(i.modes,(l,u)=>{this.TRACE_INIT(`Mode: <${u}> processing`,()=>{if(this.modes.push(u),this.config.skipValidations===!1&&this.TRACE_INIT("validatePatterns",()=>{this.lexerDefinitionErrors=this.lexerDefinitionErrors.concat(vie(l,s))}),Qt(this.lexerDefinitionErrors)){Fu(l);let h;this.TRACE_INIT("analyzeTokenTypes",()=>{h=yie(l,{lineTerminatorCharacters:this.config.lineTerminatorCharacters,positionTracking:r.positionTracking,ensureOptimizations:r.ensureOptimizations,safeMode:r.safeMode,tracer:this.TRACE_INIT})}),this.patternIdxToConfig[u]=h.patternIdxToConfig,this.charCodeToPatternIdxToConfig[u]=h.charCodeToPatternIdxToConfig,this.emptyGroups=pa({},this.emptyGroups,h.emptyGroups),this.hasCustom=h.hasCustom||this.hasCustom,this.canModeBeOptimized[u]=h.canBeOptimized}})}),this.defaultMode=i.defaultMode,!Qt(this.lexerDefinitionErrors)&&!this.config.deferDefinitionErrorsHandling){let u=qe(this.lexerDefinitionErrors,h=>h.message).join(`----------------------- +`);throw new Error(`Errors detected in definition of Lexer: +`+u)}Ee(this.lexerDefinitionWarning,l=>{e2(l.message)}),this.TRACE_INIT("Choosing sub-methods implementations",()=>{if(fN?(this.chopInput=ea,this.match=this.matchWithTest):(this.updateLastIndex=qn,this.match=this.matchWithExec),a&&(this.handleModes=qn),this.trackStartLines===!1&&(this.computeNewColumn=ea),this.trackEndLines===!1&&(this.updateTokenEndLineColumnLocation=qn),/full/i.test(this.config.positionTracking))this.createTokenInstance=this.createFullToken;else if(/onlyStart/i.test(this.config.positionTracking))this.createTokenInstance=this.createStartOnlyToken;else if(/onlyOffset/i.test(this.config.positionTracking))this.createTokenInstance=this.createOffsetOnlyToken;else throw Error(`Invalid config option: "${this.config.positionTracking}"`);this.hasCustom?(this.addToken=this.addTokenUsingPush,this.handlePayload=this.handlePayloadWithCustom):(this.addToken=this.addTokenUsingMemberAccess,this.handlePayload=this.handlePayloadNoCustom)}),this.TRACE_INIT("Failed Optimization Warnings",()=>{let l=Vr(this.canModeBeOptimized,(u,h,f)=>(h===!1&&u.push(f),u),[]);if(r.ensureOptimizations&&!Qt(l))throw Error(`Lexer Modes: < ${l.join(", ")} > cannot be optimized. + Disable the "ensureOptimizations" lexer config flag to silently ignore this and run the lexer in an un-optimized mode. + Or inspect the console log for details on how to resolve these issues.`)}),this.TRACE_INIT("clearRegExpParserCache",()=>{uie()}),this.TRACE_INIT("toFastProperties",()=>{r2(this)})})}tokenize(e,r=this.defaultMode){if(!Qt(this.lexerDefinitionErrors)){let i=qe(this.lexerDefinitionErrors,a=>a.message).join(`----------------------- +`);throw new Error(`Unable to Tokenize because Errors detected in definition of Lexer: +`+i)}return this.tokenizeInternal(e,r)}tokenizeInternal(e,r){let n,i,a,s,l,u,h,f,d,p,m,g,y,v,x,b,w=e,S=w.length,T=0,E=0,_=this.hasCustom?0:Math.floor(e.length/10),A=new Array(_),L=[],M=this.trackStartLines?1:void 0,N=this.trackStartLines?1:void 0,k=wie(this.emptyGroups),I=this.trackStartLines,C=this.config.lineTerminatorsPattern,O=0,D=[],P=[],F=[],B=[];Object.freeze(B);let $;function z(){return D}o(z,"getPossiblePatternsSlow");function Y(J){let Z=Lc(J),H=P[Z];return H===void 0?B:H}o(Y,"getPossiblePatternsOptimized");let Q=o(J=>{if(F.length===1&&J.tokenType.PUSH_MODE===void 0){let Z=this.config.errorMessageProvider.buildUnableToPopLexerModeMessage(J);L.push({offset:J.startOffset,line:J.startLine,column:J.startColumn,length:J.image.length,message:Z})}else{F.pop();let Z=ma(F);D=this.patternIdxToConfig[Z],P=this.charCodeToPatternIdxToConfig[Z],O=D.length;let H=this.canModeBeOptimized[Z]&&this.config.safeMode===!1;P&&H?$=Y:$=z}},"pop_mode");function X(J){F.push(J),P=this.charCodeToPatternIdxToConfig[J],D=this.patternIdxToConfig[J],O=D.length,O=D.length;let Z=this.canModeBeOptimized[J]&&this.config.safeMode===!1;P&&Z?$=Y:$=z}o(X,"push_mode"),X.call(this,r);let ie,j=this.config.recoveryEnabled;for(;Tu.length){u=s,h=f,ie=ce;break}}}break}}if(u!==null){if(d=u.length,p=ie.group,p!==void 0&&(m=ie.tokenTypeIdx,g=this.createTokenInstance(u,T,m,ie.tokenType,M,N,d),this.handlePayload(g,h),p===!1?E=this.addToken(A,E,g):k[p].push(g)),e=this.chopInput(e,d),T=T+d,N=this.computeNewColumn(N,d),I===!0&&ie.canLineTerminator===!0){let q=0,K,se;C.lastIndex=0;do K=C.test(u),K===!0&&(se=C.lastIndex-1,q++);while(K===!0);q!==0&&(M=M+q,N=d-se,this.updateTokenEndLineColumnLocation(g,p,se,q,M,N,d))}this.handleModes(ie,Q,X,g)}else{let q=T,K=M,se=N,ce=j===!1;for(;ce===!1&&T{"use strict";Pt();i2();s0();o(zu,"tokenLabel");o(gN,"hasTokenLabel");EIe="parent",Rie="categories",Nie="label",Mie="group",Iie="push_mode",Oie="pop_mode",Pie="longer_alt",Bie="line_breaks",Fie="start_chars_hint";o(VT,"createToken");o(CIe,"createTokenInternal");fo=VT({name:"EOF",pattern:ni.NA});Fu([fo]);o(o0,"createTokenInstance");o(s2,"tokenMatcher")});var Gu,zie,Ol,Jm=R(()=>{"use strict";l0();Pt();ns();Gu={buildMismatchTokenMessage({expected:t,actual:e,previous:r,ruleName:n}){return`Expecting ${gN(t)?`--> ${zu(t)} <--`:`token of type --> ${t.name} <--`} but found --> '${e.image}' <--`},buildNotAllInputParsedMessage({firstRedundant:t,ruleName:e}){return"Redundant input, expecting EOF but found: "+t.image},buildNoViableAltMessage({expectedPathsPerAlt:t,actual:e,previous:r,customUserDescription:n,ruleName:i}){let a="Expecting: ",l=` +but found: '`+na(e).image+"'";if(n)return a+n+l;{let u=Vr(t,(p,m)=>p.concat(m),[]),h=qe(u,p=>`[${qe(p,m=>zu(m)).join(", ")}]`),d=`one of these possible Token sequences: +${qe(h,(p,m)=>` ${m+1}. ${p}`).join(` +`)}`;return a+d+l}},buildEarlyExitMessage({expectedIterationPaths:t,actual:e,customUserDescription:r,ruleName:n}){let i="Expecting: ",s=` +but found: '`+na(e).image+"'";if(r)return i+r+s;{let u=`expecting at least one iteration which starts with one of these possible Token sequences:: + <${qe(t,h=>`[${qe(h,f=>zu(f)).join(",")}]`).join(" ,")}>`;return i+u+s}}};Object.freeze(Gu);zie={buildRuleNotFoundError(t,e){return"Invalid grammar, reference to a rule which is not defined: ->"+e.nonTerminalName+`<- +inside top level rule: ->`+t.name+"<-"}},Ol={buildDuplicateFoundError(t,e){function r(f){return f instanceof fr?f.terminalType.name:f instanceof Zr?f.nonTerminalName:""}o(r,"getExtraProductionArgument");let n=t.name,i=na(e),a=i.idx,s=Rs(i),l=r(i),u=a>0,h=`->${s}${u?a:""}<- ${l?`with argument: ->${l}<-`:""} + appears more than once (${e.length} times) in the top level rule: ->${n}<-. + For further details see: https://chevrotain.io/docs/FAQ.html#NUMERICAL_SUFFIXES + `;return h=h.replace(/[ \t]+/g," "),h=h.replace(/\s\s+/g,` +`),h},buildNamespaceConflictError(t){return`Namespace conflict found in grammar. +The grammar has both a Terminal(Token) and a Non-Terminal(Rule) named: <${t.name}>. +To resolve this make sure each Terminal and Non-Terminal names are unique +This is easy to accomplish by using the convention that Terminal names start with an uppercase letter +and Non-Terminal names start with a lower case letter.`},buildAlternationPrefixAmbiguityError(t){let e=qe(t.prefixPath,i=>zu(i)).join(", "),r=t.alternation.idx===0?"":t.alternation.idx;return`Ambiguous alternatives: <${t.ambiguityIndices.join(" ,")}> due to common lookahead prefix +in inside <${t.topLevelRule.name}> Rule, +<${e}> may appears as a prefix path in all these alternatives. +See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#COMMON_PREFIX +For Further details.`},buildAlternationAmbiguityError(t){let e=qe(t.prefixPath,i=>zu(i)).join(", "),r=t.alternation.idx===0?"":t.alternation.idx,n=`Ambiguous Alternatives Detected: <${t.ambiguityIndices.join(" ,")}> in inside <${t.topLevelRule.name}> Rule, +<${e}> may appears as a prefix path in all these alternatives. +`;return n=n+`See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#AMBIGUOUS_ALTERNATIVES +For Further details.`,n},buildEmptyRepetitionError(t){let e=Rs(t.repetition);return t.repetition.idx!==0&&(e+=t.repetition.idx),`The repetition <${e}> within Rule <${t.topLevelRule.name}> can never consume any tokens. +This could lead to an infinite loop.`},buildTokenNameError(t){return"deprecated"},buildEmptyAlternationError(t){return`Ambiguous empty alternative: <${t.emptyChoiceIdx+1}> in inside <${t.topLevelRule.name}> Rule. +Only the last alternative may be an empty alternative.`},buildTooManyAlternativesError(t){return`An Alternation cannot have more than 256 alternatives: + inside <${t.topLevelRule.name}> Rule. + has ${t.alternation.definition.length+1} alternatives.`},buildLeftRecursionError(t){let e=t.topLevelRule.name,r=qe(t.leftRecursionPath,a=>a.name),n=`${e} --> ${r.concat([e]).join(" --> ")}`;return`Left Recursion found in grammar. +rule: <${e}> can be invoked from itself (directly or indirectly) +without consuming any Tokens. The grammar path that causes this is: + ${n} + To fix this refactor your grammar to remove the left recursion. +see: https://en.wikipedia.org/wiki/LL_parser#Left_factoring.`},buildInvalidRuleNameError(t){return"deprecated"},buildDuplicateRuleNameError(t){let e;return t.topLevelRule instanceof ts?e=t.topLevelRule.name:e=t.topLevelRule,`Duplicate definition, rule: ->${e}<- is already defined in the grammar: ->${t.grammarName}<-`}}});function Gie(t,e){let r=new yN(t,e);return r.resolveRefs(),r.errors}var yN,$ie=R(()=>{"use strict";Ns();Pt();ns();o(Gie,"resolveGrammar");yN=class extends rs{static{o(this,"GastRefResolverVisitor")}constructor(e,r){super(),this.nameToTopRule=e,this.errMsgProvider=r,this.errors=[]}resolveRefs(){Ee(or(this.nameToTopRule),e=>{this.currTopLevel=e,e.accept(this)})}visitNonTerminal(e){let r=this.nameToTopRule[e.nonTerminalName];if(r)e.referencedRule=r;else{let n=this.errMsgProvider.buildRuleNotFoundError(this.currTopLevel,e);this.errors.push({message:n,type:Pi.UNRESOLVED_SUBRULE_REF,ruleName:this.currTopLevel.name,unresolvedRefName:e.nonTerminalName})}}}});function WT(t,e,r=[]){r=Qr(r);let n=[],i=0;function a(l){return l.concat(fi(t,i+1))}o(a,"remainingPathWith");function s(l){let u=WT(a(l),e,r);return n.concat(u)}for(o(s,"getAlternativesForProd");r.length{Qt(u.definition)===!1&&(n=s(u.definition))}),n;if(l instanceof fr)r.push(l.terminalType);else throw Error("non exhaustive match")}i++}return n.push({partialPath:r,suffixDef:fi(t,i)}),n}function qT(t,e,r,n){let i="EXIT_NONE_TERMINAL",a=[i],s="EXIT_ALTERNATIVE",l=!1,u=e.length,h=u-n-1,f=[],d=[];for(d.push({idx:-1,def:t,ruleStack:[],occurrenceStack:[]});!Qt(d);){let p=d.pop();if(p===s){l&&ma(d).idx<=h&&d.pop();continue}let m=p.def,g=p.idx,y=p.ruleStack,v=p.occurrenceStack;if(Qt(m))continue;let x=m[0];if(x===i){let b={idx:g,def:fi(m),ruleStack:Ru(y),occurrenceStack:Ru(v)};d.push(b)}else if(x instanceof fr)if(g=0;b--){let w=x.definition[b],S={idx:g,def:w.definition.concat(fi(m)),ruleStack:y,occurrenceStack:v};d.push(S),d.push(s)}else if(x instanceof Sn)d.push({idx:g,def:x.definition.concat(fi(m)),ruleStack:y,occurrenceStack:v});else if(x instanceof ts)d.push(SIe(x,g,y,v));else throw Error("non exhaustive match")}return f}function SIe(t,e,r,n){let i=Qr(r);i.push(t.name);let a=Qr(n);return a.push(1),{idx:e,def:t.definition,ruleStack:i,occurrenceStack:a}}var vN,UT,eg,HT,o2,YT,l2,c2=R(()=>{"use strict";Pt();iN();IT();ns();vN=class extends Pu{static{o(this,"AbstractNextPossibleTokensWalker")}constructor(e,r){super(),this.topProd=e,this.path=r,this.possibleTokTypes=[],this.nextProductionName="",this.nextProductionOccurrence=0,this.found=!1,this.isAtEndOfPath=!1}startWalking(){if(this.found=!1,this.path.ruleStack[0]!==this.topProd.name)throw Error("The path does not start with the walker's top Rule!");return this.ruleStack=Qr(this.path.ruleStack).reverse(),this.occurrenceStack=Qr(this.path.occurrenceStack).reverse(),this.ruleStack.pop(),this.occurrenceStack.pop(),this.updateExpectedNext(),this.walk(this.topProd),this.possibleTokTypes}walk(e,r=[]){this.found||super.walk(e,r)}walkProdRef(e,r,n){if(e.referencedRule.name===this.nextProductionName&&e.idx===this.nextProductionOccurrence){let i=r.concat(n);this.updateExpectedNext(),this.walk(e.referencedRule,i)}}updateExpectedNext(){Qt(this.ruleStack)?(this.nextProductionName="",this.nextProductionOccurrence=0,this.isAtEndOfPath=!0):(this.nextProductionName=this.ruleStack.pop(),this.nextProductionOccurrence=this.occurrenceStack.pop())}},UT=class extends vN{static{o(this,"NextAfterTokenWalker")}constructor(e,r){super(e,r),this.path=r,this.nextTerminalName="",this.nextTerminalOccurrence=0,this.nextTerminalName=this.path.lastTok.name,this.nextTerminalOccurrence=this.path.lastTokOccurrence}walkTerminal(e,r,n){if(this.isAtEndOfPath&&e.terminalType.name===this.nextTerminalName&&e.idx===this.nextTerminalOccurrence&&!this.found){let i=r.concat(n),a=new Sn({definition:i});this.possibleTokTypes=i0(a),this.found=!0}}},eg=class extends Pu{static{o(this,"AbstractNextTerminalAfterProductionWalker")}constructor(e,r){super(),this.topRule=e,this.occurrence=r,this.result={token:void 0,occurrence:void 0,isEndOfRule:void 0}}startWalking(){return this.walk(this.topRule),this.result}},HT=class extends eg{static{o(this,"NextTerminalAfterManyWalker")}walkMany(e,r,n){if(e.idx===this.occurrence){let i=na(r.concat(n));this.result.isEndOfRule=i===void 0,i instanceof fr&&(this.result.token=i.terminalType,this.result.occurrence=i.idx)}else super.walkMany(e,r,n)}},o2=class extends eg{static{o(this,"NextTerminalAfterManySepWalker")}walkManySep(e,r,n){if(e.idx===this.occurrence){let i=na(r.concat(n));this.result.isEndOfRule=i===void 0,i instanceof fr&&(this.result.token=i.terminalType,this.result.occurrence=i.idx)}else super.walkManySep(e,r,n)}},YT=class extends eg{static{o(this,"NextTerminalAfterAtLeastOneWalker")}walkAtLeastOne(e,r,n){if(e.idx===this.occurrence){let i=na(r.concat(n));this.result.isEndOfRule=i===void 0,i instanceof fr&&(this.result.token=i.terminalType,this.result.occurrence=i.idx)}else super.walkAtLeastOne(e,r,n)}},l2=class extends eg{static{o(this,"NextTerminalAfterAtLeastOneSepWalker")}walkAtLeastOneSep(e,r,n){if(e.idx===this.occurrence){let i=na(r.concat(n));this.result.isEndOfRule=i===void 0,i instanceof fr&&(this.result.token=i.terminalType,this.result.occurrence=i.idx)}else super.walkAtLeastOneSep(e,r,n)}};o(WT,"possiblePathsFrom");o(qT,"nextPossibleTokensAfter");o(SIe,"expandTopLevelRule")});function u2(t){if(t instanceof Jr||t==="Option")return $n.OPTION;if(t instanceof br||t==="Repetition")return $n.REPETITION;if(t instanceof An||t==="RepetitionMandatory")return $n.REPETITION_MANDATORY;if(t instanceof _n||t==="RepetitionMandatoryWithSeparator")return $n.REPETITION_MANDATORY_WITH_SEPARATOR;if(t instanceof mn||t==="RepetitionWithSeparator")return $n.REPETITION_WITH_SEPARATOR;if(t instanceof gn||t==="Alternation")return $n.ALTERNATION;throw Error("non exhaustive match")}function jT(t){let{occurrence:e,rule:r,prodType:n,maxLookahead:i}=t,a=u2(n);return a===$n.ALTERNATION?tg(e,r,i):rg(e,r,a,i)}function Uie(t,e,r,n,i,a){let s=tg(t,e,r),l=jie(s)?Zm:Bu;return a(s,n,l,i)}function Hie(t,e,r,n,i,a){let s=rg(t,e,i,r),l=jie(s)?Zm:Bu;return a(s[0],l,n)}function Yie(t,e,r,n){let i=t.length,a=Ia(t,s=>Ia(s,l=>l.length===1));if(e)return function(s){let l=qe(s,u=>u.GATE);for(let u=0;uGr(u)),l=Vr(s,(u,h,f)=>(Ee(h,d=>{Xe(u,d.tokenTypeIdx)||(u[d.tokenTypeIdx]=f),Ee(d.categoryMatches,p=>{Xe(u,p)||(u[p]=f)})}),u),{});return function(){let u=this.LA(1);return l[u.tokenTypeIdx]}}else return function(){for(let s=0;sa.length===1),i=t.length;if(n&&!r){let a=Gr(t);if(a.length===1&&Qt(a[0].categoryMatches)){let l=a[0].tokenTypeIdx;return function(){return this.LA(1).tokenTypeIdx===l}}else{let s=Vr(a,(l,u,h)=>(l[u.tokenTypeIdx]=!0,Ee(u.categoryMatches,f=>{l[f]=!0}),l),[]);return function(){let l=this.LA(1);return s[l.tokenTypeIdx]===!0}}}else return function(){e:for(let a=0;aWT([s],1)),n=Vie(r.length),i=qe(r,s=>{let l={};return Ee(s,u=>{let h=xN(u.partialPath);Ee(h,f=>{l[f]=!0})}),l}),a=r;for(let s=1;s<=e;s++){let l=a;a=Vie(l.length);for(let u=0;u{let x=xN(v.partialPath);Ee(x,b=>{i[u][b]=!0})})}}}}return n}function tg(t,e,r,n){let i=new XT(t,$n.ALTERNATION,n);return e.accept(i),qie(i.result,r)}function rg(t,e,r,n){let i=new XT(t,r);e.accept(i);let a=i.result,l=new bN(e,t,r).startWalking(),u=new Sn({definition:a}),h=new Sn({definition:l});return qie([u,h],n)}function KT(t,e){e:for(let r=0;r{let i=e[n];return r===i||i.categoryMatchesMap[r.tokenTypeIdx]})}function jie(t){return Ia(t,e=>Ia(e,r=>Ia(r,n=>Qt(n.categoryMatches))))}var $n,bN,XT,ng=R(()=>{"use strict";Pt();c2();IT();s0();ns();(function(t){t[t.OPTION=0]="OPTION",t[t.REPETITION=1]="REPETITION",t[t.REPETITION_MANDATORY=2]="REPETITION_MANDATORY",t[t.REPETITION_MANDATORY_WITH_SEPARATOR=3]="REPETITION_MANDATORY_WITH_SEPARATOR",t[t.REPETITION_WITH_SEPARATOR=4]="REPETITION_WITH_SEPARATOR",t[t.ALTERNATION=5]="ALTERNATION"})($n||($n={}));o(u2,"getProdType");o(jT,"getLookaheadPaths");o(Uie,"buildLookaheadFuncForOr");o(Hie,"buildLookaheadFuncForOptionalProd");o(Yie,"buildAlternativesLookAheadFunc");o(Wie,"buildSingleAlternativeLookaheadFunction");bN=class extends Pu{static{o(this,"RestDefinitionFinderWalker")}constructor(e,r,n){super(),this.topProd=e,this.targetOccurrence=r,this.targetProdType=n}startWalking(){return this.walk(this.topProd),this.restDef}checkIsTarget(e,r,n,i){return e.idx===this.targetOccurrence&&this.targetProdType===r?(this.restDef=n.concat(i),!0):!1}walkOption(e,r,n){this.checkIsTarget(e,$n.OPTION,r,n)||super.walkOption(e,r,n)}walkAtLeastOne(e,r,n){this.checkIsTarget(e,$n.REPETITION_MANDATORY,r,n)||super.walkOption(e,r,n)}walkAtLeastOneSep(e,r,n){this.checkIsTarget(e,$n.REPETITION_MANDATORY_WITH_SEPARATOR,r,n)||super.walkOption(e,r,n)}walkMany(e,r,n){this.checkIsTarget(e,$n.REPETITION,r,n)||super.walkOption(e,r,n)}walkManySep(e,r,n){this.checkIsTarget(e,$n.REPETITION_WITH_SEPARATOR,r,n)||super.walkOption(e,r,n)}},XT=class extends rs{static{o(this,"InsideDefinitionFinderVisitor")}constructor(e,r,n){super(),this.targetOccurrence=e,this.targetProdType=r,this.targetRef=n,this.result=[]}checkIsTarget(e,r){e.idx===this.targetOccurrence&&this.targetProdType===r&&(this.targetRef===void 0||e===this.targetRef)&&(this.result=e.definition)}visitOption(e){this.checkIsTarget(e,$n.OPTION)}visitRepetition(e){this.checkIsTarget(e,$n.REPETITION)}visitRepetitionMandatory(e){this.checkIsTarget(e,$n.REPETITION_MANDATORY)}visitRepetitionMandatoryWithSeparator(e){this.checkIsTarget(e,$n.REPETITION_MANDATORY_WITH_SEPARATOR)}visitRepetitionWithSeparator(e){this.checkIsTarget(e,$n.REPETITION_WITH_SEPARATOR)}visitAlternation(e){this.checkIsTarget(e,$n.ALTERNATION)}};o(Vie,"initializeArrayOfArrays");o(xN,"pathToHashKeys");o(AIe,"isUniquePrefixHash");o(qie,"lookAheadSequenceFromAlternatives");o(tg,"getLookaheadPathsForOr");o(rg,"getLookaheadPathsForOptionalProd");o(KT,"containsPath");o(Xie,"isStrictPrefixOfPath");o(jie,"areTokenCategoriesNotUsed")});function Kie(t){let e=t.lookaheadStrategy.validate({rules:t.rules,tokenTypes:t.tokenTypes,grammarName:t.grammarName});return qe(e,r=>Object.assign({type:Pi.CUSTOM_LOOKAHEAD_VALIDATION},r))}function Qie(t,e,r,n){let i=ga(t,u=>_Ie(u,r)),a=IIe(t,e,r),s=ga(t,u=>RIe(u,r)),l=ga(t,u=>DIe(u,t,n,r));return i.concat(a,s,l)}function _Ie(t,e){let r=new wN;t.accept(r);let n=r.allProductions,i=IL(n,LIe),a=Ls(i,l=>l.length>1);return qe(or(a),l=>{let u=na(l),h=e.buildDuplicateFoundError(t,l),f=Rs(u),d={message:h,type:Pi.DUPLICATE_PRODUCTIONS,ruleName:t.name,dslName:f,occurrence:u.idx},p=Zie(u);return p&&(d.parameter=p),d})}function LIe(t){return`${Rs(t)}_#_${t.idx}_#_${Zie(t)}`}function Zie(t){return t instanceof fr?t.terminalType.name:t instanceof Zr?t.nonTerminalName:""}function DIe(t,e,r,n){let i=[];if(Vr(e,(s,l)=>l.name===t.name?s+1:s,0)>1){let s=n.buildDuplicateRuleNameError({topLevelRule:t,grammarName:r});i.push({message:s,type:Pi.DUPLICATE_RULE_NAME,ruleName:t.name})}return i}function Jie(t,e,r){let n=[],i;return Fn(e,t)||(i=`Invalid rule override, rule: ->${t}<- cannot be overridden in the grammar: ->${r}<-as it is not defined in any of the super grammars `,n.push({message:i,type:Pi.INVALID_RULE_OVERRIDE,ruleName:t})),n}function kN(t,e,r,n=[]){let i=[],a=QT(e.definition);if(Qt(a))return[];{let s=t.name;Fn(a,t)&&i.push({message:r.buildLeftRecursionError({topLevelRule:t,leftRecursionPath:n}),type:Pi.LEFT_RECURSION,ruleName:s});let u=jh(a,n.concat([t])),h=ga(u,f=>{let d=Qr(n);return d.push(f),kN(t,f,r,d)});return i.concat(h)}}function QT(t){let e=[];if(Qt(t))return e;let r=na(t);if(r instanceof Zr)e.push(r.referencedRule);else if(r instanceof Sn||r instanceof Jr||r instanceof An||r instanceof _n||r instanceof mn||r instanceof br)e=e.concat(QT(r.definition));else if(r instanceof gn)e=Gr(qe(r.definition,a=>QT(a.definition)));else if(!(r instanceof fr))throw Error("non exhaustive match");let n=n0(r),i=t.length>1;if(n&&i){let a=fi(t);return e.concat(QT(a))}else return e}function eae(t,e){let r=new h2;t.accept(r);let n=r.alternations;return ga(n,a=>{let s=Ru(a.definition);return ga(s,(l,u)=>{let h=qT([l],[],Bu,1);return Qt(h)?[{message:e.buildEmptyAlternationError({topLevelRule:t,alternation:a,emptyChoiceIdx:u}),type:Pi.NONE_LAST_EMPTY_ALT,ruleName:t.name,occurrence:a.idx,alternative:u+1}]:[]})})}function tae(t,e,r){let n=new h2;t.accept(n);let i=n.alternations;return i=Kh(i,s=>s.ignoreAmbiguities===!0),ga(i,s=>{let l=s.idx,u=s.maxLookahead||e,h=tg(l,t,u,s),f=NIe(h,s,t,r),d=MIe(h,s,t,r);return f.concat(d)})}function RIe(t,e){let r=new h2;t.accept(r);let n=r.alternations;return ga(n,a=>a.definition.length>255?[{message:e.buildTooManyAlternativesError({topLevelRule:t,alternation:a}),type:Pi.TOO_MANY_ALTS,ruleName:t.name,occurrence:a.idx}]:[])}function rae(t,e,r){let n=[];return Ee(t,i=>{let a=new TN;i.accept(a);let s=a.allProductions;Ee(s,l=>{let u=u2(l),h=l.maxLookahead||e,f=l.idx,p=rg(f,i,u,h)[0];if(Qt(Gr(p))){let m=r.buildEmptyRepetitionError({topLevelRule:i,repetition:l});n.push({message:m,type:Pi.NO_NON_EMPTY_LOOKAHEAD,ruleName:i.name})}})}),n}function NIe(t,e,r,n){let i=[],a=Vr(t,(l,u,h)=>(e.definition[h].ignoreAmbiguities===!0||Ee(u,f=>{let d=[h];Ee(t,(p,m)=>{h!==m&&KT(p,f)&&e.definition[m].ignoreAmbiguities!==!0&&d.push(m)}),d.length>1&&!KT(i,f)&&(i.push(f),l.push({alts:d,path:f}))}),l),[]);return qe(a,l=>{let u=qe(l.alts,f=>f+1);return{message:n.buildAlternationAmbiguityError({topLevelRule:r,alternation:e,ambiguityIndices:u,prefixPath:l.path}),type:Pi.AMBIGUOUS_ALTS,ruleName:r.name,occurrence:e.idx,alternatives:l.alts}})}function MIe(t,e,r,n){let i=Vr(t,(s,l,u)=>{let h=qe(l,f=>({idx:u,path:f}));return s.concat(h)},[]);return wc(ga(i,s=>{if(e.definition[s.idx].ignoreAmbiguities===!0)return[];let u=s.idx,h=s.path,f=$r(i,p=>e.definition[p.idx].ignoreAmbiguities!==!0&&p.idx{let m=[p.idx+1,u+1],g=e.idx===0?"":e.idx;return{message:n.buildAlternationPrefixAmbiguityError({topLevelRule:r,alternation:e,ambiguityIndices:m,prefixPath:p.path}),type:Pi.AMBIGUOUS_PREFIX_ALTS,ruleName:r.name,occurrence:g,alternatives:m}})}))}function IIe(t,e,r){let n=[],i=qe(e,a=>a.name);return Ee(t,a=>{let s=a.name;if(Fn(i,s)){let l=r.buildNamespaceConflictError(a);n.push({message:l,type:Pi.CONFLICT_TOKENS_RULES_NAMESPACE,ruleName:s})}}),n}var wN,h2,TN,f2=R(()=>{"use strict";Pt();Ns();ns();ng();c2();s0();o(Kie,"validateLookahead");o(Qie,"validateGrammar");o(_Ie,"validateDuplicateProductions");o(LIe,"identifyProductionForDuplicates");o(Zie,"getExtraProductionArgument");wN=class extends rs{static{o(this,"OccurrenceValidationCollector")}constructor(){super(...arguments),this.allProductions=[]}visitNonTerminal(e){this.allProductions.push(e)}visitOption(e){this.allProductions.push(e)}visitRepetitionWithSeparator(e){this.allProductions.push(e)}visitRepetitionMandatory(e){this.allProductions.push(e)}visitRepetitionMandatoryWithSeparator(e){this.allProductions.push(e)}visitRepetition(e){this.allProductions.push(e)}visitAlternation(e){this.allProductions.push(e)}visitTerminal(e){this.allProductions.push(e)}};o(DIe,"validateRuleDoesNotAlreadyExist");o(Jie,"validateRuleIsOverridden");o(kN,"validateNoLeftRecursion");o(QT,"getFirstNoneTerminal");h2=class extends rs{static{o(this,"OrCollector")}constructor(){super(...arguments),this.alternations=[]}visitAlternation(e){this.alternations.push(e)}};o(eae,"validateEmptyOrAlternative");o(tae,"validateAmbiguousAlternationAlternatives");TN=class extends rs{static{o(this,"RepetitionCollector")}constructor(){super(...arguments),this.allProductions=[]}visitRepetitionWithSeparator(e){this.allProductions.push(e)}visitRepetitionMandatory(e){this.allProductions.push(e)}visitRepetitionMandatoryWithSeparator(e){this.allProductions.push(e)}visitRepetition(e){this.allProductions.push(e)}};o(RIe,"validateTooManyAlts");o(rae,"validateSomeNonEmptyLookaheadPath");o(NIe,"checkAlternativesAmbiguities");o(MIe,"checkPrefixAlternativesAmbiguities");o(IIe,"checkTerminalAndNoneTerminalsNameSpace")});function nae(t){let e=Xh(t,{errMsgProvider:zie}),r={};return Ee(t.rules,n=>{r[n.name]=n}),Gie(r,e.errMsgProvider)}function iae(t){return t=Xh(t,{errMsgProvider:Ol}),Qie(t.rules,t.tokenTypes,t.errMsgProvider,t.grammarName)}var aae=R(()=>{"use strict";Pt();$ie();f2();Jm();o(nae,"resolveGrammar");o(iae,"validateGrammar")});function nf(t){return Fn(uae,t.name)}var sae,oae,lae,cae,uae,ig,c0,d2,p2,m2,ag=R(()=>{"use strict";Pt();sae="MismatchedTokenException",oae="NoViableAltException",lae="EarlyExitException",cae="NotAllInputParsedException",uae=[sae,oae,lae,cae];Object.freeze(uae);o(nf,"isRecognitionException");ig=class extends Error{static{o(this,"RecognitionException")}constructor(e,r){super(e),this.token=r,this.resyncedTokens=[],Object.setPrototypeOf(this,new.target.prototype),Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}},c0=class extends ig{static{o(this,"MismatchedTokenException")}constructor(e,r,n){super(e,r),this.previousToken=n,this.name=sae}},d2=class extends ig{static{o(this,"NoViableAltException")}constructor(e,r,n){super(e,r),this.previousToken=n,this.name=oae}},p2=class extends ig{static{o(this,"NotAllInputParsedException")}constructor(e,r){super(e,r),this.name=cae}},m2=class extends ig{static{o(this,"EarlyExitException")}constructor(e,r,n){super(e,r),this.previousToken=n,this.name=lae}}});function OIe(t,e,r,n,i,a,s){let l=this.getKeyForAutomaticLookahead(n,i),u=this.firstAfterRepMap[l];if(u===void 0){let p=this.getCurrRuleFullName(),m=this.getGAstProductions()[p];u=new a(m,i).startWalking(),this.firstAfterRepMap[l]=u}let h=u.token,f=u.occurrence,d=u.isEndOfRule;this.RULE_STACK.length===1&&d&&h===void 0&&(h=fo,f=1),!(h===void 0||f===void 0)&&this.shouldInRepetitionRecoveryBeTried(h,f,s)&&this.tryInRepetitionRecovery(t,e,r,h)}var EN,SN,CN,ZT,AN=R(()=>{"use strict";l0();Pt();ag();aN();Ns();EN={},SN="InRuleRecoveryException",CN=class extends Error{static{o(this,"InRuleRecoveryException")}constructor(e){super(e),this.name=SN}},ZT=class{static{o(this,"Recoverable")}initRecoverable(e){this.firstAfterRepMap={},this.resyncFollows={},this.recoveryEnabled=Xe(e,"recoveryEnabled")?e.recoveryEnabled:is.recoveryEnabled,this.recoveryEnabled&&(this.attemptInRepetitionRecovery=OIe)}getTokenToInsert(e){let r=o0(e,"",NaN,NaN,NaN,NaN,NaN,NaN);return r.isInsertedInRecovery=!0,r}canTokenTypeBeInsertedInRecovery(e){return!0}canTokenTypeBeDeletedInRecovery(e){return!0}tryInRepetitionRecovery(e,r,n,i){let a=this.findReSyncTokenType(),s=this.exportLexerState(),l=[],u=!1,h=this.LA(1),f=this.LA(1),d=o(()=>{let p=this.LA(0),m=this.errorMessageProvider.buildMismatchTokenMessage({expected:i,actual:h,previous:p,ruleName:this.getCurrRuleFullName()}),g=new c0(m,h,this.LA(0));g.resyncedTokens=Ru(l),this.SAVE_ERROR(g)},"generateErrorMessage");for(;!u;)if(this.tokenMatcher(f,i)){d();return}else if(n.call(this)){d(),e.apply(this,r);return}else this.tokenMatcher(f,a)?u=!0:(f=this.SKIP_TOKEN(),this.addToResyncTokens(f,l));this.importLexerState(s)}shouldInRepetitionRecoveryBeTried(e,r,n){return!(n===!1||this.tokenMatcher(this.LA(1),e)||this.isBackTracking()||this.canPerformInRuleRecovery(e,this.getFollowsForInRuleRecovery(e,r)))}getFollowsForInRuleRecovery(e,r){let n=this.getCurrentGrammarPath(e,r);return this.getNextPossibleTokenTypes(n)}tryInRuleRecovery(e,r){if(this.canRecoverWithSingleTokenInsertion(e,r))return this.getTokenToInsert(e);if(this.canRecoverWithSingleTokenDeletion(e)){let n=this.SKIP_TOKEN();return this.consumeToken(),n}throw new CN("sad sad panda")}canPerformInRuleRecovery(e,r){return this.canRecoverWithSingleTokenInsertion(e,r)||this.canRecoverWithSingleTokenDeletion(e)}canRecoverWithSingleTokenInsertion(e,r){if(!this.canTokenTypeBeInsertedInRecovery(e)||Qt(r))return!1;let n=this.LA(1);return Za(r,a=>this.tokenMatcher(n,a))!==void 0}canRecoverWithSingleTokenDeletion(e){return this.canTokenTypeBeDeletedInRecovery(e)?this.tokenMatcher(this.LA(2),e):!1}isInCurrentRuleReSyncSet(e){let r=this.getCurrFollowKey(),n=this.getFollowSetFromFollowKey(r);return Fn(n,e)}findReSyncTokenType(){let e=this.flattenFollowSet(),r=this.LA(1),n=2;for(;;){let i=Za(e,a=>s2(r,a));if(i!==void 0)return i;r=this.LA(n),n++}}getCurrFollowKey(){if(this.RULE_STACK.length===1)return EN;let e=this.getLastExplicitRuleShortName(),r=this.getLastExplicitRuleOccurrenceIndex(),n=this.getPreviousExplicitRuleShortName();return{ruleName:this.shortRuleNameToFullName(e),idxInCallingRule:r,inRule:this.shortRuleNameToFullName(n)}}buildFullFollowKeyStack(){let e=this.RULE_STACK,r=this.RULE_OCCURRENCE_STACK;return qe(e,(n,i)=>i===0?EN:{ruleName:this.shortRuleNameToFullName(n),idxInCallingRule:r[i],inRule:this.shortRuleNameToFullName(e[i-1])})}flattenFollowSet(){let e=qe(this.buildFullFollowKeyStack(),r=>this.getFollowSetFromFollowKey(r));return Gr(e)}getFollowSetFromFollowKey(e){if(e===EN)return[fo];let r=e.ruleName+e.idxInCallingRule+OT+e.inRule;return this.resyncFollows[r]}addToResyncTokens(e,r){return this.tokenMatcher(e,fo)||r.push(e),r}reSyncTo(e){let r=[],n=this.LA(1);for(;this.tokenMatcher(n,e)===!1;)n=this.SKIP_TOKEN(),this.addToResyncTokens(n,r);return Ru(r)}attemptInRepetitionRecovery(e,r,n,i,a,s,l){}getCurrentGrammarPath(e,r){let n=this.getHumanReadableRuleStack(),i=Qr(this.RULE_OCCURRENCE_STACK);return{ruleStack:n,occurrenceStack:i,lastTok:e,lastTokOccurrence:r}}getHumanReadableRuleStack(){return qe(this.RULE_STACK,e=>this.shortRuleNameToFullName(e))}};o(OIe,"attemptInRepetitionRecovery")});function JT(t,e,r){return r|e|t}var ek=R(()=>{"use strict";o(JT,"getKeyForAutomaticLookahead")});var $u,_N=R(()=>{"use strict";Pt();Jm();Ns();f2();ng();$u=class{static{o(this,"LLkLookaheadStrategy")}constructor(e){var r;this.maxLookahead=(r=e?.maxLookahead)!==null&&r!==void 0?r:is.maxLookahead}validate(e){let r=this.validateNoLeftRecursion(e.rules);if(Qt(r)){let n=this.validateEmptyOrAlternatives(e.rules),i=this.validateAmbiguousAlternationAlternatives(e.rules,this.maxLookahead),a=this.validateSomeNonEmptyLookaheadPath(e.rules,this.maxLookahead);return[...r,...n,...i,...a]}return r}validateNoLeftRecursion(e){return ga(e,r=>kN(r,r,Ol))}validateEmptyOrAlternatives(e){return ga(e,r=>eae(r,Ol))}validateAmbiguousAlternationAlternatives(e,r){return ga(e,n=>tae(n,r,Ol))}validateSomeNonEmptyLookaheadPath(e,r){return rae(e,r,Ol)}buildLookaheadForAlternation(e){return Uie(e.prodOccurrence,e.rule,e.maxLookahead,e.hasPredicates,e.dynamicTokensEnabled,Yie)}buildLookaheadForOptional(e){return Hie(e.prodOccurrence,e.rule,e.maxLookahead,e.dynamicTokensEnabled,u2(e.prodType),Wie)}}});function PIe(t){tk.reset(),t.accept(tk);let e=tk.dslMethods;return tk.reset(),e}var rk,LN,tk,hae=R(()=>{"use strict";Pt();Ns();ek();ns();_N();rk=class{static{o(this,"LooksAhead")}initLooksAhead(e){this.dynamicTokensEnabled=Xe(e,"dynamicTokensEnabled")?e.dynamicTokensEnabled:is.dynamicTokensEnabled,this.maxLookahead=Xe(e,"maxLookahead")?e.maxLookahead:is.maxLookahead,this.lookaheadStrategy=Xe(e,"lookaheadStrategy")?e.lookaheadStrategy:new $u({maxLookahead:this.maxLookahead}),this.lookAheadFuncsCache=new Map}preComputeLookaheadFunctions(e){Ee(e,r=>{this.TRACE_INIT(`${r.name} Rule Lookahead`,()=>{let{alternation:n,repetition:i,option:a,repetitionMandatory:s,repetitionMandatoryWithSeparator:l,repetitionWithSeparator:u}=PIe(r);Ee(n,h=>{let f=h.idx===0?"":h.idx;this.TRACE_INIT(`${Rs(h)}${f}`,()=>{let d=this.lookaheadStrategy.buildLookaheadForAlternation({prodOccurrence:h.idx,rule:r,maxLookahead:h.maxLookahead||this.maxLookahead,hasPredicates:h.hasPredicates,dynamicTokensEnabled:this.dynamicTokensEnabled}),p=JT(this.fullRuleNameToShort[r.name],256,h.idx);this.setLaFuncCache(p,d)})}),Ee(i,h=>{this.computeLookaheadFunc(r,h.idx,768,"Repetition",h.maxLookahead,Rs(h))}),Ee(a,h=>{this.computeLookaheadFunc(r,h.idx,512,"Option",h.maxLookahead,Rs(h))}),Ee(s,h=>{this.computeLookaheadFunc(r,h.idx,1024,"RepetitionMandatory",h.maxLookahead,Rs(h))}),Ee(l,h=>{this.computeLookaheadFunc(r,h.idx,1536,"RepetitionMandatoryWithSeparator",h.maxLookahead,Rs(h))}),Ee(u,h=>{this.computeLookaheadFunc(r,h.idx,1280,"RepetitionWithSeparator",h.maxLookahead,Rs(h))})})})}computeLookaheadFunc(e,r,n,i,a,s){this.TRACE_INIT(`${s}${r===0?"":r}`,()=>{let l=this.lookaheadStrategy.buildLookaheadForOptional({prodOccurrence:r,rule:e,maxLookahead:a||this.maxLookahead,dynamicTokensEnabled:this.dynamicTokensEnabled,prodType:i}),u=JT(this.fullRuleNameToShort[e.name],n,r);this.setLaFuncCache(u,l)})}getKeyForAutomaticLookahead(e,r){let n=this.getLastExplicitRuleShortName();return JT(n,e,r)}getLaFuncFromCache(e){return this.lookAheadFuncsCache.get(e)}setLaFuncCache(e,r){this.lookAheadFuncsCache.set(e,r)}},LN=class extends rs{static{o(this,"DslMethodsCollectorVisitor")}constructor(){super(...arguments),this.dslMethods={option:[],alternation:[],repetition:[],repetitionWithSeparator:[],repetitionMandatory:[],repetitionMandatoryWithSeparator:[]}}reset(){this.dslMethods={option:[],alternation:[],repetition:[],repetitionWithSeparator:[],repetitionMandatory:[],repetitionMandatoryWithSeparator:[]}}visitOption(e){this.dslMethods.option.push(e)}visitRepetitionWithSeparator(e){this.dslMethods.repetitionWithSeparator.push(e)}visitRepetitionMandatory(e){this.dslMethods.repetitionMandatory.push(e)}visitRepetitionMandatoryWithSeparator(e){this.dslMethods.repetitionMandatoryWithSeparator.push(e)}visitRepetition(e){this.dslMethods.repetition.push(e)}visitAlternation(e){this.dslMethods.alternation.push(e)}},tk=new LN;o(PIe,"collectMethods")});function NN(t,e){isNaN(t.startOffset)===!0?(t.startOffset=e.startOffset,t.endOffset=e.endOffset):t.endOffset{"use strict";o(NN,"setNodeLocationOnlyOffset");o(MN,"setNodeLocationFull");o(fae,"addTerminalToCst");o(dae,"addNoneTerminalToCst")});function IN(t,e){Object.defineProperty(t,BIe,{enumerable:!1,configurable:!0,writable:!1,value:e})}var BIe,mae=R(()=>{"use strict";BIe="name";o(IN,"defineNameProp")});function FIe(t,e){let r=Dr(t),n=r.length;for(let i=0;is.msg);throw Error(`Errors Detected in CST Visitor <${this.constructor.name}>: + ${a.join(` + +`).replace(/\n/g,` + `)}`)}},"validateVisitor")};return r.prototype=n,r.prototype.constructor=r,r._RULE_NAMES=e,r}function yae(t,e,r){let n=o(function(){},"derivedConstructor");IN(n,t+"BaseSemanticsWithDefaults");let i=Object.create(r.prototype);return Ee(e,a=>{i[a]=FIe}),n.prototype=i,n.prototype.constructor=n,n}function zIe(t,e){return GIe(t,e)}function GIe(t,e){let r=$r(e,i=>wi(t[i])===!1),n=qe(r,i=>({msg:`Missing visitor method: <${i}> on ${t.constructor.name} CST Visitor.`,type:ON.MISSING_METHOD,methodName:i}));return wc(n)}var ON,vae=R(()=>{"use strict";Pt();mae();o(FIe,"defaultVisit");o(gae,"createBaseSemanticVisitorConstructor");o(yae,"createBaseVisitorConstructorWithDefaults");(function(t){t[t.REDUNDANT_METHOD=0]="REDUNDANT_METHOD",t[t.MISSING_METHOD=1]="MISSING_METHOD"})(ON||(ON={}));o(zIe,"validateVisitor");o(GIe,"validateMissingCstMethods")});var sk,xae=R(()=>{"use strict";pae();Pt();vae();Ns();sk=class{static{o(this,"TreeBuilder")}initTreeBuilder(e){if(this.CST_STACK=[],this.outputCst=e.outputCst,this.nodeLocationTracking=Xe(e,"nodeLocationTracking")?e.nodeLocationTracking:is.nodeLocationTracking,!this.outputCst)this.cstInvocationStateUpdate=qn,this.cstFinallyStateUpdate=qn,this.cstPostTerminal=qn,this.cstPostNonTerminal=qn,this.cstPostRule=qn;else if(/full/i.test(this.nodeLocationTracking))this.recoveryEnabled?(this.setNodeLocationFromToken=MN,this.setNodeLocationFromNode=MN,this.cstPostRule=qn,this.setInitialNodeLocation=this.setInitialNodeLocationFullRecovery):(this.setNodeLocationFromToken=qn,this.setNodeLocationFromNode=qn,this.cstPostRule=this.cstPostRuleFull,this.setInitialNodeLocation=this.setInitialNodeLocationFullRegular);else if(/onlyOffset/i.test(this.nodeLocationTracking))this.recoveryEnabled?(this.setNodeLocationFromToken=NN,this.setNodeLocationFromNode=NN,this.cstPostRule=qn,this.setInitialNodeLocation=this.setInitialNodeLocationOnlyOffsetRecovery):(this.setNodeLocationFromToken=qn,this.setNodeLocationFromNode=qn,this.cstPostRule=this.cstPostRuleOnlyOffset,this.setInitialNodeLocation=this.setInitialNodeLocationOnlyOffsetRegular);else if(/none/i.test(this.nodeLocationTracking))this.setNodeLocationFromToken=qn,this.setNodeLocationFromNode=qn,this.cstPostRule=qn,this.setInitialNodeLocation=qn;else throw Error(`Invalid config option: "${e.nodeLocationTracking}"`)}setInitialNodeLocationOnlyOffsetRecovery(e){e.location={startOffset:NaN,endOffset:NaN}}setInitialNodeLocationOnlyOffsetRegular(e){e.location={startOffset:this.LA(1).startOffset,endOffset:NaN}}setInitialNodeLocationFullRecovery(e){e.location={startOffset:NaN,startLine:NaN,startColumn:NaN,endOffset:NaN,endLine:NaN,endColumn:NaN}}setInitialNodeLocationFullRegular(e){let r=this.LA(1);e.location={startOffset:r.startOffset,startLine:r.startLine,startColumn:r.startColumn,endOffset:NaN,endLine:NaN,endColumn:NaN}}cstInvocationStateUpdate(e){let r={name:e,children:Object.create(null)};this.setInitialNodeLocation(r),this.CST_STACK.push(r)}cstFinallyStateUpdate(){this.CST_STACK.pop()}cstPostRuleFull(e){let r=this.LA(0),n=e.location;n.startOffset<=r.startOffset?(n.endOffset=r.endOffset,n.endLine=r.endLine,n.endColumn=r.endColumn):(n.startOffset=NaN,n.startLine=NaN,n.startColumn=NaN)}cstPostRuleOnlyOffset(e){let r=this.LA(0),n=e.location;n.startOffset<=r.startOffset?n.endOffset=r.endOffset:n.startOffset=NaN}cstPostTerminal(e,r){let n=this.CST_STACK[this.CST_STACK.length-1];fae(n,r,e),this.setNodeLocationFromToken(n.location,r)}cstPostNonTerminal(e,r){let n=this.CST_STACK[this.CST_STACK.length-1];dae(n,r,e),this.setNodeLocationFromNode(n.location,e.location)}getBaseCstVisitorConstructor(){if(er(this.baseCstVisitorConstructor)){let e=gae(this.className,Dr(this.gastProductionsCache));return this.baseCstVisitorConstructor=e,e}return this.baseCstVisitorConstructor}getBaseCstVisitorConstructorWithDefaults(){if(er(this.baseCstVisitorWithDefaultsConstructor)){let e=yae(this.className,Dr(this.gastProductionsCache),this.getBaseCstVisitorConstructor());return this.baseCstVisitorWithDefaultsConstructor=e,e}return this.baseCstVisitorWithDefaultsConstructor}getLastExplicitRuleShortName(){let e=this.RULE_STACK;return e[e.length-1]}getPreviousExplicitRuleShortName(){let e=this.RULE_STACK;return e[e.length-2]}getLastExplicitRuleOccurrenceIndex(){let e=this.RULE_OCCURRENCE_STACK;return e[e.length-1]}}});var ok,bae=R(()=>{"use strict";Ns();ok=class{static{o(this,"LexerAdapter")}initLexerAdapter(){this.tokVector=[],this.tokVectorLength=0,this.currIdx=-1}set input(e){if(this.selfAnalysisDone!==!0)throw Error("Missing invocation at the end of the Parser's constructor.");this.reset(),this.tokVector=e,this.tokVectorLength=e.length}get input(){return this.tokVector}SKIP_TOKEN(){return this.currIdx<=this.tokVector.length-2?(this.consumeToken(),this.LA(1)):sg}LA(e){let r=this.currIdx+e;return r<0||this.tokVectorLength<=r?sg:this.tokVector[r]}consumeToken(){this.currIdx++}exportLexerState(){return this.currIdx}importLexerState(e){this.currIdx=e}resetLexerState(){this.currIdx=-1}moveToTerminatedState(){this.currIdx=this.tokVector.length-1}getLexerPosition(){return this.exportLexerState()}}});var lk,wae=R(()=>{"use strict";Pt();ag();Ns();Jm();f2();ns();lk=class{static{o(this,"RecognizerApi")}ACTION(e){return e.call(this)}consume(e,r,n){return this.consumeInternal(r,e,n)}subrule(e,r,n){return this.subruleInternal(r,e,n)}option(e,r){return this.optionInternal(r,e)}or(e,r){return this.orInternal(r,e)}many(e,r){return this.manyInternal(e,r)}atLeastOne(e,r){return this.atLeastOneInternal(e,r)}CONSUME(e,r){return this.consumeInternal(e,0,r)}CONSUME1(e,r){return this.consumeInternal(e,1,r)}CONSUME2(e,r){return this.consumeInternal(e,2,r)}CONSUME3(e,r){return this.consumeInternal(e,3,r)}CONSUME4(e,r){return this.consumeInternal(e,4,r)}CONSUME5(e,r){return this.consumeInternal(e,5,r)}CONSUME6(e,r){return this.consumeInternal(e,6,r)}CONSUME7(e,r){return this.consumeInternal(e,7,r)}CONSUME8(e,r){return this.consumeInternal(e,8,r)}CONSUME9(e,r){return this.consumeInternal(e,9,r)}SUBRULE(e,r){return this.subruleInternal(e,0,r)}SUBRULE1(e,r){return this.subruleInternal(e,1,r)}SUBRULE2(e,r){return this.subruleInternal(e,2,r)}SUBRULE3(e,r){return this.subruleInternal(e,3,r)}SUBRULE4(e,r){return this.subruleInternal(e,4,r)}SUBRULE5(e,r){return this.subruleInternal(e,5,r)}SUBRULE6(e,r){return this.subruleInternal(e,6,r)}SUBRULE7(e,r){return this.subruleInternal(e,7,r)}SUBRULE8(e,r){return this.subruleInternal(e,8,r)}SUBRULE9(e,r){return this.subruleInternal(e,9,r)}OPTION(e){return this.optionInternal(e,0)}OPTION1(e){return this.optionInternal(e,1)}OPTION2(e){return this.optionInternal(e,2)}OPTION3(e){return this.optionInternal(e,3)}OPTION4(e){return this.optionInternal(e,4)}OPTION5(e){return this.optionInternal(e,5)}OPTION6(e){return this.optionInternal(e,6)}OPTION7(e){return this.optionInternal(e,7)}OPTION8(e){return this.optionInternal(e,8)}OPTION9(e){return this.optionInternal(e,9)}OR(e){return this.orInternal(e,0)}OR1(e){return this.orInternal(e,1)}OR2(e){return this.orInternal(e,2)}OR3(e){return this.orInternal(e,3)}OR4(e){return this.orInternal(e,4)}OR5(e){return this.orInternal(e,5)}OR6(e){return this.orInternal(e,6)}OR7(e){return this.orInternal(e,7)}OR8(e){return this.orInternal(e,8)}OR9(e){return this.orInternal(e,9)}MANY(e){this.manyInternal(0,e)}MANY1(e){this.manyInternal(1,e)}MANY2(e){this.manyInternal(2,e)}MANY3(e){this.manyInternal(3,e)}MANY4(e){this.manyInternal(4,e)}MANY5(e){this.manyInternal(5,e)}MANY6(e){this.manyInternal(6,e)}MANY7(e){this.manyInternal(7,e)}MANY8(e){this.manyInternal(8,e)}MANY9(e){this.manyInternal(9,e)}MANY_SEP(e){this.manySepFirstInternal(0,e)}MANY_SEP1(e){this.manySepFirstInternal(1,e)}MANY_SEP2(e){this.manySepFirstInternal(2,e)}MANY_SEP3(e){this.manySepFirstInternal(3,e)}MANY_SEP4(e){this.manySepFirstInternal(4,e)}MANY_SEP5(e){this.manySepFirstInternal(5,e)}MANY_SEP6(e){this.manySepFirstInternal(6,e)}MANY_SEP7(e){this.manySepFirstInternal(7,e)}MANY_SEP8(e){this.manySepFirstInternal(8,e)}MANY_SEP9(e){this.manySepFirstInternal(9,e)}AT_LEAST_ONE(e){this.atLeastOneInternal(0,e)}AT_LEAST_ONE1(e){return this.atLeastOneInternal(1,e)}AT_LEAST_ONE2(e){this.atLeastOneInternal(2,e)}AT_LEAST_ONE3(e){this.atLeastOneInternal(3,e)}AT_LEAST_ONE4(e){this.atLeastOneInternal(4,e)}AT_LEAST_ONE5(e){this.atLeastOneInternal(5,e)}AT_LEAST_ONE6(e){this.atLeastOneInternal(6,e)}AT_LEAST_ONE7(e){this.atLeastOneInternal(7,e)}AT_LEAST_ONE8(e){this.atLeastOneInternal(8,e)}AT_LEAST_ONE9(e){this.atLeastOneInternal(9,e)}AT_LEAST_ONE_SEP(e){this.atLeastOneSepFirstInternal(0,e)}AT_LEAST_ONE_SEP1(e){this.atLeastOneSepFirstInternal(1,e)}AT_LEAST_ONE_SEP2(e){this.atLeastOneSepFirstInternal(2,e)}AT_LEAST_ONE_SEP3(e){this.atLeastOneSepFirstInternal(3,e)}AT_LEAST_ONE_SEP4(e){this.atLeastOneSepFirstInternal(4,e)}AT_LEAST_ONE_SEP5(e){this.atLeastOneSepFirstInternal(5,e)}AT_LEAST_ONE_SEP6(e){this.atLeastOneSepFirstInternal(6,e)}AT_LEAST_ONE_SEP7(e){this.atLeastOneSepFirstInternal(7,e)}AT_LEAST_ONE_SEP8(e){this.atLeastOneSepFirstInternal(8,e)}AT_LEAST_ONE_SEP9(e){this.atLeastOneSepFirstInternal(9,e)}RULE(e,r,n=og){if(Fn(this.definedRulesNames,e)){let s={message:Ol.buildDuplicateRuleNameError({topLevelRule:e,grammarName:this.className}),type:Pi.DUPLICATE_RULE_NAME,ruleName:e};this.definitionErrors.push(s)}this.definedRulesNames.push(e);let i=this.defineRule(e,r,n);return this[e]=i,i}OVERRIDE_RULE(e,r,n=og){let i=Jie(e,this.definedRulesNames,this.className);this.definitionErrors=this.definitionErrors.concat(i);let a=this.defineRule(e,r,n);return this[e]=a,a}BACKTRACK(e,r){return function(){this.isBackTrackingStack.push(1);let n=this.saveRecogState();try{return e.apply(this,r),!0}catch(i){if(nf(i))return!1;throw i}finally{this.reloadRecogState(n),this.isBackTrackingStack.pop()}}}getGAstProductions(){return this.gastProductionsCache}getSerializedGastProductions(){return NT(or(this.gastProductionsCache))}}});var ck,Tae=R(()=>{"use strict";Pt();ek();ag();ng();c2();Ns();AN();l0();s0();ck=class{static{o(this,"RecognizerEngine")}initRecognizerEngine(e,r){if(this.className=this.constructor.name,this.shortRuleNameToFull={},this.fullRuleNameToShort={},this.ruleShortNameIdx=256,this.tokenMatcher=Zm,this.subruleIdx=0,this.definedRulesNames=[],this.tokensMap={},this.isBackTrackingStack=[],this.RULE_STACK=[],this.RULE_OCCURRENCE_STACK=[],this.gastProductionsCache={},Xe(r,"serializedGrammar"))throw Error(`The Parser's configuration can no longer contain a property. + See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_6-0-0 + For Further details.`);if(wt(e)){if(Qt(e))throw Error(`A Token Vocabulary cannot be empty. + Note that the first argument for the parser constructor + is no longer a Token vector (since v4.0).`);if(typeof e[0].startOffset=="number")throw Error(`The Parser constructor no longer accepts a token vector as the first argument. + See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_4-0-0 + For Further details.`)}if(wt(e))this.tokensMap=Vr(e,(a,s)=>(a[s.name]=s,a),{});else if(Xe(e,"modes")&&Ia(Gr(or(e.modes)),Die)){let a=Gr(or(e.modes)),s=Pm(a);this.tokensMap=Vr(s,(l,u)=>(l[u.name]=u,l),{})}else if(pn(e))this.tokensMap=Qr(e);else throw new Error(" argument must be An Array of Token constructors, A dictionary of Token constructors or an IMultiModeLexerDefinition");this.tokensMap.EOF=fo;let n=Xe(e,"modes")?Gr(or(e.modes)):or(e),i=Ia(n,a=>Qt(a.categoryMatches));this.tokenMatcher=i?Zm:Bu,Fu(or(this.tokensMap))}defineRule(e,r,n){if(this.selfAnalysisDone)throw Error(`Grammar rule <${e}> may not be defined after the 'performSelfAnalysis' method has been called' +Make sure that all grammar rule definitions are done before 'performSelfAnalysis' is called.`);let i=Xe(n,"resyncEnabled")?n.resyncEnabled:og.resyncEnabled,a=Xe(n,"recoveryValueFunc")?n.recoveryValueFunc:og.recoveryValueFunc,s=this.ruleShortNameIdx<<12;this.ruleShortNameIdx++,this.shortRuleNameToFull[s]=e,this.fullRuleNameToShort[e]=s;let l;return this.outputCst===!0?l=o(function(...f){try{this.ruleInvocationStateUpdate(s,e,this.subruleIdx),r.apply(this,f);let d=this.CST_STACK[this.CST_STACK.length-1];return this.cstPostRule(d),d}catch(d){return this.invokeRuleCatch(d,i,a)}finally{this.ruleFinallyStateUpdate()}},"invokeRuleWithTry"):l=o(function(...f){try{return this.ruleInvocationStateUpdate(s,e,this.subruleIdx),r.apply(this,f)}catch(d){return this.invokeRuleCatch(d,i,a)}finally{this.ruleFinallyStateUpdate()}},"invokeRuleWithTryCst"),Object.assign(l,{ruleName:e,originalGrammarAction:r})}invokeRuleCatch(e,r,n){let i=this.RULE_STACK.length===1,a=r&&!this.isBackTracking()&&this.recoveryEnabled;if(nf(e)){let s=e;if(a){let l=this.findReSyncTokenType();if(this.isInCurrentRuleReSyncSet(l))if(s.resyncedTokens=this.reSyncTo(l),this.outputCst){let u=this.CST_STACK[this.CST_STACK.length-1];return u.recoveredNode=!0,u}else return n(e);else{if(this.outputCst){let u=this.CST_STACK[this.CST_STACK.length-1];u.recoveredNode=!0,s.partialCstResult=u}throw s}}else{if(i)return this.moveToTerminatedState(),n(e);throw s}}else throw e}optionInternal(e,r){let n=this.getKeyForAutomaticLookahead(512,r);return this.optionInternalLogic(e,r,n)}optionInternalLogic(e,r,n){let i=this.getLaFuncFromCache(n),a;if(typeof e!="function"){a=e.DEF;let s=e.GATE;if(s!==void 0){let l=i;i=o(()=>s.call(this)&&l.call(this),"lookAheadFunc")}}else a=e;if(i.call(this)===!0)return a.call(this)}atLeastOneInternal(e,r){let n=this.getKeyForAutomaticLookahead(1024,e);return this.atLeastOneInternalLogic(e,r,n)}atLeastOneInternalLogic(e,r,n){let i=this.getLaFuncFromCache(n),a;if(typeof r!="function"){a=r.DEF;let s=r.GATE;if(s!==void 0){let l=i;i=o(()=>s.call(this)&&l.call(this),"lookAheadFunc")}}else a=r;if(i.call(this)===!0){let s=this.doSingleRepetition(a);for(;i.call(this)===!0&&s===!0;)s=this.doSingleRepetition(a)}else throw this.raiseEarlyExitException(e,$n.REPETITION_MANDATORY,r.ERR_MSG);this.attemptInRepetitionRecovery(this.atLeastOneInternal,[e,r],i,1024,e,YT)}atLeastOneSepFirstInternal(e,r){let n=this.getKeyForAutomaticLookahead(1536,e);this.atLeastOneSepFirstInternalLogic(e,r,n)}atLeastOneSepFirstInternalLogic(e,r,n){let i=r.DEF,a=r.SEP;if(this.getLaFuncFromCache(n).call(this)===!0){i.call(this);let l=o(()=>this.tokenMatcher(this.LA(1),a),"separatorLookAheadFunc");for(;this.tokenMatcher(this.LA(1),a)===!0;)this.CONSUME(a),i.call(this);this.attemptInRepetitionRecovery(this.repetitionSepSecondInternal,[e,a,l,i,l2],l,1536,e,l2)}else throw this.raiseEarlyExitException(e,$n.REPETITION_MANDATORY_WITH_SEPARATOR,r.ERR_MSG)}manyInternal(e,r){let n=this.getKeyForAutomaticLookahead(768,e);return this.manyInternalLogic(e,r,n)}manyInternalLogic(e,r,n){let i=this.getLaFuncFromCache(n),a;if(typeof r!="function"){a=r.DEF;let l=r.GATE;if(l!==void 0){let u=i;i=o(()=>l.call(this)&&u.call(this),"lookaheadFunction")}}else a=r;let s=!0;for(;i.call(this)===!0&&s===!0;)s=this.doSingleRepetition(a);this.attemptInRepetitionRecovery(this.manyInternal,[e,r],i,768,e,HT,s)}manySepFirstInternal(e,r){let n=this.getKeyForAutomaticLookahead(1280,e);this.manySepFirstInternalLogic(e,r,n)}manySepFirstInternalLogic(e,r,n){let i=r.DEF,a=r.SEP;if(this.getLaFuncFromCache(n).call(this)===!0){i.call(this);let l=o(()=>this.tokenMatcher(this.LA(1),a),"separatorLookAheadFunc");for(;this.tokenMatcher(this.LA(1),a)===!0;)this.CONSUME(a),i.call(this);this.attemptInRepetitionRecovery(this.repetitionSepSecondInternal,[e,a,l,i,o2],l,1280,e,o2)}}repetitionSepSecondInternal(e,r,n,i,a){for(;n();)this.CONSUME(r),i.call(this);this.attemptInRepetitionRecovery(this.repetitionSepSecondInternal,[e,r,n,i,a],n,1536,e,a)}doSingleRepetition(e){let r=this.getLexerPosition();return e.call(this),this.getLexerPosition()>r}orInternal(e,r){let n=this.getKeyForAutomaticLookahead(256,r),i=wt(e)?e:e.DEF,s=this.getLaFuncFromCache(n).call(this,i);if(s!==void 0)return i[s].ALT.call(this);this.raiseNoAltException(r,e.ERR_MSG)}ruleFinallyStateUpdate(){if(this.RULE_STACK.pop(),this.RULE_OCCURRENCE_STACK.pop(),this.cstFinallyStateUpdate(),this.RULE_STACK.length===0&&this.isAtEndOfInput()===!1){let e=this.LA(1),r=this.errorMessageProvider.buildNotAllInputParsedMessage({firstRedundant:e,ruleName:this.getCurrRuleFullName()});this.SAVE_ERROR(new p2(r,e))}}subruleInternal(e,r,n){let i;try{let a=n!==void 0?n.ARGS:void 0;return this.subruleIdx=r,i=e.apply(this,a),this.cstPostNonTerminal(i,n!==void 0&&n.LABEL!==void 0?n.LABEL:e.ruleName),i}catch(a){throw this.subruleInternalError(a,n,e.ruleName)}}subruleInternalError(e,r,n){throw nf(e)&&e.partialCstResult!==void 0&&(this.cstPostNonTerminal(e.partialCstResult,r!==void 0&&r.LABEL!==void 0?r.LABEL:n),delete e.partialCstResult),e}consumeInternal(e,r,n){let i;try{let a=this.LA(1);this.tokenMatcher(a,e)===!0?(this.consumeToken(),i=a):this.consumeInternalError(e,a,n)}catch(a){i=this.consumeInternalRecovery(e,r,a)}return this.cstPostTerminal(n!==void 0&&n.LABEL!==void 0?n.LABEL:e.name,i),i}consumeInternalError(e,r,n){let i,a=this.LA(0);throw n!==void 0&&n.ERR_MSG?i=n.ERR_MSG:i=this.errorMessageProvider.buildMismatchTokenMessage({expected:e,actual:r,previous:a,ruleName:this.getCurrRuleFullName()}),this.SAVE_ERROR(new c0(i,r,a))}consumeInternalRecovery(e,r,n){if(this.recoveryEnabled&&n.name==="MismatchedTokenException"&&!this.isBackTracking()){let i=this.getFollowsForInRuleRecovery(e,r);try{return this.tryInRuleRecovery(e,i)}catch(a){throw a.name===SN?n:a}}else throw n}saveRecogState(){let e=this.errors,r=Qr(this.RULE_STACK);return{errors:e,lexerState:this.exportLexerState(),RULE_STACK:r,CST_STACK:this.CST_STACK}}reloadRecogState(e){this.errors=e.errors,this.importLexerState(e.lexerState),this.RULE_STACK=e.RULE_STACK}ruleInvocationStateUpdate(e,r,n){this.RULE_OCCURRENCE_STACK.push(n),this.RULE_STACK.push(e),this.cstInvocationStateUpdate(r)}isBackTracking(){return this.isBackTrackingStack.length!==0}getCurrRuleFullName(){let e=this.getLastExplicitRuleShortName();return this.shortRuleNameToFull[e]}shortRuleNameToFullName(e){return this.shortRuleNameToFull[e]}isAtEndOfInput(){return this.tokenMatcher(this.LA(1),fo)}reset(){this.resetLexerState(),this.subruleIdx=0,this.isBackTrackingStack=[],this.errors=[],this.RULE_STACK=[],this.CST_STACK=[],this.RULE_OCCURRENCE_STACK=[]}}});var uk,kae=R(()=>{"use strict";ag();Pt();ng();Ns();uk=class{static{o(this,"ErrorHandler")}initErrorHandler(e){this._errors=[],this.errorMessageProvider=Xe(e,"errorMessageProvider")?e.errorMessageProvider:is.errorMessageProvider}SAVE_ERROR(e){if(nf(e))return e.context={ruleStack:this.getHumanReadableRuleStack(),ruleOccurrenceStack:Qr(this.RULE_OCCURRENCE_STACK)},this._errors.push(e),e;throw Error("Trying to save an Error which is not a RecognitionException")}get errors(){return Qr(this._errors)}set errors(e){this._errors=e}raiseEarlyExitException(e,r,n){let i=this.getCurrRuleFullName(),a=this.getGAstProductions()[i],l=rg(e,a,r,this.maxLookahead)[0],u=[];for(let f=1;f<=this.maxLookahead;f++)u.push(this.LA(f));let h=this.errorMessageProvider.buildEarlyExitMessage({expectedIterationPaths:l,actual:u,previous:this.LA(0),customUserDescription:n,ruleName:i});throw this.SAVE_ERROR(new m2(h,this.LA(1),this.LA(0)))}raiseNoAltException(e,r){let n=this.getCurrRuleFullName(),i=this.getGAstProductions()[n],a=tg(e,i,this.maxLookahead),s=[];for(let h=1;h<=this.maxLookahead;h++)s.push(this.LA(h));let l=this.LA(0),u=this.errorMessageProvider.buildNoViableAltMessage({expectedPathsPerAlt:a,actual:s,previous:l,customUserDescription:r,ruleName:this.getCurrRuleFullName()});throw this.SAVE_ERROR(new d2(u,this.LA(1),l))}}});var hk,Eae=R(()=>{"use strict";c2();Pt();hk=class{static{o(this,"ContentAssist")}initContentAssist(){}computeContentAssist(e,r){let n=this.gastProductionsCache[e];if(er(n))throw Error(`Rule ->${e}<- does not exist in this grammar.`);return qT([n],r,this.tokenMatcher,this.maxLookahead)}getNextPossibleTokenTypes(e){let r=na(e.ruleStack),i=this.getGAstProductions()[r];return new UT(i,e).startWalking()}}});function y2(t,e,r,n=!1){dk(r);let i=ma(this.recordingProdStack),a=wi(e)?e:e.DEF,s=new t({definition:[],idx:r});return n&&(s.separator=e.SEP),Xe(e,"MAX_LOOKAHEAD")&&(s.maxLookahead=e.MAX_LOOKAHEAD),this.recordingProdStack.push(s),a.call(this),i.definition.push(s),this.recordingProdStack.pop(),pk}function UIe(t,e){dk(e);let r=ma(this.recordingProdStack),n=wt(t)===!1,i=n===!1?t:t.DEF,a=new gn({definition:[],idx:e,ignoreAmbiguities:n&&t.IGNORE_AMBIGUITIES===!0});Xe(t,"MAX_LOOKAHEAD")&&(a.maxLookahead=t.MAX_LOOKAHEAD);let s=Nv(i,l=>wi(l.GATE));return a.hasPredicates=s,r.definition.push(a),Ee(i,l=>{let u=new Sn({definition:[]});a.definition.push(u),Xe(l,"IGNORE_AMBIGUITIES")?u.ignoreAmbiguities=l.IGNORE_AMBIGUITIES:Xe(l,"GATE")&&(u.ignoreAmbiguities=!0),this.recordingProdStack.push(u),l.ALT.call(this),this.recordingProdStack.pop()}),pk}function Aae(t){return t===0?"":`${t}`}function dk(t){if(t<0||t>Sae){let e=new Error(`Invalid DSL Method idx value: <${t}> + Idx value must be a none negative value smaller than ${Sae+1}`);throw e.KNOWN_RECORDER_ERROR=!0,e}}var pk,Cae,Sae,_ae,Lae,VIe,fk,Dae=R(()=>{"use strict";Pt();ns();i2();s0();l0();Ns();ek();pk={description:"This Object indicates the Parser is during Recording Phase"};Object.freeze(pk);Cae=!0,Sae=Math.pow(2,8)-1,_ae=VT({name:"RECORDING_PHASE_TOKEN",pattern:ni.NA});Fu([_ae]);Lae=o0(_ae,`This IToken indicates the Parser is in Recording Phase + See: https://chevrotain.io/docs/guide/internals.html#grammar-recording for details`,-1,-1,-1,-1,-1,-1);Object.freeze(Lae);VIe={name:`This CSTNode indicates the Parser is in Recording Phase + See: https://chevrotain.io/docs/guide/internals.html#grammar-recording for details`,children:{}},fk=class{static{o(this,"GastRecorder")}initGastRecorder(e){this.recordingProdStack=[],this.RECORDING_PHASE=!1}enableRecording(){this.RECORDING_PHASE=!0,this.TRACE_INIT("Enable Recording",()=>{for(let e=0;e<10;e++){let r=e>0?e:"";this[`CONSUME${r}`]=function(n,i){return this.consumeInternalRecord(n,e,i)},this[`SUBRULE${r}`]=function(n,i){return this.subruleInternalRecord(n,e,i)},this[`OPTION${r}`]=function(n){return this.optionInternalRecord(n,e)},this[`OR${r}`]=function(n){return this.orInternalRecord(n,e)},this[`MANY${r}`]=function(n){this.manyInternalRecord(e,n)},this[`MANY_SEP${r}`]=function(n){this.manySepFirstInternalRecord(e,n)},this[`AT_LEAST_ONE${r}`]=function(n){this.atLeastOneInternalRecord(e,n)},this[`AT_LEAST_ONE_SEP${r}`]=function(n){this.atLeastOneSepFirstInternalRecord(e,n)}}this.consume=function(e,r,n){return this.consumeInternalRecord(r,e,n)},this.subrule=function(e,r,n){return this.subruleInternalRecord(r,e,n)},this.option=function(e,r){return this.optionInternalRecord(r,e)},this.or=function(e,r){return this.orInternalRecord(r,e)},this.many=function(e,r){this.manyInternalRecord(e,r)},this.atLeastOne=function(e,r){this.atLeastOneInternalRecord(e,r)},this.ACTION=this.ACTION_RECORD,this.BACKTRACK=this.BACKTRACK_RECORD,this.LA=this.LA_RECORD})}disableRecording(){this.RECORDING_PHASE=!1,this.TRACE_INIT("Deleting Recording methods",()=>{let e=this;for(let r=0;r<10;r++){let n=r>0?r:"";delete e[`CONSUME${n}`],delete e[`SUBRULE${n}`],delete e[`OPTION${n}`],delete e[`OR${n}`],delete e[`MANY${n}`],delete e[`MANY_SEP${n}`],delete e[`AT_LEAST_ONE${n}`],delete e[`AT_LEAST_ONE_SEP${n}`]}delete e.consume,delete e.subrule,delete e.option,delete e.or,delete e.many,delete e.atLeastOne,delete e.ACTION,delete e.BACKTRACK,delete e.LA})}ACTION_RECORD(e){}BACKTRACK_RECORD(e,r){return()=>!0}LA_RECORD(e){return sg}topLevelRuleRecord(e,r){try{let n=new ts({definition:[],name:e});return n.name=e,this.recordingProdStack.push(n),r.call(this),this.recordingProdStack.pop(),n}catch(n){if(n.KNOWN_RECORDER_ERROR!==!0)try{n.message=n.message+` + This error was thrown during the "grammar recording phase" For more info see: + https://chevrotain.io/docs/guide/internals.html#grammar-recording`}catch{throw n}throw n}}optionInternalRecord(e,r){return y2.call(this,Jr,e,r)}atLeastOneInternalRecord(e,r){y2.call(this,An,r,e)}atLeastOneSepFirstInternalRecord(e,r){y2.call(this,_n,r,e,Cae)}manyInternalRecord(e,r){y2.call(this,br,r,e)}manySepFirstInternalRecord(e,r){y2.call(this,mn,r,e,Cae)}orInternalRecord(e,r){return UIe.call(this,e,r)}subruleInternalRecord(e,r,n){if(dk(r),!e||Xe(e,"ruleName")===!1){let l=new Error(` argument is invalid expecting a Parser method reference but got: <${JSON.stringify(e)}> + inside top level rule: <${this.recordingProdStack[0].name}>`);throw l.KNOWN_RECORDER_ERROR=!0,l}let i=ma(this.recordingProdStack),a=e.ruleName,s=new Zr({idx:r,nonTerminalName:a,label:n?.LABEL,referencedRule:void 0});return i.definition.push(s),this.outputCst?VIe:pk}consumeInternalRecord(e,r,n){if(dk(r),!dN(e)){let s=new Error(` argument is invalid expecting a TokenType reference but got: <${JSON.stringify(e)}> + inside top level rule: <${this.recordingProdStack[0].name}>`);throw s.KNOWN_RECORDER_ERROR=!0,s}let i=ma(this.recordingProdStack),a=new fr({idx:r,terminalType:e,label:n?.LABEL});return i.definition.push(a),Lae}};o(y2,"recordProd");o(UIe,"recordOrProd");o(Aae,"getIdxSuffix");o(dk,"assertMethodIdxIsValid")});var mk,Rae=R(()=>{"use strict";Pt();qm();Ns();mk=class{static{o(this,"PerformanceTracer")}initPerformanceTracer(e){if(Xe(e,"traceInitPerf")){let r=e.traceInitPerf,n=typeof r=="number";this.traceInitMaxIdent=n?r:1/0,this.traceInitPerf=n?r>0:r}else this.traceInitMaxIdent=0,this.traceInitPerf=is.traceInitPerf;this.traceInitIndent=-1}TRACE_INIT(e,r){if(this.traceInitPerf===!0){this.traceInitIndent++;let n=new Array(this.traceInitIndent+1).join(" ");this.traceInitIndent <${e}>`);let{time:i,value:a}=t2(r),s=i>10?console.warn:console.log;return this.traceInitIndent time: ${i}ms`),this.traceInitIndent--,a}else return r()}}});function Nae(t,e){e.forEach(r=>{let n=r.prototype;Object.getOwnPropertyNames(n).forEach(i=>{if(i==="constructor")return;let a=Object.getOwnPropertyDescriptor(n,i);a&&(a.get||a.set)?Object.defineProperty(t.prototype,i,a):t.prototype[i]=r.prototype[i]})})}var Mae=R(()=>{"use strict";o(Nae,"applyMixins")});function gk(t=void 0){return function(){return t}}var sg,is,og,Pi,v2,x2,Ns=R(()=>{"use strict";Pt();qm();cie();l0();Jm();aae();AN();hae();xae();bae();wae();Tae();kae();Eae();Dae();Rae();Mae();f2();sg=o0(fo,"",NaN,NaN,NaN,NaN,NaN,NaN);Object.freeze(sg);is=Object.freeze({recoveryEnabled:!1,maxLookahead:3,dynamicTokensEnabled:!1,outputCst:!0,errorMessageProvider:Gu,nodeLocationTracking:"none",traceInitPerf:!1,skipValidations:!1}),og=Object.freeze({recoveryValueFunc:o(()=>{},"recoveryValueFunc"),resyncEnabled:!0});(function(t){t[t.INVALID_RULE_NAME=0]="INVALID_RULE_NAME",t[t.DUPLICATE_RULE_NAME=1]="DUPLICATE_RULE_NAME",t[t.INVALID_RULE_OVERRIDE=2]="INVALID_RULE_OVERRIDE",t[t.DUPLICATE_PRODUCTIONS=3]="DUPLICATE_PRODUCTIONS",t[t.UNRESOLVED_SUBRULE_REF=4]="UNRESOLVED_SUBRULE_REF",t[t.LEFT_RECURSION=5]="LEFT_RECURSION",t[t.NONE_LAST_EMPTY_ALT=6]="NONE_LAST_EMPTY_ALT",t[t.AMBIGUOUS_ALTS=7]="AMBIGUOUS_ALTS",t[t.CONFLICT_TOKENS_RULES_NAMESPACE=8]="CONFLICT_TOKENS_RULES_NAMESPACE",t[t.INVALID_TOKEN_NAME=9]="INVALID_TOKEN_NAME",t[t.NO_NON_EMPTY_LOOKAHEAD=10]="NO_NON_EMPTY_LOOKAHEAD",t[t.AMBIGUOUS_PREFIX_ALTS=11]="AMBIGUOUS_PREFIX_ALTS",t[t.TOO_MANY_ALTS=12]="TOO_MANY_ALTS",t[t.CUSTOM_LOOKAHEAD_VALIDATION=13]="CUSTOM_LOOKAHEAD_VALIDATION"})(Pi||(Pi={}));o(gk,"EMPTY_ALT");v2=class t{static{o(this,"Parser")}static performSelfAnalysis(e){throw Error("The **static** `performSelfAnalysis` method has been deprecated. \nUse the **instance** method with the same name instead.")}performSelfAnalysis(){this.TRACE_INIT("performSelfAnalysis",()=>{let e;this.selfAnalysisDone=!0;let r=this.className;this.TRACE_INIT("toFastProps",()=>{r2(this)}),this.TRACE_INIT("Grammar Recording",()=>{try{this.enableRecording(),Ee(this.definedRulesNames,i=>{let s=this[i].originalGrammarAction,l;this.TRACE_INIT(`${i} Rule`,()=>{l=this.topLevelRuleRecord(i,s)}),this.gastProductionsCache[i]=l})}finally{this.disableRecording()}});let n=[];if(this.TRACE_INIT("Grammar Resolving",()=>{n=nae({rules:or(this.gastProductionsCache)}),this.definitionErrors=this.definitionErrors.concat(n)}),this.TRACE_INIT("Grammar Validations",()=>{if(Qt(n)&&this.skipValidations===!1){let i=iae({rules:or(this.gastProductionsCache),tokenTypes:or(this.tokensMap),errMsgProvider:Ol,grammarName:r}),a=Kie({lookaheadStrategy:this.lookaheadStrategy,rules:or(this.gastProductionsCache),tokenTypes:or(this.tokensMap),grammarName:r});this.definitionErrors=this.definitionErrors.concat(i,a)}}),Qt(this.definitionErrors)&&(this.recoveryEnabled&&this.TRACE_INIT("computeAllProdsFollows",()=>{let i=lie(or(this.gastProductionsCache));this.resyncFollows=i}),this.TRACE_INIT("ComputeLookaheadFunctions",()=>{var i,a;(a=(i=this.lookaheadStrategy).initialize)===null||a===void 0||a.call(i,{rules:or(this.gastProductionsCache)}),this.preComputeLookaheadFunctions(or(this.gastProductionsCache))})),!t.DEFER_DEFINITION_ERRORS_HANDLING&&!Qt(this.definitionErrors))throw e=qe(this.definitionErrors,i=>i.message),new Error(`Parser Definition Errors detected: + ${e.join(` +------------------------------- +`)}`)})}constructor(e,r){this.definitionErrors=[],this.selfAnalysisDone=!1;let n=this;if(n.initErrorHandler(r),n.initLexerAdapter(),n.initLooksAhead(r),n.initRecognizerEngine(e,r),n.initRecoverable(r),n.initTreeBuilder(r),n.initContentAssist(),n.initGastRecorder(r),n.initPerformanceTracer(r),Xe(r,"ignoredIssues"))throw new Error(`The IParserConfig property has been deprecated. + Please use the flag on the relevant DSL method instead. + See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#IGNORING_AMBIGUITIES + For further details.`);this.skipValidations=Xe(r,"skipValidations")?r.skipValidations:is.skipValidations}};v2.DEFER_DEFINITION_ERRORS_HANDLING=!1;Nae(v2,[ZT,rk,sk,ok,ck,lk,uk,hk,fk,mk]);x2=class extends v2{static{o(this,"EmbeddedActionsParser")}constructor(e,r=is){let n=Qr(r);n.outputCst=!1,super(e,n)}}});var Iae=R(()=>{"use strict";ns()});var Oae=R(()=>{"use strict"});var Pae=R(()=>{"use strict";Iae();Oae()});var Bae=R(()=>{"use strict";tN()});var u0=R(()=>{"use strict";tN();Ns();i2();l0();ng();_N();Jm();ag();mN();ns();ns();Pae();Bae()});function h0(t,e,r){return`${t.name}_${e}_${r}`}function $ae(t){let e={decisionMap:{},decisionStates:[],ruleToStartState:new Map,ruleToStopState:new Map,states:[]};KIe(e,t);let r=t.length;for(let n=0;nVae(t,e,s));return hg(t,e,n,r,...i)}function rOe(t,e,r){let n=ia(t,e,r,{type:af});sf(t,n);let i=hg(t,e,n,r,f0(t,e,r));return nOe(t,e,r,i)}function f0(t,e,r){let n=$r(qe(r.definition,i=>Vae(t,e,i)),i=>i!==void 0);return n.length===1?n[0]:n.length===0?void 0:aOe(t,n)}function Uae(t,e,r,n,i){let a=n.left,s=n.right,l=ia(t,e,r,{type:jIe});sf(t,l);let u=ia(t,e,r,{type:Gae});return a.loopback=l,u.loopback=l,t.decisionMap[h0(e,i?"RepetitionMandatoryWithSeparator":"RepetitionMandatory",r.idx)]=l,Ei(s,l),i===void 0?(Ei(l,a),Ei(l,u)):(Ei(l,u),Ei(l,i.left),Ei(i.right,a)),{left:a,right:u}}function Hae(t,e,r,n,i){let a=n.left,s=n.right,l=ia(t,e,r,{type:XIe});sf(t,l);let u=ia(t,e,r,{type:Gae}),h=ia(t,e,r,{type:qIe});return l.loopback=h,u.loopback=h,Ei(l,a),Ei(l,u),Ei(s,h),i!==void 0?(Ei(h,u),Ei(h,i.left),Ei(i.right,a)):Ei(h,l),t.decisionMap[h0(e,i?"RepetitionWithSeparator":"Repetition",r.idx)]=l,{left:l,right:u}}function nOe(t,e,r,n){let i=n.left,a=n.right;return Ei(i,a),t.decisionMap[h0(e,"Option",r.idx)]=i,n}function sf(t,e){return t.decisionStates.push(e),e.decision=t.decisionStates.length-1,e.decision}function hg(t,e,r,n,...i){let a=ia(t,e,n,{type:WIe,start:r});r.end=a;for(let l of i)l!==void 0?(Ei(r,l.left),Ei(l.right,a)):Ei(r,a);let s={left:r,right:a};return t.decisionMap[h0(e,iOe(n),n.idx)]=r,s}function iOe(t){if(t instanceof gn)return"Alternation";if(t instanceof Jr)return"Option";if(t instanceof br)return"Repetition";if(t instanceof mn)return"RepetitionWithSeparator";if(t instanceof An)return"RepetitionMandatory";if(t instanceof _n)return"RepetitionMandatoryWithSeparator";throw new Error("Invalid production type encountered")}function aOe(t,e){let r=e.length;for(let a=0;a{"use strict";Mm();LL();u0();o(h0,"buildATNKey");af=1,YIe=2,Fae=4,zae=5,ug=7,WIe=8,qIe=9,XIe=10,jIe=11,Gae=12,b2=class{static{o(this,"AbstractTransition")}constructor(e){this.target=e}isEpsilon(){return!1}},lg=class extends b2{static{o(this,"AtomTransition")}constructor(e,r){super(e),this.tokenType=r}},w2=class extends b2{static{o(this,"EpsilonTransition")}constructor(e){super(e)}isEpsilon(){return!0}},cg=class extends b2{static{o(this,"RuleTransition")}constructor(e,r,n){super(e),this.rule=r,this.followState=n}isEpsilon(){return!0}};o($ae,"createATN");o(KIe,"createRuleStartAndStopATNStates");o(Vae,"atom");o(QIe,"repetition");o(ZIe,"repetitionSep");o(JIe,"repetitionMandatory");o(eOe,"repetitionMandatorySep");o(tOe,"alternation");o(rOe,"option");o(f0,"block");o(Uae,"plus");o(Hae,"star");o(nOe,"optional");o(sf,"defineDecisionState");o(hg,"makeAlts");o(iOe,"getProdType");o(aOe,"makeBlock");o(BN,"tokenRef");o(sOe,"ruleRef");o(oOe,"buildRuleHandle");o(Ei,"epsilon");o(ia,"newState");o(FN,"addTransition");o(lOe,"removeState")});function zN(t,e=!0){return`${e?`a${t.alt}`:""}s${t.state.stateNumber}:${t.stack.map(r=>r.stateNumber.toString()).join("_")}`}var T2,fg,Wae=R(()=>{"use strict";Mm();T2={},fg=class{static{o(this,"ATNConfigSet")}constructor(){this.map={},this.configs=[]}get size(){return this.configs.length}finalize(){this.map={}}add(e){let r=zN(e);r in this.map||(this.map[r]=this.configs.length,this.configs.push(e))}get elements(){return this.configs}get alts(){return qe(this.configs,e=>e.alt)}get key(){let e="";for(let r in this.map)e+=r+":";return e}};o(zN,"getATNConfigKey")});function cOe(t,e){let r={};return n=>{let i=n.toString(),a=r[i];return a!==void 0||(a={atnStartState:t,decision:e,states:{}},r[i]=a),a}}function Xae(t,e=!0){let r=new Set;for(let n of t){let i=new Set;for(let a of n){if(a===void 0){if(e)break;return!1}let s=[a.tokenTypeIdx].concat(a.categoryMatches);for(let l of s)if(r.has(l)){if(!i.has(l))return!1}else r.add(l),i.add(l)}}return!0}function uOe(t){let e=t.decisionStates.length,r=Array(e);for(let n=0;nzu(i)).join(", "),r=t.production.idx===0?"":t.production.idx,n=`Ambiguous Alternatives Detected: <${t.ambiguityIndices.join(", ")}> in <${mOe(t.production)}${r}> inside <${t.topLevelRule.name}> Rule, +<${e}> may appears as a prefix path in all these alternatives. +`;return n=n+`See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#AMBIGUOUS_ALTERNATIVES +For Further details.`,n}function mOe(t){if(t instanceof Zr)return"SUBRULE";if(t instanceof Jr)return"OPTION";if(t instanceof gn)return"OR";if(t instanceof An)return"AT_LEAST_ONE";if(t instanceof _n)return"AT_LEAST_ONE_SEP";if(t instanceof mn)return"MANY_SEP";if(t instanceof br)return"MANY";if(t instanceof fr)return"CONSUME";throw Error("non exhaustive match")}function gOe(t,e,r){let n=ga(e.configs.elements,a=>a.state.transitions),i=nte(n.filter(a=>a instanceof lg).map(a=>a.tokenType),a=>a.tokenTypeIdx);return{actualToken:r,possibleTokenTypes:i,tokenPath:t}}function yOe(t,e){return t.edges[e.tokenTypeIdx]}function vOe(t,e,r){let n=new fg,i=[];for(let s of t.elements){if(r.is(s.alt)===!1)continue;if(s.state.type===ug){i.push(s);continue}let l=s.state.transitions.length;for(let u=0;u0&&!kOe(a))for(let s of i)a.add(s);return a}function xOe(t,e){if(t instanceof lg&&s2(e,t.tokenType))return t.target}function bOe(t,e){let r;for(let n of t.elements)if(e.is(n.alt)===!0){if(r===void 0)r=n.alt;else if(r!==n.alt)return}return r}function Kae(t){return{configs:t,edges:{},isAcceptState:!1,prediction:-1}}function jae(t,e,r,n){return n=Qae(t,n),e.edges[r.tokenTypeIdx]=n,n}function Qae(t,e){if(e===T2)return e;let r=e.configs.key,n=t.states[r];return n!==void 0?n:(e.configs.finalize(),t.states[r]=e,e)}function wOe(t){let e=new fg,r=t.transitions.length;for(let n=0;n0){let i=[...t.stack],s={state:i.pop(),alt:t.alt,stack:i};vk(s,e)}else e.add(t);return}r.epsilonOnlyTransitions||e.add(t);let n=r.transitions.length;for(let i=0;i1)return!0;return!1}function _Oe(t){for(let e of Array.from(t.values()))if(Object.keys(e).length===1)return!0;return!1}var yk,qae,k2,Zae=R(()=>{"use strict";u0();Yae();Wae();BL();RL();ite();Mm();fw();$w();Ww();$L();o(cOe,"createDFACache");yk=class{static{o(this,"PredicateSet")}constructor(){this.predicates=[]}is(e){return e>=this.predicates.length||this.predicates[e]}set(e,r){this.predicates[e]=r}toString(){let e="",r=this.predicates.length;for(let n=0;nconsole.log(n)}initialize(e){this.atn=$ae(e.rules),this.dfas=uOe(this.atn)}validateAmbiguousAlternationAlternatives(){return[]}validateEmptyOrAlternatives(){return[]}buildLookaheadForAlternation(e){let{prodOccurrence:r,rule:n,hasPredicates:i,dynamicTokensEnabled:a}=e,s=this.dfas,l=this.logging,u=h0(n,"Alternation",r),f=this.atn.decisionMap[u].decision,d=qe(jT({maxLookahead:1,occurrence:r,prodType:"Alternation",rule:n}),p=>qe(p,m=>m[0]));if(Xae(d,!1)&&!a){let p=Vr(d,(m,g,y)=>(Ee(g,v=>{v&&(m[v.tokenTypeIdx]=y,Ee(v.categoryMatches,x=>{m[x]=y}))}),m),{});return i?function(m){var g;let y=this.LA(1),v=p[y.tokenTypeIdx];if(m!==void 0&&v!==void 0){let x=(g=m[v])===null||g===void 0?void 0:g.GATE;if(x!==void 0&&x.call(this)===!1)return}return v}:function(){let m=this.LA(1);return p[m.tokenTypeIdx]}}else return i?function(p){let m=new yk,g=p===void 0?0:p.length;for(let v=0;vqe(p,m=>m[0]));if(Xae(d)&&d[0][0]&&!a){let p=d[0],m=Gr(p);if(m.length===1&&Qt(m[0].categoryMatches)){let y=m[0].tokenTypeIdx;return function(){return this.LA(1).tokenTypeIdx===y}}else{let g=Vr(m,(y,v)=>(v!==void 0&&(y[v.tokenTypeIdx]=!0,Ee(v.categoryMatches,x=>{y[x]=!0})),y),{});return function(){let y=this.LA(1);return g[y.tokenTypeIdx]===!0}}}return function(){let p=GN.call(this,s,f,qae,l);return typeof p=="object"?!1:p===0}}};o(Xae,"isLL1Sequence");o(uOe,"initATNSimulator");o(GN,"adaptivePredict");o(hOe,"performLookahead");o(fOe,"computeLookaheadTarget");o(dOe,"reportLookaheadAmbiguity");o(pOe,"buildAmbiguityError");o(mOe,"getProductionDslName");o(gOe,"buildAdaptivePredictError");o(yOe,"getExistingTargetState");o(vOe,"computeReachSet");o(xOe,"getReachableTarget");o(bOe,"getUniqueAlt");o(Kae,"newDFAState");o(jae,"addDFAEdge");o(Qae,"addDFAState");o(wOe,"computeStartState");o(vk,"closure");o(TOe,"getEpsilonTarget");o(kOe,"hasConfigInRuleStopState");o(EOe,"allConfigsInRuleStopStates");o(COe,"hasConflictTerminatingPrediction");o(SOe,"getConflictingAltSets");o(AOe,"hasConflictingAltSet");o(_Oe,"hasStateAssociatedWithOneAlt")});var Jae=R(()=>{"use strict";Zae()});var ese,$N,tse,xk,Ur,wr,bk,rse,VN,nse,ise,ase,sse,UN,ose,lse,cse,wk,dg,pg,HN,mg,use,YN,WN,qN,XN,jN,hse,fse,KN,dse,QN,E2,pse,mse,gse,yse,vse,xse,bse,wse,Tk,Tse,kse,Ese,Cse,Sse,Ase,_se,Lse,Dse,Rse,Nse,kk,Mse,Ise,Ose,Pse,Bse,Fse,zse,Gse,$se,Vse,Use,Hse,Yse,ZN,JN,Wse,qse,Xse,jse,Kse,Qse,Zse,Jse,eoe,eM,Fe,tM=R(()=>{"use strict";(function(t){function e(r){return typeof r=="string"}o(e,"is"),t.is=e})(ese||(ese={}));(function(t){function e(r){return typeof r=="string"}o(e,"is"),t.is=e})($N||($N={}));(function(t){t.MIN_VALUE=-2147483648,t.MAX_VALUE=2147483647;function e(r){return typeof r=="number"&&t.MIN_VALUE<=r&&r<=t.MAX_VALUE}o(e,"is"),t.is=e})(tse||(tse={}));(function(t){t.MIN_VALUE=0,t.MAX_VALUE=2147483647;function e(r){return typeof r=="number"&&t.MIN_VALUE<=r&&r<=t.MAX_VALUE}o(e,"is"),t.is=e})(xk||(xk={}));(function(t){function e(n,i){return n===Number.MAX_VALUE&&(n=xk.MAX_VALUE),i===Number.MAX_VALUE&&(i=xk.MAX_VALUE),{line:n,character:i}}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.objectLiteral(i)&&Fe.uinteger(i.line)&&Fe.uinteger(i.character)}o(r,"is"),t.is=r})(Ur||(Ur={}));(function(t){function e(n,i,a,s){if(Fe.uinteger(n)&&Fe.uinteger(i)&&Fe.uinteger(a)&&Fe.uinteger(s))return{start:Ur.create(n,i),end:Ur.create(a,s)};if(Ur.is(n)&&Ur.is(i))return{start:n,end:i};throw new Error(`Range#create called with invalid arguments[${n}, ${i}, ${a}, ${s}]`)}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.objectLiteral(i)&&Ur.is(i.start)&&Ur.is(i.end)}o(r,"is"),t.is=r})(wr||(wr={}));(function(t){function e(n,i){return{uri:n,range:i}}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.objectLiteral(i)&&wr.is(i.range)&&(Fe.string(i.uri)||Fe.undefined(i.uri))}o(r,"is"),t.is=r})(bk||(bk={}));(function(t){function e(n,i,a,s){return{targetUri:n,targetRange:i,targetSelectionRange:a,originSelectionRange:s}}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.objectLiteral(i)&&wr.is(i.targetRange)&&Fe.string(i.targetUri)&&wr.is(i.targetSelectionRange)&&(wr.is(i.originSelectionRange)||Fe.undefined(i.originSelectionRange))}o(r,"is"),t.is=r})(rse||(rse={}));(function(t){function e(n,i,a,s){return{red:n,green:i,blue:a,alpha:s}}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.objectLiteral(i)&&Fe.numberRange(i.red,0,1)&&Fe.numberRange(i.green,0,1)&&Fe.numberRange(i.blue,0,1)&&Fe.numberRange(i.alpha,0,1)}o(r,"is"),t.is=r})(VN||(VN={}));(function(t){function e(n,i){return{range:n,color:i}}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.objectLiteral(i)&&wr.is(i.range)&&VN.is(i.color)}o(r,"is"),t.is=r})(nse||(nse={}));(function(t){function e(n,i,a){return{label:n,textEdit:i,additionalTextEdits:a}}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.objectLiteral(i)&&Fe.string(i.label)&&(Fe.undefined(i.textEdit)||pg.is(i))&&(Fe.undefined(i.additionalTextEdits)||Fe.typedArray(i.additionalTextEdits,pg.is))}o(r,"is"),t.is=r})(ise||(ise={}));(function(t){t.Comment="comment",t.Imports="imports",t.Region="region"})(ase||(ase={}));(function(t){function e(n,i,a,s,l,u){let h={startLine:n,endLine:i};return Fe.defined(a)&&(h.startCharacter=a),Fe.defined(s)&&(h.endCharacter=s),Fe.defined(l)&&(h.kind=l),Fe.defined(u)&&(h.collapsedText=u),h}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.objectLiteral(i)&&Fe.uinteger(i.startLine)&&Fe.uinteger(i.startLine)&&(Fe.undefined(i.startCharacter)||Fe.uinteger(i.startCharacter))&&(Fe.undefined(i.endCharacter)||Fe.uinteger(i.endCharacter))&&(Fe.undefined(i.kind)||Fe.string(i.kind))}o(r,"is"),t.is=r})(sse||(sse={}));(function(t){function e(n,i){return{location:n,message:i}}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.defined(i)&&bk.is(i.location)&&Fe.string(i.message)}o(r,"is"),t.is=r})(UN||(UN={}));(function(t){t.Error=1,t.Warning=2,t.Information=3,t.Hint=4})(ose||(ose={}));(function(t){t.Unnecessary=1,t.Deprecated=2})(lse||(lse={}));(function(t){function e(r){let n=r;return Fe.objectLiteral(n)&&Fe.string(n.href)}o(e,"is"),t.is=e})(cse||(cse={}));(function(t){function e(n,i,a,s,l,u){let h={range:n,message:i};return Fe.defined(a)&&(h.severity=a),Fe.defined(s)&&(h.code=s),Fe.defined(l)&&(h.source=l),Fe.defined(u)&&(h.relatedInformation=u),h}o(e,"create"),t.create=e;function r(n){var i;let a=n;return Fe.defined(a)&&wr.is(a.range)&&Fe.string(a.message)&&(Fe.number(a.severity)||Fe.undefined(a.severity))&&(Fe.integer(a.code)||Fe.string(a.code)||Fe.undefined(a.code))&&(Fe.undefined(a.codeDescription)||Fe.string((i=a.codeDescription)===null||i===void 0?void 0:i.href))&&(Fe.string(a.source)||Fe.undefined(a.source))&&(Fe.undefined(a.relatedInformation)||Fe.typedArray(a.relatedInformation,UN.is))}o(r,"is"),t.is=r})(wk||(wk={}));(function(t){function e(n,i,...a){let s={title:n,command:i};return Fe.defined(a)&&a.length>0&&(s.arguments=a),s}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.defined(i)&&Fe.string(i.title)&&Fe.string(i.command)}o(r,"is"),t.is=r})(dg||(dg={}));(function(t){function e(a,s){return{range:a,newText:s}}o(e,"replace"),t.replace=e;function r(a,s){return{range:{start:a,end:a},newText:s}}o(r,"insert"),t.insert=r;function n(a){return{range:a,newText:""}}o(n,"del"),t.del=n;function i(a){let s=a;return Fe.objectLiteral(s)&&Fe.string(s.newText)&&wr.is(s.range)}o(i,"is"),t.is=i})(pg||(pg={}));(function(t){function e(n,i,a){let s={label:n};return i!==void 0&&(s.needsConfirmation=i),a!==void 0&&(s.description=a),s}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.objectLiteral(i)&&Fe.string(i.label)&&(Fe.boolean(i.needsConfirmation)||i.needsConfirmation===void 0)&&(Fe.string(i.description)||i.description===void 0)}o(r,"is"),t.is=r})(HN||(HN={}));(function(t){function e(r){let n=r;return Fe.string(n)}o(e,"is"),t.is=e})(mg||(mg={}));(function(t){function e(a,s,l){return{range:a,newText:s,annotationId:l}}o(e,"replace"),t.replace=e;function r(a,s,l){return{range:{start:a,end:a},newText:s,annotationId:l}}o(r,"insert"),t.insert=r;function n(a,s){return{range:a,newText:"",annotationId:s}}o(n,"del"),t.del=n;function i(a){let s=a;return pg.is(s)&&(HN.is(s.annotationId)||mg.is(s.annotationId))}o(i,"is"),t.is=i})(use||(use={}));(function(t){function e(n,i){return{textDocument:n,edits:i}}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.defined(i)&&KN.is(i.textDocument)&&Array.isArray(i.edits)}o(r,"is"),t.is=r})(YN||(YN={}));(function(t){function e(n,i,a){let s={kind:"create",uri:n};return i!==void 0&&(i.overwrite!==void 0||i.ignoreIfExists!==void 0)&&(s.options=i),a!==void 0&&(s.annotationId=a),s}o(e,"create"),t.create=e;function r(n){let i=n;return i&&i.kind==="create"&&Fe.string(i.uri)&&(i.options===void 0||(i.options.overwrite===void 0||Fe.boolean(i.options.overwrite))&&(i.options.ignoreIfExists===void 0||Fe.boolean(i.options.ignoreIfExists)))&&(i.annotationId===void 0||mg.is(i.annotationId))}o(r,"is"),t.is=r})(WN||(WN={}));(function(t){function e(n,i,a,s){let l={kind:"rename",oldUri:n,newUri:i};return a!==void 0&&(a.overwrite!==void 0||a.ignoreIfExists!==void 0)&&(l.options=a),s!==void 0&&(l.annotationId=s),l}o(e,"create"),t.create=e;function r(n){let i=n;return i&&i.kind==="rename"&&Fe.string(i.oldUri)&&Fe.string(i.newUri)&&(i.options===void 0||(i.options.overwrite===void 0||Fe.boolean(i.options.overwrite))&&(i.options.ignoreIfExists===void 0||Fe.boolean(i.options.ignoreIfExists)))&&(i.annotationId===void 0||mg.is(i.annotationId))}o(r,"is"),t.is=r})(qN||(qN={}));(function(t){function e(n,i,a){let s={kind:"delete",uri:n};return i!==void 0&&(i.recursive!==void 0||i.ignoreIfNotExists!==void 0)&&(s.options=i),a!==void 0&&(s.annotationId=a),s}o(e,"create"),t.create=e;function r(n){let i=n;return i&&i.kind==="delete"&&Fe.string(i.uri)&&(i.options===void 0||(i.options.recursive===void 0||Fe.boolean(i.options.recursive))&&(i.options.ignoreIfNotExists===void 0||Fe.boolean(i.options.ignoreIfNotExists)))&&(i.annotationId===void 0||mg.is(i.annotationId))}o(r,"is"),t.is=r})(XN||(XN={}));(function(t){function e(r){let n=r;return n&&(n.changes!==void 0||n.documentChanges!==void 0)&&(n.documentChanges===void 0||n.documentChanges.every(i=>Fe.string(i.kind)?WN.is(i)||qN.is(i)||XN.is(i):YN.is(i)))}o(e,"is"),t.is=e})(jN||(jN={}));(function(t){function e(n){return{uri:n}}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.defined(i)&&Fe.string(i.uri)}o(r,"is"),t.is=r})(hse||(hse={}));(function(t){function e(n,i){return{uri:n,version:i}}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.defined(i)&&Fe.string(i.uri)&&Fe.integer(i.version)}o(r,"is"),t.is=r})(fse||(fse={}));(function(t){function e(n,i){return{uri:n,version:i}}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.defined(i)&&Fe.string(i.uri)&&(i.version===null||Fe.integer(i.version))}o(r,"is"),t.is=r})(KN||(KN={}));(function(t){function e(n,i,a,s){return{uri:n,languageId:i,version:a,text:s}}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.defined(i)&&Fe.string(i.uri)&&Fe.string(i.languageId)&&Fe.integer(i.version)&&Fe.string(i.text)}o(r,"is"),t.is=r})(dse||(dse={}));(function(t){t.PlainText="plaintext",t.Markdown="markdown";function e(r){let n=r;return n===t.PlainText||n===t.Markdown}o(e,"is"),t.is=e})(QN||(QN={}));(function(t){function e(r){let n=r;return Fe.objectLiteral(r)&&QN.is(n.kind)&&Fe.string(n.value)}o(e,"is"),t.is=e})(E2||(E2={}));(function(t){t.Text=1,t.Method=2,t.Function=3,t.Constructor=4,t.Field=5,t.Variable=6,t.Class=7,t.Interface=8,t.Module=9,t.Property=10,t.Unit=11,t.Value=12,t.Enum=13,t.Keyword=14,t.Snippet=15,t.Color=16,t.File=17,t.Reference=18,t.Folder=19,t.EnumMember=20,t.Constant=21,t.Struct=22,t.Event=23,t.Operator=24,t.TypeParameter=25})(pse||(pse={}));(function(t){t.PlainText=1,t.Snippet=2})(mse||(mse={}));(function(t){t.Deprecated=1})(gse||(gse={}));(function(t){function e(n,i,a){return{newText:n,insert:i,replace:a}}o(e,"create"),t.create=e;function r(n){let i=n;return i&&Fe.string(i.newText)&&wr.is(i.insert)&&wr.is(i.replace)}o(r,"is"),t.is=r})(yse||(yse={}));(function(t){t.asIs=1,t.adjustIndentation=2})(vse||(vse={}));(function(t){function e(r){let n=r;return n&&(Fe.string(n.detail)||n.detail===void 0)&&(Fe.string(n.description)||n.description===void 0)}o(e,"is"),t.is=e})(xse||(xse={}));(function(t){function e(r){return{label:r}}o(e,"create"),t.create=e})(bse||(bse={}));(function(t){function e(r,n){return{items:r||[],isIncomplete:!!n}}o(e,"create"),t.create=e})(wse||(wse={}));(function(t){function e(n){return n.replace(/[\\`*_{}[\]()#+\-.!]/g,"\\$&")}o(e,"fromPlainText"),t.fromPlainText=e;function r(n){let i=n;return Fe.string(i)||Fe.objectLiteral(i)&&Fe.string(i.language)&&Fe.string(i.value)}o(r,"is"),t.is=r})(Tk||(Tk={}));(function(t){function e(r){let n=r;return!!n&&Fe.objectLiteral(n)&&(E2.is(n.contents)||Tk.is(n.contents)||Fe.typedArray(n.contents,Tk.is))&&(r.range===void 0||wr.is(r.range))}o(e,"is"),t.is=e})(Tse||(Tse={}));(function(t){function e(r,n){return n?{label:r,documentation:n}:{label:r}}o(e,"create"),t.create=e})(kse||(kse={}));(function(t){function e(r,n,...i){let a={label:r};return Fe.defined(n)&&(a.documentation=n),Fe.defined(i)?a.parameters=i:a.parameters=[],a}o(e,"create"),t.create=e})(Ese||(Ese={}));(function(t){t.Text=1,t.Read=2,t.Write=3})(Cse||(Cse={}));(function(t){function e(r,n){let i={range:r};return Fe.number(n)&&(i.kind=n),i}o(e,"create"),t.create=e})(Sse||(Sse={}));(function(t){t.File=1,t.Module=2,t.Namespace=3,t.Package=4,t.Class=5,t.Method=6,t.Property=7,t.Field=8,t.Constructor=9,t.Enum=10,t.Interface=11,t.Function=12,t.Variable=13,t.Constant=14,t.String=15,t.Number=16,t.Boolean=17,t.Array=18,t.Object=19,t.Key=20,t.Null=21,t.EnumMember=22,t.Struct=23,t.Event=24,t.Operator=25,t.TypeParameter=26})(Ase||(Ase={}));(function(t){t.Deprecated=1})(_se||(_se={}));(function(t){function e(r,n,i,a,s){let l={name:r,kind:n,location:{uri:a,range:i}};return s&&(l.containerName=s),l}o(e,"create"),t.create=e})(Lse||(Lse={}));(function(t){function e(r,n,i,a){return a!==void 0?{name:r,kind:n,location:{uri:i,range:a}}:{name:r,kind:n,location:{uri:i}}}o(e,"create"),t.create=e})(Dse||(Dse={}));(function(t){function e(n,i,a,s,l,u){let h={name:n,detail:i,kind:a,range:s,selectionRange:l};return u!==void 0&&(h.children=u),h}o(e,"create"),t.create=e;function r(n){let i=n;return i&&Fe.string(i.name)&&Fe.number(i.kind)&&wr.is(i.range)&&wr.is(i.selectionRange)&&(i.detail===void 0||Fe.string(i.detail))&&(i.deprecated===void 0||Fe.boolean(i.deprecated))&&(i.children===void 0||Array.isArray(i.children))&&(i.tags===void 0||Array.isArray(i.tags))}o(r,"is"),t.is=r})(Rse||(Rse={}));(function(t){t.Empty="",t.QuickFix="quickfix",t.Refactor="refactor",t.RefactorExtract="refactor.extract",t.RefactorInline="refactor.inline",t.RefactorRewrite="refactor.rewrite",t.Source="source",t.SourceOrganizeImports="source.organizeImports",t.SourceFixAll="source.fixAll"})(Nse||(Nse={}));(function(t){t.Invoked=1,t.Automatic=2})(kk||(kk={}));(function(t){function e(n,i,a){let s={diagnostics:n};return i!=null&&(s.only=i),a!=null&&(s.triggerKind=a),s}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.defined(i)&&Fe.typedArray(i.diagnostics,wk.is)&&(i.only===void 0||Fe.typedArray(i.only,Fe.string))&&(i.triggerKind===void 0||i.triggerKind===kk.Invoked||i.triggerKind===kk.Automatic)}o(r,"is"),t.is=r})(Mse||(Mse={}));(function(t){function e(n,i,a){let s={title:n},l=!0;return typeof i=="string"?(l=!1,s.kind=i):dg.is(i)?s.command=i:s.edit=i,l&&a!==void 0&&(s.kind=a),s}o(e,"create"),t.create=e;function r(n){let i=n;return i&&Fe.string(i.title)&&(i.diagnostics===void 0||Fe.typedArray(i.diagnostics,wk.is))&&(i.kind===void 0||Fe.string(i.kind))&&(i.edit!==void 0||i.command!==void 0)&&(i.command===void 0||dg.is(i.command))&&(i.isPreferred===void 0||Fe.boolean(i.isPreferred))&&(i.edit===void 0||jN.is(i.edit))}o(r,"is"),t.is=r})(Ise||(Ise={}));(function(t){function e(n,i){let a={range:n};return Fe.defined(i)&&(a.data=i),a}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.defined(i)&&wr.is(i.range)&&(Fe.undefined(i.command)||dg.is(i.command))}o(r,"is"),t.is=r})(Ose||(Ose={}));(function(t){function e(n,i){return{tabSize:n,insertSpaces:i}}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.defined(i)&&Fe.uinteger(i.tabSize)&&Fe.boolean(i.insertSpaces)}o(r,"is"),t.is=r})(Pse||(Pse={}));(function(t){function e(n,i,a){return{range:n,target:i,data:a}}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.defined(i)&&wr.is(i.range)&&(Fe.undefined(i.target)||Fe.string(i.target))}o(r,"is"),t.is=r})(Bse||(Bse={}));(function(t){function e(n,i){return{range:n,parent:i}}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.objectLiteral(i)&&wr.is(i.range)&&(i.parent===void 0||t.is(i.parent))}o(r,"is"),t.is=r})(Fse||(Fse={}));(function(t){t.namespace="namespace",t.type="type",t.class="class",t.enum="enum",t.interface="interface",t.struct="struct",t.typeParameter="typeParameter",t.parameter="parameter",t.variable="variable",t.property="property",t.enumMember="enumMember",t.event="event",t.function="function",t.method="method",t.macro="macro",t.keyword="keyword",t.modifier="modifier",t.comment="comment",t.string="string",t.number="number",t.regexp="regexp",t.operator="operator",t.decorator="decorator"})(zse||(zse={}));(function(t){t.declaration="declaration",t.definition="definition",t.readonly="readonly",t.static="static",t.deprecated="deprecated",t.abstract="abstract",t.async="async",t.modification="modification",t.documentation="documentation",t.defaultLibrary="defaultLibrary"})(Gse||(Gse={}));(function(t){function e(r){let n=r;return Fe.objectLiteral(n)&&(n.resultId===void 0||typeof n.resultId=="string")&&Array.isArray(n.data)&&(n.data.length===0||typeof n.data[0]=="number")}o(e,"is"),t.is=e})($se||($se={}));(function(t){function e(n,i){return{range:n,text:i}}o(e,"create"),t.create=e;function r(n){let i=n;return i!=null&&wr.is(i.range)&&Fe.string(i.text)}o(r,"is"),t.is=r})(Vse||(Vse={}));(function(t){function e(n,i,a){return{range:n,variableName:i,caseSensitiveLookup:a}}o(e,"create"),t.create=e;function r(n){let i=n;return i!=null&&wr.is(i.range)&&Fe.boolean(i.caseSensitiveLookup)&&(Fe.string(i.variableName)||i.variableName===void 0)}o(r,"is"),t.is=r})(Use||(Use={}));(function(t){function e(n,i){return{range:n,expression:i}}o(e,"create"),t.create=e;function r(n){let i=n;return i!=null&&wr.is(i.range)&&(Fe.string(i.expression)||i.expression===void 0)}o(r,"is"),t.is=r})(Hse||(Hse={}));(function(t){function e(n,i){return{frameId:n,stoppedLocation:i}}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.defined(i)&&wr.is(n.stoppedLocation)}o(r,"is"),t.is=r})(Yse||(Yse={}));(function(t){t.Type=1,t.Parameter=2;function e(r){return r===1||r===2}o(e,"is"),t.is=e})(ZN||(ZN={}));(function(t){function e(n){return{value:n}}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.objectLiteral(i)&&(i.tooltip===void 0||Fe.string(i.tooltip)||E2.is(i.tooltip))&&(i.location===void 0||bk.is(i.location))&&(i.command===void 0||dg.is(i.command))}o(r,"is"),t.is=r})(JN||(JN={}));(function(t){function e(n,i,a){let s={position:n,label:i};return a!==void 0&&(s.kind=a),s}o(e,"create"),t.create=e;function r(n){let i=n;return Fe.objectLiteral(i)&&Ur.is(i.position)&&(Fe.string(i.label)||Fe.typedArray(i.label,JN.is))&&(i.kind===void 0||ZN.is(i.kind))&&i.textEdits===void 0||Fe.typedArray(i.textEdits,pg.is)&&(i.tooltip===void 0||Fe.string(i.tooltip)||E2.is(i.tooltip))&&(i.paddingLeft===void 0||Fe.boolean(i.paddingLeft))&&(i.paddingRight===void 0||Fe.boolean(i.paddingRight))}o(r,"is"),t.is=r})(Wse||(Wse={}));(function(t){function e(r){return{kind:"snippet",value:r}}o(e,"createSnippet"),t.createSnippet=e})(qse||(qse={}));(function(t){function e(r,n,i,a){return{insertText:r,filterText:n,range:i,command:a}}o(e,"create"),t.create=e})(Xse||(Xse={}));(function(t){function e(r){return{items:r}}o(e,"create"),t.create=e})(jse||(jse={}));(function(t){t.Invoked=0,t.Automatic=1})(Kse||(Kse={}));(function(t){function e(r,n){return{range:r,text:n}}o(e,"create"),t.create=e})(Qse||(Qse={}));(function(t){function e(r,n){return{triggerKind:r,selectedCompletionInfo:n}}o(e,"create"),t.create=e})(Zse||(Zse={}));(function(t){function e(r){let n=r;return Fe.objectLiteral(n)&&$N.is(n.uri)&&Fe.string(n.name)}o(e,"is"),t.is=e})(Jse||(Jse={}));(function(t){function e(a,s,l,u){return new eM(a,s,l,u)}o(e,"create"),t.create=e;function r(a){let s=a;return!!(Fe.defined(s)&&Fe.string(s.uri)&&(Fe.undefined(s.languageId)||Fe.string(s.languageId))&&Fe.uinteger(s.lineCount)&&Fe.func(s.getText)&&Fe.func(s.positionAt)&&Fe.func(s.offsetAt))}o(r,"is"),t.is=r;function n(a,s){let l=a.getText(),u=i(s,(f,d)=>{let p=f.range.start.line-d.range.start.line;return p===0?f.range.start.character-d.range.start.character:p}),h=l.length;for(let f=u.length-1;f>=0;f--){let d=u[f],p=a.offsetAt(d.range.start),m=a.offsetAt(d.range.end);if(m<=h)l=l.substring(0,p)+d.newText+l.substring(m,l.length);else throw new Error("Overlapping edit");h=p}return l}o(n,"applyEdits"),t.applyEdits=n;function i(a,s){if(a.length<=1)return a;let l=a.length/2|0,u=a.slice(0,l),h=a.slice(l);i(u,s),i(h,s);let f=0,d=0,p=0;for(;f0&&e.push(r.length),this._lineOffsets=e}return this._lineOffsets}positionAt(e){e=Math.max(Math.min(e,this._content.length),0);let r=this.getLineOffsets(),n=0,i=r.length;if(i===0)return Ur.create(0,e);for(;ne?i=s:n=s+1}let a=n-1;return Ur.create(a,e-r[a])}offsetAt(e){let r=this.getLineOffsets();if(e.line>=r.length)return this._content.length;if(e.line<0)return 0;let n=r[e.line],i=e.line+1"u"}o(n,"undefined"),t.undefined=n;function i(m){return m===!0||m===!1}o(i,"boolean"),t.boolean=i;function a(m){return e.call(m)==="[object String]"}o(a,"string"),t.string=a;function s(m){return e.call(m)==="[object Number]"}o(s,"number"),t.number=s;function l(m,g,y){return e.call(m)==="[object Number]"&&g<=m&&m<=y}o(l,"numberRange"),t.numberRange=l;function u(m){return e.call(m)==="[object Number]"&&-2147483648<=m&&m<=2147483647}o(u,"integer"),t.integer=u;function h(m){return e.call(m)==="[object Number]"&&0<=m&&m<=2147483647}o(h,"uinteger"),t.uinteger=h;function f(m){return e.call(m)==="[object Function]"}o(f,"func"),t.func=f;function d(m){return m!==null&&typeof m=="object"}o(d,"objectLiteral"),t.objectLiteral=d;function p(m,g){return Array.isArray(m)&&m.every(g)}o(p,"typedArray"),t.typedArray=p})(Fe||(Fe={}))});var C2,S2,d0,p0,rM,gg,Ek=R(()=>{"use strict";tM();Vo();Rl();C2=class{static{o(this,"CstNodeBuilder")}constructor(){this.nodeStack=[]}get current(){return this.nodeStack[this.nodeStack.length-1]}buildRootNode(e){return this.rootNode=new gg(e),this.rootNode.root=this.rootNode,this.nodeStack=[this.rootNode],this.rootNode}buildCompositeNode(e){let r=new p0;return r.grammarSource=e,r.root=this.rootNode,this.current.content.push(r),this.nodeStack.push(r),r}buildLeafNode(e,r){let n=new d0(e.startOffset,e.image.length,zm(e),e.tokenType,!1);return n.grammarSource=r,n.root=this.rootNode,this.current.content.push(n),n}removeNode(e){let r=e.container;if(r){let n=r.content.indexOf(e);n>=0&&r.content.splice(n,1)}}construct(e){let r=this.current;typeof e.$type=="string"&&(this.current.astNode=e),e.$cstNode=r;let n=this.nodeStack.pop();n?.content.length===0&&this.removeNode(n)}addHiddenTokens(e){for(let r of e){let n=new d0(r.startOffset,r.image.length,zm(r),r.tokenType,!0);n.root=this.rootNode,this.addHiddenToken(this.rootNode,n)}}addHiddenToken(e,r){let{offset:n,end:i}=r;for(let a=0;al&&i=0;e--){let r=this.content[e];if(!r.hidden)return r}return this.content[this.content.length-1]}},rM=class t extends Array{static{o(this,"CstNodeContainer")}constructor(e){super(),this.parent=e,Object.setPrototypeOf(this,t.prototype)}push(...e){return this.addParents(e),super.push(...e)}unshift(...e){return this.addParents(e),super.unshift(...e)}splice(e,r,...n){return this.addParents(n),super.splice(e,r,...n)}addParents(e){for(let r of e)r.container=this.parent}},gg=class extends p0{static{o(this,"RootCstNodeImpl")}get text(){return this._text.substring(this.offset,this.end)}get fullText(){return this._text}constructor(e){super(),this._text="",this._text=e??""}}});function nM(t){return t.$type===Ck}var Ck,toe,roe,A2,_2,Sk,yg,L2,LOe,iM,D2=R(()=>{"use strict";u0();Jae();Sc();Il();es();Ek();Ck=Symbol("Datatype");o(nM,"isDataTypeNode");toe="\u200B",roe=o(t=>t.endsWith(toe)?t:t+toe,"withRuleSuffix"),A2=class{static{o(this,"AbstractLangiumParser")}constructor(e){this._unorderedGroups=new Map,this.lexer=e.parser.Lexer;let r=this.lexer.definition;this.wrapper=new iM(r,Object.assign(Object.assign({},e.parser.ParserConfig),{errorMessageProvider:e.parser.ParserErrorMessageProvider}))}alternatives(e,r){this.wrapper.wrapOr(e,r)}optional(e,r){this.wrapper.wrapOption(e,r)}many(e,r){this.wrapper.wrapMany(e,r)}atLeastOne(e,r){this.wrapper.wrapAtLeastOne(e,r)}isRecording(){return this.wrapper.IS_RECORDING}get unorderedGroups(){return this._unorderedGroups}getRuleStack(){return this.wrapper.RULE_STACK}finalize(){this.wrapper.wrapSelfAnalysis()}},_2=class extends A2{static{o(this,"LangiumParser")}get current(){return this.stack[this.stack.length-1]}constructor(e){super(e),this.nodeBuilder=new C2,this.stack=[],this.assignmentMap=new Map,this.linker=e.references.Linker,this.converter=e.parser.ValueConverter,this.astReflection=e.shared.AstReflection}rule(e,r){let n=e.fragment?void 0:Jv(e)?Ck:r0(e),i=this.wrapper.DEFINE_RULE(roe(e.name),this.startImplementation(n,r).bind(this));return e.entry&&(this.mainRule=i),i}parse(e){this.nodeBuilder.buildRootNode(e);let r=this.lexer.tokenize(e);this.wrapper.input=r.tokens;let n=this.mainRule.call(this.wrapper,{});return this.nodeBuilder.addHiddenTokens(r.hidden),this.unorderedGroups.clear(),{value:n,lexerErrors:r.errors,parserErrors:this.wrapper.errors}}startImplementation(e,r){return n=>{if(!this.isRecording()){let a={$type:e};this.stack.push(a),e===Ck&&(a.value="")}let i;try{i=r(n)}catch{i=void 0}return!this.isRecording()&&i===void 0&&(i=this.construct()),i}}consume(e,r,n){let i=this.wrapper.wrapConsume(e,r);if(!this.isRecording()&&this.isValidToken(i)){let a=this.nodeBuilder.buildLeafNode(i,n),{assignment:s,isCrossRef:l}=this.getAssignment(n),u=this.current;if(s){let h=Ho(n)?i.image:this.converter.convert(i.image,a);this.assign(s.operator,s.feature,h,a,l)}else if(nM(u)){let h=i.image;Ho(n)||(h=this.converter.convert(h,a).toString()),u.value+=h}}}isValidToken(e){return!e.isInsertedInRecovery&&!isNaN(e.startOffset)&&typeof e.endOffset=="number"&&!isNaN(e.endOffset)}subrule(e,r,n,i){let a;this.isRecording()||(a=this.nodeBuilder.buildCompositeNode(n));let s=this.wrapper.wrapSubrule(e,r,i);!this.isRecording()&&a&&a.length>0&&this.performSubruleAssignment(s,n,a)}performSubruleAssignment(e,r,n){let{assignment:i,isCrossRef:a}=this.getAssignment(r);if(i)this.assign(i.operator,i.feature,e,n,a);else if(!i){let s=this.current;if(nM(s))s.value+=e.toString();else if(typeof e=="object"&&e){let l=e.$type,u=this.assignWithoutOverride(e,s);l&&(u.$type=l);let h=u;this.stack.pop(),this.stack.push(h)}}}action(e,r){if(!this.isRecording()){let n=this.current;if(!n.$cstNode&&r.feature&&r.operator){n=this.construct(!1);let a=n.$cstNode.feature;this.nodeBuilder.buildCompositeNode(a)}let i={$type:e};this.stack.pop(),this.stack.push(i),r.feature&&r.operator&&this.assign(r.operator,r.feature,n,n.$cstNode,!1)}}construct(e=!0){if(this.isRecording())return;let r=this.current;return ET(r),this.nodeBuilder.construct(r),e&&this.stack.pop(),nM(r)?this.converter.convert(r.value,r.$cstNode):(NR(this.astReflection,r),r)}getAssignment(e){if(!this.assignmentMap.has(e)){let r=Qd(e,Nl);this.assignmentMap.set(e,{assignment:r,isCrossRef:r?Kd(r.terminal):!1})}return this.assignmentMap.get(e)}assign(e,r,n,i,a){let s=this.current,l;switch(a&&typeof n=="string"?l=this.linker.buildReference(s,r,i,n):l=n,e){case"=":{s[r]=l;break}case"?=":{s[r]=!0;break}case"+=":Array.isArray(s[r])||(s[r]=[]),s[r].push(l)}}assignWithoutOverride(e,r){for(let[n,i]of Object.entries(r)){let a=e[n];a===void 0?e[n]=i:Array.isArray(a)&&Array.isArray(i)&&(i.push(...a),e[n]=i)}return e}get definitionErrors(){return this.wrapper.definitionErrors}},Sk=class{static{o(this,"AbstractParserErrorMessageProvider")}buildMismatchTokenMessage(e){return Gu.buildMismatchTokenMessage(e)}buildNotAllInputParsedMessage(e){return Gu.buildNotAllInputParsedMessage(e)}buildNoViableAltMessage(e){return Gu.buildNoViableAltMessage(e)}buildEarlyExitMessage(e){return Gu.buildEarlyExitMessage(e)}},yg=class extends Sk{static{o(this,"LangiumParserErrorMessageProvider")}buildMismatchTokenMessage({expected:e,actual:r}){return`Expecting ${e.LABEL?"`"+e.LABEL+"`":e.name.endsWith(":KW")?`keyword '${e.name.substring(0,e.name.length-3)}'`:`token of type '${e.name}'`} but found \`${r.image}\`.`}buildNotAllInputParsedMessage({firstRedundant:e}){return`Expecting end of file but found \`${e.image}\`.`}},L2=class extends A2{static{o(this,"LangiumCompletionParser")}constructor(){super(...arguments),this.tokens=[],this.elementStack=[],this.lastElementStack=[],this.nextTokenIndex=0,this.stackSize=0}action(){}construct(){}parse(e){this.resetState();let r=this.lexer.tokenize(e);return this.tokens=r.tokens,this.wrapper.input=[...this.tokens],this.mainRule.call(this.wrapper,{}),this.unorderedGroups.clear(),{tokens:this.tokens,elementStack:[...this.lastElementStack],tokenIndex:this.nextTokenIndex}}rule(e,r){let n=this.wrapper.DEFINE_RULE(roe(e.name),this.startImplementation(r).bind(this));return e.entry&&(this.mainRule=n),n}resetState(){this.elementStack=[],this.lastElementStack=[],this.nextTokenIndex=0,this.stackSize=0}startImplementation(e){return r=>{let n=this.keepStackSize();try{e(r)}finally{this.resetStackSize(n)}}}removeUnexpectedElements(){this.elementStack.splice(this.stackSize)}keepStackSize(){let e=this.elementStack.length;return this.stackSize=e,e}resetStackSize(e){this.removeUnexpectedElements(),this.stackSize=e}consume(e,r,n){this.wrapper.wrapConsume(e,r),this.isRecording()||(this.lastElementStack=[...this.elementStack,n],this.nextTokenIndex=this.currIdx+1)}subrule(e,r,n,i){this.before(n),this.wrapper.wrapSubrule(e,r,i),this.after(n)}before(e){this.isRecording()||this.elementStack.push(e)}after(e){if(!this.isRecording()){let r=this.elementStack.lastIndexOf(e);r>=0&&this.elementStack.splice(r)}}get currIdx(){return this.wrapper.currIdx}},LOe={recoveryEnabled:!0,nodeLocationTracking:"full",skipValidations:!0,errorMessageProvider:new yg},iM=class extends x2{static{o(this,"ChevrotainWrapper")}constructor(e,r){let n=r&&"maxLookahead"in r;super(e,Object.assign(Object.assign(Object.assign({},LOe),{lookaheadStrategy:n?new $u({maxLookahead:r.maxLookahead}):new k2}),r))}get IS_RECORDING(){return this.RECORDING_PHASE}DEFINE_RULE(e,r){return this.RULE(e,r)}wrapSelfAnalysis(){this.performSelfAnalysis()}wrapConsume(e,r){return this.consume(e,r)}wrapSubrule(e,r,n){return this.subrule(e,r,{ARGS:[n]})}wrapOr(e,r){this.or(e,r)}wrapOption(e,r){this.option(e,r)}wrapMany(e,r){this.many(e,r)}wrapAtLeastOne(e,r){this.atLeastOne(e,r)}}});function _k(t,e,r){return DOe({parser:e,tokens:r,rules:new Map,ruleNames:new Map},t),e}function DOe(t,e){let r=Qv(e,!1),n=Kr(e.rules).filter(Oa).filter(i=>r.has(i));for(let i of n){let a=Object.assign(Object.assign({},t),{consume:1,optional:1,subrule:1,many:1,or:1});a.rules.set(i.name,t.parser.rule(i,m0(a,i.definition)))}}function m0(t,e,r=!1){let n;if(Ho(e))n=BOe(t,e);else if(Iu(e))n=ROe(t,e);else if(Nl(e))n=m0(t,e.terminal);else if(Kd(e))n=noe(t,e);else if(Ml(e))n=NOe(t,e);else if(wT(e))n=IOe(t,e);else if(kT(e))n=OOe(t,e);else if(rf(e))n=POe(t,e);else if(fR(e)){let i=t.consume++;n=o(()=>t.parser.consume(i,fo,e),"method")}else throw new jd(e.$cstNode,`Unexpected element type: ${e.$type}`);return ioe(t,r?void 0:Ak(e),n,e.cardinality)}function ROe(t,e){let r=r0(e);return()=>t.parser.action(r,e)}function NOe(t,e){let r=e.rule.ref;if(Oa(r)){let n=t.subrule++,i=e.arguments.length>0?MOe(r,e.arguments):()=>({});return a=>t.parser.subrule(n,aoe(t,r),e,i(a))}else if(Uo(r)){let n=t.consume++,i=aM(t,r.name);return()=>t.parser.consume(n,i,e)}else if(r)tf(r);else throw new jd(e.$cstNode,`Undefined rule type: ${e.$type}`)}function MOe(t,e){let r=e.map(n=>Vu(n.value));return n=>{let i={};for(let a=0;ae(n)||r(n)}else if(FD(t)){let e=Vu(t.left),r=Vu(t.right);return n=>e(n)&&r(n)}else if(YD(t)){let e=Vu(t.value);return r=>!e(r)}else if(jD(t)){let e=t.parameter.ref.name;return r=>r!==void 0&&r[e]===!0}else if(PD(t)){let e=!!t.true;return()=>e}tf(t)}function IOe(t,e){if(e.elements.length===1)return m0(t,e.elements[0]);{let r=[];for(let i of e.elements){let a={ALT:m0(t,i,!0)},s=Ak(i);s&&(a.GATE=Vu(s)),r.push(a)}let n=t.or++;return i=>t.parser.alternatives(n,r.map(a=>{let s={ALT:o(()=>a.ALT(i),"ALT")},l=a.GATE;return l&&(s.GATE=()=>l(i)),s}))}}function OOe(t,e){if(e.elements.length===1)return m0(t,e.elements[0]);let r=[];for(let l of e.elements){let u={ALT:m0(t,l,!0)},h=Ak(l);h&&(u.GATE=Vu(h)),r.push(u)}let n=t.or++,i=o((l,u)=>{let h=u.getRuleStack().join("-");return`uGroup_${l}_${h}`},"idFunc"),a=o(l=>t.parser.alternatives(n,r.map((u,h)=>{let f={ALT:o(()=>!0,"ALT")},d=t.parser;f.ALT=()=>{if(u.ALT(l),!d.isRecording()){let m=i(n,d);d.unorderedGroups.get(m)||d.unorderedGroups.set(m,[]);let g=d.unorderedGroups.get(m);typeof g?.[h]>"u"&&(g[h]=!0)}};let p=u.GATE;return p?f.GATE=()=>p(l):f.GATE=()=>{let m=d.unorderedGroups.get(i(n,d));return!m?.[h]},f})),"alternatives"),s=ioe(t,Ak(e),a,"*");return l=>{s(l),t.parser.isRecording()||t.parser.unorderedGroups.delete(i(n,t.parser))}}function POe(t,e){let r=e.elements.map(n=>m0(t,n));return n=>r.forEach(i=>i(n))}function Ak(t){if(rf(t))return t.guardCondition}function noe(t,e,r=e.terminal){if(r)if(Ml(r)&&Oa(r.rule.ref)){let n=t.subrule++;return i=>t.parser.subrule(n,aoe(t,r.rule.ref),e,i)}else if(Ml(r)&&Uo(r.rule.ref)){let n=t.consume++,i=aM(t,r.rule.ref.name);return()=>t.parser.consume(n,i,e)}else if(Ho(r)){let n=t.consume++,i=aM(t,r.value);return()=>t.parser.consume(n,i,e)}else throw new Error("Could not build cross reference parser");else{if(!e.type.ref)throw new Error("Could not resolve reference to type: "+e.type.$refText);let n=DT(e.type.ref),i=n?.terminal;if(!i)throw new Error("Could not find name assignment for type: "+r0(e.type.ref));return noe(t,e,i)}}function BOe(t,e){let r=t.consume++,n=t.tokens[e.value];if(!n)throw new Error("Could not find token for keyword: "+e.value);return()=>t.parser.consume(r,n,e)}function ioe(t,e,r,n){let i=e&&Vu(e);if(!n)if(i){let a=t.or++;return s=>t.parser.alternatives(a,[{ALT:o(()=>r(s),"ALT"),GATE:o(()=>i(s),"GATE")},{ALT:gk(),GATE:o(()=>!i(s),"GATE")}])}else return r;if(n==="*"){let a=t.many++;return s=>t.parser.many(a,{DEF:o(()=>r(s),"DEF"),GATE:i?()=>i(s):void 0})}else if(n==="+"){let a=t.many++;if(i){let s=t.or++;return l=>t.parser.alternatives(s,[{ALT:o(()=>t.parser.atLeastOne(a,{DEF:o(()=>r(l),"DEF")}),"ALT"),GATE:o(()=>i(l),"GATE")},{ALT:gk(),GATE:o(()=>!i(l),"GATE")}])}else return s=>t.parser.atLeastOne(a,{DEF:o(()=>r(s),"DEF")})}else if(n==="?"){let a=t.optional++;return s=>t.parser.optional(a,{DEF:o(()=>r(s),"DEF"),GATE:i?()=>i(s):void 0})}else tf(n)}function aoe(t,e){let r=FOe(t,e),n=t.rules.get(r);if(!n)throw new Error(`Rule "${r}" not found."`);return n}function FOe(t,e){if(Oa(e))return e.name;if(t.ruleNames.has(e))return t.ruleNames.get(e);{let r=e,n=r.$container,i=e.$type;for(;!Oa(n);)(rf(n)||wT(n)||kT(n))&&(i=n.elements.indexOf(r).toString()+":"+i),r=n,n=n.$container;return i=n.name+":"+i,t.ruleNames.set(e,i),i}}function aM(t,e){let r=t.tokens[e];if(!r)throw new Error(`Token "${e}" not found."`);return r}var sM=R(()=>{"use strict";u0();Sc();pT();Ds();Il();o(_k,"createParser");o(DOe,"buildRules");o(m0,"buildElement");o(ROe,"buildAction");o(NOe,"buildRuleCall");o(MOe,"buildRuleCallPredicate");o(Vu,"buildPredicate");o(IOe,"buildAlternatives");o(OOe,"buildUnorderedGroup");o(POe,"buildGroup");o(Ak,"getGuardCondition");o(noe,"buildCrossReference");o(BOe,"buildKeyword");o(ioe,"wrap");o(aoe,"getRule");o(FOe,"getRuleName");o(aM,"getToken")});function oM(t){let e=t.Grammar,r=t.parser.Lexer,n=new L2(t);return _k(e,n,r.definition),n.finalize(),n}var lM=R(()=>{"use strict";D2();sM();o(oM,"createCompletionParser")});function cM(t){let e=soe(t);return e.finalize(),e}function soe(t){let e=t.Grammar,r=t.parser.Lexer,n=new _2(t);return _k(e,n,r.definition)}var uM=R(()=>{"use strict";D2();sM();o(cM,"createLangiumParser");o(soe,"prepareLangiumParser")});var g0,hM=R(()=>{"use strict";u0();Sc();es();Il();Um();Ds();g0=class{static{o(this,"DefaultTokenBuilder")}buildTokens(e,r){let n=Kr(Qv(e,!1)),i=this.buildTerminalTokens(n),a=this.buildKeywordTokens(n,i,r);return i.forEach(s=>{let l=s.PATTERN;typeof l=="object"&&l&&"test"in l&&_T(l)?a.unshift(s):a.push(s)}),a}buildTerminalTokens(e){return e.filter(Uo).filter(r=>!r.fragment).map(r=>this.buildTerminalToken(r)).toArray()}buildTerminalToken(e){let r=Hm(e),n=this.requiresCustomPattern(r)?this.regexPatternFunction(r):r,i={name:e.name,PATTERN:n,LINE_BREAKS:!0};return e.hidden&&(i.GROUP=_T(r)?ni.SKIPPED:"hidden"),i}requiresCustomPattern(e){return e.flags.includes("u")?!0:!!(e.source.includes("?<=")||e.source.includes("?(r.lastIndex=i,r.exec(n))}buildKeywordTokens(e,r,n){return e.filter(Oa).flatMap(i=>Ac(i).filter(Ho)).distinct(i=>i.value).toArray().sort((i,a)=>a.value.length-i.value.length).map(i=>this.buildKeywordToken(i,r,!!n?.caseInsensitive))}buildKeywordToken(e,r,n){return{name:e.value,PATTERN:this.buildKeywordPattern(e,n),LONGER_ALT:this.findLongerAlt(e,r)}}buildKeywordPattern(e,r){return r?new RegExp(zR(e.value)):e.value}findLongerAlt(e,r){return r.reduce((n,i)=>{let a=i?.PATTERN;return a?.source&&GR("^"+a.source+"$",e.value)&&n.push(i),n},[])}}});var y0,Dc,fM=R(()=>{"use strict";Sc();Il();y0=class{static{o(this,"DefaultValueConverter")}convert(e,r){let n=r.grammarSource;if(Kd(n)&&(n=UR(n)),Ml(n)){let i=n.rule.ref;if(!i)throw new Error("This cst node was not parsed by a rule.");return this.runConverter(i,e,r)}return e}runConverter(e,r,n){var i;switch(e.name.toUpperCase()){case"INT":return Dc.convertInt(r);case"STRING":return Dc.convertString(r);case"ID":return Dc.convertID(r)}switch((i=QR(e))===null||i===void 0?void 0:i.toLowerCase()){case"number":return Dc.convertNumber(r);case"boolean":return Dc.convertBoolean(r);case"bigint":return Dc.convertBigint(r);case"date":return Dc.convertDate(r);default:return r}}};(function(t){function e(h){let f="";for(let d=1;d{"use strict";Object.defineProperty(mM,"__esModule",{value:!0});var dM;function pM(){if(dM===void 0)throw new Error("No runtime abstraction layer installed");return dM}o(pM,"RAL");(function(t){function e(r){if(r===void 0)throw new Error("No runtime abstraction layer provided");dM=r}o(e,"install"),t.install=e})(pM||(pM={}));mM.default=pM});var coe=gi(Pa=>{"use strict";Object.defineProperty(Pa,"__esModule",{value:!0});Pa.stringArray=Pa.array=Pa.func=Pa.error=Pa.number=Pa.string=Pa.boolean=void 0;function zOe(t){return t===!0||t===!1}o(zOe,"boolean");Pa.boolean=zOe;function ooe(t){return typeof t=="string"||t instanceof String}o(ooe,"string");Pa.string=ooe;function GOe(t){return typeof t=="number"||t instanceof Number}o(GOe,"number");Pa.number=GOe;function $Oe(t){return t instanceof Error}o($Oe,"error");Pa.error=$Oe;function VOe(t){return typeof t=="function"}o(VOe,"func");Pa.func=VOe;function loe(t){return Array.isArray(t)}o(loe,"array");Pa.array=loe;function UOe(t){return loe(t)&&t.every(e=>ooe(e))}o(UOe,"stringArray");Pa.stringArray=UOe});var vM=gi(vg=>{"use strict";Object.defineProperty(vg,"__esModule",{value:!0});vg.Emitter=vg.Event=void 0;var HOe=gM(),uoe;(function(t){let e={dispose(){}};t.None=function(){return e}})(uoe||(vg.Event=uoe={}));var yM=class{static{o(this,"CallbackList")}add(e,r=null,n){this._callbacks||(this._callbacks=[],this._contexts=[]),this._callbacks.push(e),this._contexts.push(r),Array.isArray(n)&&n.push({dispose:o(()=>this.remove(e,r),"dispose")})}remove(e,r=null){if(!this._callbacks)return;let n=!1;for(let i=0,a=this._callbacks.length;i{this._callbacks||(this._callbacks=new yM),this._options&&this._options.onFirstListenerAdd&&this._callbacks.isEmpty()&&this._options.onFirstListenerAdd(this),this._callbacks.add(e,r);let i={dispose:o(()=>{this._callbacks&&(this._callbacks.remove(e,r),i.dispose=t._noop,this._options&&this._options.onLastListenerRemove&&this._callbacks.isEmpty()&&this._options.onLastListenerRemove(this))},"dispose")};return Array.isArray(n)&&n.push(i),i}),this._event}fire(e){this._callbacks&&this._callbacks.invoke.call(this._callbacks,e)}dispose(){this._callbacks&&(this._callbacks.dispose(),this._callbacks=void 0)}};vg.Emitter=Lk;Lk._noop=function(){}});var hoe=gi(xg=>{"use strict";Object.defineProperty(xg,"__esModule",{value:!0});xg.CancellationTokenSource=xg.CancellationToken=void 0;var YOe=gM(),WOe=coe(),xM=vM(),Dk;(function(t){t.None=Object.freeze({isCancellationRequested:!1,onCancellationRequested:xM.Event.None}),t.Cancelled=Object.freeze({isCancellationRequested:!0,onCancellationRequested:xM.Event.None});function e(r){let n=r;return n&&(n===t.None||n===t.Cancelled||WOe.boolean(n.isCancellationRequested)&&!!n.onCancellationRequested)}o(e,"is"),t.is=e})(Dk||(xg.CancellationToken=Dk={}));var qOe=Object.freeze(function(t,e){let r=(0,YOe.default)().timer.setTimeout(t.bind(e),0);return{dispose(){r.dispose()}}}),Rk=class{static{o(this,"MutableToken")}constructor(){this._isCancelled=!1}cancel(){this._isCancelled||(this._isCancelled=!0,this._emitter&&(this._emitter.fire(void 0),this.dispose()))}get isCancellationRequested(){return this._isCancelled}get onCancellationRequested(){return this._isCancelled?qOe:(this._emitter||(this._emitter=new xM.Emitter),this._emitter.event)}dispose(){this._emitter&&(this._emitter.dispose(),this._emitter=void 0)}},bM=class{static{o(this,"CancellationTokenSource")}get token(){return this._token||(this._token=new Rk),this._token}cancel(){this._token?this._token.cancel():this._token=Dk.Cancelled}dispose(){this._token?this._token instanceof Rk&&this._token.dispose():this._token=Dk.None}};xg.CancellationTokenSource=bM});var pr={};var Wo=R(()=>{"use strict";dr(pr,Xi(hoe(),1))});function TM(){return new Promise(t=>{typeof setImmediate>"u"?setTimeout(t,0):setImmediate(t)})}function doe(){return wM=Date.now(),new pr.CancellationTokenSource}function poe(t){foe=t}function of(t){return t===Rc}async function Bi(t){if(t===pr.CancellationToken.None)return;let e=Date.now();if(e-wM>=foe&&(wM=e,await TM()),t.isCancellationRequested)throw Rc}var wM,foe,Rc,as,qo=R(()=>{"use strict";Wo();o(TM,"delayNextTick");wM=0,foe=10;o(doe,"startCancelableOperation");o(poe,"setInterruptionPeriod");Rc=Symbol("OperationCancelled");o(of,"isOperationCancelled");o(Bi,"interruptAndCheck");as=class{static{o(this,"Deferred")}constructor(){this.promise=new Promise((e,r)=>{this.resolve=n=>(e(n),this),this.reject=n=>(r(n),this)})}}});function kM(t,e){if(t.length<=1)return t;let r=t.length/2|0,n=t.slice(0,r),i=t.slice(r);kM(n,e),kM(i,e);let a=0,s=0,l=0;for(;ar.line||e.line===r.line&&e.character>r.character?{start:r,end:e}:t}function XOe(t){let e=yoe(t.range);return e!==t.range?{newText:t.newText,range:e}:t}var Nk,bg,voe=R(()=>{"use strict";Nk=class t{static{o(this,"FullTextDocument")}constructor(e,r,n,i){this._uri=e,this._languageId=r,this._version=n,this._content=i,this._lineOffsets=void 0}get uri(){return this._uri}get languageId(){return this._languageId}get version(){return this._version}getText(e){if(e){let r=this.offsetAt(e.start),n=this.offsetAt(e.end);return this._content.substring(r,n)}return this._content}update(e,r){for(let n of e)if(t.isIncremental(n)){let i=yoe(n.range),a=this.offsetAt(i.start),s=this.offsetAt(i.end);this._content=this._content.substring(0,a)+n.text+this._content.substring(s,this._content.length);let l=Math.max(i.start.line,0),u=Math.max(i.end.line,0),h=this._lineOffsets,f=moe(n.text,!1,a);if(u-l===f.length)for(let p=0,m=f.length;pe?i=s:n=s+1}let a=n-1;return e=this.ensureBeforeEOL(e,r[a]),{line:a,character:e-r[a]}}offsetAt(e){let r=this.getLineOffsets();if(e.line>=r.length)return this._content.length;if(e.line<0)return 0;let n=r[e.line];if(e.character<=0)return n;let i=e.line+1r&&goe(this._content.charCodeAt(e-1));)e--;return e}get lineCount(){return this.getLineOffsets().length}static isIncremental(e){let r=e;return r!=null&&typeof r.text=="string"&&r.range!==void 0&&(r.rangeLength===void 0||typeof r.rangeLength=="number")}static isFull(e){let r=e;return r!=null&&typeof r.text=="string"&&r.range===void 0&&r.rangeLength===void 0}};(function(t){function e(i,a,s,l){return new Nk(i,a,s,l)}o(e,"create"),t.create=e;function r(i,a,s){if(i instanceof Nk)return i.update(a,s),i;throw new Error("TextDocument.update: document must be created by TextDocument.create")}o(r,"update"),t.update=r;function n(i,a){let s=i.getText(),l=kM(a.map(XOe),(f,d)=>{let p=f.range.start.line-d.range.start.line;return p===0?f.range.start.character-d.range.start.character:p}),u=0,h=[];for(let f of l){let d=i.offsetAt(f.range.start);if(du&&h.push(s.substring(u,d)),f.newText.length&&h.push(f.newText),u=i.offsetAt(f.range.end)}return h.push(s.substr(u)),h.join("")}o(n,"applyEdits"),t.applyEdits=n})(bg||(bg={}));o(kM,"mergeSort");o(moe,"computeLineOffsets");o(goe,"isEOL");o(yoe,"getWellformedRange");o(XOe,"getWellformedEdit")});var xoe,Ms,wg,EM=R(()=>{"use strict";(()=>{"use strict";var t={470:i=>{function a(u){if(typeof u!="string")throw new TypeError("Path must be a string. Received "+JSON.stringify(u))}o(a,"e");function s(u,h){for(var f,d="",p=0,m=-1,g=0,y=0;y<=u.length;++y){if(y2){var v=d.lastIndexOf("/");if(v!==d.length-1){v===-1?(d="",p=0):p=(d=d.slice(0,v)).length-1-d.lastIndexOf("/"),m=y,g=0;continue}}else if(d.length===2||d.length===1){d="",p=0,m=y,g=0;continue}}h&&(d.length>0?d+="/..":d="..",p=2)}else d.length>0?d+="/"+u.slice(m+1,y):d=u.slice(m+1,y),p=y-m-1;m=y,g=0}else f===46&&g!==-1?++g:g=-1}return d}o(s,"r");var l={resolve:o(function(){for(var u,h="",f=!1,d=arguments.length-1;d>=-1&&!f;d--){var p;d>=0?p=arguments[d]:(u===void 0&&(u=process.cwd()),p=u),a(p),p.length!==0&&(h=p+"/"+h,f=p.charCodeAt(0)===47)}return h=s(h,!f),f?h.length>0?"/"+h:"/":h.length>0?h:"."},"resolve"),normalize:o(function(u){if(a(u),u.length===0)return".";var h=u.charCodeAt(0)===47,f=u.charCodeAt(u.length-1)===47;return(u=s(u,!h)).length!==0||h||(u="."),u.length>0&&f&&(u+="/"),h?"/"+u:u},"normalize"),isAbsolute:o(function(u){return a(u),u.length>0&&u.charCodeAt(0)===47},"isAbsolute"),join:o(function(){if(arguments.length===0)return".";for(var u,h=0;h0&&(u===void 0?u=f:u+="/"+f)}return u===void 0?".":l.normalize(u)},"join"),relative:o(function(u,h){if(a(u),a(h),u===h||(u=l.resolve(u))===(h=l.resolve(h)))return"";for(var f=1;fy){if(h.charCodeAt(m+x)===47)return h.slice(m+x+1);if(x===0)return h.slice(m+x)}else p>y&&(u.charCodeAt(f+x)===47?v=x:x===0&&(v=0));break}var b=u.charCodeAt(f+x);if(b!==h.charCodeAt(m+x))break;b===47&&(v=x)}var w="";for(x=f+v+1;x<=d;++x)x!==d&&u.charCodeAt(x)!==47||(w.length===0?w+="..":w+="/..");return w.length>0?w+h.slice(m+v):(m+=v,h.charCodeAt(m)===47&&++m,h.slice(m))},"relative"),_makeLong:o(function(u){return u},"_makeLong"),dirname:o(function(u){if(a(u),u.length===0)return".";for(var h=u.charCodeAt(0),f=h===47,d=-1,p=!0,m=u.length-1;m>=1;--m)if((h=u.charCodeAt(m))===47){if(!p){d=m;break}}else p=!1;return d===-1?f?"/":".":f&&d===1?"//":u.slice(0,d)},"dirname"),basename:o(function(u,h){if(h!==void 0&&typeof h!="string")throw new TypeError('"ext" argument must be a string');a(u);var f,d=0,p=-1,m=!0;if(h!==void 0&&h.length>0&&h.length<=u.length){if(h.length===u.length&&h===u)return"";var g=h.length-1,y=-1;for(f=u.length-1;f>=0;--f){var v=u.charCodeAt(f);if(v===47){if(!m){d=f+1;break}}else y===-1&&(m=!1,y=f+1),g>=0&&(v===h.charCodeAt(g)?--g==-1&&(p=f):(g=-1,p=y))}return d===p?p=y:p===-1&&(p=u.length),u.slice(d,p)}for(f=u.length-1;f>=0;--f)if(u.charCodeAt(f)===47){if(!m){d=f+1;break}}else p===-1&&(m=!1,p=f+1);return p===-1?"":u.slice(d,p)},"basename"),extname:o(function(u){a(u);for(var h=-1,f=0,d=-1,p=!0,m=0,g=u.length-1;g>=0;--g){var y=u.charCodeAt(g);if(y!==47)d===-1&&(p=!1,d=g+1),y===46?h===-1?h=g:m!==1&&(m=1):h!==-1&&(m=-1);else if(!p){f=g+1;break}}return h===-1||d===-1||m===0||m===1&&h===d-1&&h===f+1?"":u.slice(h,d)},"extname"),format:o(function(u){if(u===null||typeof u!="object")throw new TypeError('The "pathObject" argument must be of type Object. Received type '+typeof u);return function(h,f){var d=f.dir||f.root,p=f.base||(f.name||"")+(f.ext||"");return d?d===f.root?d+p:d+"/"+p:p}(0,u)},"format"),parse:o(function(u){a(u);var h={root:"",dir:"",base:"",ext:"",name:""};if(u.length===0)return h;var f,d=u.charCodeAt(0),p=d===47;p?(h.root="/",f=1):f=0;for(var m=-1,g=0,y=-1,v=!0,x=u.length-1,b=0;x>=f;--x)if((d=u.charCodeAt(x))!==47)y===-1&&(v=!1,y=x+1),d===46?m===-1?m=x:b!==1&&(b=1):m!==-1&&(b=-1);else if(!v){g=x+1;break}return m===-1||y===-1||b===0||b===1&&m===y-1&&m===g+1?y!==-1&&(h.base=h.name=g===0&&p?u.slice(1,y):u.slice(g,y)):(g===0&&p?(h.name=u.slice(1,m),h.base=u.slice(1,y)):(h.name=u.slice(g,m),h.base=u.slice(g,y)),h.ext=u.slice(m,y)),g>0?h.dir=u.slice(0,g-1):p&&(h.dir="/"),h},"parse"),sep:"/",delimiter:":",win32:null,posix:null};l.posix=l,i.exports=l}},e={};function r(i){var a=e[i];if(a!==void 0)return a.exports;var s=e[i]={exports:{}};return t[i](s,s.exports,r),s.exports}o(r,"r"),r.d=(i,a)=>{for(var s in a)r.o(a,s)&&!r.o(i,s)&&Object.defineProperty(i,s,{enumerable:!0,get:a[s]})},r.o=(i,a)=>Object.prototype.hasOwnProperty.call(i,a),r.r=i=>{typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(i,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(i,"__esModule",{value:!0})};var n={};(()=>{let i;r.r(n),r.d(n,{URI:o(()=>p,"URI"),Utils:o(()=>M,"Utils")}),typeof process=="object"?i=process.platform==="win32":typeof navigator=="object"&&(i=navigator.userAgent.indexOf("Windows")>=0);let a=/^\w[\w\d+.-]*$/,s=/^\//,l=/^\/\//;function u(N,k){if(!N.scheme&&k)throw new Error(`[UriError]: Scheme is missing: {scheme: "", authority: "${N.authority}", path: "${N.path}", query: "${N.query}", fragment: "${N.fragment}"}`);if(N.scheme&&!a.test(N.scheme))throw new Error("[UriError]: Scheme contains illegal characters.");if(N.path){if(N.authority){if(!s.test(N.path))throw new Error('[UriError]: If a URI contains an authority component, then the path component must either be empty or begin with a slash ("/") character')}else if(l.test(N.path))throw new Error('[UriError]: If a URI does not contain an authority component, then the path cannot begin with two slash characters ("//")')}}o(u,"s");let h="",f="/",d=/^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/;class p{static{o(this,"f")}static isUri(k){return k instanceof p||!!k&&typeof k.authority=="string"&&typeof k.fragment=="string"&&typeof k.path=="string"&&typeof k.query=="string"&&typeof k.scheme=="string"&&typeof k.fsPath=="string"&&typeof k.with=="function"&&typeof k.toString=="function"}scheme;authority;path;query;fragment;constructor(k,I,C,O,D,P=!1){typeof k=="object"?(this.scheme=k.scheme||h,this.authority=k.authority||h,this.path=k.path||h,this.query=k.query||h,this.fragment=k.fragment||h):(this.scheme=function(F,B){return F||B?F:"file"}(k,P),this.authority=I||h,this.path=function(F,B){switch(F){case"https":case"http":case"file":B?B[0]!==f&&(B=f+B):B=f}return B}(this.scheme,C||h),this.query=O||h,this.fragment=D||h,u(this,P))}get fsPath(){return b(this,!1)}with(k){if(!k)return this;let{scheme:I,authority:C,path:O,query:D,fragment:P}=k;return I===void 0?I=this.scheme:I===null&&(I=h),C===void 0?C=this.authority:C===null&&(C=h),O===void 0?O=this.path:O===null&&(O=h),D===void 0?D=this.query:D===null&&(D=h),P===void 0?P=this.fragment:P===null&&(P=h),I===this.scheme&&C===this.authority&&O===this.path&&D===this.query&&P===this.fragment?this:new g(I,C,O,D,P)}static parse(k,I=!1){let C=d.exec(k);return C?new g(C[2]||h,E(C[4]||h),E(C[5]||h),E(C[7]||h),E(C[9]||h),I):new g(h,h,h,h,h)}static file(k){let I=h;if(i&&(k=k.replace(/\\/g,f)),k[0]===f&&k[1]===f){let C=k.indexOf(f,2);C===-1?(I=k.substring(2),k=f):(I=k.substring(2,C),k=k.substring(C)||f)}return new g("file",I,k,h,h)}static from(k){let I=new g(k.scheme,k.authority,k.path,k.query,k.fragment);return u(I,!0),I}toString(k=!1){return w(this,k)}toJSON(){return this}static revive(k){if(k){if(k instanceof p)return k;{let I=new g(k);return I._formatted=k.external,I._fsPath=k._sep===m?k.fsPath:null,I}}return k}}let m=i?1:void 0;class g extends p{static{o(this,"l")}_formatted=null;_fsPath=null;get fsPath(){return this._fsPath||(this._fsPath=b(this,!1)),this._fsPath}toString(k=!1){return k?w(this,!0):(this._formatted||(this._formatted=w(this,!1)),this._formatted)}toJSON(){let k={$mid:1};return this._fsPath&&(k.fsPath=this._fsPath,k._sep=m),this._formatted&&(k.external=this._formatted),this.path&&(k.path=this.path),this.scheme&&(k.scheme=this.scheme),this.authority&&(k.authority=this.authority),this.query&&(k.query=this.query),this.fragment&&(k.fragment=this.fragment),k}}let y={58:"%3A",47:"%2F",63:"%3F",35:"%23",91:"%5B",93:"%5D",64:"%40",33:"%21",36:"%24",38:"%26",39:"%27",40:"%28",41:"%29",42:"%2A",43:"%2B",44:"%2C",59:"%3B",61:"%3D",32:"%20"};function v(N,k,I){let C,O=-1;for(let D=0;D=97&&P<=122||P>=65&&P<=90||P>=48&&P<=57||P===45||P===46||P===95||P===126||k&&P===47||I&&P===91||I&&P===93||I&&P===58)O!==-1&&(C+=encodeURIComponent(N.substring(O,D)),O=-1),C!==void 0&&(C+=N.charAt(D));else{C===void 0&&(C=N.substr(0,D));let F=y[P];F!==void 0?(O!==-1&&(C+=encodeURIComponent(N.substring(O,D)),O=-1),C+=F):O===-1&&(O=D)}}return O!==-1&&(C+=encodeURIComponent(N.substring(O))),C!==void 0?C:N}o(v,"d");function x(N){let k;for(let I=0;I1&&N.scheme==="file"?`//${N.authority}${N.path}`:N.path.charCodeAt(0)===47&&(N.path.charCodeAt(1)>=65&&N.path.charCodeAt(1)<=90||N.path.charCodeAt(1)>=97&&N.path.charCodeAt(1)<=122)&&N.path.charCodeAt(2)===58?k?N.path.substr(1):N.path[1].toLowerCase()+N.path.substr(2):N.path,i&&(I=I.replace(/\//g,"\\")),I}o(b,"m");function w(N,k){let I=k?x:v,C="",{scheme:O,authority:D,path:P,query:F,fragment:B}=N;if(O&&(C+=O,C+=":"),(D||O==="file")&&(C+=f,C+=f),D){let $=D.indexOf("@");if($!==-1){let z=D.substr(0,$);D=D.substr($+1),$=z.lastIndexOf(":"),$===-1?C+=I(z,!1,!1):(C+=I(z.substr(0,$),!1,!1),C+=":",C+=I(z.substr($+1),!1,!0)),C+="@"}D=D.toLowerCase(),$=D.lastIndexOf(":"),$===-1?C+=I(D,!1,!0):(C+=I(D.substr(0,$),!1,!0),C+=D.substr($))}if(P){if(P.length>=3&&P.charCodeAt(0)===47&&P.charCodeAt(2)===58){let $=P.charCodeAt(1);$>=65&&$<=90&&(P=`/${String.fromCharCode($+32)}:${P.substr(3)}`)}else if(P.length>=2&&P.charCodeAt(1)===58){let $=P.charCodeAt(0);$>=65&&$<=90&&(P=`${String.fromCharCode($+32)}:${P.substr(2)}`)}C+=I(P,!0,!1)}return F&&(C+="?",C+=I(F,!1,!1)),B&&(C+="#",C+=k?B:v(B,!1,!1)),C}o(w,"y");function S(N){try{return decodeURIComponent(N)}catch{return N.length>3?N.substr(0,3)+S(N.substr(3)):N}}o(S,"v");let T=/(%[0-9A-Za-z][0-9A-Za-z])+/g;function E(N){return N.match(T)?N.replace(T,k=>S(k)):N}o(E,"C");var _=r(470);let A=_.posix||_,L="/";var M;(function(N){N.joinPath=function(k,...I){return k.with({path:A.join(k.path,...I)})},N.resolvePath=function(k,...I){let C=k.path,O=!1;C[0]!==L&&(C=L+C,O=!0);let D=A.resolve(C,...I);return O&&D[0]===L&&!k.authority&&(D=D.substring(1)),k.with({path:D})},N.dirname=function(k){if(k.path.length===0||k.path===L)return k;let I=A.dirname(k.path);return I.length===1&&I.charCodeAt(0)===46&&(I=""),k.with({path:I})},N.basename=function(k){return A.basename(k.path)},N.extname=function(k){return A.extname(k.path)}})(M||(M={}))})(),xoe=n})();({URI:Ms,Utils:wg}=xoe)});var ss,Nc=R(()=>{"use strict";EM();(function(t){t.basename=wg.basename,t.dirname=wg.dirname,t.extname=wg.extname,t.joinPath=wg.joinPath,t.resolvePath=wg.resolvePath;function e(n,i){return n?.toString()===i?.toString()}o(e,"equals"),t.equals=e;function r(n,i){let a=typeof n=="string"?n:n.path,s=typeof i=="string"?i:i.path,l=a.split("/").filter(p=>p.length>0),u=s.split("/").filter(p=>p.length>0),h=0;for(;h{"use strict";voe();Tg();Wo();Ds();Nc();(function(t){t[t.Changed=0]="Changed",t[t.Parsed=1]="Parsed",t[t.IndexedContent=2]="IndexedContent",t[t.ComputedScopes=3]="ComputedScopes",t[t.Linked=4]="Linked",t[t.IndexedReferences=5]="IndexedReferences",t[t.Validated=6]="Validated"})(yn||(yn={}));R2=class{static{o(this,"DefaultLangiumDocumentFactory")}constructor(e){this.serviceRegistry=e.ServiceRegistry,this.textDocuments=e.workspace.TextDocuments,this.fileSystemProvider=e.workspace.FileSystemProvider}async fromUri(e,r=pr.CancellationToken.None){let n=await this.fileSystemProvider.readFile(e);return this.createAsync(e,n,r)}fromTextDocument(e,r,n){return r=r??Ms.parse(e.uri),n?this.createAsync(r,e,n):this.create(r,e)}fromString(e,r,n){return n?this.createAsync(r,e,n):this.create(r,e)}fromModel(e,r){return this.create(r,{$model:e})}create(e,r){if(typeof r=="string"){let n=this.parse(e,r);return this.createLangiumDocument(n,e,void 0,r)}else if("$model"in r){let n={value:r.$model,parserErrors:[],lexerErrors:[]};return this.createLangiumDocument(n,e)}else{let n=this.parse(e,r.getText());return this.createLangiumDocument(n,e,r)}}async createAsync(e,r,n){if(typeof r=="string"){let i=await this.parseAsync(e,r,n);return this.createLangiumDocument(i,e,void 0,r)}else{let i=await this.parseAsync(e,r.getText(),n);return this.createLangiumDocument(i,e,r)}}createLangiumDocument(e,r,n,i){let a;if(n)a={parseResult:e,uri:r,state:yn.Parsed,references:[],textDocument:n};else{let s=this.createTextDocumentGetter(r,i);a={parseResult:e,uri:r,state:yn.Parsed,references:[],get textDocument(){return s()}}}return e.value.$document=a,a}async update(e,r){var n,i;let a=(n=e.parseResult.value.$cstNode)===null||n===void 0?void 0:n.root.fullText,s=(i=this.textDocuments)===null||i===void 0?void 0:i.get(e.uri.toString()),l=s?s.getText():await this.fileSystemProvider.readFile(e.uri);if(s)Object.defineProperty(e,"textDocument",{value:s});else{let u=this.createTextDocumentGetter(e.uri,l);Object.defineProperty(e,"textDocument",{get:u})}return a!==l&&(e.parseResult=await this.parseAsync(e.uri,l,r),e.parseResult.value.$document=e),e.state=yn.Parsed,e}parse(e,r){return this.serviceRegistry.getServices(e).parser.LangiumParser.parse(r)}parseAsync(e,r,n){return this.serviceRegistry.getServices(e).parser.AsyncParser.parse(r,n)}createTextDocumentGetter(e,r){let n=this.serviceRegistry,i;return()=>i??(i=bg.create(e.toString(),n.getServices(e).LanguageMetaData.languageId,0,r??""))}},N2=class{static{o(this,"DefaultLangiumDocuments")}constructor(e){this.documentMap=new Map,this.langiumDocumentFactory=e.workspace.LangiumDocumentFactory}get all(){return Kr(this.documentMap.values())}addDocument(e){let r=e.uri.toString();if(this.documentMap.has(r))throw new Error(`A document with the URI '${r}' is already present.`);this.documentMap.set(r,e)}getDocument(e){let r=e.toString();return this.documentMap.get(r)}async getOrCreateDocument(e,r){let n=this.getDocument(e);return n||(n=await this.langiumDocumentFactory.fromUri(e,r),this.addDocument(n),n)}createDocument(e,r,n){if(n)return this.langiumDocumentFactory.fromString(r,e,n).then(i=>(this.addDocument(i),i));{let i=this.langiumDocumentFactory.fromString(r,e);return this.addDocument(i),i}}hasDocument(e){return this.documentMap.has(e.toString())}invalidateDocument(e){let r=e.toString(),n=this.documentMap.get(r);return n&&(n.state=yn.Changed,n.precomputedScopes=void 0,n.references=[],n.diagnostics=void 0),n}deleteDocument(e){let r=e.toString(),n=this.documentMap.get(r);return n&&(n.state=yn.Changed,this.documentMap.delete(r)),n}}});var M2,CM=R(()=>{"use strict";Wo();Vo();es();qo();Tg();M2=class{static{o(this,"DefaultLinker")}constructor(e){this.reflection=e.shared.AstReflection,this.langiumDocuments=()=>e.shared.workspace.LangiumDocuments,this.scopeProvider=e.references.ScopeProvider,this.astNodeLocator=e.workspace.AstNodeLocator}async link(e,r=pr.CancellationToken.None){for(let n of Yo(e.parseResult.value))await Bi(r),$m(n).forEach(i=>this.doLink(i,e))}doLink(e,r){let n=e.reference;if(n._ref===void 0)try{let i=this.getCandidate(e);if(Wd(i))n._ref=i;else if(n._nodeDescription=i,this.langiumDocuments().hasDocument(i.documentUri)){let a=this.loadAstNode(i);n._ref=a??this.createLinkingError(e,i)}}catch(i){n._ref=Object.assign(Object.assign({},e),{message:`An error occurred while resolving reference to '${n.$refText}': ${i}`})}r.references.push(n)}unlink(e){for(let r of e.references)delete r._ref,delete r._nodeDescription;e.references=[]}getCandidate(e){let n=this.scopeProvider.getScope(e).getElement(e.reference.$refText);return n??this.createLinkingError(e)}buildReference(e,r,n,i){let a=this,s={$refNode:n,$refText:i,get ref(){var l;if(Xn(this._ref))return this._ref;if(ED(this._nodeDescription)){let u=a.loadAstNode(this._nodeDescription);this._ref=u??a.createLinkingError({reference:s,container:e,property:r},this._nodeDescription)}else if(this._ref===void 0){let u=a.getLinkedNode({reference:s,container:e,property:r});if(u.error&&Oi(e).state{"use strict";Il();o(boe,"isNamed");I2=class{static{o(this,"DefaultNameProvider")}getName(e){if(boe(e))return e.name}getNameNode(e){return Zv(e.$cstNode,"name")}}});var O2,AM=R(()=>{"use strict";Il();Vo();es();Rl();Ds();Nc();O2=class{static{o(this,"DefaultReferences")}constructor(e){this.nameProvider=e.references.NameProvider,this.index=e.shared.workspace.IndexManager,this.nodeLocator=e.workspace.AstNodeLocator}findDeclaration(e){if(e){let r=jR(e),n=e.astNode;if(r&&n){let i=n[r.feature];if(xa(i))return i.ref;if(Array.isArray(i)){for(let a of i)if(xa(a)&&a.$refNode&&a.$refNode.offset<=e.offset&&a.$refNode.end>=e.end)return a.ref}}if(n){let i=this.nameProvider.getNameNode(n);if(i&&(i===e||SD(e,i)))return n}}}findDeclarationNode(e){let r=this.findDeclaration(e);if(r?.$cstNode){let n=this.nameProvider.getNameNode(r);return n??r.$cstNode}}findReferences(e,r){let n=[];if(r.includeDeclaration){let a=this.getReferenceToSelf(e);a&&n.push(a)}let i=this.index.findAllReferences(e,this.nodeLocator.getAstNodePath(e));return r.documentUri&&(i=i.filter(a=>ss.equals(a.sourceUri,r.documentUri))),n.push(...i),Kr(n)}getReferenceToSelf(e){let r=this.nameProvider.getNameNode(e);if(r){let n=Oi(e),i=this.nodeLocator.getAstNodePath(e);return{sourceUri:n.uri,sourcePath:i,targetUri:n.uri,targetPath:i,segment:Xd(r),local:!0}}}}});var Mc,v0,kg=R(()=>{"use strict";Ds();Mc=class{static{o(this,"MultiMap")}constructor(e){if(this.map=new Map,e)for(let[r,n]of e)this.add(r,n)}get size(){return Fm.sum(Kr(this.map.values()).map(e=>e.length))}clear(){this.map.clear()}delete(e,r){if(r===void 0)return this.map.delete(e);{let n=this.map.get(e);if(n){let i=n.indexOf(r);if(i>=0)return n.length===1?this.map.delete(e):n.splice(i,1),!0}return!1}}get(e){var r;return(r=this.map.get(e))!==null&&r!==void 0?r:[]}has(e,r){if(r===void 0)return this.map.has(e);{let n=this.map.get(e);return n?n.indexOf(r)>=0:!1}}add(e,r){return this.map.has(e)?this.map.get(e).push(r):this.map.set(e,[r]),this}addAll(e,r){return this.map.has(e)?this.map.get(e).push(...r):this.map.set(e,Array.from(r)),this}forEach(e){this.map.forEach((r,n)=>r.forEach(i=>e(i,n,this)))}[Symbol.iterator](){return this.entries().iterator()}entries(){return Kr(this.map.entries()).flatMap(([e,r])=>r.map(n=>[e,n]))}keys(){return Kr(this.map.keys())}values(){return Kr(this.map.values()).flat()}entriesGroupedByKey(){return Kr(this.map.entries())}},v0=class{static{o(this,"BiMap")}get size(){return this.map.size}constructor(e){if(this.map=new Map,this.inverse=new Map,e)for(let[r,n]of e)this.set(r,n)}clear(){this.map.clear(),this.inverse.clear()}set(e,r){return this.map.set(e,r),this.inverse.set(r,e),this}get(e){return this.map.get(e)}getKey(e){return this.inverse.get(e)}delete(e){let r=this.map.get(e);return r!==void 0?(this.map.delete(e),this.inverse.delete(r),!0):!1}}});var P2,_M=R(()=>{"use strict";Wo();es();kg();qo();P2=class{static{o(this,"DefaultScopeComputation")}constructor(e){this.nameProvider=e.references.NameProvider,this.descriptions=e.workspace.AstNodeDescriptionProvider}async computeExports(e,r=pr.CancellationToken.None){return this.computeExportsForNode(e.parseResult.value,e,void 0,r)}async computeExportsForNode(e,r,n=Wv,i=pr.CancellationToken.None){let a=[];this.exportNode(e,a,r);for(let s of n(e))await Bi(i),this.exportNode(s,a,r);return a}exportNode(e,r,n){let i=this.nameProvider.getName(e);i&&r.push(this.descriptions.createDescription(e,i,n))}async computeLocalScopes(e,r=pr.CancellationToken.None){let n=e.parseResult.value,i=new Mc;for(let a of Ac(n))await Bi(r),this.processNode(a,e,i);return i}processNode(e,r,n){let i=e.$container;if(i){let a=this.nameProvider.getName(e);a&&n.add(i,this.descriptions.createDescription(e,a,r))}}}});var Eg,B2,jOe,LM=R(()=>{"use strict";Ds();Eg=class{static{o(this,"StreamScope")}constructor(e,r,n){var i;this.elements=e,this.outerScope=r,this.caseInsensitive=(i=n?.caseInsensitive)!==null&&i!==void 0?i:!1}getAllElements(){return this.outerScope?this.elements.concat(this.outerScope.getAllElements()):this.elements}getElement(e){let r=this.caseInsensitive?this.elements.find(n=>n.name.toLowerCase()===e.toLowerCase()):this.elements.find(n=>n.name===e);if(r)return r;if(this.outerScope)return this.outerScope.getElement(e)}},B2=class{static{o(this,"MapScope")}constructor(e,r,n){var i;this.elements=new Map,this.caseInsensitive=(i=n?.caseInsensitive)!==null&&i!==void 0?i:!1;for(let a of e){let s=this.caseInsensitive?a.name.toLowerCase():a.name;this.elements.set(s,a)}this.outerScope=r}getElement(e){let r=this.caseInsensitive?e.toLowerCase():e,n=this.elements.get(r);if(n)return n;if(this.outerScope)return this.outerScope.getElement(e)}getAllElements(){let e=Kr(this.elements.values());return this.outerScope&&(e=e.concat(this.outerScope.getAllElements())),e}},jOe={getElement(){},getAllElements(){return Gv}}});var Cg,F2,x0,Mk,Sg,Ik=R(()=>{"use strict";Cg=class{static{o(this,"DisposableCache")}constructor(){this.toDispose=[],this.isDisposed=!1}onDispose(e){this.toDispose.push(e)}dispose(){this.throwIfDisposed(),this.clear(),this.isDisposed=!0,this.toDispose.forEach(e=>e.dispose())}throwIfDisposed(){if(this.isDisposed)throw new Error("This cache has already been disposed")}},F2=class extends Cg{static{o(this,"SimpleCache")}constructor(){super(...arguments),this.cache=new Map}has(e){return this.throwIfDisposed(),this.cache.has(e)}set(e,r){this.throwIfDisposed(),this.cache.set(e,r)}get(e,r){if(this.throwIfDisposed(),this.cache.has(e))return this.cache.get(e);if(r){let n=r();return this.cache.set(e,n),n}else return}delete(e){return this.throwIfDisposed(),this.cache.delete(e)}clear(){this.throwIfDisposed(),this.cache.clear()}},x0=class extends Cg{static{o(this,"ContextCache")}constructor(e){super(),this.cache=new Map,this.converter=e??(r=>r)}has(e,r){return this.throwIfDisposed(),this.cacheForContext(e).has(r)}set(e,r,n){this.throwIfDisposed(),this.cacheForContext(e).set(r,n)}get(e,r,n){this.throwIfDisposed();let i=this.cacheForContext(e);if(i.has(r))return i.get(r);if(n){let a=n();return i.set(r,a),a}else return}delete(e,r){return this.throwIfDisposed(),this.cacheForContext(e).delete(r)}clear(e){if(this.throwIfDisposed(),e){let r=this.converter(e);this.cache.delete(r)}else this.cache.clear()}cacheForContext(e){let r=this.converter(e),n=this.cache.get(r);return n||(n=new Map,this.cache.set(r,n)),n}},Mk=class extends x0{static{o(this,"DocumentCache")}constructor(e){super(r=>r.toString()),this.onDispose(e.workspace.DocumentBuilder.onUpdate((r,n)=>{let i=r.concat(n);for(let a of i)this.clear(a)}))}},Sg=class extends F2{static{o(this,"WorkspaceCache")}constructor(e){super(),this.onDispose(e.workspace.DocumentBuilder.onUpdate(()=>{this.clear()}))}}});var z2,DM=R(()=>{"use strict";LM();es();Ds();Ik();z2=class{static{o(this,"DefaultScopeProvider")}constructor(e){this.reflection=e.shared.AstReflection,this.nameProvider=e.references.NameProvider,this.descriptions=e.workspace.AstNodeDescriptionProvider,this.indexManager=e.shared.workspace.IndexManager,this.globalScopeCache=new Sg(e.shared)}getScope(e){let r=[],n=this.reflection.getReferenceType(e),i=Oi(e.container).precomputedScopes;if(i){let s=e.container;do{let l=i.get(s);l.length>0&&r.push(Kr(l).filter(u=>this.reflection.isSubtype(u.type,n))),s=s.$container}while(s)}let a=this.getGlobalScope(n,e);for(let s=r.length-1;s>=0;s--)a=this.createScope(r[s],a);return a}createScope(e,r,n){return new Eg(Kr(e),r,n)}createScopeForNodes(e,r,n){let i=Kr(e).map(a=>{let s=this.nameProvider.getName(a);if(s)return this.descriptions.createDescription(a,s)}).nonNullable();return new Eg(i,r,n)}getGlobalScope(e,r){return this.globalScopeCache.get(e,()=>new B2(this.indexManager.allElements(e)))}}});function RM(t){return typeof t.$comment=="string"}function woe(t){return typeof t=="object"&&!!t&&("$ref"in t||"$error"in t)}var G2,Ok=R(()=>{"use strict";EM();Vo();es();Il();o(RM,"isAstNodeWithComment");o(woe,"isIntermediateReference");G2=class{static{o(this,"DefaultJsonSerializer")}constructor(e){this.ignoreProperties=new Set(["$container","$containerProperty","$containerIndex","$document","$cstNode"]),this.langiumDocuments=e.shared.workspace.LangiumDocuments,this.astNodeLocator=e.workspace.AstNodeLocator,this.nameProvider=e.references.NameProvider,this.commentProvider=e.documentation.CommentProvider}serialize(e,r={}){let n=r?.replacer,i=o((s,l)=>this.replacer(s,l,r),"defaultReplacer"),a=n?(s,l)=>n(s,l,i):i;try{return this.currentDocument=Oi(e),JSON.stringify(e,a,r?.space)}finally{this.currentDocument=void 0}}deserialize(e,r={}){let n=JSON.parse(e);return this.linkNode(n,n,r),n}replacer(e,r,{refText:n,sourceText:i,textRegions:a,comments:s,uriConverter:l}){var u,h,f,d;if(!this.ignoreProperties.has(e))if(xa(r)){let p=r.ref,m=n?r.$refText:void 0;if(p){let g=Oi(p),y="";this.currentDocument&&this.currentDocument!==g&&(l?y=l(g.uri,r):y=g.uri.toString());let v=this.astNodeLocator.getAstNodePath(p);return{$ref:`${y}#${v}`,$refText:m}}else return{$error:(h=(u=r.error)===null||u===void 0?void 0:u.message)!==null&&h!==void 0?h:"Could not resolve reference",$refText:m}}else if(Xn(r)){let p;if(a&&(p=this.addAstNodeRegionWithAssignmentsTo(Object.assign({},r)),(!e||r.$document)&&p?.$textRegion&&(p.$textRegion.documentURI=(f=this.currentDocument)===null||f===void 0?void 0:f.uri.toString())),i&&!e&&(p??(p=Object.assign({},r)),p.$sourceText=(d=r.$cstNode)===null||d===void 0?void 0:d.text),s){p??(p=Object.assign({},r));let m=this.commentProvider.getComment(r);m&&(p.$comment=m.replace(/\r/g,""))}return p??r}else return r}addAstNodeRegionWithAssignmentsTo(e){let r=o(n=>({offset:n.offset,end:n.end,length:n.length,range:n.range}),"createDocumentSegment");if(e.$cstNode){let n=e.$textRegion=r(e.$cstNode),i=n.assignments={};return Object.keys(e).filter(a=>!a.startsWith("$")).forEach(a=>{let s=YR(e.$cstNode,a).map(r);s.length!==0&&(i[a]=s)}),e}}linkNode(e,r,n,i,a,s){for(let[u,h]of Object.entries(e))if(Array.isArray(h))for(let f=0;f{"use strict";Nc();$2=class{static{o(this,"DefaultServiceRegistry")}register(e){if(!this.singleton&&!this.map){this.singleton=e;return}if(!this.map&&(this.map={},this.singleton)){for(let r of this.singleton.LanguageMetaData.fileExtensions)this.map[r]=this.singleton;this.singleton=void 0}for(let r of e.LanguageMetaData.fileExtensions)this.map[r]!==void 0&&this.map[r]!==e&&console.warn(`The file extension ${r} is used by multiple languages. It is now assigned to '${e.LanguageMetaData.languageId}'.`),this.map[r]=e}getServices(e){if(this.singleton!==void 0)return this.singleton;if(this.map===void 0)throw new Error("The service registry is empty. Use `register` to register the services of a language.");let r=ss.extname(e),n=this.map[r];if(!n)throw new Error(`The service registry contains no services for the extension '${r}'.`);return n}get all(){return this.singleton!==void 0?[this.singleton]:this.map!==void 0?Object.values(this.map):[]}}});function Pk(t){return{code:t}}var Ag,V2,U2=R(()=>{"use strict";kg();qo();Ds();o(Pk,"diagnosticData");(function(t){t.all=["fast","slow","built-in"]})(Ag||(Ag={}));V2=class{static{o(this,"ValidationRegistry")}constructor(e){this.entries=new Mc,this.reflection=e.shared.AstReflection}register(e,r=this,n="fast"){if(n==="built-in")throw new Error("The 'built-in' category is reserved for lexer, parser, and linker errors.");for(let[i,a]of Object.entries(e)){let s=a;if(Array.isArray(s))for(let l of s){let u={check:this.wrapValidationException(l,r),category:n};this.addEntry(i,u)}else if(typeof s=="function"){let l={check:this.wrapValidationException(s,r),category:n};this.addEntry(i,l)}}}wrapValidationException(e,r){return async(n,i,a)=>{try{await e.call(r,n,i,a)}catch(s){if(of(s))throw s;console.error("An error occurred during validation:",s);let l=s instanceof Error?s.message:String(s);s instanceof Error&&s.stack&&console.error(s.stack),i("error","An error occurred during validation: "+l,{node:n})}}}addEntry(e,r){if(e==="AstNode"){this.entries.add("AstNode",r);return}for(let n of this.reflection.getAllSubTypes(e))this.entries.add(n,r)}getChecks(e,r){let n=Kr(this.entries.get(e)).concat(this.entries.get("AstNode"));return r&&(n=n.filter(i=>r.includes(i.category))),n.map(i=>i.check)}}});function Toe(t){if(t.range)return t.range;let e;return typeof t.property=="string"?e=Zv(t.node.$cstNode,t.property,t.index):typeof t.keyword=="string"&&(e=qR(t.node.$cstNode,t.keyword,t.index)),e??(e=t.node.$cstNode),e?e.range:{start:{line:0,character:0},end:{line:0,character:0}}}function Bk(t){switch(t){case"error":return 1;case"warning":return 2;case"info":return 3;case"hint":return 4;default:throw new Error("Invalid diagnostic severity: "+t)}}var H2,Uu,MM=R(()=>{"use strict";Wo();Il();es();Rl();qo();U2();H2=class{static{o(this,"DefaultDocumentValidator")}constructor(e){this.validationRegistry=e.validation.ValidationRegistry,this.metadata=e.LanguageMetaData}async validateDocument(e,r={},n=pr.CancellationToken.None){let i=e.parseResult,a=[];if(await Bi(n),(!r.categories||r.categories.includes("built-in"))&&(this.processLexingErrors(i,a,r),r.stopAfterLexingErrors&&a.some(s=>{var l;return((l=s.data)===null||l===void 0?void 0:l.code)===Uu.LexingError})||(this.processParsingErrors(i,a,r),r.stopAfterParsingErrors&&a.some(s=>{var l;return((l=s.data)===null||l===void 0?void 0:l.code)===Uu.ParsingError}))||(this.processLinkingErrors(e,a,r),r.stopAfterLinkingErrors&&a.some(s=>{var l;return((l=s.data)===null||l===void 0?void 0:l.code)===Uu.LinkingError}))))return a;try{a.push(...await this.validateAst(i.value,r,n))}catch(s){if(of(s))throw s;console.error("An error occurred during validation:",s)}return await Bi(n),a}processLexingErrors(e,r,n){for(let i of e.lexerErrors){let a={severity:Bk("error"),range:{start:{line:i.line-1,character:i.column-1},end:{line:i.line-1,character:i.column+i.length-1}},message:i.message,data:Pk(Uu.LexingError),source:this.getSource()};r.push(a)}}processParsingErrors(e,r,n){for(let i of e.parserErrors){let a;if(isNaN(i.token.startOffset)){if("previousToken"in i){let s=i.previousToken;if(isNaN(s.startOffset)){let l={line:0,character:0};a={start:l,end:l}}else{let l={line:s.endLine-1,character:s.endColumn};a={start:l,end:l}}}}else a=zm(i.token);if(a){let s={severity:Bk("error"),range:a,message:i.message,data:Pk(Uu.ParsingError),source:this.getSource()};r.push(s)}}}processLinkingErrors(e,r,n){for(let i of e.references){let a=i.error;if(a){let s={node:a.container,property:a.property,index:a.index,data:{code:Uu.LinkingError,containerType:a.container.$type,property:a.property,refText:a.reference.$refText}};r.push(this.toDiagnostic("error",a.message,s))}}}async validateAst(e,r,n=pr.CancellationToken.None){let i=[],a=o((s,l,u)=>{i.push(this.toDiagnostic(s,l,u))},"acceptor");return await Promise.all(Yo(e).map(async s=>{await Bi(n);let l=this.validationRegistry.getChecks(s.$type,r.categories);for(let u of l)await u(s,a,n)})),i}toDiagnostic(e,r,n){return{message:r,range:Toe(n),severity:Bk(e),code:n.code,codeDescription:n.codeDescription,tags:n.tags,relatedInformation:n.relatedInformation,data:n.data,source:this.getSource()}}getSource(){return this.metadata.languageId}};o(Toe,"getDiagnosticRange");o(Bk,"toDiagnosticSeverity");(function(t){t.LexingError="lexing-error",t.ParsingError="parsing-error",t.LinkingError="linking-error"})(Uu||(Uu={}))});var Y2,W2,IM=R(()=>{"use strict";Wo();Vo();es();Rl();qo();Nc();Y2=class{static{o(this,"DefaultAstNodeDescriptionProvider")}constructor(e){this.astNodeLocator=e.workspace.AstNodeLocator,this.nameProvider=e.references.NameProvider}createDescription(e,r,n=Oi(e)){r??(r=this.nameProvider.getName(e));let i=this.astNodeLocator.getAstNodePath(e);if(!r)throw new Error(`Node at path ${i} has no name.`);let a,s=o(()=>{var l;return a??(a=Xd((l=this.nameProvider.getNameNode(e))!==null&&l!==void 0?l:e.$cstNode))},"nameSegmentGetter");return{node:e,name:r,get nameSegment(){return s()},selectionSegment:Xd(e.$cstNode),type:e.$type,documentUri:n.uri,path:i}}},W2=class{static{o(this,"DefaultReferenceDescriptionProvider")}constructor(e){this.nodeLocator=e.workspace.AstNodeLocator}async createDescriptions(e,r=pr.CancellationToken.None){let n=[],i=e.parseResult.value;for(let a of Yo(i))await Bi(r),$m(a).filter(s=>!Wd(s)).forEach(s=>{let l=this.createDescription(s);l&&n.push(l)});return n}createDescription(e){let r=e.reference.$nodeDescription,n=e.reference.$refNode;if(!r||!n)return;let i=Oi(e.container).uri;return{sourceUri:i,sourcePath:this.nodeLocator.getAstNodePath(e.container),targetUri:r.documentUri,targetPath:r.path,segment:Xd(n),local:ss.equals(r.documentUri,i)}}}});var q2,OM=R(()=>{"use strict";q2=class{static{o(this,"DefaultAstNodeLocator")}constructor(){this.segmentSeparator="/",this.indexSeparator="@"}getAstNodePath(e){if(e.$container){let r=this.getAstNodePath(e.$container),n=this.getPathSegment(e);return r+this.segmentSeparator+n}return""}getPathSegment({$containerProperty:e,$containerIndex:r}){if(!e)throw new Error("Missing '$containerProperty' in AST node.");return r!==void 0?e+this.indexSeparator+r:e}getAstNode(e,r){return r.split(this.segmentSeparator).reduce((i,a)=>{if(!i||a.length===0)return i;let s=a.indexOf(this.indexSeparator);if(s>0){let l=a.substring(0,s),u=parseInt(a.substring(s+1)),h=i[l];return h?.[u]}return i[a]},e)}}});var X2,PM=R(()=>{"use strict";qo();X2=class{static{o(this,"DefaultConfigurationProvider")}constructor(e){this._ready=new as,this.settings={},this.workspaceConfig=!1,this.serviceRegistry=e.ServiceRegistry}get ready(){return this._ready.promise}initialize(e){var r,n;this.workspaceConfig=(n=(r=e.capabilities.workspace)===null||r===void 0?void 0:r.configuration)!==null&&n!==void 0?n:!1}async initialized(e){if(this.workspaceConfig){if(e.register){let r=this.serviceRegistry.all;e.register({section:r.map(n=>this.toSectionName(n.LanguageMetaData.languageId))})}if(e.fetchConfiguration){let r=this.serviceRegistry.all.map(i=>({section:this.toSectionName(i.LanguageMetaData.languageId)})),n=await e.fetchConfiguration(r);r.forEach((i,a)=>{this.updateSectionConfiguration(i.section,n[a])})}}this._ready.resolve()}updateConfiguration(e){e.settings&&Object.keys(e.settings).forEach(r=>{this.updateSectionConfiguration(r,e.settings[r])})}updateSectionConfiguration(e,r){this.settings[e]=r}async getConfiguration(e,r){await this.ready;let n=this.toSectionName(e);if(this.settings[n])return this.settings[n][r]}toSectionName(e){return`${e}`}}});var b0,BM=R(()=>{"use strict";(function(t){function e(r){return{dispose:o(async()=>await r(),"dispose")}}o(e,"create"),t.create=e})(b0||(b0={}))});var j2,FM=R(()=>{"use strict";Wo();BM();kg();qo();Ds();U2();Tg();j2=class{static{o(this,"DefaultDocumentBuilder")}constructor(e){this.updateBuildOptions={validation:{categories:["built-in","fast"]}},this.updateListeners=[],this.buildPhaseListeners=new Mc,this.buildState=new Map,this.documentBuildWaiters=new Map,this.currentState=yn.Changed,this.langiumDocuments=e.workspace.LangiumDocuments,this.langiumDocumentFactory=e.workspace.LangiumDocumentFactory,this.indexManager=e.workspace.IndexManager,this.serviceRegistry=e.ServiceRegistry}async build(e,r={},n=pr.CancellationToken.None){var i,a;for(let s of e){let l=s.uri.toString();if(s.state===yn.Validated){if(typeof r.validation=="boolean"&&r.validation)s.state=yn.IndexedReferences,s.diagnostics=void 0,this.buildState.delete(l);else if(typeof r.validation=="object"){let u=this.buildState.get(l),h=(i=u?.result)===null||i===void 0?void 0:i.validationChecks;if(h){let d=((a=r.validation.categories)!==null&&a!==void 0?a:Ag.all).filter(p=>!h.includes(p));d.length>0&&(this.buildState.set(l,{completed:!1,options:{validation:Object.assign(Object.assign({},r.validation),{categories:d})},result:u.result}),s.state=yn.IndexedReferences)}}}else this.buildState.delete(l)}this.currentState=yn.Changed,await this.emitUpdate(e.map(s=>s.uri),[]),await this.buildDocuments(e,r,n)}async update(e,r,n=pr.CancellationToken.None){this.currentState=yn.Changed;for(let s of r)this.langiumDocuments.deleteDocument(s),this.buildState.delete(s.toString()),this.indexManager.remove(s);for(let s of e){if(!this.langiumDocuments.invalidateDocument(s)){let u=this.langiumDocumentFactory.fromModel({$type:"INVALID"},s);u.state=yn.Changed,this.langiumDocuments.addDocument(u)}this.buildState.delete(s.toString())}let i=Kr(e).concat(r).map(s=>s.toString()).toSet();this.langiumDocuments.all.filter(s=>!i.has(s.uri.toString())&&this.shouldRelink(s,i)).forEach(s=>{this.serviceRegistry.getServices(s.uri).references.Linker.unlink(s),s.state=Math.min(s.state,yn.ComputedScopes),s.diagnostics=void 0}),await this.emitUpdate(e,r),await Bi(n);let a=this.langiumDocuments.all.filter(s=>{var l;return s.staten(e,r)))}shouldRelink(e,r){return e.references.some(n=>n.error!==void 0)?!0:this.indexManager.isAffected(e,r)}onUpdate(e){return this.updateListeners.push(e),b0.create(()=>{let r=this.updateListeners.indexOf(e);r>=0&&this.updateListeners.splice(r,1)})}async buildDocuments(e,r,n){this.prepareBuild(e,r),await this.runCancelable(e,yn.Parsed,n,a=>this.langiumDocumentFactory.update(a,n)),await this.runCancelable(e,yn.IndexedContent,n,a=>this.indexManager.updateContent(a,n)),await this.runCancelable(e,yn.ComputedScopes,n,async a=>{let s=this.serviceRegistry.getServices(a.uri).references.ScopeComputation;a.precomputedScopes=await s.computeLocalScopes(a,n)}),await this.runCancelable(e,yn.Linked,n,a=>this.serviceRegistry.getServices(a.uri).references.Linker.link(a,n)),await this.runCancelable(e,yn.IndexedReferences,n,a=>this.indexManager.updateReferences(a,n));let i=e.filter(a=>this.shouldValidate(a));await this.runCancelable(i,yn.Validated,n,a=>this.validate(a,n));for(let a of e){let s=this.buildState.get(a.uri.toString());s&&(s.completed=!0)}}prepareBuild(e,r){for(let n of e){let i=n.uri.toString(),a=this.buildState.get(i);(!a||a.completed)&&this.buildState.set(i,{completed:!1,options:r,result:a?.result})}}async runCancelable(e,r,n,i){let a=e.filter(s=>s.state{this.buildPhaseListeners.delete(e,r)})}waitUntil(e,r,n){let i;if(r&&"path"in r?i=r:n=r,n??(n=pr.CancellationToken.None),i){let a=this.langiumDocuments.getDocument(i);if(a&&a.state>e)return Promise.resolve(i)}return this.currentState>=e?Promise.resolve(void 0):n.isCancellationRequested?Promise.reject(Rc):new Promise((a,s)=>{let l=this.onBuildPhase(e,()=>{if(l.dispose(),u.dispose(),i){let h=this.langiumDocuments.getDocument(i);a(h?.uri)}else a(void 0)}),u=n.onCancellationRequested(()=>{l.dispose(),u.dispose(),s(Rc)})})}async notifyBuildPhase(e,r,n){if(e.length===0)return;let i=this.buildPhaseListeners.get(r);for(let a of i)await Bi(n),await a(e,n)}shouldValidate(e){return!!this.getBuildOptions(e).validation}async validate(e,r){var n,i;let a=this.serviceRegistry.getServices(e.uri).validation.DocumentValidator,s=this.getBuildOptions(e).validation,l=typeof s=="object"?s:void 0,u=await a.validateDocument(e,l,r);e.diagnostics?e.diagnostics.push(...u):e.diagnostics=u;let h=this.buildState.get(e.uri.toString());if(h){(n=h.result)!==null&&n!==void 0||(h.result={});let f=(i=l?.categories)!==null&&i!==void 0?i:Ag.all;h.result.validationChecks?h.result.validationChecks.push(...f):h.result.validationChecks=[...f]}}getBuildOptions(e){var r,n;return(n=(r=this.buildState.get(e.uri.toString()))===null||r===void 0?void 0:r.options)!==null&&n!==void 0?n:{}}}});var K2,zM=R(()=>{"use strict";es();Ik();Wo();Ds();Nc();K2=class{static{o(this,"DefaultIndexManager")}constructor(e){this.symbolIndex=new Map,this.symbolByTypeIndex=new x0,this.referenceIndex=new Map,this.documents=e.workspace.LangiumDocuments,this.serviceRegistry=e.ServiceRegistry,this.astReflection=e.AstReflection}findAllReferences(e,r){let n=Oi(e).uri,i=[];return this.referenceIndex.forEach(a=>{a.forEach(s=>{ss.equals(s.targetUri,n)&&s.targetPath===r&&i.push(s)})}),Kr(i)}allElements(e,r){let n=Kr(this.symbolIndex.keys());return r&&(n=n.filter(i=>!r||r.has(i))),n.map(i=>this.getFileDescriptions(i,e)).flat()}getFileDescriptions(e,r){var n;return r?this.symbolByTypeIndex.get(e,r,()=>{var a;return((a=this.symbolIndex.get(e))!==null&&a!==void 0?a:[]).filter(l=>this.astReflection.isSubtype(l.type,r))}):(n=this.symbolIndex.get(e))!==null&&n!==void 0?n:[]}remove(e){let r=e.toString();this.symbolIndex.delete(r),this.symbolByTypeIndex.clear(r),this.referenceIndex.delete(r)}async updateContent(e,r=pr.CancellationToken.None){let i=await this.serviceRegistry.getServices(e.uri).references.ScopeComputation.computeExports(e,r),a=e.uri.toString();this.symbolIndex.set(a,i),this.symbolByTypeIndex.clear(a)}async updateReferences(e,r=pr.CancellationToken.None){let i=await this.serviceRegistry.getServices(e.uri).workspace.ReferenceDescriptionProvider.createDescriptions(e,r);this.referenceIndex.set(e.uri.toString(),i)}isAffected(e,r){let n=this.referenceIndex.get(e.uri.toString());return n?n.some(i=>!i.local&&r.has(i.targetUri.toString())):!1}}});var Q2,GM=R(()=>{"use strict";Wo();qo();Nc();Q2=class{static{o(this,"DefaultWorkspaceManager")}constructor(e){this.initialBuildOptions={},this._ready=new as,this.serviceRegistry=e.ServiceRegistry,this.langiumDocuments=e.workspace.LangiumDocuments,this.documentBuilder=e.workspace.DocumentBuilder,this.fileSystemProvider=e.workspace.FileSystemProvider,this.mutex=e.workspace.WorkspaceLock}get ready(){return this._ready.promise}initialize(e){var r;this.folders=(r=e.workspaceFolders)!==null&&r!==void 0?r:void 0}initialized(e){return this.mutex.write(r=>{var n;return this.initializeWorkspace((n=this.folders)!==null&&n!==void 0?n:[],r)})}async initializeWorkspace(e,r=pr.CancellationToken.None){let n=await this.performStartup(e);await Bi(r),await this.documentBuilder.build(n,this.initialBuildOptions,r)}async performStartup(e){let r=this.serviceRegistry.all.flatMap(a=>a.LanguageMetaData.fileExtensions),n=[],i=o(a=>{n.push(a),this.langiumDocuments.hasDocument(a.uri)||this.langiumDocuments.addDocument(a)},"collector");return await this.loadAdditionalDocuments(e,i),await Promise.all(e.map(a=>[a,this.getRootFolder(a)]).map(async a=>this.traverseFolder(...a,r,i))),this._ready.resolve(),n}loadAdditionalDocuments(e,r){return Promise.resolve()}getRootFolder(e){return Ms.parse(e.uri)}async traverseFolder(e,r,n,i){let a=await this.fileSystemProvider.readDirectory(r);await Promise.all(a.map(async s=>{if(this.includeEntry(e,s,n)){if(s.isDirectory)await this.traverseFolder(e,s.uri,n,i);else if(s.isFile){let l=await this.langiumDocuments.getOrCreateDocument(s.uri);i(l)}}}))}includeEntry(e,r,n){let i=ss.basename(r.uri);if(i.startsWith("."))return!1;if(r.isDirectory)return i!=="node_modules"&&i!=="out";if(r.isFile){let a=ss.extname(r.uri);return n.includes(a)}return!1}}});function koe(t){return Array.isArray(t)&&(t.length===0||"name"in t[0])}function VM(t){return t&&"modes"in t&&"defaultMode"in t}function $M(t){return!koe(t)&&!VM(t)}var Z2,UM=R(()=>{"use strict";u0();Z2=class{static{o(this,"DefaultLexer")}constructor(e){let r=e.parser.TokenBuilder.buildTokens(e.Grammar,{caseInsensitive:e.LanguageMetaData.caseInsensitive});this.tokenTypes=this.toTokenTypeDictionary(r);let n=$M(r)?Object.values(r):r;this.chevrotainLexer=new ni(n,{positionTracking:"full"})}get definition(){return this.tokenTypes}tokenize(e){var r;let n=this.chevrotainLexer.tokenize(e);return{tokens:n.tokens,errors:n.errors,hidden:(r=n.groups.hidden)!==null&&r!==void 0?r:[]}}toTokenTypeDictionary(e){if($M(e))return e;let r=VM(e)?Object.values(e.modes).flat():e,n={};return r.forEach(i=>n[i.name]=i),n}};o(koe,"isTokenTypeArray");o(VM,"isIMultiModeLexerDefinition");o($M,"isTokenTypeDictionary")});function WM(t,e,r){let n,i;typeof t=="string"?(i=e,n=r):(i=t.range.start,n=e),i||(i=Ur.create(0,0));let a=Soe(t),s=XM(n),l=QOe({lines:a,position:i,options:s});return rPe({index:0,tokens:l,position:i})}function qM(t,e){let r=XM(e),n=Soe(t);if(n.length===0)return!1;let i=n[0],a=n[n.length-1],s=r.start,l=r.end;return!!s?.exec(i)&&!!l?.exec(a)}function Soe(t){let e="";return typeof t=="string"?e=t:e=t.text,e.split(BR)}function QOe(t){var e,r,n;let i=[],a=t.position.line,s=t.position.character;for(let l=0;l=f.length){if(i.length>0){let m=Ur.create(a,s);i.push({type:"break",content:"",range:wr.create(m,m)})}}else{Eoe.lastIndex=d;let m=Eoe.exec(f);if(m){let g=m[0],y=m[1],v=Ur.create(a,s+d),x=Ur.create(a,s+d+g.length);i.push({type:"tag",content:y,range:wr.create(v,x)}),d+=g.length,d=YM(f,d)}if(d0&&i[i.length-1].type==="break"?i.slice(0,-1):i}function ZOe(t,e,r,n){let i=[];if(t.length===0){let a=Ur.create(r,n),s=Ur.create(r,n+e.length);i.push({type:"text",content:e,range:wr.create(a,s)})}else{let a=0;for(let l of t){let u=l.index,h=e.substring(a,u);h.length>0&&i.push({type:"text",content:e.substring(a,u),range:wr.create(Ur.create(r,a+n),Ur.create(r,u+n))});let f=h.length+1,d=l[1];if(i.push({type:"inline-tag",content:d,range:wr.create(Ur.create(r,a+f+n),Ur.create(r,a+f+d.length+n))}),f+=d.length,l.length===4){f+=l[2].length;let p=l[3];i.push({type:"text",content:p,range:wr.create(Ur.create(r,a+f+n),Ur.create(r,a+f+p.length+n))})}else i.push({type:"text",content:"",range:wr.create(Ur.create(r,a+f+n),Ur.create(r,a+f+n))});a=u+l[0].length}let s=e.substring(a);s.length>0&&i.push({type:"text",content:s,range:wr.create(Ur.create(r,a+n),Ur.create(r,a+n+s.length))})}return i}function YM(t,e){let r=t.substring(e).match(JOe);return r?e+r.index:t.length}function tPe(t){let e=t.match(ePe);if(e&&typeof e.index=="number")return e.index}function rPe(t){var e,r,n,i;let a=Ur.create(t.position.line,t.position.character);if(t.tokens.length===0)return new Fk([],wr.create(a,a));let s=[];for(;t.index0){let u=YM(e,a);s=e.substring(u),e=e.substring(0,a)}return(t==="linkcode"||t==="link"&&r.link==="code")&&(s=`\`${s}\``),(i=(n=r.renderLink)===null||n===void 0?void 0:n.call(r,e,s))!==null&&i!==void 0?i:oPe(e,s)}}function oPe(t,e){try{return Ms.parse(t,!0),`[${e}](${t})`}catch{return t}}function Coe(t){return t.endsWith(` +`)?` +`:` + +`}var Eoe,KOe,JOe,ePe,Fk,J2,ex,zk,jM=R(()=>{"use strict";tM();Um();Nc();o(WM,"parseJSDoc");o(qM,"isJSDoc");o(Soe,"getLines");Eoe=/\s*(@([\p{L}][\p{L}\p{N}]*)?)/uy,KOe=/\{(@[\p{L}][\p{L}\p{N}]*)(\s*)([^\r\n}]+)?\}/gu;o(QOe,"tokenize");o(ZOe,"buildInlineTokens");JOe=/\S/,ePe=/\s*$/;o(YM,"skipWhitespace");o(tPe,"lastCharacter");o(rPe,"parseJSDocComment");o(nPe,"parseJSDocElement");o(iPe,"appendEmptyLine");o(Aoe,"parseJSDocText");o(aPe,"parseJSDocInline");o(_oe,"parseJSDocTag");o(Loe,"parseJSDocLine");o(XM,"normalizeOptions");o(HM,"normalizeOption");Fk=class{static{o(this,"JSDocCommentImpl")}constructor(e,r){this.elements=e,this.range=r}getTag(e){return this.getAllTags().find(r=>r.name===e)}getTags(e){return this.getAllTags().filter(r=>r.name===e)}getAllTags(){return this.elements.filter(e=>"name"in e)}toString(){let e="";for(let r of this.elements)if(e.length===0)e=r.toString();else{let n=r.toString();e+=Coe(e)+n}return e.trim()}toMarkdown(e){let r="";for(let n of this.elements)if(r.length===0)r=n.toMarkdown(e);else{let i=n.toMarkdown(e);r+=Coe(r)+i}return r.trim()}},J2=class{static{o(this,"JSDocTagImpl")}constructor(e,r,n,i){this.name=e,this.content=r,this.inline=n,this.range=i}toString(){let e=`@${this.name}`,r=this.content.toString();return this.content.inlines.length===1?e=`${e} ${r}`:this.content.inlines.length>1&&(e=`${e} +${r}`),this.inline?`{${e}}`:e}toMarkdown(e){var r,n;return(n=(r=e?.renderTag)===null||r===void 0?void 0:r.call(e,this))!==null&&n!==void 0?n:this.toMarkdownDefault(e)}toMarkdownDefault(e){let r=this.content.toMarkdown(e);if(this.inline){let a=sPe(this.name,r,e??{});if(typeof a=="string")return a}let n="";e?.tag==="italic"||e?.tag===void 0?n="*":e?.tag==="bold"?n="**":e?.tag==="bold-italic"&&(n="***");let i=`${n}@${this.name}${n}`;return this.content.inlines.length===1?i=`${i} \u2014 ${r}`:this.content.inlines.length>1&&(i=`${i} +${r}`),this.inline?`{${i}}`:i}};o(sPe,"renderInlineTag");o(oPe,"renderLinkDefault");ex=class{static{o(this,"JSDocTextImpl")}constructor(e,r){this.inlines=e,this.range=r}toString(){let e="";for(let r=0;rn.range.start.line&&(e+=` +`)}return e}toMarkdown(e){let r="";for(let n=0;ni.range.start.line&&(r+=` +`)}return r}},zk=class{static{o(this,"JSDocLineImpl")}constructor(e,r){this.text=e,this.range=r}toString(){return this.text}toMarkdown(){return this.text}};o(Coe,"fillNewlines")});var tx,KM=R(()=>{"use strict";es();jM();tx=class{static{o(this,"JSDocDocumentationProvider")}constructor(e){this.indexManager=e.shared.workspace.IndexManager,this.commentProvider=e.documentation.CommentProvider}getDocumentation(e){let r=this.commentProvider.getComment(e);if(r&&qM(r))return WM(r).toMarkdown({renderLink:o((i,a)=>this.documentationLinkRenderer(e,i,a),"renderLink"),renderTag:o(i=>this.documentationTagRenderer(e,i),"renderTag")})}documentationLinkRenderer(e,r,n){var i;let a=(i=this.findNameInPrecomputedScopes(e,r))!==null&&i!==void 0?i:this.findNameInGlobalScope(e,r);if(a&&a.nameSegment){let s=a.nameSegment.range.start.line+1,l=a.nameSegment.range.start.character+1,u=a.documentUri.with({fragment:`L${s},${l}`});return`[${n}](${u.toString()})`}else return}documentationTagRenderer(e,r){}findNameInPrecomputedScopes(e,r){let i=Oi(e).precomputedScopes;if(!i)return;let a=e;do{let l=i.get(a).find(u=>u.name===r);if(l)return l;a=a.$container}while(a)}findNameInGlobalScope(e,r){return this.indexManager.allElements().find(i=>i.name===r)}}});var rx,QM=R(()=>{"use strict";Ok();Rl();rx=class{static{o(this,"DefaultCommentProvider")}constructor(e){this.grammarConfig=()=>e.parser.GrammarConfig}getComment(e){var r;return RM(e)?e.$comment:(r=_D(e.$cstNode,this.grammarConfig().multilineCommentRules))===null||r===void 0?void 0:r.text}}});var ii={};var ZM=R(()=>{"use strict";dr(ii,Xi(vM(),1))});var nx,JM,eI,tI=R(()=>{"use strict";qo();ZM();nx=class{static{o(this,"DefaultAsyncParser")}constructor(e){this.syncParser=e.parser.LangiumParser}parse(e){return Promise.resolve(this.syncParser.parse(e))}},JM=class{static{o(this,"AbstractThreadedAsyncParser")}constructor(e){this.threadCount=8,this.terminationDelay=200,this.workerPool=[],this.queue=[],this.hydrator=e.serializer.Hydrator}initializeWorkers(){for(;this.workerPool.length{if(this.queue.length>0){let r=this.queue.shift();r&&(e.lock(),r.resolve(e))}}),this.workerPool.push(e)}}async parse(e,r){let n=await this.acquireParserWorker(r),i=new as,a,s=r.onCancellationRequested(()=>{a=setTimeout(()=>{this.terminateWorker(n)},this.terminationDelay)});return n.parse(e).then(l=>{let u=this.hydrator.hydrate(l);i.resolve(u)}).catch(l=>{i.reject(l)}).finally(()=>{s.dispose(),clearTimeout(a)}),i.promise}terminateWorker(e){e.terminate();let r=this.workerPool.indexOf(e);r>=0&&this.workerPool.splice(r,1)}async acquireParserWorker(e){this.initializeWorkers();for(let n of this.workerPool)if(n.ready)return n.lock(),n;let r=new as;return e.onCancellationRequested(()=>{let n=this.queue.indexOf(r);n>=0&&this.queue.splice(n,1),r.reject(Rc)}),this.queue.push(r),r.promise}},eI=class{static{o(this,"ParserWorker")}get ready(){return this._ready}get onReady(){return this.onReadyEmitter.event}constructor(e,r,n,i){this.onReadyEmitter=new ii.Emitter,this.deferred=new as,this._ready=!0,this._parsing=!1,this.sendMessage=e,this._terminate=i,r(a=>{let s=a;this.deferred.resolve(s),this.unlock()}),n(a=>{this.deferred.reject(a),this.unlock()})}terminate(){this.deferred.reject(Rc),this._terminate()}lock(){this._ready=!1}unlock(){this._parsing=!1,this._ready=!0,this.onReadyEmitter.fire()}parse(e){if(this._parsing)throw new Error("Parser worker is busy");return this._parsing=!0,this.deferred=new as,this.sendMessage(e),this.deferred.promise}}});var ix,rI=R(()=>{"use strict";Wo();qo();ix=class{static{o(this,"DefaultWorkspaceLock")}constructor(){this.previousTokenSource=new pr.CancellationTokenSource,this.writeQueue=[],this.readQueue=[],this.done=!0}write(e){this.cancelWrite();let r=new pr.CancellationTokenSource;return this.previousTokenSource=r,this.enqueue(this.writeQueue,e,r.token)}read(e){return this.enqueue(this.readQueue,e)}enqueue(e,r,n){let i=new as,a={action:r,deferred:i,cancellationToken:n??pr.CancellationToken.None};return e.push(a),this.performNextOperation(),i.promise}async performNextOperation(){if(!this.done)return;let e=[];if(this.writeQueue.length>0)e.push(this.writeQueue.shift());else if(this.readQueue.length>0)e.push(...this.readQueue.splice(0,this.readQueue.length));else return;this.done=!1,await Promise.all(e.map(async({action:r,deferred:n,cancellationToken:i})=>{try{let a=await Promise.resolve().then(()=>r(i));n.resolve(a)}catch(a){of(a)?n.resolve(void 0):n.reject(a)}})),this.done=!0,this.performNextOperation()}cancelWrite(){this.previousTokenSource.cancel()}}});var ax,nI=R(()=>{"use strict";Ek();Sc();Vo();es();kg();Rl();ax=class{static{o(this,"DefaultHydrator")}constructor(e){this.grammarElementIdMap=new v0,this.tokenTypeIdMap=new v0,this.grammar=e.Grammar,this.lexer=e.parser.Lexer,this.linker=e.references.Linker}dehydrate(e){return{lexerErrors:e.lexerErrors.map(r=>Object.assign({},r)),parserErrors:e.parserErrors.map(r=>Object.assign({},r)),value:this.dehydrateAstNode(e.value,this.createDehyrationContext(e.value))}}createDehyrationContext(e){let r=new Map,n=new Map;for(let i of Yo(e))r.set(i,{});if(e.$cstNode)for(let i of qd(e.$cstNode))n.set(i,{});return{astNodes:r,cstNodes:n}}dehydrateAstNode(e,r){let n=r.astNodes.get(e);n.$type=e.$type,n.$containerIndex=e.$containerIndex,n.$containerProperty=e.$containerProperty,e.$cstNode!==void 0&&(n.$cstNode=this.dehydrateCstNode(e.$cstNode,r));for(let[i,a]of Object.entries(e))if(!i.startsWith("$"))if(Array.isArray(a)){let s=[];n[i]=s;for(let l of a)Xn(l)?s.push(this.dehydrateAstNode(l,r)):xa(l)?s.push(this.dehydrateReference(l,r)):s.push(l)}else Xn(a)?n[i]=this.dehydrateAstNode(a,r):xa(a)?n[i]=this.dehydrateReference(a,r):a!==void 0&&(n[i]=a);return n}dehydrateReference(e,r){let n={};return n.$refText=e.$refText,e.$refNode&&(n.$refNode=r.cstNodes.get(e.$refNode)),n}dehydrateCstNode(e,r){let n=r.cstNodes.get(e);return zv(e)?n.fullText=e.fullText:n.grammarSource=this.getGrammarElementId(e.grammarSource),n.hidden=e.hidden,n.astNode=r.astNodes.get(e.astNode),co(e)?n.content=e.content.map(i=>this.dehydrateCstNode(i,r)):ef(e)&&(n.tokenType=e.tokenType.name,n.offset=e.offset,n.length=e.length,n.startLine=e.range.start.line,n.startColumn=e.range.start.character,n.endLine=e.range.end.line,n.endColumn=e.range.end.character),n}hydrate(e){let r=e.value,n=this.createHydrationContext(r);return"$cstNode"in r&&this.hydrateCstNode(r.$cstNode,n),{lexerErrors:e.lexerErrors,parserErrors:e.parserErrors,value:this.hydrateAstNode(r,n)}}createHydrationContext(e){let r=new Map,n=new Map;for(let a of Yo(e))r.set(a,{});let i;if(e.$cstNode)for(let a of qd(e.$cstNode)){let s;"fullText"in a?(s=new gg(a.fullText),i=s):"content"in a?s=new p0:"tokenType"in a&&(s=this.hydrateCstLeafNode(a)),s&&(n.set(a,s),s.root=i)}return{astNodes:r,cstNodes:n}}hydrateAstNode(e,r){let n=r.astNodes.get(e);n.$type=e.$type,n.$containerIndex=e.$containerIndex,n.$containerProperty=e.$containerProperty,e.$cstNode&&(n.$cstNode=r.cstNodes.get(e.$cstNode));for(let[i,a]of Object.entries(e))if(!i.startsWith("$"))if(Array.isArray(a)){let s=[];n[i]=s;for(let l of a)Xn(l)?s.push(this.setParent(this.hydrateAstNode(l,r),n)):xa(l)?s.push(this.hydrateReference(l,n,i,r)):s.push(l)}else Xn(a)?n[i]=this.setParent(this.hydrateAstNode(a,r),n):xa(a)?n[i]=this.hydrateReference(a,n,i,r):a!==void 0&&(n[i]=a);return n}setParent(e,r){return e.$container=r,e}hydrateReference(e,r,n,i){return this.linker.buildReference(r,n,i.cstNodes.get(e.$refNode),e.$refText)}hydrateCstNode(e,r,n=0){let i=r.cstNodes.get(e);if(typeof e.grammarSource=="number"&&(i.grammarSource=this.getGrammarElement(e.grammarSource)),i.astNode=r.astNodes.get(e.astNode),co(i))for(let a of e.content){let s=this.hydrateCstNode(a,r,n++);i.content.push(s)}return i}hydrateCstLeafNode(e){let r=this.getTokenType(e.tokenType),n=e.offset,i=e.length,a=e.startLine,s=e.startColumn,l=e.endLine,u=e.endColumn,h=e.hidden;return new d0(n,i,{start:{line:a,character:s},end:{line:l,character:u}},r,h)}getTokenType(e){return this.lexer.definition[e]}getGrammarElementId(e){return this.grammarElementIdMap.size===0&&this.createGrammarElementIdMap(),this.grammarElementIdMap.get(e)}getGrammarElement(e){this.grammarElementIdMap.size===0&&this.createGrammarElementIdMap();let r=this.grammarElementIdMap.getKey(e);if(r)return r;throw new Error("Invalid grammar element id: "+e)}createGrammarElementIdMap(){let e=0;for(let r of Yo(this.grammar))Uv(r)&&this.grammarElementIdMap.set(r,e++)}}});function po(t){return{documentation:{CommentProvider:o(e=>new rx(e),"CommentProvider"),DocumentationProvider:o(e=>new tx(e),"DocumentationProvider")},parser:{AsyncParser:o(e=>new nx(e),"AsyncParser"),GrammarConfig:o(e=>JR(e),"GrammarConfig"),LangiumParser:o(e=>cM(e),"LangiumParser"),CompletionParser:o(e=>oM(e),"CompletionParser"),ValueConverter:o(()=>new y0,"ValueConverter"),TokenBuilder:o(()=>new g0,"TokenBuilder"),Lexer:o(e=>new Z2(e),"Lexer"),ParserErrorMessageProvider:o(()=>new yg,"ParserErrorMessageProvider")},workspace:{AstNodeLocator:o(()=>new q2,"AstNodeLocator"),AstNodeDescriptionProvider:o(e=>new Y2(e),"AstNodeDescriptionProvider"),ReferenceDescriptionProvider:o(e=>new W2(e),"ReferenceDescriptionProvider")},references:{Linker:o(e=>new M2(e),"Linker"),NameProvider:o(()=>new I2,"NameProvider"),ScopeProvider:o(e=>new z2(e),"ScopeProvider"),ScopeComputation:o(e=>new P2(e),"ScopeComputation"),References:o(e=>new O2(e),"References")},serializer:{Hydrator:o(e=>new ax(e),"Hydrator"),JsonSerializer:o(e=>new G2(e),"JsonSerializer")},validation:{DocumentValidator:o(e=>new H2(e),"DocumentValidator"),ValidationRegistry:o(e=>new V2(e),"ValidationRegistry")},shared:o(()=>t.shared,"shared")}}function mo(t){return{ServiceRegistry:o(()=>new $2,"ServiceRegistry"),workspace:{LangiumDocuments:o(e=>new N2(e),"LangiumDocuments"),LangiumDocumentFactory:o(e=>new R2(e),"LangiumDocumentFactory"),DocumentBuilder:o(e=>new j2(e),"DocumentBuilder"),IndexManager:o(e=>new K2(e),"IndexManager"),WorkspaceManager:o(e=>new Q2(e),"WorkspaceManager"),FileSystemProvider:o(e=>t.fileSystemProvider(e),"FileSystemProvider"),WorkspaceLock:o(()=>new ix,"WorkspaceLock"),ConfigurationProvider:o(e=>new X2(e),"ConfigurationProvider")}}}var iI=R(()=>{"use strict";eN();lM();uM();hM();fM();CM();SM();AM();_M();DM();Ok();NM();MM();U2();IM();OM();PM();FM();Tg();zM();GM();UM();KM();QM();D2();tI();rI();nI();o(po,"createDefaultCoreModule");o(mo,"createDefaultSharedCoreModule")});function Fi(t,e,r,n,i,a,s,l,u){let h=[t,e,r,n,i,a,s,l,u].reduce(Gk,{});return Moe(h)}function Noe(t){if(t&&t[sI])for(let e of Object.values(t))Noe(e);return t}function Moe(t,e){let r=new Proxy({},{deleteProperty:o(()=>!1,"deleteProperty"),get:o((n,i)=>Roe(n,i,t,e||r),"get"),getOwnPropertyDescriptor:o((n,i)=>(Roe(n,i,t,e||r),Object.getOwnPropertyDescriptor(n,i)),"getOwnPropertyDescriptor"),has:o((n,i)=>i in t,"has"),ownKeys:o(()=>[...Reflect.ownKeys(t),sI],"ownKeys")});return r[sI]=!0,r}function Roe(t,e,r,n){if(e in t){if(t[e]instanceof Error)throw new Error("Construction failure. Please make sure that your dependencies are constructable.",{cause:t[e]});if(t[e]===Doe)throw new Error('Cycle detected. Please make "'+String(e)+'" lazy. See https://langium.org/docs/configuration-services/#resolving-cyclic-dependencies');return t[e]}else if(e in r){let i=r[e];t[e]=Doe;try{t[e]=typeof i=="function"?i(n):Moe(i,n)}catch(a){throw t[e]=a instanceof Error?a:void 0,a}return t[e]}else return}function Gk(t,e){if(e){for(let[r,n]of Object.entries(e))if(n!==void 0){let i=t[r];i!==null&&n!==null&&typeof i=="object"&&typeof n=="object"?t[r]=Gk(i,n):t[r]=n}}return t}var aI,sI,Doe,oI=R(()=>{"use strict";(function(t){t.merge=(e,r)=>Gk(Gk({},e),r)})(aI||(aI={}));o(Fi,"inject");sI=Symbol("isProxy");o(Noe,"eagerLoad");o(Moe,"_inject");Doe=Symbol();o(Roe,"_resolve");o(Gk,"_merge")});var Ioe=R(()=>{"use strict"});var Ooe=R(()=>{"use strict";QM();KM();jM()});var Poe=R(()=>{"use strict"});var Boe=R(()=>{"use strict";eN();Poe()});var Foe=R(()=>{"use strict"});var zoe=R(()=>{"use strict";tI();lM();Ek();uM();D2();UM();Foe();hM();fM()});var Goe=R(()=>{"use strict";CM();SM();AM();LM();_M();DM()});var $oe=R(()=>{"use strict";nI();Ok()});var $k,go,lI=R(()=>{"use strict";$k=class{static{o(this,"EmptyFileSystemProvider")}readFile(){throw new Error("No file system is available.")}async readDirectory(){return[]}},go={fileSystemProvider:o(()=>new $k,"fileSystemProvider")}});function uPe(){let t=Fi(mo(go),cPe),e=Fi(po({shared:t}),lPe);return t.ServiceRegistry.register(e),e}function lf(t){var e;let r=uPe(),n=r.serializer.JsonSerializer.deserialize(t);return r.shared.workspace.LangiumDocumentFactory.fromModel(n,Ms.parse(`memory://${(e=n.name)!==null&&e!==void 0?e:"grammar"}.langium`)),n}var lPe,cPe,Voe=R(()=>{"use strict";iI();oI();Sc();lI();Nc();lPe={Grammar:o(()=>{},"Grammar"),LanguageMetaData:o(()=>({caseInsensitive:!1,fileExtensions:[".langium"],languageId:"langium"}),"LanguageMetaData")},cPe={AstReflection:o(()=>new Gm,"AstReflection")};o(uPe,"createMinimalGrammarServices");o(lf,"loadGrammarFromJson")});var Rr={};hr(Rr,{AstUtils:()=>CT,BiMap:()=>v0,Cancellation:()=>pr,ContextCache:()=>x0,CstUtils:()=>dT,DONE_RESULT:()=>Ja,Deferred:()=>as,Disposable:()=>b0,DisposableCache:()=>Cg,DocumentCache:()=>Mk,EMPTY_STREAM:()=>Gv,ErrorWithLocation:()=>jd,GrammarUtils:()=>RT,MultiMap:()=>Mc,OperationCancelled:()=>Rc,Reduction:()=>Fm,RegExpUtils:()=>LT,SimpleCache:()=>F2,StreamImpl:()=>uo,TreeStreamImpl:()=>Cc,URI:()=>Ms,UriUtils:()=>ss,WorkspaceCache:()=>Sg,assertUnreachable:()=>tf,delayNextTick:()=>TM,interruptAndCheck:()=>Bi,isOperationCancelled:()=>of,loadGrammarFromJson:()=>lf,setInterruptionPeriod:()=>poe,startCancelableOperation:()=>doe,stream:()=>Kr});var Uoe=R(()=>{"use strict";Ik();ZM();dr(Rr,ii);kg();BM();pT();Voe();qo();Ds();Nc();es();Wo();Rl();Il();Um()});var Hoe=R(()=>{"use strict";MM();U2()});var Yoe=R(()=>{"use strict";IM();OM();PM();FM();Tg();lI();zM();rI();GM()});var ba={};hr(ba,{AbstractAstReflection:()=>Yd,AbstractCstNode:()=>S2,AbstractLangiumParser:()=>A2,AbstractParserErrorMessageProvider:()=>Sk,AbstractThreadedAsyncParser:()=>JM,AstUtils:()=>CT,BiMap:()=>v0,Cancellation:()=>pr,CompositeCstNodeImpl:()=>p0,ContextCache:()=>x0,CstNodeBuilder:()=>C2,CstUtils:()=>dT,DONE_RESULT:()=>Ja,DatatypeSymbol:()=>Ck,DefaultAstNodeDescriptionProvider:()=>Y2,DefaultAstNodeLocator:()=>q2,DefaultAsyncParser:()=>nx,DefaultCommentProvider:()=>rx,DefaultConfigurationProvider:()=>X2,DefaultDocumentBuilder:()=>j2,DefaultDocumentValidator:()=>H2,DefaultHydrator:()=>ax,DefaultIndexManager:()=>K2,DefaultJsonSerializer:()=>G2,DefaultLangiumDocumentFactory:()=>R2,DefaultLangiumDocuments:()=>N2,DefaultLexer:()=>Z2,DefaultLinker:()=>M2,DefaultNameProvider:()=>I2,DefaultReferenceDescriptionProvider:()=>W2,DefaultReferences:()=>O2,DefaultScopeComputation:()=>P2,DefaultScopeProvider:()=>z2,DefaultServiceRegistry:()=>$2,DefaultTokenBuilder:()=>g0,DefaultValueConverter:()=>y0,DefaultWorkspaceLock:()=>ix,DefaultWorkspaceManager:()=>Q2,Deferred:()=>as,Disposable:()=>b0,DisposableCache:()=>Cg,DocumentCache:()=>Mk,DocumentState:()=>yn,DocumentValidator:()=>Uu,EMPTY_SCOPE:()=>jOe,EMPTY_STREAM:()=>Gv,EmptyFileSystem:()=>go,EmptyFileSystemProvider:()=>$k,ErrorWithLocation:()=>jd,GrammarAST:()=>Yv,GrammarUtils:()=>RT,JSDocDocumentationProvider:()=>tx,LangiumCompletionParser:()=>L2,LangiumParser:()=>_2,LangiumParserErrorMessageProvider:()=>yg,LeafCstNodeImpl:()=>d0,MapScope:()=>B2,Module:()=>aI,MultiMap:()=>Mc,OperationCancelled:()=>Rc,ParserWorker:()=>eI,Reduction:()=>Fm,RegExpUtils:()=>LT,RootCstNodeImpl:()=>gg,SimpleCache:()=>F2,StreamImpl:()=>uo,StreamScope:()=>Eg,TextDocument:()=>bg,TreeStreamImpl:()=>Cc,URI:()=>Ms,UriUtils:()=>ss,ValidationCategory:()=>Ag,ValidationRegistry:()=>V2,ValueConverter:()=>Dc,WorkspaceCache:()=>Sg,assertUnreachable:()=>tf,createCompletionParser:()=>oM,createDefaultCoreModule:()=>po,createDefaultSharedCoreModule:()=>mo,createGrammarConfig:()=>JR,createLangiumParser:()=>cM,delayNextTick:()=>TM,diagnosticData:()=>Pk,eagerLoad:()=>Noe,getDiagnosticRange:()=>Toe,inject:()=>Fi,interruptAndCheck:()=>Bi,isAstNode:()=>Xn,isAstNodeDescription:()=>ED,isAstNodeWithComment:()=>RM,isCompositeCstNode:()=>co,isIMultiModeLexerDefinition:()=>VM,isJSDoc:()=>qM,isLeafCstNode:()=>ef,isLinkingError:()=>Wd,isNamed:()=>boe,isOperationCancelled:()=>of,isReference:()=>xa,isRootCstNode:()=>zv,isTokenTypeArray:()=>koe,isTokenTypeDictionary:()=>$M,loadGrammarFromJson:()=>lf,parseJSDoc:()=>WM,prepareLangiumParser:()=>soe,setInterruptionPeriod:()=>poe,startCancelableOperation:()=>doe,stream:()=>Kr,toDiagnosticSeverity:()=>Bk});var Ic=R(()=>{"use strict";iI();oI();NM();Ioe();Vo();Ooe();Boe();zoe();Goe();$oe();Uoe();dr(ba,Rr);Hoe();Yoe();Sc()});function Joe(t){return Pl.isInstance(t,Zoe)}function ele(t){return Pl.isInstance(t,cI)}function tle(t){return Pl.isInstance(t,uI)}function rle(t){return Pl.isInstance(t,pPe)}function nle(t){return Pl.isInstance(t,hI)}function ale(t){return Pl.isInstance(t,ile)}function sle(t){return Pl.isInstance(t,fI)}function lle(t){return Pl.isInstance(t,ole)}function ule(t){return Pl.isInstance(t,cle)}function fle(t){return Pl.isInstance(t,hle)}function ple(t){return Pl.isInstance(t,dle)}var hPe,Tt,Qoe,Zoe,cI,fPe,dPe,uI,pPe,hI,ile,fI,ole,cle,hle,dle,mPe,mle,Pl,Woe,gPe,qoe,yPe,Xoe,vPe,joe,xPe,Koe,bPe,wPe,TPe,kPe,EPe,CPe,Bl,dI,pI,mI,gI,yI,SPe,APe,_Pe,LPe,_g,w0,Xo,DPe,jo=R(()=>{"use strict";Ic();Ic();Ic();Ic();hPe=Object.defineProperty,Tt=o((t,e)=>hPe(t,"name",{value:e,configurable:!0}),"__name"),Qoe="Statement",Zoe="Architecture";o(Joe,"isArchitecture");Tt(Joe,"isArchitecture");cI="Branch";o(ele,"isBranch");Tt(ele,"isBranch");fPe="Checkout",dPe="CherryPicking",uI="Commit";o(tle,"isCommit");Tt(tle,"isCommit");pPe="Common";o(rle,"isCommon");Tt(rle,"isCommon");hI="GitGraph";o(nle,"isGitGraph");Tt(nle,"isGitGraph");ile="Info";o(ale,"isInfo");Tt(ale,"isInfo");fI="Merge";o(sle,"isMerge");Tt(sle,"isMerge");ole="Packet";o(lle,"isPacket");Tt(lle,"isPacket");cle="PacketBlock";o(ule,"isPacketBlock");Tt(ule,"isPacketBlock");hle="Pie";o(fle,"isPie");Tt(fle,"isPie");dle="PieSection";o(ple,"isPieSection");Tt(ple,"isPieSection");mPe="Direction",mle=class extends Yd{static{o(this,"MermaidAstReflection")}static{Tt(this,"MermaidAstReflection")}getAllTypes(){return["Architecture","Branch","Checkout","CherryPicking","Commit","Common","Direction","Edge","GitGraph","Group","Info","Junction","Merge","Packet","PacketBlock","Pie","PieSection","Service","Statement"]}computeIsSubtype(t,e){switch(t){case cI:case fPe:case dPe:case uI:case fI:return this.isSubtype(Qoe,e);case mPe:return this.isSubtype(hI,e);default:return!1}}getReferenceType(t){let e=`${t.container.$type}:${t.property}`;switch(e){default:throw new Error(`${e} is not a valid reference id.`)}}getTypeMetaData(t){switch(t){case"Architecture":return{name:"Architecture",properties:[{name:"accDescr"},{name:"accTitle"},{name:"edges",defaultValue:[]},{name:"groups",defaultValue:[]},{name:"junctions",defaultValue:[]},{name:"services",defaultValue:[]},{name:"title"}]};case"Branch":return{name:"Branch",properties:[{name:"name"},{name:"order"}]};case"Checkout":return{name:"Checkout",properties:[{name:"branch"}]};case"CherryPicking":return{name:"CherryPicking",properties:[{name:"id"},{name:"parent"},{name:"tags",defaultValue:[]}]};case"Commit":return{name:"Commit",properties:[{name:"id"},{name:"message"},{name:"tags",defaultValue:[]},{name:"type"}]};case"Common":return{name:"Common",properties:[{name:"accDescr"},{name:"accTitle"},{name:"title"}]};case"Edge":return{name:"Edge",properties:[{name:"lhsDir"},{name:"lhsGroup",defaultValue:!1},{name:"lhsId"},{name:"lhsInto",defaultValue:!1},{name:"rhsDir"},{name:"rhsGroup",defaultValue:!1},{name:"rhsId"},{name:"rhsInto",defaultValue:!1},{name:"title"}]};case"GitGraph":return{name:"GitGraph",properties:[{name:"accDescr"},{name:"accTitle"},{name:"statements",defaultValue:[]},{name:"title"}]};case"Group":return{name:"Group",properties:[{name:"icon"},{name:"id"},{name:"in"},{name:"title"}]};case"Info":return{name:"Info",properties:[{name:"accDescr"},{name:"accTitle"},{name:"title"}]};case"Junction":return{name:"Junction",properties:[{name:"id"},{name:"in"}]};case"Merge":return{name:"Merge",properties:[{name:"branch"},{name:"id"},{name:"tags",defaultValue:[]},{name:"type"}]};case"Packet":return{name:"Packet",properties:[{name:"accDescr"},{name:"accTitle"},{name:"blocks",defaultValue:[]},{name:"title"}]};case"PacketBlock":return{name:"PacketBlock",properties:[{name:"end"},{name:"label"},{name:"start"}]};case"Pie":return{name:"Pie",properties:[{name:"accDescr"},{name:"accTitle"},{name:"sections",defaultValue:[]},{name:"showData",defaultValue:!1},{name:"title"}]};case"PieSection":return{name:"PieSection",properties:[{name:"label"},{name:"value"}]};case"Service":return{name:"Service",properties:[{name:"icon"},{name:"iconText"},{name:"id"},{name:"in"},{name:"title"}]};case"Direction":return{name:"Direction",properties:[{name:"accDescr"},{name:"accTitle"},{name:"dir"},{name:"statements",defaultValue:[]},{name:"title"}]};default:return{name:t,properties:[]}}}},Pl=new mle,gPe=Tt(()=>Woe??(Woe=lf('{"$type":"Grammar","isDeclared":true,"name":"Info","imports":[],"rules":[{"$type":"ParserRule","name":"Info","entry":true,"definition":{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@3"},"arguments":[],"cardinality":"*"},{"$type":"Keyword","value":"info"},{"$type":"RuleCall","rule":{"$ref":"#/rules@3"},"arguments":[],"cardinality":"*"},{"$type":"Group","elements":[{"$type":"Keyword","value":"showInfo"},{"$type":"RuleCall","rule":{"$ref":"#/rules@3"},"arguments":[],"cardinality":"*"}],"cardinality":"?"},{"$type":"RuleCall","rule":{"$ref":"#/rules@1"},"arguments":[],"cardinality":"?"}]},"definesHiddenTokens":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"TitleAndAccessibilities","fragment":true,"definition":{"$type":"Group","elements":[{"$type":"Alternatives","elements":[{"$type":"Assignment","feature":"accDescr","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@4"},"arguments":[]}},{"$type":"Assignment","feature":"accTitle","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@5"},"arguments":[]}},{"$type":"Assignment","feature":"title","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@6"},"arguments":[]}}]},{"$type":"RuleCall","rule":{"$ref":"#/rules@2"},"arguments":[]}],"cardinality":"+"},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"EOL","fragment":true,"dataType":"string","definition":{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@3"},"arguments":[],"cardinality":"+"},{"$type":"EndOfFile"}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"NEWLINE","definition":{"$type":"RegexToken","regex":"/\\\\r?\\\\n/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_DESCR","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accDescr(?:[\\\\t ]*:([^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)|\\\\s*{([^}]*)})/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accTitle[\\\\t ]*:(?:[^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*title(?:[\\\\t ][^\\\\n\\\\r]*?(?=%%)|[\\\\t ][^\\\\n\\\\r]*|)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","hidden":true,"name":"WHITESPACE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]+/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"YAML","definition":{"$type":"RegexToken","regex":"/---[\\\\t ]*\\\\r?\\\\n(?:[\\\\S\\\\s]*?\\\\r?\\\\n)?---(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"DIRECTIVE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%{[\\\\S\\\\s]*?}%%(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"SINGLE_LINE_COMMENT","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%[^\\\\n\\\\r]*/"},"fragment":false}],"definesHiddenTokens":false,"hiddenTokens":[],"interfaces":[{"$type":"Interface","name":"Common","attributes":[{"$type":"TypeAttribute","name":"accDescr","isOptional":true,"type":{"$type":"SimpleType","primitiveType":"string"}},{"$type":"TypeAttribute","name":"accTitle","isOptional":true,"type":{"$type":"SimpleType","primitiveType":"string"}},{"$type":"TypeAttribute","name":"title","isOptional":true,"type":{"$type":"SimpleType","primitiveType":"string"}}],"superTypes":[]}],"types":[],"usedGrammars":[]}')),"InfoGrammar"),yPe=Tt(()=>qoe??(qoe=lf(`{"$type":"Grammar","isDeclared":true,"name":"Packet","imports":[],"rules":[{"$type":"ParserRule","name":"Packet","entry":true,"definition":{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@6"},"arguments":[],"cardinality":"*"},{"$type":"Keyword","value":"packet-beta"},{"$type":"Alternatives","elements":[{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@6"},"arguments":[],"cardinality":"*"},{"$type":"RuleCall","rule":{"$ref":"#/rules@4"},"arguments":[]},{"$type":"Assignment","feature":"blocks","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@1"},"arguments":[]},"cardinality":"*"}]},{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@6"},"arguments":[],"cardinality":"+"},{"$type":"Assignment","feature":"blocks","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@1"},"arguments":[]},"cardinality":"+"}]},{"$type":"RuleCall","rule":{"$ref":"#/rules@6"},"arguments":[],"cardinality":"*"}]}]},"definesHiddenTokens":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"PacketBlock","definition":{"$type":"Group","elements":[{"$type":"Assignment","feature":"start","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@2"},"arguments":[]}},{"$type":"Group","elements":[{"$type":"Keyword","value":"-"},{"$type":"Assignment","feature":"end","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@2"},"arguments":[]}}],"cardinality":"?"},{"$type":"Keyword","value":":"},{"$type":"Assignment","feature":"label","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@3"},"arguments":[]}},{"$type":"RuleCall","rule":{"$ref":"#/rules@5"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"INT","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"RegexToken","regex":"/0|[1-9][0-9]*/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"STRING","definition":{"$type":"RegexToken","regex":"/\\"[^\\"]*\\"|'[^']*'/"},"fragment":false,"hidden":false},{"$type":"ParserRule","name":"TitleAndAccessibilities","fragment":true,"definition":{"$type":"Group","elements":[{"$type":"Alternatives","elements":[{"$type":"Assignment","feature":"accDescr","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@7"},"arguments":[]}},{"$type":"Assignment","feature":"accTitle","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@8"},"arguments":[]}},{"$type":"Assignment","feature":"title","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@9"},"arguments":[]}}]},{"$type":"RuleCall","rule":{"$ref":"#/rules@5"},"arguments":[]}],"cardinality":"+"},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"EOL","fragment":true,"dataType":"string","definition":{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@6"},"arguments":[],"cardinality":"+"},{"$type":"EndOfFile"}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"NEWLINE","definition":{"$type":"RegexToken","regex":"/\\\\r?\\\\n/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_DESCR","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accDescr(?:[\\\\t ]*:([^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)|\\\\s*{([^}]*)})/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accTitle[\\\\t ]*:(?:[^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*title(?:[\\\\t ][^\\\\n\\\\r]*?(?=%%)|[\\\\t ][^\\\\n\\\\r]*|)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","hidden":true,"name":"WHITESPACE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]+/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"YAML","definition":{"$type":"RegexToken","regex":"/---[\\\\t ]*\\\\r?\\\\n(?:[\\\\S\\\\s]*?\\\\r?\\\\n)?---(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"DIRECTIVE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%{[\\\\S\\\\s]*?}%%(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"SINGLE_LINE_COMMENT","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%[^\\\\n\\\\r]*/"},"fragment":false}],"definesHiddenTokens":false,"hiddenTokens":[],"interfaces":[{"$type":"Interface","name":"Common","attributes":[{"$type":"TypeAttribute","name":"accDescr","isOptional":true,"type":{"$type":"SimpleType","primitiveType":"string"}},{"$type":"TypeAttribute","name":"accTitle","isOptional":true,"type":{"$type":"SimpleType","primitiveType":"string"}},{"$type":"TypeAttribute","name":"title","isOptional":true,"type":{"$type":"SimpleType","primitiveType":"string"}}],"superTypes":[]}],"types":[],"usedGrammars":[]}`)),"PacketGrammar"),vPe=Tt(()=>Xoe??(Xoe=lf('{"$type":"Grammar","isDeclared":true,"name":"Pie","imports":[],"rules":[{"$type":"ParserRule","name":"Pie","entry":true,"definition":{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@6"},"arguments":[],"cardinality":"*"},{"$type":"Keyword","value":"pie"},{"$type":"Assignment","feature":"showData","operator":"?=","terminal":{"$type":"Keyword","value":"showData"},"cardinality":"?"},{"$type":"Alternatives","elements":[{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@6"},"arguments":[],"cardinality":"*"},{"$type":"RuleCall","rule":{"$ref":"#/rules@4"},"arguments":[]},{"$type":"Assignment","feature":"sections","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@1"},"arguments":[]},"cardinality":"*"}]},{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@6"},"arguments":[],"cardinality":"+"},{"$type":"Assignment","feature":"sections","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@1"},"arguments":[]},"cardinality":"+"}]},{"$type":"RuleCall","rule":{"$ref":"#/rules@6"},"arguments":[],"cardinality":"*"}]}]},"definesHiddenTokens":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"PieSection","definition":{"$type":"Group","elements":[{"$type":"Assignment","feature":"label","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@2"},"arguments":[]}},{"$type":"Keyword","value":":"},{"$type":"Assignment","feature":"value","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@3"},"arguments":[]}},{"$type":"RuleCall","rule":{"$ref":"#/rules@5"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"PIE_SECTION_LABEL","definition":{"$type":"RegexToken","regex":"/\\"[^\\"]+\\"/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"PIE_SECTION_VALUE","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"RegexToken","regex":"/(0|[1-9][0-9]*)(\\\\.[0-9]+)?/"},"fragment":false,"hidden":false},{"$type":"ParserRule","name":"TitleAndAccessibilities","fragment":true,"definition":{"$type":"Group","elements":[{"$type":"Alternatives","elements":[{"$type":"Assignment","feature":"accDescr","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@7"},"arguments":[]}},{"$type":"Assignment","feature":"accTitle","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@8"},"arguments":[]}},{"$type":"Assignment","feature":"title","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@9"},"arguments":[]}}]},{"$type":"RuleCall","rule":{"$ref":"#/rules@5"},"arguments":[]}],"cardinality":"+"},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"EOL","fragment":true,"dataType":"string","definition":{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@6"},"arguments":[],"cardinality":"+"},{"$type":"EndOfFile"}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"NEWLINE","definition":{"$type":"RegexToken","regex":"/\\\\r?\\\\n/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_DESCR","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accDescr(?:[\\\\t ]*:([^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)|\\\\s*{([^}]*)})/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accTitle[\\\\t ]*:(?:[^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*title(?:[\\\\t ][^\\\\n\\\\r]*?(?=%%)|[\\\\t ][^\\\\n\\\\r]*|)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","hidden":true,"name":"WHITESPACE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]+/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"YAML","definition":{"$type":"RegexToken","regex":"/---[\\\\t ]*\\\\r?\\\\n(?:[\\\\S\\\\s]*?\\\\r?\\\\n)?---(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"DIRECTIVE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%{[\\\\S\\\\s]*?}%%(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"SINGLE_LINE_COMMENT","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%[^\\\\n\\\\r]*/"},"fragment":false}],"definesHiddenTokens":false,"hiddenTokens":[],"interfaces":[{"$type":"Interface","name":"Common","attributes":[{"$type":"TypeAttribute","name":"accDescr","isOptional":true,"type":{"$type":"SimpleType","primitiveType":"string"}},{"$type":"TypeAttribute","name":"accTitle","isOptional":true,"type":{"$type":"SimpleType","primitiveType":"string"}},{"$type":"TypeAttribute","name":"title","isOptional":true,"type":{"$type":"SimpleType","primitiveType":"string"}}],"superTypes":[]}],"types":[],"usedGrammars":[]}')),"PieGrammar"),xPe=Tt(()=>joe??(joe=lf('{"$type":"Grammar","isDeclared":true,"name":"Architecture","imports":[],"rules":[{"$type":"ParserRule","name":"Architecture","entry":true,"definition":{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@18"},"arguments":[],"cardinality":"*"},{"$type":"Keyword","value":"architecture-beta"},{"$type":"Alternatives","elements":[{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@18"},"arguments":[],"cardinality":"*"},{"$type":"RuleCall","rule":{"$ref":"#/rules@16"},"arguments":[]}]},{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@18"},"arguments":[],"cardinality":"*"},{"$type":"RuleCall","rule":{"$ref":"#/rules@1"},"arguments":[],"cardinality":"*"}]},{"$type":"RuleCall","rule":{"$ref":"#/rules@18"},"arguments":[],"cardinality":"*"}]}]},"definesHiddenTokens":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Statement","fragment":true,"definition":{"$type":"Alternatives","elements":[{"$type":"Assignment","feature":"groups","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@5"},"arguments":[]}},{"$type":"Assignment","feature":"services","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@6"},"arguments":[]}},{"$type":"Assignment","feature":"junctions","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@7"},"arguments":[]}},{"$type":"Assignment","feature":"edges","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@8"},"arguments":[]}}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"LeftPort","fragment":true,"definition":{"$type":"Group","elements":[{"$type":"Keyword","value":":"},{"$type":"Assignment","feature":"lhsDir","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@9"},"arguments":[]}}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"RightPort","fragment":true,"definition":{"$type":"Group","elements":[{"$type":"Assignment","feature":"rhsDir","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@9"},"arguments":[]}},{"$type":"Keyword","value":":"}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Arrow","fragment":true,"definition":{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@2"},"arguments":[]},{"$type":"Assignment","feature":"lhsInto","operator":"?=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@15"},"arguments":[]},"cardinality":"?"},{"$type":"Alternatives","elements":[{"$type":"Keyword","value":"--"},{"$type":"Group","elements":[{"$type":"Keyword","value":"-"},{"$type":"Assignment","feature":"title","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@13"},"arguments":[]}},{"$type":"Keyword","value":"-"}]}]},{"$type":"Assignment","feature":"rhsInto","operator":"?=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@15"},"arguments":[]},"cardinality":"?"},{"$type":"RuleCall","rule":{"$ref":"#/rules@3"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Group","definition":{"$type":"Group","elements":[{"$type":"Keyword","value":"group"},{"$type":"Assignment","feature":"id","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@10"},"arguments":[]}},{"$type":"Assignment","feature":"icon","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@12"},"arguments":[]},"cardinality":"?"},{"$type":"Assignment","feature":"title","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@13"},"arguments":[]},"cardinality":"?"},{"$type":"Group","elements":[{"$type":"Keyword","value":"in"},{"$type":"Assignment","feature":"in","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@10"},"arguments":[]}}],"cardinality":"?"},{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Service","definition":{"$type":"Group","elements":[{"$type":"Keyword","value":"service"},{"$type":"Assignment","feature":"id","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@10"},"arguments":[]}},{"$type":"Alternatives","elements":[{"$type":"Assignment","feature":"iconText","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@11"},"arguments":[]}},{"$type":"Assignment","feature":"icon","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@12"},"arguments":[]}}],"cardinality":"?"},{"$type":"Assignment","feature":"title","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@13"},"arguments":[]},"cardinality":"?"},{"$type":"Group","elements":[{"$type":"Keyword","value":"in"},{"$type":"Assignment","feature":"in","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@10"},"arguments":[]}}],"cardinality":"?"},{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Junction","definition":{"$type":"Group","elements":[{"$type":"Keyword","value":"junction"},{"$type":"Assignment","feature":"id","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@10"},"arguments":[]}},{"$type":"Group","elements":[{"$type":"Keyword","value":"in"},{"$type":"Assignment","feature":"in","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@10"},"arguments":[]}}],"cardinality":"?"},{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Edge","definition":{"$type":"Group","elements":[{"$type":"Assignment","feature":"lhsId","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@10"},"arguments":[]}},{"$type":"Assignment","feature":"lhsGroup","operator":"?=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@14"},"arguments":[]},"cardinality":"?"},{"$type":"RuleCall","rule":{"$ref":"#/rules@4"},"arguments":[]},{"$type":"Assignment","feature":"rhsId","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@10"},"arguments":[]}},{"$type":"Assignment","feature":"rhsGroup","operator":"?=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@14"},"arguments":[]},"cardinality":"?"},{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"ARROW_DIRECTION","definition":{"$type":"TerminalAlternatives","elements":[{"$type":"TerminalAlternatives","elements":[{"$type":"TerminalAlternatives","elements":[{"$type":"CharacterRange","left":{"$type":"Keyword","value":"L"}},{"$type":"CharacterRange","left":{"$type":"Keyword","value":"R"}}]},{"$type":"CharacterRange","left":{"$type":"Keyword","value":"T"}}]},{"$type":"CharacterRange","left":{"$type":"Keyword","value":"B"}}]},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ARCH_ID","definition":{"$type":"RegexToken","regex":"/[\\\\w]+/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ARCH_TEXT_ICON","definition":{"$type":"RegexToken","regex":"/\\\\(\\"[^\\"]+\\"\\\\)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ARCH_ICON","definition":{"$type":"RegexToken","regex":"/\\\\([\\\\w-:]+\\\\)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ARCH_TITLE","definition":{"$type":"RegexToken","regex":"/\\\\[[\\\\w ]+\\\\]/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ARROW_GROUP","definition":{"$type":"RegexToken","regex":"/\\\\{group\\\\}/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ARROW_INTO","definition":{"$type":"RegexToken","regex":"/<|>/"},"fragment":false,"hidden":false},{"$type":"ParserRule","name":"TitleAndAccessibilities","fragment":true,"definition":{"$type":"Group","elements":[{"$type":"Alternatives","elements":[{"$type":"Assignment","feature":"accDescr","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@19"},"arguments":[]}},{"$type":"Assignment","feature":"accTitle","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[]}},{"$type":"Assignment","feature":"title","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@21"},"arguments":[]}}]},{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}],"cardinality":"+"},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"EOL","fragment":true,"dataType":"string","definition":{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@18"},"arguments":[],"cardinality":"+"},{"$type":"EndOfFile"}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"NEWLINE","definition":{"$type":"RegexToken","regex":"/\\\\r?\\\\n/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_DESCR","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accDescr(?:[\\\\t ]*:([^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)|\\\\s*{([^}]*)})/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accTitle[\\\\t ]*:(?:[^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*title(?:[\\\\t ][^\\\\n\\\\r]*?(?=%%)|[\\\\t ][^\\\\n\\\\r]*|)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","hidden":true,"name":"WHITESPACE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]+/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"YAML","definition":{"$type":"RegexToken","regex":"/---[\\\\t ]*\\\\r?\\\\n(?:[\\\\S\\\\s]*?\\\\r?\\\\n)?---(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"DIRECTIVE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%{[\\\\S\\\\s]*?}%%(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"SINGLE_LINE_COMMENT","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%[^\\\\n\\\\r]*/"},"fragment":false}],"definesHiddenTokens":false,"hiddenTokens":[],"interfaces":[{"$type":"Interface","name":"Common","attributes":[{"$type":"TypeAttribute","name":"accDescr","isOptional":true,"type":{"$type":"SimpleType","primitiveType":"string"}},{"$type":"TypeAttribute","name":"accTitle","isOptional":true,"type":{"$type":"SimpleType","primitiveType":"string"}},{"$type":"TypeAttribute","name":"title","isOptional":true,"type":{"$type":"SimpleType","primitiveType":"string"}}],"superTypes":[]}],"types":[],"usedGrammars":[]}')),"ArchitectureGrammar"),bPe=Tt(()=>Koe??(Koe=lf(`{"$type":"Grammar","isDeclared":true,"name":"GitGraph","interfaces":[{"$type":"Interface","name":"Common","attributes":[{"$type":"TypeAttribute","name":"accDescr","isOptional":true,"type":{"$type":"SimpleType","primitiveType":"string"}},{"$type":"TypeAttribute","name":"accTitle","isOptional":true,"type":{"$type":"SimpleType","primitiveType":"string"}},{"$type":"TypeAttribute","name":"title","isOptional":true,"type":{"$type":"SimpleType","primitiveType":"string"}}],"superTypes":[]}],"rules":[{"$type":"ParserRule","name":"TitleAndAccessibilities","fragment":true,"definition":{"$type":"Group","elements":[{"$type":"Alternatives","elements":[{"$type":"Assignment","feature":"accDescr","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@3"},"arguments":[]}},{"$type":"Assignment","feature":"accTitle","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@4"},"arguments":[]}},{"$type":"Assignment","feature":"title","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@5"},"arguments":[]}}]},{"$type":"RuleCall","rule":{"$ref":"#/rules@1"},"arguments":[]}],"cardinality":"+"},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"EOL","fragment":true,"dataType":"string","definition":{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@2"},"arguments":[],"cardinality":"+"},{"$type":"EndOfFile"}]},"definesHiddenTokens":false,"entry":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"NEWLINE","definition":{"$type":"RegexToken","regex":"/\\\\r?\\\\n/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_DESCR","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accDescr(?:[\\\\t ]*:([^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)|\\\\s*{([^}]*)})/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ACC_TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*accTitle[\\\\t ]*:(?:[^\\\\n\\\\r]*?(?=%%)|[^\\\\n\\\\r]*)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"TITLE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*title(?:[\\\\t ][^\\\\n\\\\r]*?(?=%%)|[\\\\t ][^\\\\n\\\\r]*|)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","hidden":true,"name":"WHITESPACE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]+/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"YAML","definition":{"$type":"RegexToken","regex":"/---[\\\\t ]*\\\\r?\\\\n(?:[\\\\S\\\\s]*?\\\\r?\\\\n)?---(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"DIRECTIVE","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%{[\\\\S\\\\s]*?}%%(?:\\\\r?\\\\n|(?!\\\\S))/"},"fragment":false},{"$type":"TerminalRule","hidden":true,"name":"SINGLE_LINE_COMMENT","definition":{"$type":"RegexToken","regex":"/[\\\\t ]*%%[^\\\\n\\\\r]*/"},"fragment":false},{"$type":"ParserRule","name":"GitGraph","entry":true,"definition":{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@2"},"arguments":[],"cardinality":"*"},{"$type":"Alternatives","elements":[{"$type":"Keyword","value":"gitGraph"},{"$type":"Group","elements":[{"$type":"Keyword","value":"gitGraph"},{"$type":"Keyword","value":":"}]},{"$type":"Keyword","value":"gitGraph:"},{"$type":"Group","elements":[{"$type":"Keyword","value":"gitGraph"},{"$type":"RuleCall","rule":{"$ref":"#/rules@12"},"arguments":[]},{"$type":"Keyword","value":":"}]}]},{"$type":"RuleCall","rule":{"$ref":"#/rules@2"},"arguments":[],"cardinality":"*"},{"$type":"Group","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@2"},"arguments":[],"cardinality":"*"},{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@0"},"arguments":[]},{"$type":"Assignment","feature":"statements","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@11"},"arguments":[]}},{"$type":"RuleCall","rule":{"$ref":"#/rules@2"},"arguments":[]}],"cardinality":"*"}]}]},"definesHiddenTokens":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Statement","definition":{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@13"},"arguments":[]},{"$type":"RuleCall","rule":{"$ref":"#/rules@14"},"arguments":[]},{"$type":"RuleCall","rule":{"$ref":"#/rules@15"},"arguments":[]},{"$type":"RuleCall","rule":{"$ref":"#/rules@16"},"arguments":[]},{"$type":"RuleCall","rule":{"$ref":"#/rules@17"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Direction","definition":{"$type":"Assignment","feature":"dir","operator":"=","terminal":{"$type":"Alternatives","elements":[{"$type":"Keyword","value":"LR"},{"$type":"Keyword","value":"TB"},{"$type":"Keyword","value":"BT"}]}},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Commit","definition":{"$type":"Group","elements":[{"$type":"Keyword","value":"commit"},{"$type":"Alternatives","elements":[{"$type":"Group","elements":[{"$type":"Keyword","value":"id:"},{"$type":"Assignment","feature":"id","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[]}}]},{"$type":"Group","elements":[{"$type":"Keyword","value":"msg:","cardinality":"?"},{"$type":"Assignment","feature":"message","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[]}}]},{"$type":"Group","elements":[{"$type":"Keyword","value":"tag:"},{"$type":"Assignment","feature":"tags","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[]}}]},{"$type":"Group","elements":[{"$type":"Keyword","value":"type:"},{"$type":"Assignment","feature":"type","operator":"=","terminal":{"$type":"Alternatives","elements":[{"$type":"Keyword","value":"NORMAL"},{"$type":"Keyword","value":"REVERSE"},{"$type":"Keyword","value":"HIGHLIGHT"}]}}]}],"cardinality":"*"},{"$type":"RuleCall","rule":{"$ref":"#/rules@1"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Branch","definition":{"$type":"Group","elements":[{"$type":"Keyword","value":"branch"},{"$type":"Assignment","feature":"name","operator":"=","terminal":{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@19"},"arguments":[]},{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[]}]}},{"$type":"Group","elements":[{"$type":"Keyword","value":"order:"},{"$type":"Assignment","feature":"order","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@18"},"arguments":[]}}],"cardinality":"?"},{"$type":"RuleCall","rule":{"$ref":"#/rules@1"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Merge","definition":{"$type":"Group","elements":[{"$type":"Keyword","value":"merge"},{"$type":"Assignment","feature":"branch","operator":"=","terminal":{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@19"},"arguments":[]},{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[]}]}},{"$type":"Alternatives","elements":[{"$type":"Group","elements":[{"$type":"Keyword","value":"id:"},{"$type":"Assignment","feature":"id","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[]}}]},{"$type":"Group","elements":[{"$type":"Keyword","value":"tag:"},{"$type":"Assignment","feature":"tags","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[]}}]},{"$type":"Group","elements":[{"$type":"Keyword","value":"type:"},{"$type":"Assignment","feature":"type","operator":"=","terminal":{"$type":"Alternatives","elements":[{"$type":"Keyword","value":"NORMAL"},{"$type":"Keyword","value":"REVERSE"},{"$type":"Keyword","value":"HIGHLIGHT"}]}}]}],"cardinality":"*"},{"$type":"RuleCall","rule":{"$ref":"#/rules@1"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"Checkout","definition":{"$type":"Group","elements":[{"$type":"Alternatives","elements":[{"$type":"Keyword","value":"checkout"},{"$type":"Keyword","value":"switch"}]},{"$type":"Assignment","feature":"branch","operator":"=","terminal":{"$type":"Alternatives","elements":[{"$type":"RuleCall","rule":{"$ref":"#/rules@19"},"arguments":[]},{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[]}]}},{"$type":"RuleCall","rule":{"$ref":"#/rules@1"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"ParserRule","name":"CherryPicking","definition":{"$type":"Group","elements":[{"$type":"Keyword","value":"cherry-pick"},{"$type":"Alternatives","elements":[{"$type":"Group","elements":[{"$type":"Keyword","value":"id:"},{"$type":"Assignment","feature":"id","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[]}}]},{"$type":"Group","elements":[{"$type":"Keyword","value":"tag:"},{"$type":"Assignment","feature":"tags","operator":"+=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[]}}]},{"$type":"Group","elements":[{"$type":"Keyword","value":"parent:"},{"$type":"Assignment","feature":"parent","operator":"=","terminal":{"$type":"RuleCall","rule":{"$ref":"#/rules@20"},"arguments":[]}}]}],"cardinality":"*"},{"$type":"RuleCall","rule":{"$ref":"#/rules@1"},"arguments":[]}]},"definesHiddenTokens":false,"entry":false,"fragment":false,"hiddenTokens":[],"parameters":[],"wildcard":false},{"$type":"TerminalRule","name":"INT","type":{"$type":"ReturnType","name":"number"},"definition":{"$type":"RegexToken","regex":"/[0-9]+(?=\\\\s)/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"ID","type":{"$type":"ReturnType","name":"string"},"definition":{"$type":"RegexToken","regex":"/\\\\w([-\\\\./\\\\w]*[-\\\\w])?/"},"fragment":false,"hidden":false},{"$type":"TerminalRule","name":"STRING","definition":{"$type":"RegexToken","regex":"/\\"[^\\"]*\\"|'[^']*'/"},"fragment":false,"hidden":false}],"definesHiddenTokens":false,"hiddenTokens":[],"imports":[],"types":[],"usedGrammars":[]}`)),"GitGraphGrammar"),wPe={languageId:"info",fileExtensions:[".mmd",".mermaid"],caseInsensitive:!1},TPe={languageId:"packet",fileExtensions:[".mmd",".mermaid"],caseInsensitive:!1},kPe={languageId:"pie",fileExtensions:[".mmd",".mermaid"],caseInsensitive:!1},EPe={languageId:"architecture",fileExtensions:[".mmd",".mermaid"],caseInsensitive:!1},CPe={languageId:"gitGraph",fileExtensions:[".mmd",".mermaid"],caseInsensitive:!1},Bl={AstReflection:Tt(()=>new mle,"AstReflection")},dI={Grammar:Tt(()=>gPe(),"Grammar"),LanguageMetaData:Tt(()=>wPe,"LanguageMetaData"),parser:{}},pI={Grammar:Tt(()=>yPe(),"Grammar"),LanguageMetaData:Tt(()=>TPe,"LanguageMetaData"),parser:{}},mI={Grammar:Tt(()=>vPe(),"Grammar"),LanguageMetaData:Tt(()=>kPe,"LanguageMetaData"),parser:{}},gI={Grammar:Tt(()=>xPe(),"Grammar"),LanguageMetaData:Tt(()=>EPe,"LanguageMetaData"),parser:{}},yI={Grammar:Tt(()=>bPe(),"Grammar"),LanguageMetaData:Tt(()=>CPe,"LanguageMetaData"),parser:{}},SPe=/accDescr(?:[\t ]*:([^\n\r]*)|\s*{([^}]*)})/,APe=/accTitle[\t ]*:([^\n\r]*)/,_Pe=/title([\t ][^\n\r]*|)/,LPe={ACC_DESCR:SPe,ACC_TITLE:APe,TITLE:_Pe},_g=class extends y0{static{o(this,"AbstractMermaidValueConverter")}static{Tt(this,"AbstractMermaidValueConverter")}runConverter(t,e,r){let n=this.runCommonConverter(t,e,r);return n===void 0&&(n=this.runCustomConverter(t,e,r)),n===void 0?super.runConverter(t,e,r):n}runCommonConverter(t,e,r){let n=LPe[t.name];if(n===void 0)return;let i=n.exec(e);if(i!==null){if(i[1]!==void 0)return i[1].trim().replace(/[\t ]{2,}/gm," ");if(i[2]!==void 0)return i[2].replace(/^\s*/gm,"").replace(/\s+$/gm,"").replace(/[\t ]{2,}/gm," ").replace(/[\n\r]{2,}/gm,` +`)}}},w0=class extends _g{static{o(this,"CommonValueConverter")}static{Tt(this,"CommonValueConverter")}runCustomConverter(t,e,r){}},Xo=class extends g0{static{o(this,"AbstractMermaidTokenBuilder")}static{Tt(this,"AbstractMermaidTokenBuilder")}constructor(t){super(),this.keywords=new Set(t)}buildKeywordTokens(t,e,r){let n=super.buildKeywordTokens(t,e,r);return n.forEach(i=>{this.keywords.has(i.name)&&i.PATTERN!==void 0&&(i.PATTERN=new RegExp(i.PATTERN.toString()+"(?:(?=%%)|(?!\\S))"))}),n}},DPe=class extends Xo{static{o(this,"CommonTokenBuilder")}static{Tt(this,"CommonTokenBuilder")}}});function Uk(t=go){let e=Fi(mo(t),Bl),r=Fi(po({shared:e}),yI,Vk);return e.ServiceRegistry.register(r),{shared:e,GitGraph:r}}var RPe,Vk,vI=R(()=>{"use strict";jo();Ic();RPe=class extends Xo{static{o(this,"GitGraphTokenBuilder")}static{Tt(this,"GitGraphTokenBuilder")}constructor(){super(["gitGraph"])}},Vk={parser:{TokenBuilder:Tt(()=>new RPe,"TokenBuilder"),ValueConverter:Tt(()=>new w0,"ValueConverter")}};o(Uk,"createGitGraphServices");Tt(Uk,"createGitGraphServices")});function Yk(t=go){let e=Fi(mo(t),Bl),r=Fi(po({shared:e}),dI,Hk);return e.ServiceRegistry.register(r),{shared:e,Info:r}}var NPe,Hk,xI=R(()=>{"use strict";jo();Ic();NPe=class extends Xo{static{o(this,"InfoTokenBuilder")}static{Tt(this,"InfoTokenBuilder")}constructor(){super(["info","showInfo"])}},Hk={parser:{TokenBuilder:Tt(()=>new NPe,"TokenBuilder"),ValueConverter:Tt(()=>new w0,"ValueConverter")}};o(Yk,"createInfoServices");Tt(Yk,"createInfoServices")});function qk(t=go){let e=Fi(mo(t),Bl),r=Fi(po({shared:e}),pI,Wk);return e.ServiceRegistry.register(r),{shared:e,Packet:r}}var MPe,Wk,bI=R(()=>{"use strict";jo();Ic();MPe=class extends Xo{static{o(this,"PacketTokenBuilder")}static{Tt(this,"PacketTokenBuilder")}constructor(){super(["packet-beta"])}},Wk={parser:{TokenBuilder:Tt(()=>new MPe,"TokenBuilder"),ValueConverter:Tt(()=>new w0,"ValueConverter")}};o(qk,"createPacketServices");Tt(qk,"createPacketServices")});function jk(t=go){let e=Fi(mo(t),Bl),r=Fi(po({shared:e}),mI,Xk);return e.ServiceRegistry.register(r),{shared:e,Pie:r}}var IPe,OPe,Xk,wI=R(()=>{"use strict";jo();Ic();IPe=class extends Xo{static{o(this,"PieTokenBuilder")}static{Tt(this,"PieTokenBuilder")}constructor(){super(["pie","showData"])}},OPe=class extends _g{static{o(this,"PieValueConverter")}static{Tt(this,"PieValueConverter")}runCustomConverter(t,e,r){if(t.name==="PIE_SECTION_LABEL")return e.replace(/"/g,"").trim()}},Xk={parser:{TokenBuilder:Tt(()=>new IPe,"TokenBuilder"),ValueConverter:Tt(()=>new OPe,"ValueConverter")}};o(jk,"createPieServices");Tt(jk,"createPieServices")});function Qk(t=go){let e=Fi(mo(t),Bl),r=Fi(po({shared:e}),gI,Kk);return e.ServiceRegistry.register(r),{shared:e,Architecture:r}}var PPe,BPe,Kk,TI=R(()=>{"use strict";jo();Ic();PPe=class extends Xo{static{o(this,"ArchitectureTokenBuilder")}static{Tt(this,"ArchitectureTokenBuilder")}constructor(){super(["architecture"])}},BPe=class extends _g{static{o(this,"ArchitectureValueConverter")}static{Tt(this,"ArchitectureValueConverter")}runCustomConverter(t,e,r){if(t.name==="ARCH_ICON")return e.replace(/[()]/g,"").trim();if(t.name==="ARCH_TEXT_ICON")return e.replace(/["()]/g,"");if(t.name==="ARCH_TITLE")return e.replace(/[[\]]/g,"").trim()}},Kk={parser:{TokenBuilder:Tt(()=>new PPe,"TokenBuilder"),ValueConverter:Tt(()=>new BPe,"ValueConverter")}};o(Qk,"createArchitectureServices");Tt(Qk,"createArchitectureServices")});var gle={};hr(gle,{InfoModule:()=>Hk,createInfoServices:()=>Yk});var yle=R(()=>{"use strict";xI();jo()});var vle={};hr(vle,{PacketModule:()=>Wk,createPacketServices:()=>qk});var xle=R(()=>{"use strict";bI();jo()});var ble={};hr(ble,{PieModule:()=>Xk,createPieServices:()=>jk});var wle=R(()=>{"use strict";wI();jo()});var Tle={};hr(Tle,{ArchitectureModule:()=>Kk,createArchitectureServices:()=>Qk});var kle=R(()=>{"use strict";TI();jo()});var Ele={};hr(Ele,{GitGraphModule:()=>Vk,createGitGraphServices:()=>Uk});var Cle=R(()=>{"use strict";vI();jo()});async function Fl(t,e){let r=FPe[t];if(!r)throw new Error(`Unknown diagram type: ${t}`);T0[t]||await r();let i=T0[t].parse(e);if(i.lexerErrors.length>0||i.parserErrors.length>0)throw new zPe(i);return i.value}var T0,FPe,zPe,Lg=R(()=>{"use strict";vI();xI();bI();wI();TI();jo();T0={},FPe={info:Tt(async()=>{let{createInfoServices:t}=await Promise.resolve().then(()=>(yle(),gle)),e=t().Info.parser.LangiumParser;T0.info=e},"info"),packet:Tt(async()=>{let{createPacketServices:t}=await Promise.resolve().then(()=>(xle(),vle)),e=t().Packet.parser.LangiumParser;T0.packet=e},"packet"),pie:Tt(async()=>{let{createPieServices:t}=await Promise.resolve().then(()=>(wle(),ble)),e=t().Pie.parser.LangiumParser;T0.pie=e},"pie"),architecture:Tt(async()=>{let{createArchitectureServices:t}=await Promise.resolve().then(()=>(kle(),Tle)),e=t().Architecture.parser.LangiumParser;T0.architecture=e},"architecture"),gitGraph:Tt(async()=>{let{createGitGraphServices:t}=await Promise.resolve().then(()=>(Cle(),Ele)),e=t().GitGraph.parser.LangiumParser;T0.gitGraph=e},"gitGraph")};o(Fl,"parse");Tt(Fl,"parse");zPe=class extends Error{static{o(this,"MermaidParseError")}constructor(t){let e=t.lexerErrors.map(n=>n.message).join(` +`),r=t.parserErrors.map(n=>n.message).join(` +`);super(`Parsing failed: ${e} ${r}`),this.result=t}static{Tt(this,"MermaidParseError")}}});function cf(t,e){t.accDescr&&e.setAccDescription?.(t.accDescr),t.accTitle&&e.setAccTitle?.(t.accTitle),t.title&&e.setDiagramTitle?.(t.title)}var sx=R(()=>{"use strict";o(cf,"populateCommonDb")});var Hr,Zk=R(()=>{"use strict";Hr={NORMAL:0,REVERSE:1,HIGHLIGHT:2,MERGE:3,CHERRY_PICK:4}});var uf,Jk=R(()=>{"use strict";uf=class{constructor(e){this.init=e;this.records=this.init()}static{o(this,"ImperativeState")}reset(){this.records=this.init()}}});function kI(){return J_({length:7})}function $Pe(t,e){let r=Object.create(null);return t.reduce((n,i)=>{let a=e(i);return r[a]||(r[a]=!0,n.push(i)),n},[])}function Sle(t,e,r){let n=t.indexOf(e);n===-1?t.push(r):t.splice(n,1,r)}function _le(t){let e=t.reduce((i,a)=>i.seq>a.seq?i:a,t[0]),r="";t.forEach(function(i){i===e?r+=" *":r+=" |"});let n=[r,e.id,e.seq];for(let i in pt.records.branches)pt.records.branches.get(i)===e.id&&n.push(i);if(V.debug(n.join(" ")),e.parents&&e.parents.length==2&&e.parents[0]&&e.parents[1]){let i=pt.records.commits.get(e.parents[0]);Sle(t,e,i),e.parents[1]&&t.push(pt.records.commits.get(e.parents[1]))}else{if(e.parents.length==0)return;if(e.parents[0]){let i=pt.records.commits.get(e.parents[0]);Sle(t,e,i)}}t=$Pe(t,i=>i.id),_le(t)}var GPe,k0,pt,VPe,UPe,HPe,YPe,WPe,qPe,XPe,Ale,jPe,KPe,QPe,ZPe,JPe,Lle,eBe,tBe,rBe,eE,EI=R(()=>{"use strict";ut();xr();qs();rr();bi();Zk();Jk();sl();GPe=mr.gitGraph,k0=o(()=>Ts({...GPe,...Or().gitGraph}),"getConfig"),pt=new uf(()=>{let t=k0(),e=t.mainBranchName,r=t.mainBranchOrder;return{mainBranchName:e,commits:new Map,head:null,branchConfig:new Map([[e,{name:e,order:r}]]),branches:new Map([[e,null]]),currBranch:e,direction:"LR",seq:0,options:{}}});o(kI,"getID");o($Pe,"uniqBy");VPe=o(function(t){pt.records.direction=t},"setDirection"),UPe=o(function(t){V.debug("options str",t),t=t?.trim(),t=t||"{}";try{pt.records.options=JSON.parse(t)}catch(e){V.error("error while parsing gitGraph options",e.message)}},"setOptions"),HPe=o(function(){return pt.records.options},"getOptions"),YPe=o(function(t){let e=t.msg,r=t.id,n=t.type,i=t.tags;V.info("commit",e,r,n,i),V.debug("Entering commit:",e,r,n,i);let a=k0();r=We.sanitizeText(r,a),e=We.sanitizeText(e,a),i=i?.map(l=>We.sanitizeText(l,a));let s={id:r||pt.records.seq+"-"+kI(),message:e,seq:pt.records.seq++,type:n??Hr.NORMAL,tags:i??[],parents:pt.records.head==null?[]:[pt.records.head.id],branch:pt.records.currBranch};pt.records.head=s,V.info("main branch",a.mainBranchName),pt.records.commits.set(s.id,s),pt.records.branches.set(pt.records.currBranch,s.id),V.debug("in pushCommit "+s.id)},"commit"),WPe=o(function(t){let e=t.name,r=t.order;if(e=We.sanitizeText(e,k0()),pt.records.branches.has(e))throw new Error(`Trying to create an existing branch. (Help: Either use a new name if you want create a new branch or try using "checkout ${e}")`);pt.records.branches.set(e,pt.records.head!=null?pt.records.head.id:null),pt.records.branchConfig.set(e,{name:e,order:r}),Ale(e),V.debug("in createBranch")},"branch"),qPe=o(t=>{let e=t.branch,r=t.id,n=t.type,i=t.tags,a=k0();e=We.sanitizeText(e,a),r&&(r=We.sanitizeText(r,a));let s=pt.records.branches.get(pt.records.currBranch),l=pt.records.branches.get(e),u=s?pt.records.commits.get(s):void 0,h=l?pt.records.commits.get(l):void 0;if(u&&h&&u.branch===e)throw new Error(`Cannot merge branch '${e}' into itself.`);if(pt.records.currBranch===e){let p=new Error('Incorrect usage of "merge". Cannot merge a branch to itself');throw p.hash={text:`merge ${e}`,token:`merge ${e}`,expected:["branch abc"]},p}if(u===void 0||!u){let p=new Error(`Incorrect usage of "merge". Current branch (${pt.records.currBranch})has no commits`);throw p.hash={text:`merge ${e}`,token:`merge ${e}`,expected:["commit"]},p}if(!pt.records.branches.has(e)){let p=new Error('Incorrect usage of "merge". Branch to be merged ('+e+") does not exist");throw p.hash={text:`merge ${e}`,token:`merge ${e}`,expected:[`branch ${e}`]},p}if(h===void 0||!h){let p=new Error('Incorrect usage of "merge". Branch to be merged ('+e+") has no commits");throw p.hash={text:`merge ${e}`,token:`merge ${e}`,expected:['"commit"']},p}if(u===h){let p=new Error('Incorrect usage of "merge". Both branches have same head');throw p.hash={text:`merge ${e}`,token:`merge ${e}`,expected:["branch abc"]},p}if(r&&pt.records.commits.has(r)){let p=new Error('Incorrect usage of "merge". Commit with id:'+r+" already exists, use different custom Id");throw p.hash={text:`merge ${e} ${r} ${n} ${i?.join(" ")}`,token:`merge ${e} ${r} ${n} ${i?.join(" ")}`,expected:[`merge ${e} ${r}_UNIQUE ${n} ${i?.join(" ")}`]},p}let f=l||"",d={id:r||`${pt.records.seq}-${kI()}`,message:`merged branch ${e} into ${pt.records.currBranch}`,seq:pt.records.seq++,parents:pt.records.head==null?[]:[pt.records.head.id,f],branch:pt.records.currBranch,type:Hr.MERGE,customType:n,customId:!!r,tags:i??[]};pt.records.head=d,pt.records.commits.set(d.id,d),pt.records.branches.set(pt.records.currBranch,d.id),V.debug(pt.records.branches),V.debug("in mergeBranch")},"merge"),XPe=o(function(t){let e=t.id,r=t.targetId,n=t.tags,i=t.parent;V.debug("Entering cherryPick:",e,r,n);let a=k0();if(e=We.sanitizeText(e,a),r=We.sanitizeText(r,a),n=n?.map(u=>We.sanitizeText(u,a)),i=We.sanitizeText(i,a),!e||!pt.records.commits.has(e)){let u=new Error('Incorrect usage of "cherryPick". Source commit id should exist and provided');throw u.hash={text:`cherryPick ${e} ${r}`,token:`cherryPick ${e} ${r}`,expected:["cherry-pick abc"]},u}let s=pt.records.commits.get(e);if(s===void 0||!s)throw new Error('Incorrect usage of "cherryPick". Source commit id should exist and provided');if(i&&!(Array.isArray(s.parents)&&s.parents.includes(i)))throw new Error("Invalid operation: The specified parent commit is not an immediate parent of the cherry-picked commit.");let l=s.branch;if(s.type===Hr.MERGE&&!i)throw new Error("Incorrect usage of cherry-pick: If the source commit is a merge commit, an immediate parent commit must be specified.");if(!r||!pt.records.commits.has(r)){if(l===pt.records.currBranch){let d=new Error('Incorrect usage of "cherryPick". Source commit is already on current branch');throw d.hash={text:`cherryPick ${e} ${r}`,token:`cherryPick ${e} ${r}`,expected:["cherry-pick abc"]},d}let u=pt.records.branches.get(pt.records.currBranch);if(u===void 0||!u){let d=new Error(`Incorrect usage of "cherry-pick". Current branch (${pt.records.currBranch})has no commits`);throw d.hash={text:`cherryPick ${e} ${r}`,token:`cherryPick ${e} ${r}`,expected:["cherry-pick abc"]},d}let h=pt.records.commits.get(u);if(h===void 0||!h){let d=new Error(`Incorrect usage of "cherry-pick". Current branch (${pt.records.currBranch})has no commits`);throw d.hash={text:`cherryPick ${e} ${r}`,token:`cherryPick ${e} ${r}`,expected:["cherry-pick abc"]},d}let f={id:pt.records.seq+"-"+kI(),message:`cherry-picked ${s?.message} into ${pt.records.currBranch}`,seq:pt.records.seq++,parents:pt.records.head==null?[]:[pt.records.head.id,s.id],branch:pt.records.currBranch,type:Hr.CHERRY_PICK,tags:n?n.filter(Boolean):[`cherry-pick:${s.id}${s.type===Hr.MERGE?`|parent:${i}`:""}`]};pt.records.head=f,pt.records.commits.set(f.id,f),pt.records.branches.set(pt.records.currBranch,f.id),V.debug(pt.records.branches),V.debug("in cherryPick")}},"cherryPick"),Ale=o(function(t){if(t=We.sanitizeText(t,k0()),pt.records.branches.has(t)){pt.records.currBranch=t;let e=pt.records.branches.get(pt.records.currBranch);e===void 0||!e?pt.records.head=null:pt.records.head=pt.records.commits.get(e)??null}else{let e=new Error(`Trying to checkout branch which is not yet created. (Help try using "branch ${t}")`);throw e.hash={text:`checkout ${t}`,token:`checkout ${t}`,expected:[`branch ${t}`]},e}},"checkout");o(Sle,"upsert");o(_le,"prettyPrintCommitHistory");jPe=o(function(){V.debug(pt.records.commits);let t=Lle()[0];_le([t])},"prettyPrint"),KPe=o(function(){pt.reset(),vr()},"clear"),QPe=o(function(){return[...pt.records.branchConfig.values()].map((e,r)=>e.order!==null&&e.order!==void 0?e:{...e,order:parseFloat(`0.${r}`)}).sort((e,r)=>(e.order??0)-(r.order??0)).map(({name:e})=>({name:e}))},"getBranchesAsObjArray"),ZPe=o(function(){return pt.records.branches},"getBranches"),JPe=o(function(){return pt.records.commits},"getCommits"),Lle=o(function(){let t=[...pt.records.commits.values()];return t.forEach(function(e){V.debug(e.id)}),t.sort((e,r)=>e.seq-r.seq),t},"getCommitsArray"),eBe=o(function(){return pt.records.currBranch},"getCurrentBranch"),tBe=o(function(){return pt.records.direction},"getDirection"),rBe=o(function(){return pt.records.head},"getHead"),eE={commitType:Hr,getConfig:k0,setDirection:VPe,setOptions:UPe,getOptions:HPe,commit:YPe,branch:WPe,merge:qPe,cherryPick:XPe,checkout:Ale,prettyPrint:jPe,clear:KPe,getBranchesAsObjArray:QPe,getBranches:ZPe,getCommits:JPe,getCommitsArray:Lle,getCurrentBranch:eBe,getDirection:tBe,getHead:rBe,setAccTitle:kr,getAccTitle:Ar,getAccDescription:Lr,setAccDescription:_r,setDiagramTitle:nn,getDiagramTitle:Xr}});var nBe,iBe,aBe,sBe,oBe,lBe,cBe,Dle,Rle=R(()=>{"use strict";Lg();ut();sx();EI();Zk();nBe=o((t,e)=>{cf(t,e),t.dir&&e.setDirection(t.dir);for(let r of t.statements)iBe(r,e)},"populate"),iBe=o((t,e)=>{let n={Commit:o(i=>e.commit(aBe(i)),"Commit"),Branch:o(i=>e.branch(sBe(i)),"Branch"),Merge:o(i=>e.merge(oBe(i)),"Merge"),Checkout:o(i=>e.checkout(lBe(i)),"Checkout"),CherryPicking:o(i=>e.cherryPick(cBe(i)),"CherryPicking")}[t.$type];n?n(t):V.error(`Unknown statement type: ${t.$type}`)},"parseStatement"),aBe=o(t=>({id:t.id,msg:t.message??"",type:t.type!==void 0?Hr[t.type]:Hr.NORMAL,tags:t.tags??void 0}),"parseCommit"),sBe=o(t=>({name:t.name,order:t.order??0}),"parseBranch"),oBe=o(t=>({branch:t.branch,id:t.id??"",type:t.type!==void 0?Hr[t.type]:void 0,tags:t.tags??void 0}),"parseMerge"),lBe=o(t=>t.branch,"parseCheckout"),cBe=o(t=>({id:t.id,targetId:"",tags:t.tags?.length===0?void 0:t.tags,parent:t.parent}),"parseCherryPicking"),Dle={parse:o(async t=>{let e=await Fl("gitGraph",t);V.debug(e),nBe(e,eE)},"parse")}});var uBe,Ko,ff,df,Oc,Hu,E0,Is,Os,tE,ox,rE,hf,Tr,hBe,Mle,Ile,fBe,dBe,pBe,mBe,gBe,yBe,vBe,xBe,bBe,wBe,TBe,kBe,Nle,EBe,lx,CBe,SBe,ABe,_Be,LBe,Ole,Ple=R(()=>{"use strict";Zt();_t();ut();xr();Zk();uBe=de(),Ko=uBe?.gitGraph,ff=10,df=40,Oc=4,Hu=2,E0=8,Is=new Map,Os=new Map,tE=30,ox=new Map,rE=[],hf=0,Tr="LR",hBe=o(()=>{Is.clear(),Os.clear(),ox.clear(),hf=0,rE=[],Tr="LR"},"clear"),Mle=o(t=>{let e=document.createElementNS("http://www.w3.org/2000/svg","text");return(typeof t=="string"?t.split(/\\n|\n|/gi):t).forEach(n=>{let i=document.createElementNS("http://www.w3.org/2000/svg","tspan");i.setAttributeNS("http://www.w3.org/XML/1998/namespace","xml:space","preserve"),i.setAttribute("dy","1em"),i.setAttribute("x","0"),i.setAttribute("class","row"),i.textContent=n.trim(),e.appendChild(i)}),e},"drawText"),Ile=o(t=>{let e,r,n;return Tr==="BT"?(r=o((i,a)=>i<=a,"comparisonFunc"),n=1/0):(r=o((i,a)=>i>=a,"comparisonFunc"),n=0),t.forEach(i=>{let a=Tr==="TB"||Tr=="BT"?Os.get(i)?.y:Os.get(i)?.x;a!==void 0&&r(a,n)&&(e=i,n=a)}),e},"findClosestParent"),fBe=o(t=>{let e="",r=1/0;return t.forEach(n=>{let i=Os.get(n).y;i<=r&&(e=n,r=i)}),e||void 0},"findClosestParentBT"),dBe=o((t,e,r)=>{let n=r,i=r,a=[];t.forEach(s=>{let l=e.get(s);if(!l)throw new Error(`Commit not found for key ${s}`);l.parents.length?(n=mBe(l),i=Math.max(n,i)):a.push(l),gBe(l,n)}),n=i,a.forEach(s=>{yBe(s,n,r)}),t.forEach(s=>{let l=e.get(s);if(l?.parents.length){let u=fBe(l.parents);n=Os.get(u).y-df,n<=i&&(i=n);let h=Is.get(l.branch).pos,f=n-ff;Os.set(l.id,{x:h,y:f})}})},"setParallelBTPos"),pBe=o(t=>{let e=Ile(t.parents.filter(n=>n!==null));if(!e)throw new Error(`Closest parent not found for commit ${t.id}`);let r=Os.get(e)?.y;if(r===void 0)throw new Error(`Closest parent position not found for commit ${t.id}`);return r},"findClosestParentPos"),mBe=o(t=>pBe(t)+df,"calculateCommitPosition"),gBe=o((t,e)=>{let r=Is.get(t.branch);if(!r)throw new Error(`Branch not found for commit ${t.id}`);let n=r.pos,i=e+ff;return Os.set(t.id,{x:n,y:i}),{x:n,y:i}},"setCommitPosition"),yBe=o((t,e,r)=>{let n=Is.get(t.branch);if(!n)throw new Error(`Branch not found for commit ${t.id}`);let i=e+r,a=n.pos;Os.set(t.id,{x:a,y:i})},"setRootPosition"),vBe=o((t,e,r,n,i,a)=>{if(a===Hr.HIGHLIGHT)t.append("rect").attr("x",r.x-10).attr("y",r.y-10).attr("width",20).attr("height",20).attr("class",`commit ${e.id} commit-highlight${i%E0} ${n}-outer`),t.append("rect").attr("x",r.x-6).attr("y",r.y-6).attr("width",12).attr("height",12).attr("class",`commit ${e.id} commit${i%E0} ${n}-inner`);else if(a===Hr.CHERRY_PICK)t.append("circle").attr("cx",r.x).attr("cy",r.y).attr("r",10).attr("class",`commit ${e.id} ${n}`),t.append("circle").attr("cx",r.x-3).attr("cy",r.y+2).attr("r",2.75).attr("fill","#fff").attr("class",`commit ${e.id} ${n}`),t.append("circle").attr("cx",r.x+3).attr("cy",r.y+2).attr("r",2.75).attr("fill","#fff").attr("class",`commit ${e.id} ${n}`),t.append("line").attr("x1",r.x+3).attr("y1",r.y+1).attr("x2",r.x).attr("y2",r.y-5).attr("stroke","#fff").attr("class",`commit ${e.id} ${n}`),t.append("line").attr("x1",r.x-3).attr("y1",r.y+1).attr("x2",r.x).attr("y2",r.y-5).attr("stroke","#fff").attr("class",`commit ${e.id} ${n}`);else{let s=t.append("circle");if(s.attr("cx",r.x),s.attr("cy",r.y),s.attr("r",e.type===Hr.MERGE?9:10),s.attr("class",`commit ${e.id} commit${i%E0}`),a===Hr.MERGE){let l=t.append("circle");l.attr("cx",r.x),l.attr("cy",r.y),l.attr("r",6),l.attr("class",`commit ${n} ${e.id} commit${i%E0}`)}a===Hr.REVERSE&&t.append("path").attr("d",`M ${r.x-5},${r.y-5}L${r.x+5},${r.y+5}M${r.x-5},${r.y+5}L${r.x+5},${r.y-5}`).attr("class",`commit ${n} ${e.id} commit${i%E0}`)}},"drawCommitBullet"),xBe=o((t,e,r,n)=>{if(e.type!==Hr.CHERRY_PICK&&(e.customId&&e.type===Hr.MERGE||e.type!==Hr.MERGE)&&Ko?.showCommitLabel){let i=t.append("g"),a=i.insert("rect").attr("class","commit-label-bkg"),s=i.append("text").attr("x",n).attr("y",r.y+25).attr("class","commit-label").text(e.id),l=s.node()?.getBBox();if(l&&(a.attr("x",r.posWithOffset-l.width/2-Hu).attr("y",r.y+13.5).attr("width",l.width+2*Hu).attr("height",l.height+2*Hu),Tr==="TB"||Tr==="BT"?(a.attr("x",r.x-(l.width+4*Oc+5)).attr("y",r.y-12),s.attr("x",r.x-(l.width+4*Oc)).attr("y",r.y+l.height-12)):s.attr("x",r.posWithOffset-l.width/2),Ko.rotateCommitLabel))if(Tr==="TB"||Tr==="BT")s.attr("transform","rotate(-45, "+r.x+", "+r.y+")"),a.attr("transform","rotate(-45, "+r.x+", "+r.y+")");else{let u=-7.5-(l.width+10)/25*9.5,h=10+l.width/25*8.5;i.attr("transform","translate("+u+", "+h+") rotate(-45, "+n+", "+r.y+")")}}},"drawCommitLabel"),bBe=o((t,e,r,n)=>{if(e.tags.length>0){let i=0,a=0,s=0,l=[];for(let u of e.tags.reverse()){let h=t.insert("polygon"),f=t.append("circle"),d=t.append("text").attr("y",r.y-16-i).attr("class","tag-label").text(u),p=d.node()?.getBBox();if(!p)throw new Error("Tag bbox not found");a=Math.max(a,p.width),s=Math.max(s,p.height),d.attr("x",r.posWithOffset-p.width/2),l.push({tag:d,hole:f,rect:h,yOffset:i}),i+=20}for(let{tag:u,hole:h,rect:f,yOffset:d}of l){let p=s/2,m=r.y-19.2-d;if(f.attr("class","tag-label-bkg").attr("points",` + ${n-a/2-Oc/2},${m+Hu} + ${n-a/2-Oc/2},${m-Hu} + ${r.posWithOffset-a/2-Oc},${m-p-Hu} + ${r.posWithOffset+a/2+Oc},${m-p-Hu} + ${r.posWithOffset+a/2+Oc},${m+p+Hu} + ${r.posWithOffset-a/2-Oc},${m+p+Hu}`),h.attr("cy",m).attr("cx",n-a/2+Oc/2).attr("r",1.5).attr("class","tag-hole"),Tr==="TB"||Tr==="BT"){let g=n+d;f.attr("class","tag-label-bkg").attr("points",` + ${r.x},${g+2} + ${r.x},${g-2} + ${r.x+ff},${g-p-2} + ${r.x+ff+a+4},${g-p-2} + ${r.x+ff+a+4},${g+p+2} + ${r.x+ff},${g+p+2}`).attr("transform","translate(12,12) rotate(45, "+r.x+","+n+")"),h.attr("cx",r.x+Oc/2).attr("cy",g).attr("transform","translate(12,12) rotate(45, "+r.x+","+n+")"),u.attr("x",r.x+5).attr("y",g+3).attr("transform","translate(14,14) rotate(45, "+r.x+","+n+")")}}}},"drawCommitTags"),wBe=o(t=>{switch(t.customType??t.type){case Hr.NORMAL:return"commit-normal";case Hr.REVERSE:return"commit-reverse";case Hr.HIGHLIGHT:return"commit-highlight";case Hr.MERGE:return"commit-merge";case Hr.CHERRY_PICK:return"commit-cherry-pick";default:return"commit-normal"}},"getCommitClassType"),TBe=o((t,e,r,n)=>{let i={x:0,y:0};if(t.parents.length>0){let a=Ile(t.parents);if(a){let s=n.get(a)??i;return e==="TB"?s.y+df:e==="BT"?(n.get(t.id)??i).y-df:s.x+df}}else return e==="TB"?tE:e==="BT"?(n.get(t.id)??i).y-df:0;return 0},"calculatePosition"),kBe=o((t,e,r)=>{let n=Tr==="BT"&&r?e:e+ff,i=Tr==="TB"||Tr==="BT"?n:Is.get(t.branch)?.pos,a=Tr==="TB"||Tr==="BT"?Is.get(t.branch)?.pos:n;if(a===void 0||i===void 0)throw new Error(`Position were undefined for commit ${t.id}`);return{x:a,y:i,posWithOffset:n}},"getCommitPosition"),Nle=o((t,e,r)=>{if(!Ko)throw new Error("GitGraph config not found");let n=t.append("g").attr("class","commit-bullets"),i=t.append("g").attr("class","commit-labels"),a=Tr==="TB"||Tr==="BT"?tE:0,s=[...e.keys()],l=Ko?.parallelCommits??!1,u=o((f,d)=>{let p=e.get(f)?.seq,m=e.get(d)?.seq;return p!==void 0&&m!==void 0?p-m:0},"sortKeys"),h=s.sort(u);Tr==="BT"&&(l&&dBe(h,e,a),h=h.reverse()),h.forEach(f=>{let d=e.get(f);if(!d)throw new Error(`Commit not found for key ${f}`);l&&(a=TBe(d,Tr,a,Os));let p=kBe(d,a,l);if(r){let m=wBe(d),g=d.customType??d.type,y=Is.get(d.branch)?.index??0;vBe(n,d,p,m,y,g),xBe(i,d,p,a),bBe(i,d,p,a)}Tr==="TB"||Tr==="BT"?Os.set(d.id,{x:p.x,y:p.posWithOffset}):Os.set(d.id,{x:p.posWithOffset,y:p.y}),a=Tr==="BT"&&l?a+df:a+df+ff,a>hf&&(hf=a)})},"drawCommits"),EBe=o((t,e,r,n,i)=>{let s=(Tr==="TB"||Tr==="BT"?r.xh.branch===s,"isOnBranchToGetCurve"),u=o(h=>h.seq>t.seq&&h.sequ(h)&&l(h))},"shouldRerouteArrow"),lx=o((t,e,r=0)=>{let n=t+Math.abs(t-e)/2;if(r>5)return n;if(rE.every(s=>Math.abs(s-n)>=10))return rE.push(n),n;let a=Math.abs(t-e);return lx(t,e-a/5,r+1)},"findLane"),CBe=o((t,e,r,n)=>{let i=Os.get(e.id),a=Os.get(r.id);if(i===void 0||a===void 0)throw new Error(`Commit positions not found for commits ${e.id} and ${r.id}`);let s=EBe(e,r,i,a,n),l="",u="",h=0,f=0,d=Is.get(r.branch)?.index;r.type===Hr.MERGE&&e.id!==r.parents[0]&&(d=Is.get(e.branch)?.index);let p;if(s){l="A 10 10, 0, 0, 0,",u="A 10 10, 0, 0, 1,",h=10,f=10;let m=i.ya.x&&(l="A 20 20, 0, 0, 0,",u="A 20 20, 0, 0, 1,",h=20,f=20,r.type===Hr.MERGE&&e.id!==r.parents[0]?p=`M ${i.x} ${i.y} L ${i.x} ${a.y-h} ${u} ${i.x-f} ${a.y} L ${a.x} ${a.y}`:p=`M ${i.x} ${i.y} L ${a.x+h} ${i.y} ${l} ${a.x} ${i.y+f} L ${a.x} ${a.y}`),i.x===a.x&&(p=`M ${i.x} ${i.y} L ${a.x} ${a.y}`)):Tr==="BT"?(i.xa.x&&(l="A 20 20, 0, 0, 0,",u="A 20 20, 0, 0, 1,",h=20,f=20,r.type===Hr.MERGE&&e.id!==r.parents[0]?p=`M ${i.x} ${i.y} L ${i.x} ${a.y+h} ${l} ${i.x-f} ${a.y} L ${a.x} ${a.y}`:p=`M ${i.x} ${i.y} L ${a.x-h} ${i.y} ${l} ${a.x} ${i.y-f} L ${a.x} ${a.y}`),i.x===a.x&&(p=`M ${i.x} ${i.y} L ${a.x} ${a.y}`)):(i.ya.y&&(r.type===Hr.MERGE&&e.id!==r.parents[0]?p=`M ${i.x} ${i.y} L ${a.x-h} ${i.y} ${l} ${a.x} ${i.y-f} L ${a.x} ${a.y}`:p=`M ${i.x} ${i.y} L ${i.x} ${a.y+h} ${u} ${i.x+f} ${a.y} L ${a.x} ${a.y}`),i.y===a.y&&(p=`M ${i.x} ${i.y} L ${a.x} ${a.y}`));if(p===void 0)throw new Error("Line definition not found");t.append("path").attr("d",p).attr("class","arrow arrow"+d%E0)},"drawArrow"),SBe=o((t,e)=>{let r=t.append("g").attr("class","commit-arrows");[...e.keys()].forEach(n=>{let i=e.get(n);i.parents&&i.parents.length>0&&i.parents.forEach(a=>{CBe(r,e.get(a),i,e)})})},"drawArrows"),ABe=o((t,e)=>{let r=t.append("g");e.forEach((n,i)=>{let a=i%E0,s=Is.get(n.name)?.pos;if(s===void 0)throw new Error(`Position not found for branch ${n.name}`);let l=r.append("line");l.attr("x1",0),l.attr("y1",s),l.attr("x2",hf),l.attr("y2",s),l.attr("class","branch branch"+a),Tr==="TB"?(l.attr("y1",tE),l.attr("x1",s),l.attr("y2",hf),l.attr("x2",s)):Tr==="BT"&&(l.attr("y1",hf),l.attr("x1",s),l.attr("y2",tE),l.attr("x2",s)),rE.push(s);let u=n.name,h=Mle(u),f=r.insert("rect"),p=r.insert("g").attr("class","branchLabel").insert("g").attr("class","label branch-label"+a);p.node().appendChild(h);let m=h.getBBox();f.attr("class","branchLabelBkg label"+a).attr("rx",4).attr("ry",4).attr("x",-m.width-4-(Ko?.rotateCommitLabel===!0?30:0)).attr("y",-m.height/2+8).attr("width",m.width+18).attr("height",m.height+4),p.attr("transform","translate("+(-m.width-14-(Ko?.rotateCommitLabel===!0?30:0))+", "+(s-m.height/2-1)+")"),Tr==="TB"?(f.attr("x",s-m.width/2-10).attr("y",0),p.attr("transform","translate("+(s-m.width/2-5)+", 0)")):Tr==="BT"?(f.attr("x",s-m.width/2-10).attr("y",hf),p.attr("transform","translate("+(s-m.width/2-5)+", "+hf+")")):f.attr("transform","translate(-19, "+(s-m.height/2)+")")})},"drawBranches"),_Be=o(function(t,e,r,n,i){return Is.set(t,{pos:e,index:r}),e+=50+(i?40:0)+(Tr==="TB"||Tr==="BT"?n.width/2:0),e},"setBranchPosition"),LBe=o(function(t,e,r,n){if(hBe(),V.debug("in gitgraph renderer",t+` +`,"id:",e,r),!Ko)throw new Error("GitGraph config not found");let i=Ko.rotateCommitLabel??!1,a=n.db;ox=a.getCommits();let s=a.getBranchesAsObjArray();Tr=a.getDirection();let l=$e(`[id="${e}"]`),u=0;s.forEach((h,f)=>{let d=Mle(h.name),p=l.append("g"),m=p.insert("g").attr("class","branchLabel"),g=m.insert("g").attr("class","label branch-label");g.node()?.appendChild(d);let y=d.getBBox();u=_Be(h.name,u,f,y,i),g.remove(),m.remove(),p.remove()}),Nle(l,ox,!1),Ko.showBranches&&ABe(l,s),SBe(l,ox),Nle(l,ox,!0),Lt.insertTitle(l,"gitTitleText",Ko.titleTopMargin??0,a.getDiagramTitle()),aS(void 0,l,Ko.diagramPadding,Ko.useMaxWidth)},"draw"),Ole={draw:LBe}});var DBe,Ble,Fle=R(()=>{"use strict";DBe=o(t=>` + .commit-id, + .commit-msg, + .branch-label { + fill: lightgrey; + color: lightgrey; + font-family: 'trebuchet ms', verdana, arial, sans-serif; + font-family: var(--mermaid-font-family); + } + ${[0,1,2,3,4,5,6,7].map(e=>` + .branch-label${e} { fill: ${t["gitBranchLabel"+e]}; } + .commit${e} { stroke: ${t["git"+e]}; fill: ${t["git"+e]}; } + .commit-highlight${e} { stroke: ${t["gitInv"+e]}; fill: ${t["gitInv"+e]}; } + .label${e} { fill: ${t["git"+e]}; } + .arrow${e} { stroke: ${t["git"+e]}; } + `).join(` +`)} + + .branch { + stroke-width: 1; + stroke: ${t.lineColor}; + stroke-dasharray: 2; + } + .commit-label { font-size: ${t.commitLabelFontSize}; fill: ${t.commitLabelColor};} + .commit-label-bkg { font-size: ${t.commitLabelFontSize}; fill: ${t.commitLabelBackground}; opacity: 0.5; } + .tag-label { font-size: ${t.tagLabelFontSize}; fill: ${t.tagLabelColor};} + .tag-label-bkg { fill: ${t.tagLabelBackground}; stroke: ${t.tagLabelBorder}; } + .tag-hole { fill: ${t.textColor}; } + + .commit-merge { + stroke: ${t.primaryColor}; + fill: ${t.primaryColor}; + } + .commit-reverse { + stroke: ${t.primaryColor}; + fill: ${t.primaryColor}; + stroke-width: 3; + } + .commit-highlight-outer { + } + .commit-highlight-inner { + stroke: ${t.primaryColor}; + fill: ${t.primaryColor}; + } + + .arrow { stroke-width: 8; stroke-linecap: round; fill: none} + .gitTitleText { + text-anchor: middle; + font-size: 18px; + fill: ${t.textColor}; + } +`,"getStyles"),Ble=DBe});var zle={};hr(zle,{diagram:()=>RBe});var RBe,Gle=R(()=>{"use strict";Rle();EI();Ple();Fle();RBe={parser:Dle,db:eE,renderer:Ole,styles:Ble}});var CI,Ule,Hle=R(()=>{"use strict";CI=function(){var t=o(function(I,C,O,D){for(O=O||{},D=I.length;D--;O[I[D]]=C);return O},"o"),e=[6,8,10,12,13,14,15,16,17,18,20,21,22,23,24,25,26,27,28,29,30,31,33,35,36,38,40],r=[1,26],n=[1,27],i=[1,28],a=[1,29],s=[1,30],l=[1,31],u=[1,32],h=[1,33],f=[1,34],d=[1,9],p=[1,10],m=[1,11],g=[1,12],y=[1,13],v=[1,14],x=[1,15],b=[1,16],w=[1,19],S=[1,20],T=[1,21],E=[1,22],_=[1,23],A=[1,25],L=[1,35],M={trace:o(function(){},"trace"),yy:{},symbols_:{error:2,start:3,gantt:4,document:5,EOF:6,line:7,SPACE:8,statement:9,NL:10,weekday:11,weekday_monday:12,weekday_tuesday:13,weekday_wednesday:14,weekday_thursday:15,weekday_friday:16,weekday_saturday:17,weekday_sunday:18,weekend:19,weekend_friday:20,weekend_saturday:21,dateFormat:22,inclusiveEndDates:23,topAxis:24,axisFormat:25,tickInterval:26,excludes:27,includes:28,todayMarker:29,title:30,acc_title:31,acc_title_value:32,acc_descr:33,acc_descr_value:34,acc_descr_multiline_value:35,section:36,clickStatement:37,taskTxt:38,taskData:39,click:40,callbackname:41,callbackargs:42,href:43,clickStatementDebug:44,$accept:0,$end:1},terminals_:{2:"error",4:"gantt",6:"EOF",8:"SPACE",10:"NL",12:"weekday_monday",13:"weekday_tuesday",14:"weekday_wednesday",15:"weekday_thursday",16:"weekday_friday",17:"weekday_saturday",18:"weekday_sunday",20:"weekend_friday",21:"weekend_saturday",22:"dateFormat",23:"inclusiveEndDates",24:"topAxis",25:"axisFormat",26:"tickInterval",27:"excludes",28:"includes",29:"todayMarker",30:"title",31:"acc_title",32:"acc_title_value",33:"acc_descr",34:"acc_descr_value",35:"acc_descr_multiline_value",36:"section",38:"taskTxt",39:"taskData",40:"click",41:"callbackname",42:"callbackargs",43:"href"},productions_:[0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[11,1],[11,1],[11,1],[11,1],[11,1],[11,1],[11,1],[19,1],[19,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,1],[9,2],[9,2],[9,1],[9,1],[9,1],[9,2],[37,2],[37,3],[37,3],[37,4],[37,3],[37,4],[37,2],[44,2],[44,3],[44,3],[44,4],[44,3],[44,4],[44,2]],performAction:o(function(C,O,D,P,F,B,$){var z=B.length-1;switch(F){case 1:return B[z-1];case 2:this.$=[];break;case 3:B[z-1].push(B[z]),this.$=B[z-1];break;case 4:case 5:this.$=B[z];break;case 6:case 7:this.$=[];break;case 8:P.setWeekday("monday");break;case 9:P.setWeekday("tuesday");break;case 10:P.setWeekday("wednesday");break;case 11:P.setWeekday("thursday");break;case 12:P.setWeekday("friday");break;case 13:P.setWeekday("saturday");break;case 14:P.setWeekday("sunday");break;case 15:P.setWeekend("friday");break;case 16:P.setWeekend("saturday");break;case 17:P.setDateFormat(B[z].substr(11)),this.$=B[z].substr(11);break;case 18:P.enableInclusiveEndDates(),this.$=B[z].substr(18);break;case 19:P.TopAxis(),this.$=B[z].substr(8);break;case 20:P.setAxisFormat(B[z].substr(11)),this.$=B[z].substr(11);break;case 21:P.setTickInterval(B[z].substr(13)),this.$=B[z].substr(13);break;case 22:P.setExcludes(B[z].substr(9)),this.$=B[z].substr(9);break;case 23:P.setIncludes(B[z].substr(9)),this.$=B[z].substr(9);break;case 24:P.setTodayMarker(B[z].substr(12)),this.$=B[z].substr(12);break;case 27:P.setDiagramTitle(B[z].substr(6)),this.$=B[z].substr(6);break;case 28:this.$=B[z].trim(),P.setAccTitle(this.$);break;case 29:case 30:this.$=B[z].trim(),P.setAccDescription(this.$);break;case 31:P.addSection(B[z].substr(8)),this.$=B[z].substr(8);break;case 33:P.addTask(B[z-1],B[z]),this.$="task";break;case 34:this.$=B[z-1],P.setClickEvent(B[z-1],B[z],null);break;case 35:this.$=B[z-2],P.setClickEvent(B[z-2],B[z-1],B[z]);break;case 36:this.$=B[z-2],P.setClickEvent(B[z-2],B[z-1],null),P.setLink(B[z-2],B[z]);break;case 37:this.$=B[z-3],P.setClickEvent(B[z-3],B[z-2],B[z-1]),P.setLink(B[z-3],B[z]);break;case 38:this.$=B[z-2],P.setClickEvent(B[z-2],B[z],null),P.setLink(B[z-2],B[z-1]);break;case 39:this.$=B[z-3],P.setClickEvent(B[z-3],B[z-1],B[z]),P.setLink(B[z-3],B[z-2]);break;case 40:this.$=B[z-1],P.setLink(B[z-1],B[z]);break;case 41:case 47:this.$=B[z-1]+" "+B[z];break;case 42:case 43:case 45:this.$=B[z-2]+" "+B[z-1]+" "+B[z];break;case 44:case 46:this.$=B[z-3]+" "+B[z-2]+" "+B[z-1]+" "+B[z];break}},"anonymous"),table:[{3:1,4:[1,2]},{1:[3]},t(e,[2,2],{5:3}),{6:[1,4],7:5,8:[1,6],9:7,10:[1,8],11:17,12:r,13:n,14:i,15:a,16:s,17:l,18:u,19:18,20:h,21:f,22:d,23:p,24:m,25:g,26:y,27:v,28:x,29:b,30:w,31:S,33:T,35:E,36:_,37:24,38:A,40:L},t(e,[2,7],{1:[2,1]}),t(e,[2,3]),{9:36,11:17,12:r,13:n,14:i,15:a,16:s,17:l,18:u,19:18,20:h,21:f,22:d,23:p,24:m,25:g,26:y,27:v,28:x,29:b,30:w,31:S,33:T,35:E,36:_,37:24,38:A,40:L},t(e,[2,5]),t(e,[2,6]),t(e,[2,17]),t(e,[2,18]),t(e,[2,19]),t(e,[2,20]),t(e,[2,21]),t(e,[2,22]),t(e,[2,23]),t(e,[2,24]),t(e,[2,25]),t(e,[2,26]),t(e,[2,27]),{32:[1,37]},{34:[1,38]},t(e,[2,30]),t(e,[2,31]),t(e,[2,32]),{39:[1,39]},t(e,[2,8]),t(e,[2,9]),t(e,[2,10]),t(e,[2,11]),t(e,[2,12]),t(e,[2,13]),t(e,[2,14]),t(e,[2,15]),t(e,[2,16]),{41:[1,40],43:[1,41]},t(e,[2,4]),t(e,[2,28]),t(e,[2,29]),t(e,[2,33]),t(e,[2,34],{42:[1,42],43:[1,43]}),t(e,[2,40],{41:[1,44]}),t(e,[2,35],{43:[1,45]}),t(e,[2,36]),t(e,[2,38],{42:[1,46]}),t(e,[2,37]),t(e,[2,39])],defaultActions:{},parseError:o(function(C,O){if(O.recoverable)this.trace(C);else{var D=new Error(C);throw D.hash=O,D}},"parseError"),parse:o(function(C){var O=this,D=[0],P=[],F=[null],B=[],$=this.table,z="",Y=0,Q=0,X=0,ie=2,j=1,J=B.slice.call(arguments,1),Z=Object.create(this.lexer),H={yy:{}};for(var q in this.yy)Object.prototype.hasOwnProperty.call(this.yy,q)&&(H.yy[q]=this.yy[q]);Z.setInput(C,H.yy),H.yy.lexer=Z,H.yy.parser=this,typeof Z.yylloc>"u"&&(Z.yylloc={});var K=Z.yylloc;B.push(K);var se=Z.options&&Z.options.ranges;typeof H.yy.parseError=="function"?this.parseError=H.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function ce(ge){D.length=D.length-2*ge,F.length=F.length-ge,B.length=B.length-ge}o(ce,"popStack");function ue(){var ge;return ge=P.pop()||Z.lex()||j,typeof ge!="number"&&(ge instanceof Array&&(P=ge,ge=P.pop()),ge=O.symbols_[ge]||ge),ge}o(ue,"lex");for(var te,De,oe,ke,Ie,Se,Ue={},Pe,_e,me,W;;){if(oe=D[D.length-1],this.defaultActions[oe]?ke=this.defaultActions[oe]:((te===null||typeof te>"u")&&(te=ue()),ke=$[oe]&&$[oe][te]),typeof ke>"u"||!ke.length||!ke[0]){var fe="";W=[];for(Pe in $[oe])this.terminals_[Pe]&&Pe>ie&&W.push("'"+this.terminals_[Pe]+"'");Z.showPosition?fe="Parse error on line "+(Y+1)+`: +`+Z.showPosition()+` +Expecting `+W.join(", ")+", got '"+(this.terminals_[te]||te)+"'":fe="Parse error on line "+(Y+1)+": Unexpected "+(te==j?"end of input":"'"+(this.terminals_[te]||te)+"'"),this.parseError(fe,{text:Z.match,token:this.terminals_[te]||te,line:Z.yylineno,loc:K,expected:W})}if(ke[0]instanceof Array&&ke.length>1)throw new Error("Parse Error: multiple actions possible at state: "+oe+", token: "+te);switch(ke[0]){case 1:D.push(te),F.push(Z.yytext),B.push(Z.yylloc),D.push(ke[1]),te=null,De?(te=De,De=null):(Q=Z.yyleng,z=Z.yytext,Y=Z.yylineno,K=Z.yylloc,X>0&&X--);break;case 2:if(_e=this.productions_[ke[1]][1],Ue.$=F[F.length-_e],Ue._$={first_line:B[B.length-(_e||1)].first_line,last_line:B[B.length-1].last_line,first_column:B[B.length-(_e||1)].first_column,last_column:B[B.length-1].last_column},se&&(Ue._$.range=[B[B.length-(_e||1)].range[0],B[B.length-1].range[1]]),Se=this.performAction.apply(Ue,[z,Q,Y,H.yy,ke[1],F,B].concat(J)),typeof Se<"u")return Se;_e&&(D=D.slice(0,-1*_e*2),F=F.slice(0,-1*_e),B=B.slice(0,-1*_e)),D.push(this.productions_[ke[1]][0]),F.push(Ue.$),B.push(Ue._$),me=$[D[D.length-2]][D[D.length-1]],D.push(me);break;case 3:return!0}}return!0},"parse")},N=function(){var I={EOF:1,parseError:o(function(O,D){if(this.yy.parser)this.yy.parser.parseError(O,D);else throw new Error(O)},"parseError"),setInput:o(function(C,O){return this.yy=O||this.yy||{},this._input=C,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:o(function(){var C=this._input[0];this.yytext+=C,this.yyleng++,this.offset++,this.match+=C,this.matched+=C;var O=C.match(/(?:\r\n?|\n).*/g);return O?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),C},"input"),unput:o(function(C){var O=C.length,D=C.split(/(?:\r\n?|\n)/g);this._input=C+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-O),this.offset-=O;var P=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),D.length-1&&(this.yylineno-=D.length-1);var F=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:D?(D.length===P.length?this.yylloc.first_column:0)+P[P.length-D.length].length-D[0].length:this.yylloc.first_column-O},this.options.ranges&&(this.yylloc.range=[F[0],F[0]+this.yyleng-O]),this.yyleng=this.yytext.length,this},"unput"),more:o(function(){return this._more=!0,this},"more"),reject:o(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:o(function(C){this.unput(this.match.slice(C))},"less"),pastInput:o(function(){var C=this.matched.substr(0,this.matched.length-this.match.length);return(C.length>20?"...":"")+C.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:o(function(){var C=this.match;return C.length<20&&(C+=this._input.substr(0,20-C.length)),(C.substr(0,20)+(C.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:o(function(){var C=this.pastInput(),O=new Array(C.length+1).join("-");return C+this.upcomingInput()+` +`+O+"^"},"showPosition"),test_match:o(function(C,O){var D,P,F;if(this.options.backtrack_lexer&&(F={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(F.yylloc.range=this.yylloc.range.slice(0))),P=C[0].match(/(?:\r\n?|\n).*/g),P&&(this.yylineno+=P.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:P?P[P.length-1].length-P[P.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+C[0].length},this.yytext+=C[0],this.match+=C[0],this.matches=C,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(C[0].length),this.matched+=C[0],D=this.performAction.call(this,this.yy,this,O,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),D)return D;if(this._backtrack){for(var B in F)this[B]=F[B];return!1}return!1},"test_match"),next:o(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var C,O,D,P;this._more||(this.yytext="",this.match="");for(var F=this._currentRules(),B=0;BO[0].length)){if(O=D,P=B,this.options.backtrack_lexer){if(C=this.test_match(D,F[B]),C!==!1)return C;if(this._backtrack){O=!1;continue}else return!1}else if(!this.options.flex)break}return O?(C=this.test_match(O,F[P]),C!==!1?C:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:o(function(){var O=this.next();return O||this.lex()},"lex"),begin:o(function(O){this.conditionStack.push(O)},"begin"),popState:o(function(){var O=this.conditionStack.length-1;return O>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:o(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:o(function(O){return O=this.conditionStack.length-1-Math.abs(O||0),O>=0?this.conditionStack[O]:"INITIAL"},"topState"),pushState:o(function(O){this.begin(O)},"pushState"),stateStackSize:o(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:o(function(O,D,P,F){var B=F;switch(P){case 0:return this.begin("open_directive"),"open_directive";break;case 1:return this.begin("acc_title"),31;break;case 2:return this.popState(),"acc_title_value";break;case 3:return this.begin("acc_descr"),33;break;case 4:return this.popState(),"acc_descr_value";break;case 5:this.begin("acc_descr_multiline");break;case 6:this.popState();break;case 7:return"acc_descr_multiline_value";case 8:break;case 9:break;case 10:break;case 11:return 10;case 12:break;case 13:break;case 14:this.begin("href");break;case 15:this.popState();break;case 16:return 43;case 17:this.begin("callbackname");break;case 18:this.popState();break;case 19:this.popState(),this.begin("callbackargs");break;case 20:return 41;case 21:this.popState();break;case 22:return 42;case 23:this.begin("click");break;case 24:this.popState();break;case 25:return 40;case 26:return 4;case 27:return 22;case 28:return 23;case 29:return 24;case 30:return 25;case 31:return 26;case 32:return 28;case 33:return 27;case 34:return 29;case 35:return 12;case 36:return 13;case 37:return 14;case 38:return 15;case 39:return 16;case 40:return 17;case 41:return 18;case 42:return 20;case 43:return 21;case 44:return"date";case 45:return 30;case 46:return"accDescription";case 47:return 36;case 48:return 38;case 49:return 39;case 50:return":";case 51:return 6;case 52:return"INVALID"}},"anonymous"),rules:[/^(?:%%\{)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:%%(?!\{)*[^\n]*)/i,/^(?:[^\}]%%*[^\n]*)/i,/^(?:%%*[^\n]*[\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:%[^\n]*)/i,/^(?:href[\s]+["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:call[\s]+)/i,/^(?:\([\s]*\))/i,/^(?:\()/i,/^(?:[^(]*)/i,/^(?:\))/i,/^(?:[^)]*)/i,/^(?:click[\s]+)/i,/^(?:[\s\n])/i,/^(?:[^\s\n]*)/i,/^(?:gantt\b)/i,/^(?:dateFormat\s[^#\n;]+)/i,/^(?:inclusiveEndDates\b)/i,/^(?:topAxis\b)/i,/^(?:axisFormat\s[^#\n;]+)/i,/^(?:tickInterval\s[^#\n;]+)/i,/^(?:includes\s[^#\n;]+)/i,/^(?:excludes\s[^#\n;]+)/i,/^(?:todayMarker\s[^\n;]+)/i,/^(?:weekday\s+monday\b)/i,/^(?:weekday\s+tuesday\b)/i,/^(?:weekday\s+wednesday\b)/i,/^(?:weekday\s+thursday\b)/i,/^(?:weekday\s+friday\b)/i,/^(?:weekday\s+saturday\b)/i,/^(?:weekday\s+sunday\b)/i,/^(?:weekend\s+friday\b)/i,/^(?:weekend\s+saturday\b)/i,/^(?:\d\d\d\d-\d\d-\d\d\b)/i,/^(?:title\s[^\n]+)/i,/^(?:accDescription\s[^#\n;]+)/i,/^(?:section\s[^\n]+)/i,/^(?:[^:\n]+)/i,/^(?::[^#\n;]+)/i,/^(?::)/i,/^(?:$)/i,/^(?:.)/i],conditions:{acc_descr_multiline:{rules:[6,7],inclusive:!1},acc_descr:{rules:[4],inclusive:!1},acc_title:{rules:[2],inclusive:!1},callbackargs:{rules:[21,22],inclusive:!1},callbackname:{rules:[18,19,20],inclusive:!1},href:{rules:[15,16],inclusive:!1},click:{rules:[24,25],inclusive:!1},INITIAL:{rules:[0,1,3,5,8,9,10,11,12,13,14,17,23,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52],inclusive:!0}}};return I}();M.lexer=N;function k(){this.yy={}}return o(k,"Parser"),k.prototype=M,M.Parser=k,new k}();CI.parser=CI;Ule=CI});var Yle=gi((SI,AI)=>{"use strict";(function(t,e){typeof SI=="object"&&typeof AI<"u"?AI.exports=e():typeof define=="function"&&define.amd?define(e):(t=typeof globalThis<"u"?globalThis:t||self).dayjs_plugin_isoWeek=e()})(SI,function(){"use strict";var t="day";return function(e,r,n){var i=o(function(l){return l.add(4-l.isoWeekday(),t)},"a"),a=r.prototype;a.isoWeekYear=function(){return i(this).year()},a.isoWeek=function(l){if(!this.$utils().u(l))return this.add(7*(l-this.isoWeek()),t);var u,h,f,d,p=i(this),m=(u=this.isoWeekYear(),h=this.$u,f=(h?n.utc:n)().year(u).startOf("year"),d=4-f.isoWeekday(),f.isoWeekday()>4&&(d+=7),f.add(d,t));return p.diff(m,"week")+1},a.isoWeekday=function(l){return this.$utils().u(l)?this.day()||7:this.day(this.day()%7?l:l-7)};var s=a.startOf;a.startOf=function(l,u){var h=this.$utils(),f=!!h.u(u)||u;return h.p(l)==="isoweek"?f?this.date(this.date()-(this.isoWeekday()-1)).startOf("day"):this.date(this.date()-1-(this.isoWeekday()-1)+7).endOf("day"):s.bind(this)(l,u)}}})});var Wle=gi((_I,LI)=>{"use strict";(function(t,e){typeof _I=="object"&&typeof LI<"u"?LI.exports=e():typeof define=="function"&&define.amd?define(e):(t=typeof globalThis<"u"?globalThis:t||self).dayjs_plugin_customParseFormat=e()})(_I,function(){"use strict";var t={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},e=/(\[[^[]*\])|([-_:/.,()\s]+)|(A|a|Q|YYYY|YY?|ww?|MM?M?M?|Do|DD?|hh?|HH?|mm?|ss?|S{1,3}|z|ZZ?)/g,r=/\d/,n=/\d\d/,i=/\d\d?/,a=/\d*[^-_:/,()\s\d]+/,s={},l=o(function(g){return(g=+g)+(g>68?1900:2e3)},"a"),u=o(function(g){return function(y){this[g]=+y}},"f"),h=[/[+-]\d\d:?(\d\d)?|Z/,function(g){(this.zone||(this.zone={})).offset=function(y){if(!y||y==="Z")return 0;var v=y.match(/([+-]|\d\d)/g),x=60*v[1]+(+v[2]||0);return x===0?0:v[0]==="+"?-x:x}(g)}],f=o(function(g){var y=s[g];return y&&(y.indexOf?y:y.s.concat(y.f))},"u"),d=o(function(g,y){var v,x=s.meridiem;if(x){for(var b=1;b<=24;b+=1)if(g.indexOf(x(b,0,y))>-1){v=b>12;break}}else v=g===(y?"pm":"PM");return v},"d"),p={A:[a,function(g){this.afternoon=d(g,!1)}],a:[a,function(g){this.afternoon=d(g,!0)}],Q:[r,function(g){this.month=3*(g-1)+1}],S:[r,function(g){this.milliseconds=100*+g}],SS:[n,function(g){this.milliseconds=10*+g}],SSS:[/\d{3}/,function(g){this.milliseconds=+g}],s:[i,u("seconds")],ss:[i,u("seconds")],m:[i,u("minutes")],mm:[i,u("minutes")],H:[i,u("hours")],h:[i,u("hours")],HH:[i,u("hours")],hh:[i,u("hours")],D:[i,u("day")],DD:[n,u("day")],Do:[a,function(g){var y=s.ordinal,v=g.match(/\d+/);if(this.day=v[0],y)for(var x=1;x<=31;x+=1)y(x).replace(/\[|\]/g,"")===g&&(this.day=x)}],w:[i,u("week")],ww:[n,u("week")],M:[i,u("month")],MM:[n,u("month")],MMM:[a,function(g){var y=f("months"),v=(f("monthsShort")||y.map(function(x){return x.slice(0,3)})).indexOf(g)+1;if(v<1)throw new Error;this.month=v%12||v}],MMMM:[a,function(g){var y=f("months").indexOf(g)+1;if(y<1)throw new Error;this.month=y%12||y}],Y:[/[+-]?\d+/,u("year")],YY:[n,function(g){this.year=l(g)}],YYYY:[/\d{4}/,u("year")],Z:h,ZZ:h};function m(g){var y,v;y=g,v=s&&s.formats;for(var x=(g=y.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g,function(A,L,M){var N=M&&M.toUpperCase();return L||v[M]||t[M]||v[N].replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,function(k,I,C){return I||C.slice(1)})})).match(e),b=x.length,w=0;w-1)return new Date((D==="X"?1e3:1)*O);var B=m(D)(O),$=B.year,z=B.month,Y=B.day,Q=B.hours,X=B.minutes,ie=B.seconds,j=B.milliseconds,J=B.zone,Z=B.week,H=new Date,q=Y||($||z?1:H.getDate()),K=$||H.getFullYear(),se=0;$&&!z||(se=z>0?z-1:H.getMonth());var ce,ue=Q||0,te=X||0,De=ie||0,oe=j||0;return J?new Date(Date.UTC(K,se,q,ue,te,De,oe+60*J.offset*1e3)):P?new Date(Date.UTC(K,se,q,ue,te,De,oe)):(ce=new Date(K,se,q,ue,te,De,oe),Z&&(ce=F(ce).week(Z).toDate()),ce)}catch{return new Date("")}}(S,_,T,v),this.init(),N&&N!==!0&&(this.$L=this.locale(N).$L),M&&S!=this.format(_)&&(this.$d=new Date("")),s={}}else if(_ instanceof Array)for(var k=_.length,I=1;I<=k;I+=1){E[1]=_[I-1];var C=v.apply(this,E);if(C.isValid()){this.$d=C.$d,this.$L=C.$L,this.init();break}I===k&&(this.$d=new Date(""))}else b.call(this,w)}}})});var qle=gi((DI,RI)=>{"use strict";(function(t,e){typeof DI=="object"&&typeof RI<"u"?RI.exports=e():typeof define=="function"&&define.amd?define(e):(t=typeof globalThis<"u"?globalThis:t||self).dayjs_plugin_advancedFormat=e()})(DI,function(){"use strict";return function(t,e){var r=e.prototype,n=r.format;r.format=function(i){var a=this,s=this.$locale();if(!this.isValid())return n.bind(this)(i);var l=this.$utils(),u=(i||"YYYY-MM-DDTHH:mm:ssZ").replace(/\[([^\]]+)]|Q|wo|ww|w|WW|W|zzz|z|gggg|GGGG|Do|X|x|k{1,2}|S/g,function(h){switch(h){case"Q":return Math.ceil((a.$M+1)/3);case"Do":return s.ordinal(a.$D);case"gggg":return a.weekYear();case"GGGG":return a.isoWeekYear();case"wo":return s.ordinal(a.week(),"W");case"w":case"ww":return l.s(a.week(),h==="w"?1:2,"0");case"W":case"WW":return l.s(a.isoWeek(),h==="W"?1:2,"0");case"k":case"kk":return l.s(String(a.$H===0?24:a.$H),h==="k"?1:2,"0");case"X":return Math.floor(a.$d.getTime()/1e3);case"x":return a.$d.getTime();case"z":return"["+a.offsetName()+"]";case"zzz":return"["+a.offsetName("long")+"]";default:return h}});return n.bind(this)(u)}}})});function cce(t,e,r){let n=!0;for(;n;)n=!1,r.forEach(function(i){let a="^\\s*"+i+"\\s*$",s=new RegExp(a);t[0].match(s)&&(e[i]=!0,t.shift(1),n=!0)})}var Kle,yo,Qle,Zle,Jle,Xle,Pc,OI,PI,BI,cx,ux,FI,zI,aE,Rg,GI,ece,$I,hx,VI,UI,sE,NI,OBe,PBe,BBe,FBe,zBe,GBe,$Be,VBe,UBe,HBe,YBe,WBe,qBe,XBe,jBe,KBe,QBe,ZBe,JBe,eFe,tFe,rFe,nFe,tce,iFe,aFe,sFe,rce,oFe,MI,nce,ice,nE,Dg,lFe,cFe,II,iE,zi,ace,uFe,C0,hFe,jle,fFe,sce,dFe,oce,pFe,mFe,lce,uce=R(()=>{"use strict";Kle=Xi(Up(),1),yo=Xi(Nb(),1),Qle=Xi(Yle(),1),Zle=Xi(Wle(),1),Jle=Xi(qle(),1);ut();_t();xr();bi();yo.default.extend(Qle.default);yo.default.extend(Zle.default);yo.default.extend(Jle.default);Xle={friday:5,saturday:6},Pc="",OI="",BI="",cx=[],ux=[],FI=new Map,zI=[],aE=[],Rg="",GI="",ece=["active","done","crit","milestone"],$I=[],hx=!1,VI=!1,UI="sunday",sE="saturday",NI=0,OBe=o(function(){zI=[],aE=[],Rg="",$I=[],nE=0,II=void 0,iE=void 0,zi=[],Pc="",OI="",GI="",PI=void 0,BI="",cx=[],ux=[],hx=!1,VI=!1,NI=0,FI=new Map,vr(),UI="sunday",sE="saturday"},"clear"),PBe=o(function(t){OI=t},"setAxisFormat"),BBe=o(function(){return OI},"getAxisFormat"),FBe=o(function(t){PI=t},"setTickInterval"),zBe=o(function(){return PI},"getTickInterval"),GBe=o(function(t){BI=t},"setTodayMarker"),$Be=o(function(){return BI},"getTodayMarker"),VBe=o(function(t){Pc=t},"setDateFormat"),UBe=o(function(){hx=!0},"enableInclusiveEndDates"),HBe=o(function(){return hx},"endDatesAreInclusive"),YBe=o(function(){VI=!0},"enableTopAxis"),WBe=o(function(){return VI},"topAxisEnabled"),qBe=o(function(t){GI=t},"setDisplayMode"),XBe=o(function(){return GI},"getDisplayMode"),jBe=o(function(){return Pc},"getDateFormat"),KBe=o(function(t){cx=t.toLowerCase().split(/[\s,]+/)},"setIncludes"),QBe=o(function(){return cx},"getIncludes"),ZBe=o(function(t){ux=t.toLowerCase().split(/[\s,]+/)},"setExcludes"),JBe=o(function(){return ux},"getExcludes"),eFe=o(function(){return FI},"getLinks"),tFe=o(function(t){Rg=t,zI.push(t)},"addSection"),rFe=o(function(){return zI},"getSections"),nFe=o(function(){let t=jle(),e=10,r=0;for(;!t&&r[\d\w- ]+)/.exec(r);if(i!==null){let s=null;for(let u of i.groups.ids.split(" ")){let h=C0(u);h!==void 0&&(!s||h.endTime>s.endTime)&&(s=h)}if(s)return s.endTime;let l=new Date;return l.setHours(0,0,0,0),l}let a=(0,yo.default)(r,e.trim(),!0);if(a.isValid())return a.toDate();{V.debug("Invalid date:"+r),V.debug("With date format:"+e.trim());let s=new Date(r);if(s===void 0||isNaN(s.getTime())||s.getFullYear()<-1e4||s.getFullYear()>1e4)throw new Error("Invalid date:"+r);return s}},"getStartDate"),nce=o(function(t){let e=/^(\d+(?:\.\d+)?)([Mdhmswy]|ms)$/.exec(t.trim());return e!==null?[Number.parseFloat(e[1]),e[2]]:[NaN,"ms"]},"parseDuration"),ice=o(function(t,e,r,n=!1){r=r.trim();let a=/^until\s+(?[\d\w- ]+)/.exec(r);if(a!==null){let f=null;for(let p of a.groups.ids.split(" ")){let m=C0(p);m!==void 0&&(!f||m.startTime{window.open(r,"_self")}),FI.set(n,r))}),sce(t,"clickable")},"setLink"),sce=o(function(t,e){t.split(",").forEach(function(r){let n=C0(r);n!==void 0&&n.classes.push(e)})},"setClass"),dFe=o(function(t,e,r){if(de().securityLevel!=="loose"||e===void 0)return;let n=[];if(typeof r=="string"){n=r.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);for(let a=0;a{Lt.runFunc(e,...n)})},"setClickFun"),oce=o(function(t,e){$I.push(function(){let r=document.querySelector(`[id="${t}"]`);r!==null&&r.addEventListener("click",function(){e()})},function(){let r=document.querySelector(`[id="${t}-text"]`);r!==null&&r.addEventListener("click",function(){e()})})},"pushFun"),pFe=o(function(t,e,r){t.split(",").forEach(function(n){dFe(n,e,r)}),sce(t,"clickable")},"setClickEvent"),mFe=o(function(t){$I.forEach(function(e){e(t)})},"bindFunctions"),lce={getConfig:o(()=>de().gantt,"getConfig"),clear:OBe,setDateFormat:VBe,getDateFormat:jBe,enableInclusiveEndDates:UBe,endDatesAreInclusive:HBe,enableTopAxis:YBe,topAxisEnabled:WBe,setAxisFormat:PBe,getAxisFormat:BBe,setTickInterval:FBe,getTickInterval:zBe,setTodayMarker:GBe,getTodayMarker:$Be,setAccTitle:kr,getAccTitle:Ar,setDiagramTitle:nn,getDiagramTitle:Xr,setDisplayMode:qBe,getDisplayMode:XBe,setAccDescription:_r,getAccDescription:Lr,addSection:tFe,getSections:rFe,getTasks:nFe,addTask:uFe,findTaskById:C0,addTaskOrg:hFe,setIncludes:KBe,getIncludes:QBe,setExcludes:ZBe,getExcludes:JBe,setClickEvent:pFe,setLink:fFe,getLinks:eFe,bindFunctions:mFe,parseDuration:nce,isInvalidDate:tce,setWeekday:iFe,getWeekday:aFe,setWeekend:sFe};o(cce,"getTaskTags")});var oE,gFe,hce,yFe,Yu,vFe,fce,dce=R(()=>{"use strict";oE=Xi(Nb(),1);ut();Zt();rr();_t();Yn();gFe=o(function(){V.debug("Something is calling, setConf, remove the call")},"setConf"),hce={monday:_h,tuesday:k3,wednesday:E3,thursday:cc,friday:C3,saturday:S3,sunday:yl},yFe=o((t,e)=>{let r=[...t].map(()=>-1/0),n=[...t].sort((a,s)=>a.startTime-s.startTime||a.order-s.order),i=0;for(let a of n)for(let s=0;s=r[s]){r[s]=a.endTime,a.order=s+e,s>i&&(i=s);break}return i},"getMaxIntersections"),vFe=o(function(t,e,r,n){let i=de().gantt,a=de().securityLevel,s;a==="sandbox"&&(s=$e("#i"+e));let l=a==="sandbox"?$e(s.nodes()[0].contentDocument.body):$e("body"),u=a==="sandbox"?s.nodes()[0].contentDocument:document,h=u.getElementById(e);Yu=h.parentElement.offsetWidth,Yu===void 0&&(Yu=1200),i.useWidth!==void 0&&(Yu=i.useWidth);let f=n.db.getTasks(),d=[];for(let A of f)d.push(A.type);d=_(d);let p={},m=2*i.topPadding;if(n.db.getDisplayMode()==="compact"||i.displayMode==="compact"){let A={};for(let M of f)A[M.section]===void 0?A[M.section]=[M]:A[M.section].push(M);let L=0;for(let M of Object.keys(A)){let N=yFe(A[M],L)+1;L+=N,m+=N*(i.barHeight+i.barGap),p[M]=N}}else{m+=f.length*(i.barHeight+i.barGap);for(let A of d)p[A]=f.filter(L=>L.type===A).length}h.setAttribute("viewBox","0 0 "+Yu+" "+m);let g=l.select(`[id="${e}"]`),y=L3().domain([I4(f,function(A){return A.startTime}),M4(f,function(A){return A.endTime})]).rangeRound([0,Yu-i.leftPadding-i.rightPadding]);function v(A,L){let M=A.startTime,N=L.startTime,k=0;return M>N?k=1:M$.order))].map($=>A.find(z=>z.order===$));g.append("g").selectAll("rect").data(D).enter().append("rect").attr("x",0).attr("y",function($,z){return z=$.order,z*L+M-2}).attr("width",function(){return C-i.rightPadding/2}).attr("height",L).attr("class",function($){for(let[z,Y]of d.entries())if($.type===Y)return"section section"+z%i.numberSectionStyles;return"section section0"});let P=g.append("g").selectAll("rect").data(A).enter(),F=n.db.getLinks();if(P.append("rect").attr("id",function($){return $.id}).attr("rx",3).attr("ry",3).attr("x",function($){return $.milestone?y($.startTime)+N+.5*(y($.endTime)-y($.startTime))-.5*k:y($.startTime)+N}).attr("y",function($,z){return z=$.order,z*L+M}).attr("width",function($){return $.milestone?k:y($.renderEndTime||$.endTime)-y($.startTime)}).attr("height",k).attr("transform-origin",function($,z){return z=$.order,(y($.startTime)+N+.5*(y($.endTime)-y($.startTime))).toString()+"px "+(z*L+M+.5*k).toString()+"px"}).attr("class",function($){let z="task",Y="";$.classes.length>0&&(Y=$.classes.join(" "));let Q=0;for(let[ie,j]of d.entries())$.type===j&&(Q=ie%i.numberSectionStyles);let X="";return $.active?$.crit?X+=" activeCrit":X=" active":$.done?$.crit?X=" doneCrit":X=" done":$.crit&&(X+=" crit"),X.length===0&&(X=" task"),$.milestone&&(X=" milestone "+X),X+=Q,X+=" "+Y,z+X}),P.append("text").attr("id",function($){return $.id+"-text"}).text(function($){return $.task}).attr("font-size",i.fontSize).attr("x",function($){let z=y($.startTime),Y=y($.renderEndTime||$.endTime);$.milestone&&(z+=.5*(y($.endTime)-y($.startTime))-.5*k),$.milestone&&(Y=z+k);let Q=this.getBBox().width;return Q>Y-z?Y+Q+1.5*i.leftPadding>C?z+N-5:Y+N+5:(Y-z)/2+z+N}).attr("y",function($,z){return z=$.order,z*L+i.barHeight/2+(i.fontSize/2-2)+M}).attr("text-height",k).attr("class",function($){let z=y($.startTime),Y=y($.endTime);$.milestone&&(Y=z+k);let Q=this.getBBox().width,X="";$.classes.length>0&&(X=$.classes.join(" "));let ie=0;for(let[J,Z]of d.entries())$.type===Z&&(ie=J%i.numberSectionStyles);let j="";return $.active&&($.crit?j="activeCritText"+ie:j="activeText"+ie),$.done?$.crit?j=j+" doneCritText"+ie:j=j+" doneText"+ie:$.crit&&(j=j+" critText"+ie),$.milestone&&(j+=" milestoneText"),Q>Y-z?Y+Q+1.5*i.leftPadding>C?X+" taskTextOutsideLeft taskTextOutside"+ie+" "+j:X+" taskTextOutsideRight taskTextOutside"+ie+" "+j+" width-"+Q:X+" taskText taskText"+ie+" "+j+" width-"+Q}),de().securityLevel==="sandbox"){let $;$=$e("#i"+e);let z=$.nodes()[0].contentDocument;P.filter(function(Y){return F.has(Y.id)}).each(function(Y){var Q=z.querySelector("#"+Y.id),X=z.querySelector("#"+Y.id+"-text");let ie=Q.parentNode;var j=z.createElement("a");j.setAttribute("xlink:href",F.get(Y.id)),j.setAttribute("target","_top"),ie.appendChild(j),j.appendChild(Q),j.appendChild(X)})}}o(b,"drawRects");function w(A,L,M,N,k,I,C,O){if(C.length===0&&O.length===0)return;let D,P;for(let{startTime:Q,endTime:X}of I)(D===void 0||QP)&&(P=X);if(!D||!P)return;if((0,oE.default)(P).diff((0,oE.default)(D),"year")>5){V.warn("The difference between the min and max time is more than 5 years. This will cause performance issues. Skipping drawing exclude days.");return}let F=n.db.getDateFormat(),B=[],$=null,z=(0,oE.default)(D);for(;z.valueOf()<=P;)n.db.isInvalidDate(z,F,C,O)?$?$.end=z:$={start:z,end:z}:$&&(B.push($),$=null),z=z.add(1,"d");g.append("g").selectAll("rect").data(B).enter().append("rect").attr("id",function(Q){return"exclude-"+Q.start.format("YYYY-MM-DD")}).attr("x",function(Q){return y(Q.start)+M}).attr("y",i.gridLineStartPadding).attr("width",function(Q){let X=Q.end.add(1,"day");return y(X)-y(Q.start)}).attr("height",k-L-i.gridLineStartPadding).attr("transform-origin",function(Q,X){return(y(Q.start)+M+.5*(y(Q.end)-y(Q.start))).toString()+"px "+(X*A+.5*k).toString()+"px"}).attr("class","exclude-range")}o(w,"drawExcludeDays");function S(A,L,M,N){let k=vS(y).tickSize(-N+L+i.gridLineStartPadding).tickFormat(md(n.db.getAxisFormat()||i.axisFormat||"%Y-%m-%d")),C=/^([1-9]\d*)(millisecond|second|minute|hour|day|week|month)$/.exec(n.db.getTickInterval()||i.tickInterval);if(C!==null){let O=C[1],D=C[2],P=n.db.getWeekday()||i.weekday;switch(D){case"millisecond":k.ticks(oc.every(O));break;case"second":k.ticks(Ks.every(O));break;case"minute":k.ticks(gu.every(O));break;case"hour":k.ticks(yu.every(O));break;case"day":k.ticks(Do.every(O));break;case"week":k.ticks(hce[P].every(O));break;case"month":k.ticks(vu.every(O));break}}if(g.append("g").attr("class","grid").attr("transform","translate("+A+", "+(N-50)+")").call(k).selectAll("text").style("text-anchor","middle").attr("fill","#000").attr("stroke","none").attr("font-size",10).attr("dy","1em"),n.db.topAxisEnabled()||i.topAxis){let O=yS(y).tickSize(-N+L+i.gridLineStartPadding).tickFormat(md(n.db.getAxisFormat()||i.axisFormat||"%Y-%m-%d"));if(C!==null){let D=C[1],P=C[2],F=n.db.getWeekday()||i.weekday;switch(P){case"millisecond":O.ticks(oc.every(D));break;case"second":O.ticks(Ks.every(D));break;case"minute":O.ticks(gu.every(D));break;case"hour":O.ticks(yu.every(D));break;case"day":O.ticks(Do.every(D));break;case"week":O.ticks(hce[F].every(D));break;case"month":O.ticks(vu.every(D));break}}g.append("g").attr("class","grid").attr("transform","translate("+A+", "+L+")").call(O).selectAll("text").style("text-anchor","middle").attr("fill","#000").attr("stroke","none").attr("font-size",10)}}o(S,"makeGrid");function T(A,L){let M=0,N=Object.keys(p).map(k=>[k,p[k]]);g.append("g").selectAll("text").data(N).enter().append(function(k){let I=k[0].split(We.lineBreakRegex),C=-(I.length-1)/2,O=u.createElementNS("http://www.w3.org/2000/svg","text");O.setAttribute("dy",C+"em");for(let[D,P]of I.entries()){let F=u.createElementNS("http://www.w3.org/2000/svg","tspan");F.setAttribute("alignment-baseline","central"),F.setAttribute("x","10"),D>0&&F.setAttribute("dy","1em"),F.textContent=P,O.appendChild(F)}return O}).attr("x",10).attr("y",function(k,I){if(I>0)for(let C=0;C{"use strict";xFe=o(t=>` + .mermaid-main-font { + font-family: var(--mermaid-font-family, "trebuchet ms", verdana, arial, sans-serif); + } + + .exclude-range { + fill: ${t.excludeBkgColor}; + } + + .section { + stroke: none; + opacity: 0.2; + } + + .section0 { + fill: ${t.sectionBkgColor}; + } + + .section2 { + fill: ${t.sectionBkgColor2}; + } + + .section1, + .section3 { + fill: ${t.altSectionBkgColor}; + opacity: 0.2; + } + + .sectionTitle0 { + fill: ${t.titleColor}; + } + + .sectionTitle1 { + fill: ${t.titleColor}; + } + + .sectionTitle2 { + fill: ${t.titleColor}; + } + + .sectionTitle3 { + fill: ${t.titleColor}; + } + + .sectionTitle { + text-anchor: start; + font-family: var(--mermaid-font-family, "trebuchet ms", verdana, arial, sans-serif); + } + + + /* Grid and axis */ + + .grid .tick { + stroke: ${t.gridColor}; + opacity: 0.8; + shape-rendering: crispEdges; + } + + .grid .tick text { + font-family: ${t.fontFamily}; + fill: ${t.textColor}; + } + + .grid path { + stroke-width: 0; + } + + + /* Today line */ + + .today { + fill: none; + stroke: ${t.todayLineColor}; + stroke-width: 2px; + } + + + /* Task styling */ + + /* Default task */ + + .task { + stroke-width: 2; + } + + .taskText { + text-anchor: middle; + font-family: var(--mermaid-font-family, "trebuchet ms", verdana, arial, sans-serif); + } + + .taskTextOutsideRight { + fill: ${t.taskTextDarkColor}; + text-anchor: start; + font-family: var(--mermaid-font-family, "trebuchet ms", verdana, arial, sans-serif); + } + + .taskTextOutsideLeft { + fill: ${t.taskTextDarkColor}; + text-anchor: end; + } + + + /* Special case clickable */ + + .task.clickable { + cursor: pointer; + } + + .taskText.clickable { + cursor: pointer; + fill: ${t.taskTextClickableColor} !important; + font-weight: bold; + } + + .taskTextOutsideLeft.clickable { + cursor: pointer; + fill: ${t.taskTextClickableColor} !important; + font-weight: bold; + } + + .taskTextOutsideRight.clickable { + cursor: pointer; + fill: ${t.taskTextClickableColor} !important; + font-weight: bold; + } + + + /* Specific task settings for the sections*/ + + .taskText0, + .taskText1, + .taskText2, + .taskText3 { + fill: ${t.taskTextColor}; + } + + .task0, + .task1, + .task2, + .task3 { + fill: ${t.taskBkgColor}; + stroke: ${t.taskBorderColor}; + } + + .taskTextOutside0, + .taskTextOutside2 + { + fill: ${t.taskTextOutsideColor}; + } + + .taskTextOutside1, + .taskTextOutside3 { + fill: ${t.taskTextOutsideColor}; + } + + + /* Active task */ + + .active0, + .active1, + .active2, + .active3 { + fill: ${t.activeTaskBkgColor}; + stroke: ${t.activeTaskBorderColor}; + } + + .activeText0, + .activeText1, + .activeText2, + .activeText3 { + fill: ${t.taskTextDarkColor} !important; + } + + + /* Completed task */ + + .done0, + .done1, + .done2, + .done3 { + stroke: ${t.doneTaskBorderColor}; + fill: ${t.doneTaskBkgColor}; + stroke-width: 2; + } + + .doneText0, + .doneText1, + .doneText2, + .doneText3 { + fill: ${t.taskTextDarkColor} !important; + } + + + /* Tasks on the critical line */ + + .crit0, + .crit1, + .crit2, + .crit3 { + stroke: ${t.critBorderColor}; + fill: ${t.critBkgColor}; + stroke-width: 2; + } + + .activeCrit0, + .activeCrit1, + .activeCrit2, + .activeCrit3 { + stroke: ${t.critBorderColor}; + fill: ${t.activeTaskBkgColor}; + stroke-width: 2; + } + + .doneCrit0, + .doneCrit1, + .doneCrit2, + .doneCrit3 { + stroke: ${t.critBorderColor}; + fill: ${t.doneTaskBkgColor}; + stroke-width: 2; + cursor: pointer; + shape-rendering: crispEdges; + } + + .milestone { + transform: rotate(45deg) scale(0.8,0.8); + } + + .milestoneText { + font-style: italic; + } + .doneCritText0, + .doneCritText1, + .doneCritText2, + .doneCritText3 { + fill: ${t.taskTextDarkColor} !important; + } + + .activeCritText0, + .activeCritText1, + .activeCritText2, + .activeCritText3 { + fill: ${t.taskTextDarkColor} !important; + } + + .titleText { + text-anchor: middle; + font-size: 18px; + fill: ${t.titleColor||t.textColor}; + font-family: var(--mermaid-font-family, "trebuchet ms", verdana, arial, sans-serif); + } +`,"getStyles"),pce=xFe});var gce={};hr(gce,{diagram:()=>bFe});var bFe,yce=R(()=>{"use strict";Hle();uce();dce();mce();bFe={parser:Ule,db:lce,renderer:fce,styles:pce}});var bce,wce=R(()=>{"use strict";Lg();ut();bce={parse:o(async t=>{let e=await Fl("info",t);V.debug(e)},"parse")}});var fx,HI=R(()=>{fx="11.2.0"});var CFe,SFe,Tce,kce=R(()=>{"use strict";HI();CFe={version:fx},SFe=o(()=>CFe.version,"getVersion"),Tce={getVersion:SFe}});var Ps,pf=R(()=>{"use strict";Zt();_t();Ps=o(t=>{let{securityLevel:e}=de(),r=$e("body");if(e==="sandbox"){let a=$e(`#i${t}`).node()?.contentDocument??document;r=$e(a.body)}return r.select(`#${t}`)},"selectSvgElement")});var AFe,Ece,Cce=R(()=>{"use strict";ut();pf();Yn();AFe=o((t,e,r)=>{V.debug(`rendering info diagram +`+t);let n=Ps(e);Sr(n,100,400,!0),n.append("g").append("text").attr("x",100).attr("y",40).attr("class","version").attr("font-size",32).style("text-anchor","middle").text(`v${r}`)},"draw"),Ece={draw:AFe}});var Sce={};hr(Sce,{diagram:()=>_Fe});var _Fe,Ace=R(()=>{"use strict";wce();kce();Cce();_Fe={parser:bce,db:Tce,renderer:Ece}});var Dce,YI,lE,WI,RFe,NFe,MFe,IFe,OFe,PFe,BFe,cE,qI=R(()=>{"use strict";ut();bi();sl();Dce=mr.pie,YI={sections:new Map,showData:!1,config:Dce},lE=YI.sections,WI=YI.showData,RFe=structuredClone(Dce),NFe=o(()=>structuredClone(RFe),"getConfig"),MFe=o(()=>{lE=new Map,WI=YI.showData,vr()},"clear"),IFe=o(({label:t,value:e})=>{lE.has(t)||(lE.set(t,e),V.debug(`added new section: ${t}, with value: ${e}`))},"addSection"),OFe=o(()=>lE,"getSections"),PFe=o(t=>{WI=t},"setShowData"),BFe=o(()=>WI,"getShowData"),cE={getConfig:NFe,clear:MFe,setDiagramTitle:nn,getDiagramTitle:Xr,setAccTitle:kr,getAccTitle:Ar,setAccDescription:_r,getAccDescription:Lr,addSection:IFe,getSections:OFe,setShowData:PFe,getShowData:BFe}});var FFe,Rce,Nce=R(()=>{"use strict";Lg();ut();sx();qI();FFe=o((t,e)=>{cf(t,e),e.setShowData(t.showData),t.sections.map(e.addSection)},"populateDb"),Rce={parse:o(async t=>{let e=await Fl("pie",t);V.debug(e),FFe(e,cE)},"parse")}});var zFe,Mce,Ice=R(()=>{"use strict";zFe=o(t=>` + .pieCircle{ + stroke: ${t.pieStrokeColor}; + stroke-width : ${t.pieStrokeWidth}; + opacity : ${t.pieOpacity}; + } + .pieOuterCircle{ + stroke: ${t.pieOuterStrokeColor}; + stroke-width: ${t.pieOuterStrokeWidth}; + fill: none; + } + .pieTitleText { + text-anchor: middle; + font-size: ${t.pieTitleTextSize}; + fill: ${t.pieTitleTextColor}; + font-family: ${t.fontFamily}; + } + .slice { + font-family: ${t.fontFamily}; + fill: ${t.pieSectionTextColor}; + font-size:${t.pieSectionTextSize}; + // fill: white; + } + .legend text { + fill: ${t.pieLegendTextColor}; + font-family: ${t.fontFamily}; + font-size: ${t.pieLegendTextSize}; + } +`,"getStyles"),Mce=zFe});var GFe,$Fe,Oce,Pce=R(()=>{"use strict";Zt();_t();ut();pf();Yn();xr();GFe=o(t=>{let e=[...t.entries()].map(n=>({label:n[0],value:n[1]})).sort((n,i)=>i.value-n.value);return O3().value(n=>n.value)(e)},"createPieArcs"),$Fe=o((t,e,r,n)=>{V.debug(`rendering pie chart +`+t);let i=n.db,a=de(),s=Ts(i.getConfig(),a.pie),l=40,u=18,h=4,f=450,d=f,p=Ps(e),m=p.append("g");m.attr("transform","translate("+d/2+","+f/2+")");let{themeVariables:g}=a,[y]=mc(g.pieOuterStrokeWidth);y??=2;let v=s.textPosition,x=Math.min(d,f)/2-l,b=bl().innerRadius(0).outerRadius(x),w=bl().innerRadius(x*v).outerRadius(x*v);m.append("circle").attr("cx",0).attr("cy",0).attr("r",x+y/2).attr("class","pieOuterCircle");let S=i.getSections(),T=GFe(S),E=[g.pie1,g.pie2,g.pie3,g.pie4,g.pie5,g.pie6,g.pie7,g.pie8,g.pie9,g.pie10,g.pie11,g.pie12],_=pu(E);m.selectAll("mySlices").data(T).enter().append("path").attr("d",b).attr("fill",k=>_(k.data.label)).attr("class","pieCircle");let A=0;S.forEach(k=>{A+=k}),m.selectAll("mySlices").data(T).enter().append("text").text(k=>(k.data.value/A*100).toFixed(0)+"%").attr("transform",k=>"translate("+w.centroid(k)+")").style("text-anchor","middle").attr("class","slice"),m.append("text").text(i.getDiagramTitle()).attr("x",0).attr("y",-(f-50)/2).attr("class","pieTitleText");let L=m.selectAll(".legend").data(_.domain()).enter().append("g").attr("class","legend").attr("transform",(k,I)=>{let C=u+h,O=C*_.domain().length/2,D=12*u,P=I*C-O;return"translate("+D+","+P+")"});L.append("rect").attr("width",u).attr("height",u).style("fill",_).style("stroke",_),L.data(T).append("text").attr("x",u+h).attr("y",u-h).text(k=>{let{label:I,value:C}=k.data;return i.getShowData()?`${I} [${C}]`:I});let M=Math.max(...L.selectAll("text").nodes().map(k=>k?.getBoundingClientRect().width??0)),N=d+l+u+h+M;p.attr("viewBox",`0 0 ${N} ${f}`),Sr(p,f,N,s.useMaxWidth)},"draw"),Oce={draw:$Fe}});var Bce={};hr(Bce,{diagram:()=>VFe});var VFe,Fce=R(()=>{"use strict";Nce();qI();Ice();Pce();VFe={parser:Rce,db:cE,renderer:Oce,styles:Mce}});var XI,$ce,Vce=R(()=>{"use strict";XI=function(){var t=o(function(we,Te,Ce,Ae){for(Ce=Ce||{},Ae=we.length;Ae--;Ce[we[Ae]]=Te);return Ce},"o"),e=[1,3],r=[1,4],n=[1,5],i=[1,6],a=[1,7],s=[1,4,5,10,12,13,14,18,25,35,37,39,41,42,48,50,51,52,53,54,55,56,57,60,61,63,64,65,66,67],l=[1,4,5,10,12,13,14,18,25,28,35,37,39,41,42,48,50,51,52,53,54,55,56,57,60,61,63,64,65,66,67],u=[55,56,57],h=[2,36],f=[1,37],d=[1,36],p=[1,38],m=[1,35],g=[1,43],y=[1,41],v=[1,14],x=[1,23],b=[1,18],w=[1,19],S=[1,20],T=[1,21],E=[1,22],_=[1,24],A=[1,25],L=[1,26],M=[1,27],N=[1,28],k=[1,29],I=[1,32],C=[1,33],O=[1,34],D=[1,39],P=[1,40],F=[1,42],B=[1,44],$=[1,62],z=[1,61],Y=[4,5,8,10,12,13,14,18,44,47,49,55,56,57,63,64,65,66,67],Q=[1,65],X=[1,66],ie=[1,67],j=[1,68],J=[1,69],Z=[1,70],H=[1,71],q=[1,72],K=[1,73],se=[1,74],ce=[1,75],ue=[1,76],te=[4,5,6,7,8,9,10,11,12,13,14,15,18],De=[1,90],oe=[1,91],ke=[1,92],Ie=[1,99],Se=[1,93],Ue=[1,96],Pe=[1,94],_e=[1,95],me=[1,97],W=[1,98],fe=[1,102],ge=[10,55,56,57],re=[4,5,6,8,10,11,13,17,18,19,20,55,56,57],he={trace:o(function(){},"trace"),yy:{},symbols_:{error:2,idStringToken:3,ALPHA:4,NUM:5,NODE_STRING:6,DOWN:7,MINUS:8,DEFAULT:9,COMMA:10,COLON:11,AMP:12,BRKT:13,MULT:14,UNICODE_TEXT:15,styleComponent:16,UNIT:17,SPACE:18,STYLE:19,PCT:20,idString:21,style:22,stylesOpt:23,classDefStatement:24,CLASSDEF:25,start:26,eol:27,QUADRANT:28,document:29,line:30,statement:31,axisDetails:32,quadrantDetails:33,points:34,title:35,title_value:36,acc_title:37,acc_title_value:38,acc_descr:39,acc_descr_value:40,acc_descr_multiline_value:41,section:42,text:43,point_start:44,point_x:45,point_y:46,class_name:47,"X-AXIS":48,"AXIS-TEXT-DELIMITER":49,"Y-AXIS":50,QUADRANT_1:51,QUADRANT_2:52,QUADRANT_3:53,QUADRANT_4:54,NEWLINE:55,SEMI:56,EOF:57,alphaNumToken:58,textNoTagsToken:59,STR:60,MD_STR:61,alphaNum:62,PUNCTUATION:63,PLUS:64,EQUALS:65,DOT:66,UNDERSCORE:67,$accept:0,$end:1},terminals_:{2:"error",4:"ALPHA",5:"NUM",6:"NODE_STRING",7:"DOWN",8:"MINUS",9:"DEFAULT",10:"COMMA",11:"COLON",12:"AMP",13:"BRKT",14:"MULT",15:"UNICODE_TEXT",17:"UNIT",18:"SPACE",19:"STYLE",20:"PCT",25:"CLASSDEF",28:"QUADRANT",35:"title",36:"title_value",37:"acc_title",38:"acc_title_value",39:"acc_descr",40:"acc_descr_value",41:"acc_descr_multiline_value",42:"section",44:"point_start",45:"point_x",46:"point_y",47:"class_name",48:"X-AXIS",49:"AXIS-TEXT-DELIMITER",50:"Y-AXIS",51:"QUADRANT_1",52:"QUADRANT_2",53:"QUADRANT_3",54:"QUADRANT_4",55:"NEWLINE",56:"SEMI",57:"EOF",60:"STR",61:"MD_STR",63:"PUNCTUATION",64:"PLUS",65:"EQUALS",66:"DOT",67:"UNDERSCORE"},productions_:[0,[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[3,1],[16,1],[16,1],[16,1],[16,1],[16,1],[16,1],[16,1],[16,1],[16,1],[16,1],[21,1],[21,2],[22,1],[22,2],[23,1],[23,3],[24,5],[26,2],[26,2],[26,2],[29,0],[29,2],[30,2],[31,0],[31,1],[31,2],[31,1],[31,1],[31,1],[31,2],[31,2],[31,2],[31,1],[31,1],[34,4],[34,5],[34,5],[34,6],[32,4],[32,3],[32,2],[32,4],[32,3],[32,2],[33,2],[33,2],[33,2],[33,2],[27,1],[27,1],[27,1],[43,1],[43,2],[43,1],[43,1],[62,1],[62,2],[58,1],[58,1],[58,1],[58,1],[58,1],[58,1],[58,1],[58,1],[58,1],[58,1],[58,1],[59,1],[59,1],[59,1]],performAction:o(function(Te,Ce,Ae,Ge,Me,ye,He){var ze=ye.length-1;switch(Me){case 23:this.$=ye[ze];break;case 24:this.$=ye[ze-1]+""+ye[ze];break;case 26:this.$=ye[ze-1]+ye[ze];break;case 27:this.$=[ye[ze].trim()];break;case 28:ye[ze-2].push(ye[ze].trim()),this.$=ye[ze-2];break;case 29:this.$=ye[ze-4],Ge.addClass(ye[ze-2],ye[ze]);break;case 37:this.$=[];break;case 42:this.$=ye[ze].trim(),Ge.setDiagramTitle(this.$);break;case 43:this.$=ye[ze].trim(),Ge.setAccTitle(this.$);break;case 44:case 45:this.$=ye[ze].trim(),Ge.setAccDescription(this.$);break;case 46:Ge.addSection(ye[ze].substr(8)),this.$=ye[ze].substr(8);break;case 47:Ge.addPoint(ye[ze-3],"",ye[ze-1],ye[ze],[]);break;case 48:Ge.addPoint(ye[ze-4],ye[ze-3],ye[ze-1],ye[ze],[]);break;case 49:Ge.addPoint(ye[ze-4],"",ye[ze-2],ye[ze-1],ye[ze]);break;case 50:Ge.addPoint(ye[ze-5],ye[ze-4],ye[ze-2],ye[ze-1],ye[ze]);break;case 51:Ge.setXAxisLeftText(ye[ze-2]),Ge.setXAxisRightText(ye[ze]);break;case 52:ye[ze-1].text+=" \u27F6 ",Ge.setXAxisLeftText(ye[ze-1]);break;case 53:Ge.setXAxisLeftText(ye[ze]);break;case 54:Ge.setYAxisBottomText(ye[ze-2]),Ge.setYAxisTopText(ye[ze]);break;case 55:ye[ze-1].text+=" \u27F6 ",Ge.setYAxisBottomText(ye[ze-1]);break;case 56:Ge.setYAxisBottomText(ye[ze]);break;case 57:Ge.setQuadrant1Text(ye[ze]);break;case 58:Ge.setQuadrant2Text(ye[ze]);break;case 59:Ge.setQuadrant3Text(ye[ze]);break;case 60:Ge.setQuadrant4Text(ye[ze]);break;case 64:this.$={text:ye[ze],type:"text"};break;case 65:this.$={text:ye[ze-1].text+""+ye[ze],type:ye[ze-1].type};break;case 66:this.$={text:ye[ze],type:"text"};break;case 67:this.$={text:ye[ze],type:"markdown"};break;case 68:this.$=ye[ze];break;case 69:this.$=ye[ze-1]+""+ye[ze];break}},"anonymous"),table:[{18:e,26:1,27:2,28:r,55:n,56:i,57:a},{1:[3]},{18:e,26:8,27:2,28:r,55:n,56:i,57:a},{18:e,26:9,27:2,28:r,55:n,56:i,57:a},t(s,[2,33],{29:10}),t(l,[2,61]),t(l,[2,62]),t(l,[2,63]),{1:[2,30]},{1:[2,31]},t(u,h,{30:11,31:12,24:13,32:15,33:16,34:17,43:30,58:31,1:[2,32],4:f,5:d,10:p,12:m,13:g,14:y,18:v,25:x,35:b,37:w,39:S,41:T,42:E,48:_,50:A,51:L,52:M,53:N,54:k,60:I,61:C,63:O,64:D,65:P,66:F,67:B}),t(s,[2,34]),{27:45,55:n,56:i,57:a},t(u,[2,37]),t(u,h,{24:13,32:15,33:16,34:17,43:30,58:31,31:46,4:f,5:d,10:p,12:m,13:g,14:y,18:v,25:x,35:b,37:w,39:S,41:T,42:E,48:_,50:A,51:L,52:M,53:N,54:k,60:I,61:C,63:O,64:D,65:P,66:F,67:B}),t(u,[2,39]),t(u,[2,40]),t(u,[2,41]),{36:[1,47]},{38:[1,48]},{40:[1,49]},t(u,[2,45]),t(u,[2,46]),{18:[1,50]},{4:f,5:d,10:p,12:m,13:g,14:y,43:51,58:31,60:I,61:C,63:O,64:D,65:P,66:F,67:B},{4:f,5:d,10:p,12:m,13:g,14:y,43:52,58:31,60:I,61:C,63:O,64:D,65:P,66:F,67:B},{4:f,5:d,10:p,12:m,13:g,14:y,43:53,58:31,60:I,61:C,63:O,64:D,65:P,66:F,67:B},{4:f,5:d,10:p,12:m,13:g,14:y,43:54,58:31,60:I,61:C,63:O,64:D,65:P,66:F,67:B},{4:f,5:d,10:p,12:m,13:g,14:y,43:55,58:31,60:I,61:C,63:O,64:D,65:P,66:F,67:B},{4:f,5:d,10:p,12:m,13:g,14:y,43:56,58:31,60:I,61:C,63:O,64:D,65:P,66:F,67:B},{4:f,5:d,8:$,10:p,12:m,13:g,14:y,18:z,44:[1,57],47:[1,58],58:60,59:59,63:O,64:D,65:P,66:F,67:B},t(Y,[2,64]),t(Y,[2,66]),t(Y,[2,67]),t(Y,[2,70]),t(Y,[2,71]),t(Y,[2,72]),t(Y,[2,73]),t(Y,[2,74]),t(Y,[2,75]),t(Y,[2,76]),t(Y,[2,77]),t(Y,[2,78]),t(Y,[2,79]),t(Y,[2,80]),t(s,[2,35]),t(u,[2,38]),t(u,[2,42]),t(u,[2,43]),t(u,[2,44]),{3:64,4:Q,5:X,6:ie,7:j,8:J,9:Z,10:H,11:q,12:K,13:se,14:ce,15:ue,21:63},t(u,[2,53],{59:59,58:60,4:f,5:d,8:$,10:p,12:m,13:g,14:y,18:z,49:[1,77],63:O,64:D,65:P,66:F,67:B}),t(u,[2,56],{59:59,58:60,4:f,5:d,8:$,10:p,12:m,13:g,14:y,18:z,49:[1,78],63:O,64:D,65:P,66:F,67:B}),t(u,[2,57],{59:59,58:60,4:f,5:d,8:$,10:p,12:m,13:g,14:y,18:z,63:O,64:D,65:P,66:F,67:B}),t(u,[2,58],{59:59,58:60,4:f,5:d,8:$,10:p,12:m,13:g,14:y,18:z,63:O,64:D,65:P,66:F,67:B}),t(u,[2,59],{59:59,58:60,4:f,5:d,8:$,10:p,12:m,13:g,14:y,18:z,63:O,64:D,65:P,66:F,67:B}),t(u,[2,60],{59:59,58:60,4:f,5:d,8:$,10:p,12:m,13:g,14:y,18:z,63:O,64:D,65:P,66:F,67:B}),{45:[1,79]},{44:[1,80]},t(Y,[2,65]),t(Y,[2,81]),t(Y,[2,82]),t(Y,[2,83]),{3:82,4:Q,5:X,6:ie,7:j,8:J,9:Z,10:H,11:q,12:K,13:se,14:ce,15:ue,18:[1,81]},t(te,[2,23]),t(te,[2,1]),t(te,[2,2]),t(te,[2,3]),t(te,[2,4]),t(te,[2,5]),t(te,[2,6]),t(te,[2,7]),t(te,[2,8]),t(te,[2,9]),t(te,[2,10]),t(te,[2,11]),t(te,[2,12]),t(u,[2,52],{58:31,43:83,4:f,5:d,10:p,12:m,13:g,14:y,60:I,61:C,63:O,64:D,65:P,66:F,67:B}),t(u,[2,55],{58:31,43:84,4:f,5:d,10:p,12:m,13:g,14:y,60:I,61:C,63:O,64:D,65:P,66:F,67:B}),{46:[1,85]},{45:[1,86]},{4:De,5:oe,6:ke,8:Ie,11:Se,13:Ue,16:89,17:Pe,18:_e,19:me,20:W,22:88,23:87},t(te,[2,24]),t(u,[2,51],{59:59,58:60,4:f,5:d,8:$,10:p,12:m,13:g,14:y,18:z,63:O,64:D,65:P,66:F,67:B}),t(u,[2,54],{59:59,58:60,4:f,5:d,8:$,10:p,12:m,13:g,14:y,18:z,63:O,64:D,65:P,66:F,67:B}),t(u,[2,47],{22:88,16:89,23:100,4:De,5:oe,6:ke,8:Ie,11:Se,13:Ue,17:Pe,18:_e,19:me,20:W}),{46:[1,101]},t(u,[2,29],{10:fe}),t(ge,[2,27],{16:103,4:De,5:oe,6:ke,8:Ie,11:Se,13:Ue,17:Pe,18:_e,19:me,20:W}),t(re,[2,25]),t(re,[2,13]),t(re,[2,14]),t(re,[2,15]),t(re,[2,16]),t(re,[2,17]),t(re,[2,18]),t(re,[2,19]),t(re,[2,20]),t(re,[2,21]),t(re,[2,22]),t(u,[2,49],{10:fe}),t(u,[2,48],{22:88,16:89,23:104,4:De,5:oe,6:ke,8:Ie,11:Se,13:Ue,17:Pe,18:_e,19:me,20:W}),{4:De,5:oe,6:ke,8:Ie,11:Se,13:Ue,16:89,17:Pe,18:_e,19:me,20:W,22:105},t(re,[2,26]),t(u,[2,50],{10:fe}),t(ge,[2,28],{16:103,4:De,5:oe,6:ke,8:Ie,11:Se,13:Ue,17:Pe,18:_e,19:me,20:W})],defaultActions:{8:[2,30],9:[2,31]},parseError:o(function(Te,Ce){if(Ce.recoverable)this.trace(Te);else{var Ae=new Error(Te);throw Ae.hash=Ce,Ae}},"parseError"),parse:o(function(Te){var Ce=this,Ae=[0],Ge=[],Me=[null],ye=[],He=this.table,ze="",Ze=0,gt=0,yt=0,tt=2,Ye=1,Je=ye.slice.call(arguments,1),Ve=Object.create(this.lexer),je={yy:{}};for(var kt in this.yy)Object.prototype.hasOwnProperty.call(this.yy,kt)&&(je.yy[kt]=this.yy[kt]);Ve.setInput(Te,je.yy),je.yy.lexer=Ve,je.yy.parser=this,typeof Ve.yylloc>"u"&&(Ve.yylloc={});var at=Ve.yylloc;ye.push(at);var xt=Ve.options&&Ve.options.ranges;typeof je.yy.parseError=="function"?this.parseError=je.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function it(on){Ae.length=Ae.length-2*on,Me.length=Me.length-on,ye.length=ye.length-on}o(it,"popStack");function dt(){var on;return on=Ge.pop()||Ve.lex()||Ye,typeof on!="number"&&(on instanceof Array&&(Ge=on,on=Ge.pop()),on=Ce.symbols_[on]||on),on}o(dt,"lex");for(var lt,It,mt,St,gr,xn,jt={},rn,Er,Kn,hn;;){if(mt=Ae[Ae.length-1],this.defaultActions[mt]?St=this.defaultActions[mt]:((lt===null||typeof lt>"u")&&(lt=dt()),St=He[mt]&&He[mt][lt]),typeof St>"u"||!St.length||!St[0]){var Qn="";hn=[];for(rn in He[mt])this.terminals_[rn]&&rn>tt&&hn.push("'"+this.terminals_[rn]+"'");Ve.showPosition?Qn="Parse error on line "+(Ze+1)+`: +`+Ve.showPosition()+` +Expecting `+hn.join(", ")+", got '"+(this.terminals_[lt]||lt)+"'":Qn="Parse error on line "+(Ze+1)+": Unexpected "+(lt==Ye?"end of input":"'"+(this.terminals_[lt]||lt)+"'"),this.parseError(Qn,{text:Ve.match,token:this.terminals_[lt]||lt,line:Ve.yylineno,loc:at,expected:hn})}if(St[0]instanceof Array&&St.length>1)throw new Error("Parse Error: multiple actions possible at state: "+mt+", token: "+lt);switch(St[0]){case 1:Ae.push(lt),Me.push(Ve.yytext),ye.push(Ve.yylloc),Ae.push(St[1]),lt=null,It?(lt=It,It=null):(gt=Ve.yyleng,ze=Ve.yytext,Ze=Ve.yylineno,at=Ve.yylloc,yt>0&&yt--);break;case 2:if(Er=this.productions_[St[1]][1],jt.$=Me[Me.length-Er],jt._$={first_line:ye[ye.length-(Er||1)].first_line,last_line:ye[ye.length-1].last_line,first_column:ye[ye.length-(Er||1)].first_column,last_column:ye[ye.length-1].last_column},xt&&(jt._$.range=[ye[ye.length-(Er||1)].range[0],ye[ye.length-1].range[1]]),xn=this.performAction.apply(jt,[ze,gt,Ze,je.yy,St[1],Me,ye].concat(Je)),typeof xn<"u")return xn;Er&&(Ae=Ae.slice(0,-1*Er*2),Me=Me.slice(0,-1*Er),ye=ye.slice(0,-1*Er)),Ae.push(this.productions_[St[1]][0]),Me.push(jt.$),ye.push(jt._$),Kn=He[Ae[Ae.length-2]][Ae[Ae.length-1]],Ae.push(Kn);break;case 3:return!0}}return!0},"parse")},ne=function(){var we={EOF:1,parseError:o(function(Ce,Ae){if(this.yy.parser)this.yy.parser.parseError(Ce,Ae);else throw new Error(Ce)},"parseError"),setInput:o(function(Te,Ce){return this.yy=Ce||this.yy||{},this._input=Te,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:o(function(){var Te=this._input[0];this.yytext+=Te,this.yyleng++,this.offset++,this.match+=Te,this.matched+=Te;var Ce=Te.match(/(?:\r\n?|\n).*/g);return Ce?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),Te},"input"),unput:o(function(Te){var Ce=Te.length,Ae=Te.split(/(?:\r\n?|\n)/g);this._input=Te+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-Ce),this.offset-=Ce;var Ge=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),Ae.length-1&&(this.yylineno-=Ae.length-1);var Me=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:Ae?(Ae.length===Ge.length?this.yylloc.first_column:0)+Ge[Ge.length-Ae.length].length-Ae[0].length:this.yylloc.first_column-Ce},this.options.ranges&&(this.yylloc.range=[Me[0],Me[0]+this.yyleng-Ce]),this.yyleng=this.yytext.length,this},"unput"),more:o(function(){return this._more=!0,this},"more"),reject:o(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:o(function(Te){this.unput(this.match.slice(Te))},"less"),pastInput:o(function(){var Te=this.matched.substr(0,this.matched.length-this.match.length);return(Te.length>20?"...":"")+Te.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:o(function(){var Te=this.match;return Te.length<20&&(Te+=this._input.substr(0,20-Te.length)),(Te.substr(0,20)+(Te.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:o(function(){var Te=this.pastInput(),Ce=new Array(Te.length+1).join("-");return Te+this.upcomingInput()+` +`+Ce+"^"},"showPosition"),test_match:o(function(Te,Ce){var Ae,Ge,Me;if(this.options.backtrack_lexer&&(Me={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(Me.yylloc.range=this.yylloc.range.slice(0))),Ge=Te[0].match(/(?:\r\n?|\n).*/g),Ge&&(this.yylineno+=Ge.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:Ge?Ge[Ge.length-1].length-Ge[Ge.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+Te[0].length},this.yytext+=Te[0],this.match+=Te[0],this.matches=Te,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(Te[0].length),this.matched+=Te[0],Ae=this.performAction.call(this,this.yy,this,Ce,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),Ae)return Ae;if(this._backtrack){for(var ye in Me)this[ye]=Me[ye];return!1}return!1},"test_match"),next:o(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var Te,Ce,Ae,Ge;this._more||(this.yytext="",this.match="");for(var Me=this._currentRules(),ye=0;yeCe[0].length)){if(Ce=Ae,Ge=ye,this.options.backtrack_lexer){if(Te=this.test_match(Ae,Me[ye]),Te!==!1)return Te;if(this._backtrack){Ce=!1;continue}else return!1}else if(!this.options.flex)break}return Ce?(Te=this.test_match(Ce,Me[Ge]),Te!==!1?Te:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:o(function(){var Ce=this.next();return Ce||this.lex()},"lex"),begin:o(function(Ce){this.conditionStack.push(Ce)},"begin"),popState:o(function(){var Ce=this.conditionStack.length-1;return Ce>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:o(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:o(function(Ce){return Ce=this.conditionStack.length-1-Math.abs(Ce||0),Ce>=0?this.conditionStack[Ce]:"INITIAL"},"topState"),pushState:o(function(Ce){this.begin(Ce)},"pushState"),stateStackSize:o(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:o(function(Ce,Ae,Ge,Me){var ye=Me;switch(Ge){case 0:break;case 1:break;case 2:return 55;case 3:break;case 4:return this.begin("title"),35;break;case 5:return this.popState(),"title_value";break;case 6:return this.begin("acc_title"),37;break;case 7:return this.popState(),"acc_title_value";break;case 8:return this.begin("acc_descr"),39;break;case 9:return this.popState(),"acc_descr_value";break;case 10:this.begin("acc_descr_multiline");break;case 11:this.popState();break;case 12:return"acc_descr_multiline_value";case 13:return 48;case 14:return 50;case 15:return 49;case 16:return 51;case 17:return 52;case 18:return 53;case 19:return 54;case 20:return 25;case 21:this.begin("md_string");break;case 22:return"MD_STR";case 23:this.popState();break;case 24:this.begin("string");break;case 25:this.popState();break;case 26:return"STR";case 27:this.begin("class_name");break;case 28:return this.popState(),47;break;case 29:return this.begin("point_start"),44;break;case 30:return this.begin("point_x"),45;break;case 31:this.popState();break;case 32:this.popState(),this.begin("point_y");break;case 33:return this.popState(),46;break;case 34:return 28;case 35:return 4;case 36:return 11;case 37:return 64;case 38:return 10;case 39:return 65;case 40:return 65;case 41:return 14;case 42:return 13;case 43:return 67;case 44:return 66;case 45:return 12;case 46:return 8;case 47:return 5;case 48:return 18;case 49:return 56;case 50:return 63;case 51:return 57}},"anonymous"),rules:[/^(?:%%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n\r]+)/i,/^(?:%%[^\n]*)/i,/^(?:title\b)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?: *x-axis *)/i,/^(?: *y-axis *)/i,/^(?: *--+> *)/i,/^(?: *quadrant-1 *)/i,/^(?: *quadrant-2 *)/i,/^(?: *quadrant-3 *)/i,/^(?: *quadrant-4 *)/i,/^(?:classDef\b)/i,/^(?:["][`])/i,/^(?:[^`"]+)/i,/^(?:[`]["])/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?::::)/i,/^(?:^\w+)/i,/^(?:\s*:\s*\[\s*)/i,/^(?:(1)|(0(.\d+)?))/i,/^(?:\s*\] *)/i,/^(?:\s*,\s*)/i,/^(?:(1)|(0(.\d+)?))/i,/^(?: *quadrantChart *)/i,/^(?:[A-Za-z]+)/i,/^(?::)/i,/^(?:\+)/i,/^(?:,)/i,/^(?:=)/i,/^(?:=)/i,/^(?:\*)/i,/^(?:#)/i,/^(?:[\_])/i,/^(?:\.)/i,/^(?:&)/i,/^(?:-)/i,/^(?:[0-9]+)/i,/^(?:\s)/i,/^(?:;)/i,/^(?:[!"#$%&'*+,-.`?\\_/])/i,/^(?:$)/i],conditions:{class_name:{rules:[28],inclusive:!1},point_y:{rules:[33],inclusive:!1},point_x:{rules:[32],inclusive:!1},point_start:{rules:[30,31],inclusive:!1},acc_descr_multiline:{rules:[11,12],inclusive:!1},acc_descr:{rules:[9],inclusive:!1},acc_title:{rules:[7],inclusive:!1},title:{rules:[5],inclusive:!1},md_string:{rules:[22,23],inclusive:!1},string:{rules:[25,26],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,6,8,10,13,14,15,16,17,18,19,20,21,24,27,29,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51],inclusive:!0}}};return we}();he.lexer=ne;function ae(){this.yy={}}return o(ae,"Parser"),ae.prototype=he,he.Parser=ae,new ae}();XI.parser=XI;$ce=XI});var os,uE,Uce=R(()=>{"use strict";Zt();sl();ut();jb();os=hp(),uE=class{constructor(){this.classes=new Map;this.config=this.getDefaultConfig(),this.themeConfig=this.getDefaultThemeConfig(),this.data=this.getDefaultData()}static{o(this,"QuadrantBuilder")}getDefaultData(){return{titleText:"",quadrant1Text:"",quadrant2Text:"",quadrant3Text:"",quadrant4Text:"",xAxisLeftText:"",xAxisRightText:"",yAxisBottomText:"",yAxisTopText:"",points:[]}}getDefaultConfig(){return{showXAxis:!0,showYAxis:!0,showTitle:!0,chartHeight:mr.quadrantChart?.chartWidth||500,chartWidth:mr.quadrantChart?.chartHeight||500,titlePadding:mr.quadrantChart?.titlePadding||10,titleFontSize:mr.quadrantChart?.titleFontSize||20,quadrantPadding:mr.quadrantChart?.quadrantPadding||5,xAxisLabelPadding:mr.quadrantChart?.xAxisLabelPadding||5,yAxisLabelPadding:mr.quadrantChart?.yAxisLabelPadding||5,xAxisLabelFontSize:mr.quadrantChart?.xAxisLabelFontSize||16,yAxisLabelFontSize:mr.quadrantChart?.yAxisLabelFontSize||16,quadrantLabelFontSize:mr.quadrantChart?.quadrantLabelFontSize||16,quadrantTextTopPadding:mr.quadrantChart?.quadrantTextTopPadding||5,pointTextPadding:mr.quadrantChart?.pointTextPadding||5,pointLabelFontSize:mr.quadrantChart?.pointLabelFontSize||12,pointRadius:mr.quadrantChart?.pointRadius||5,xAxisPosition:mr.quadrantChart?.xAxisPosition||"top",yAxisPosition:mr.quadrantChart?.yAxisPosition||"left",quadrantInternalBorderStrokeWidth:mr.quadrantChart?.quadrantInternalBorderStrokeWidth||1,quadrantExternalBorderStrokeWidth:mr.quadrantChart?.quadrantExternalBorderStrokeWidth||2}}getDefaultThemeConfig(){return{quadrant1Fill:os.quadrant1Fill,quadrant2Fill:os.quadrant2Fill,quadrant3Fill:os.quadrant3Fill,quadrant4Fill:os.quadrant4Fill,quadrant1TextFill:os.quadrant1TextFill,quadrant2TextFill:os.quadrant2TextFill,quadrant3TextFill:os.quadrant3TextFill,quadrant4TextFill:os.quadrant4TextFill,quadrantPointFill:os.quadrantPointFill,quadrantPointTextFill:os.quadrantPointTextFill,quadrantXAxisTextFill:os.quadrantXAxisTextFill,quadrantYAxisTextFill:os.quadrantYAxisTextFill,quadrantTitleFill:os.quadrantTitleFill,quadrantInternalBorderStrokeFill:os.quadrantInternalBorderStrokeFill,quadrantExternalBorderStrokeFill:os.quadrantExternalBorderStrokeFill}}clear(){this.config=this.getDefaultConfig(),this.themeConfig=this.getDefaultThemeConfig(),this.data=this.getDefaultData(),this.classes=new Map,V.info("clear called")}setData(e){this.data={...this.data,...e}}addPoints(e){this.data.points=[...e,...this.data.points]}addClass(e,r){this.classes.set(e,r)}setConfig(e){V.trace("setConfig called with: ",e),this.config={...this.config,...e}}setThemeConfig(e){V.trace("setThemeConfig called with: ",e),this.themeConfig={...this.themeConfig,...e}}calculateSpace(e,r,n,i){let a=this.config.xAxisLabelPadding*2+this.config.xAxisLabelFontSize,s={top:e==="top"&&r?a:0,bottom:e==="bottom"&&r?a:0},l=this.config.yAxisLabelPadding*2+this.config.yAxisLabelFontSize,u={left:this.config.yAxisPosition==="left"&&n?l:0,right:this.config.yAxisPosition==="right"&&n?l:0},h=this.config.titleFontSize+this.config.titlePadding*2,f={top:i?h:0},d=this.config.quadrantPadding+u.left,p=this.config.quadrantPadding+s.top+f.top,m=this.config.chartWidth-this.config.quadrantPadding*2-u.left-u.right,g=this.config.chartHeight-this.config.quadrantPadding*2-s.top-s.bottom-f.top,y=m/2,v=g/2;return{xAxisSpace:s,yAxisSpace:u,titleSpace:f,quadrantSpace:{quadrantLeft:d,quadrantTop:p,quadrantWidth:m,quadrantHalfWidth:y,quadrantHeight:g,quadrantHalfHeight:v}}}getAxisLabels(e,r,n,i){let{quadrantSpace:a,titleSpace:s}=i,{quadrantHalfHeight:l,quadrantHeight:u,quadrantLeft:h,quadrantHalfWidth:f,quadrantTop:d,quadrantWidth:p}=a,m=!!this.data.xAxisRightText,g=!!this.data.yAxisTopText,y=[];return this.data.xAxisLeftText&&r&&y.push({text:this.data.xAxisLeftText,fill:this.themeConfig.quadrantXAxisTextFill,x:h+(m?f/2:0),y:e==="top"?this.config.xAxisLabelPadding+s.top:this.config.xAxisLabelPadding+d+u+this.config.quadrantPadding,fontSize:this.config.xAxisLabelFontSize,verticalPos:m?"center":"left",horizontalPos:"top",rotation:0}),this.data.xAxisRightText&&r&&y.push({text:this.data.xAxisRightText,fill:this.themeConfig.quadrantXAxisTextFill,x:h+f+(m?f/2:0),y:e==="top"?this.config.xAxisLabelPadding+s.top:this.config.xAxisLabelPadding+d+u+this.config.quadrantPadding,fontSize:this.config.xAxisLabelFontSize,verticalPos:m?"center":"left",horizontalPos:"top",rotation:0}),this.data.yAxisBottomText&&n&&y.push({text:this.data.yAxisBottomText,fill:this.themeConfig.quadrantYAxisTextFill,x:this.config.yAxisPosition==="left"?this.config.yAxisLabelPadding:this.config.yAxisLabelPadding+h+p+this.config.quadrantPadding,y:d+u-(g?l/2:0),fontSize:this.config.yAxisLabelFontSize,verticalPos:g?"center":"left",horizontalPos:"top",rotation:-90}),this.data.yAxisTopText&&n&&y.push({text:this.data.yAxisTopText,fill:this.themeConfig.quadrantYAxisTextFill,x:this.config.yAxisPosition==="left"?this.config.yAxisLabelPadding:this.config.yAxisLabelPadding+h+p+this.config.quadrantPadding,y:d+l-(g?l/2:0),fontSize:this.config.yAxisLabelFontSize,verticalPos:g?"center":"left",horizontalPos:"top",rotation:-90}),y}getQuadrants(e){let{quadrantSpace:r}=e,{quadrantHalfHeight:n,quadrantLeft:i,quadrantHalfWidth:a,quadrantTop:s}=r,l=[{text:{text:this.data.quadrant1Text,fill:this.themeConfig.quadrant1TextFill,x:0,y:0,fontSize:this.config.quadrantLabelFontSize,verticalPos:"center",horizontalPos:"middle",rotation:0},x:i+a,y:s,width:a,height:n,fill:this.themeConfig.quadrant1Fill},{text:{text:this.data.quadrant2Text,fill:this.themeConfig.quadrant2TextFill,x:0,y:0,fontSize:this.config.quadrantLabelFontSize,verticalPos:"center",horizontalPos:"middle",rotation:0},x:i,y:s,width:a,height:n,fill:this.themeConfig.quadrant2Fill},{text:{text:this.data.quadrant3Text,fill:this.themeConfig.quadrant3TextFill,x:0,y:0,fontSize:this.config.quadrantLabelFontSize,verticalPos:"center",horizontalPos:"middle",rotation:0},x:i,y:s+n,width:a,height:n,fill:this.themeConfig.quadrant3Fill},{text:{text:this.data.quadrant4Text,fill:this.themeConfig.quadrant4TextFill,x:0,y:0,fontSize:this.config.quadrantLabelFontSize,verticalPos:"center",horizontalPos:"middle",rotation:0},x:i+a,y:s+n,width:a,height:n,fill:this.themeConfig.quadrant4Fill}];for(let u of l)u.text.x=u.x+u.width/2,this.data.points.length===0?(u.text.y=u.y+u.height/2,u.text.horizontalPos="middle"):(u.text.y=u.y+this.config.quadrantTextTopPadding,u.text.horizontalPos="top");return l}getQuadrantPoints(e){let{quadrantSpace:r}=e,{quadrantHeight:n,quadrantLeft:i,quadrantTop:a,quadrantWidth:s}=r,l=gl().domain([0,1]).range([i,s+i]),u=gl().domain([0,1]).range([n+a,a]);return this.data.points.map(f=>{let d=this.classes.get(f.className);return d&&(f={...d,...f}),{x:l(f.x),y:u(f.y),fill:f.color??this.themeConfig.quadrantPointFill,radius:f.radius??this.config.pointRadius,text:{text:f.text,fill:this.themeConfig.quadrantPointTextFill,x:l(f.x),y:u(f.y)+this.config.pointTextPadding,verticalPos:"center",horizontalPos:"top",fontSize:this.config.pointLabelFontSize,rotation:0},strokeColor:f.strokeColor??this.themeConfig.quadrantPointFill,strokeWidth:f.strokeWidth??"0px"}})}getBorders(e){let r=this.config.quadrantExternalBorderStrokeWidth/2,{quadrantSpace:n}=e,{quadrantHalfHeight:i,quadrantHeight:a,quadrantLeft:s,quadrantHalfWidth:l,quadrantTop:u,quadrantWidth:h}=n;return[{strokeFill:this.themeConfig.quadrantExternalBorderStrokeFill,strokeWidth:this.config.quadrantExternalBorderStrokeWidth,x1:s-r,y1:u,x2:s+h+r,y2:u},{strokeFill:this.themeConfig.quadrantExternalBorderStrokeFill,strokeWidth:this.config.quadrantExternalBorderStrokeWidth,x1:s+h,y1:u+r,x2:s+h,y2:u+a-r},{strokeFill:this.themeConfig.quadrantExternalBorderStrokeFill,strokeWidth:this.config.quadrantExternalBorderStrokeWidth,x1:s-r,y1:u+a,x2:s+h+r,y2:u+a},{strokeFill:this.themeConfig.quadrantExternalBorderStrokeFill,strokeWidth:this.config.quadrantExternalBorderStrokeWidth,x1:s,y1:u+r,x2:s,y2:u+a-r},{strokeFill:this.themeConfig.quadrantInternalBorderStrokeFill,strokeWidth:this.config.quadrantInternalBorderStrokeWidth,x1:s+l,y1:u+r,x2:s+l,y2:u+a-r},{strokeFill:this.themeConfig.quadrantInternalBorderStrokeFill,strokeWidth:this.config.quadrantInternalBorderStrokeWidth,x1:s+r,y1:u+i,x2:s+h-r,y2:u+i}]}getTitle(e){if(e)return{text:this.data.titleText,fill:this.themeConfig.quadrantTitleFill,fontSize:this.config.titleFontSize,horizontalPos:"top",verticalPos:"center",rotation:0,y:this.config.titlePadding,x:this.config.chartWidth/2}}build(){let e=this.config.showXAxis&&!!(this.data.xAxisLeftText||this.data.xAxisRightText),r=this.config.showYAxis&&!!(this.data.yAxisTopText||this.data.yAxisBottomText),n=this.config.showTitle&&!!this.data.titleText,i=this.data.points.length>0?"bottom":this.config.xAxisPosition,a=this.calculateSpace(i,e,r,n);return{points:this.getQuadrantPoints(a),quadrants:this.getQuadrants(a),axisLabels:this.getAxisLabels(i,e,r,a),borderLines:this.getBorders(a),title:this.getTitle(n)}}}});function jI(t){return!/^#?([\dA-Fa-f]{6}|[\dA-Fa-f]{3})$/.test(t)}function Hce(t){return!/^\d+$/.test(t)}function Yce(t){return!/^\d+px$/.test(t)}var S0,Wce=R(()=>{"use strict";S0=class extends Error{static{o(this,"InvalidStyleError")}constructor(e,r,n){super(`value for ${e} ${r} is invalid, please use a valid ${n}`),this.name="InvalidStyleError"}};o(jI,"validateHexCode");o(Hce,"validateNumber");o(Yce,"validateSizeInPixels")});function Wu(t){return qr(t.trim(),YFe)}function WFe(t){wa.setData({quadrant1Text:Wu(t.text)})}function qFe(t){wa.setData({quadrant2Text:Wu(t.text)})}function XFe(t){wa.setData({quadrant3Text:Wu(t.text)})}function jFe(t){wa.setData({quadrant4Text:Wu(t.text)})}function KFe(t){wa.setData({xAxisLeftText:Wu(t.text)})}function QFe(t){wa.setData({xAxisRightText:Wu(t.text)})}function ZFe(t){wa.setData({yAxisTopText:Wu(t.text)})}function JFe(t){wa.setData({yAxisBottomText:Wu(t.text)})}function KI(t){let e={};for(let r of t){let[n,i]=r.trim().split(/\s*:\s*/);if(n==="radius"){if(Hce(i))throw new S0(n,i,"number");e.radius=parseInt(i)}else if(n==="color"){if(jI(i))throw new S0(n,i,"hex code");e.color=i}else if(n==="stroke-color"){if(jI(i))throw new S0(n,i,"hex code");e.strokeColor=i}else if(n==="stroke-width"){if(Yce(i))throw new S0(n,i,"number of pixels (eg. 10px)");e.strokeWidth=i}else throw new Error(`style named ${n} is not supported.`)}return e}function eze(t,e,r,n,i){let a=KI(i);wa.addPoints([{x:r,y:n,text:Wu(t.text),className:e,...a}])}function tze(t,e){wa.addClass(t,KI(e))}function rze(t){wa.setConfig({chartWidth:t})}function nze(t){wa.setConfig({chartHeight:t})}function ize(){let t=de(),{themeVariables:e,quadrantChart:r}=t;return r&&wa.setConfig(r),wa.setThemeConfig({quadrant1Fill:e.quadrant1Fill,quadrant2Fill:e.quadrant2Fill,quadrant3Fill:e.quadrant3Fill,quadrant4Fill:e.quadrant4Fill,quadrant1TextFill:e.quadrant1TextFill,quadrant2TextFill:e.quadrant2TextFill,quadrant3TextFill:e.quadrant3TextFill,quadrant4TextFill:e.quadrant4TextFill,quadrantPointFill:e.quadrantPointFill,quadrantPointTextFill:e.quadrantPointTextFill,quadrantXAxisTextFill:e.quadrantXAxisTextFill,quadrantYAxisTextFill:e.quadrantYAxisTextFill,quadrantExternalBorderStrokeFill:e.quadrantExternalBorderStrokeFill,quadrantInternalBorderStrokeFill:e.quadrantInternalBorderStrokeFill,quadrantTitleFill:e.quadrantTitleFill}),wa.setData({titleText:Xr()}),wa.build()}var YFe,wa,aze,qce,Xce=R(()=>{"use strict";_t();rr();bi();Uce();Wce();YFe=de();o(Wu,"textSanitizer");wa=new uE;o(WFe,"setQuadrant1Text");o(qFe,"setQuadrant2Text");o(XFe,"setQuadrant3Text");o(jFe,"setQuadrant4Text");o(KFe,"setXAxisLeftText");o(QFe,"setXAxisRightText");o(ZFe,"setYAxisTopText");o(JFe,"setYAxisBottomText");o(KI,"parseStyles");o(eze,"addPoint");o(tze,"addClass");o(rze,"setWidth");o(nze,"setHeight");o(ize,"getQuadrantData");aze=o(function(){wa.clear(),vr()},"clear"),qce={setWidth:rze,setHeight:nze,setQuadrant1Text:WFe,setQuadrant2Text:qFe,setQuadrant3Text:XFe,setQuadrant4Text:jFe,setXAxisLeftText:KFe,setXAxisRightText:QFe,setYAxisTopText:ZFe,setYAxisBottomText:JFe,parseStyles:KI,addPoint:eze,addClass:tze,getQuadrantData:ize,clear:aze,setAccTitle:kr,getAccTitle:Ar,setDiagramTitle:nn,getDiagramTitle:Xr,getAccDescription:Lr,setAccDescription:_r}});var sze,jce,Kce=R(()=>{"use strict";Zt();_t();ut();Yn();sze=o((t,e,r,n)=>{function i(A){return A==="top"?"hanging":"middle"}o(i,"getDominantBaseLine");function a(A){return A==="left"?"start":"middle"}o(a,"getTextAnchor");function s(A){return`translate(${A.x}, ${A.y}) rotate(${A.rotation||0})`}o(s,"getTransformation");let l=de();V.debug(`Rendering quadrant chart +`+t);let u=l.securityLevel,h;u==="sandbox"&&(h=$e("#i"+e));let d=(u==="sandbox"?$e(h.nodes()[0].contentDocument.body):$e("body")).select(`[id="${e}"]`),p=d.append("g").attr("class","main"),m=l.quadrantChart?.chartWidth??500,g=l.quadrantChart?.chartHeight??500;Sr(d,g,m,l.quadrantChart?.useMaxWidth??!0),d.attr("viewBox","0 0 "+m+" "+g),n.db.setHeight(g),n.db.setWidth(m);let y=n.db.getQuadrantData(),v=p.append("g").attr("class","quadrants"),x=p.append("g").attr("class","border"),b=p.append("g").attr("class","data-points"),w=p.append("g").attr("class","labels"),S=p.append("g").attr("class","title");y.title&&S.append("text").attr("x",0).attr("y",0).attr("fill",y.title.fill).attr("font-size",y.title.fontSize).attr("dominant-baseline",i(y.title.horizontalPos)).attr("text-anchor",a(y.title.verticalPos)).attr("transform",s(y.title)).text(y.title.text),y.borderLines&&x.selectAll("line").data(y.borderLines).enter().append("line").attr("x1",A=>A.x1).attr("y1",A=>A.y1).attr("x2",A=>A.x2).attr("y2",A=>A.y2).style("stroke",A=>A.strokeFill).style("stroke-width",A=>A.strokeWidth);let T=v.selectAll("g.quadrant").data(y.quadrants).enter().append("g").attr("class","quadrant");T.append("rect").attr("x",A=>A.x).attr("y",A=>A.y).attr("width",A=>A.width).attr("height",A=>A.height).attr("fill",A=>A.fill),T.append("text").attr("x",0).attr("y",0).attr("fill",A=>A.text.fill).attr("font-size",A=>A.text.fontSize).attr("dominant-baseline",A=>i(A.text.horizontalPos)).attr("text-anchor",A=>a(A.text.verticalPos)).attr("transform",A=>s(A.text)).text(A=>A.text.text),w.selectAll("g.label").data(y.axisLabels).enter().append("g").attr("class","label").append("text").attr("x",0).attr("y",0).text(A=>A.text).attr("fill",A=>A.fill).attr("font-size",A=>A.fontSize).attr("dominant-baseline",A=>i(A.horizontalPos)).attr("text-anchor",A=>a(A.verticalPos)).attr("transform",A=>s(A));let _=b.selectAll("g.data-point").data(y.points).enter().append("g").attr("class","data-point");_.append("circle").attr("cx",A=>A.x).attr("cy",A=>A.y).attr("r",A=>A.radius).attr("fill",A=>A.fill).attr("stroke",A=>A.strokeColor).attr("stroke-width",A=>A.strokeWidth),_.append("text").attr("x",0).attr("y",0).text(A=>A.text.text).attr("fill",A=>A.text.fill).attr("font-size",A=>A.text.fontSize).attr("dominant-baseline",A=>i(A.text.horizontalPos)).attr("text-anchor",A=>a(A.text.verticalPos)).attr("transform",A=>s(A.text))},"draw"),jce={draw:sze}});var Qce={};hr(Qce,{diagram:()=>oze});var oze,Zce=R(()=>{"use strict";Vce();Xce();Kce();oze={parser:$ce,db:qce,renderer:jce,styles:o(()=>"","styles")}});var QI,tue,rue=R(()=>{"use strict";QI=function(){var t=o(function(O,D,P,F){for(P=P||{},F=O.length;F--;P[O[F]]=D);return P},"o"),e=[1,10,12,14,16,18,19,21,23],r=[2,6],n=[1,3],i=[1,5],a=[1,6],s=[1,7],l=[1,5,10,12,14,16,18,19,21,23,34,35,36],u=[1,25],h=[1,26],f=[1,28],d=[1,29],p=[1,30],m=[1,31],g=[1,32],y=[1,33],v=[1,34],x=[1,35],b=[1,36],w=[1,37],S=[1,43],T=[1,42],E=[1,47],_=[1,50],A=[1,10,12,14,16,18,19,21,23,34,35,36],L=[1,10,12,14,16,18,19,21,23,24,26,27,28,34,35,36],M=[1,10,12,14,16,18,19,21,23,24,26,27,28,34,35,36,41,42,43,44,45,46,47,48,49,50],N=[1,64],k={trace:o(function(){},"trace"),yy:{},symbols_:{error:2,start:3,eol:4,XYCHART:5,chartConfig:6,document:7,CHART_ORIENTATION:8,statement:9,title:10,text:11,X_AXIS:12,parseXAxis:13,Y_AXIS:14,parseYAxis:15,LINE:16,plotData:17,BAR:18,acc_title:19,acc_title_value:20,acc_descr:21,acc_descr_value:22,acc_descr_multiline_value:23,SQUARE_BRACES_START:24,commaSeparatedNumbers:25,SQUARE_BRACES_END:26,NUMBER_WITH_DECIMAL:27,COMMA:28,xAxisData:29,bandData:30,ARROW_DELIMITER:31,commaSeparatedTexts:32,yAxisData:33,NEWLINE:34,SEMI:35,EOF:36,alphaNum:37,STR:38,MD_STR:39,alphaNumToken:40,AMP:41,NUM:42,ALPHA:43,PLUS:44,EQUALS:45,MULT:46,DOT:47,BRKT:48,MINUS:49,UNDERSCORE:50,$accept:0,$end:1},terminals_:{2:"error",5:"XYCHART",8:"CHART_ORIENTATION",10:"title",12:"X_AXIS",14:"Y_AXIS",16:"LINE",18:"BAR",19:"acc_title",20:"acc_title_value",21:"acc_descr",22:"acc_descr_value",23:"acc_descr_multiline_value",24:"SQUARE_BRACES_START",26:"SQUARE_BRACES_END",27:"NUMBER_WITH_DECIMAL",28:"COMMA",31:"ARROW_DELIMITER",34:"NEWLINE",35:"SEMI",36:"EOF",38:"STR",39:"MD_STR",41:"AMP",42:"NUM",43:"ALPHA",44:"PLUS",45:"EQUALS",46:"MULT",47:"DOT",48:"BRKT",49:"MINUS",50:"UNDERSCORE"},productions_:[0,[3,2],[3,3],[3,2],[3,1],[6,1],[7,0],[7,2],[9,2],[9,2],[9,2],[9,2],[9,2],[9,3],[9,2],[9,3],[9,2],[9,2],[9,1],[17,3],[25,3],[25,1],[13,1],[13,2],[13,1],[29,1],[29,3],[30,3],[32,3],[32,1],[15,1],[15,2],[15,1],[33,3],[4,1],[4,1],[4,1],[11,1],[11,1],[11,1],[37,1],[37,2],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1],[40,1]],performAction:o(function(D,P,F,B,$,z,Y){var Q=z.length-1;switch($){case 5:B.setOrientation(z[Q]);break;case 9:B.setDiagramTitle(z[Q].text.trim());break;case 12:B.setLineData({text:"",type:"text"},z[Q]);break;case 13:B.setLineData(z[Q-1],z[Q]);break;case 14:B.setBarData({text:"",type:"text"},z[Q]);break;case 15:B.setBarData(z[Q-1],z[Q]);break;case 16:this.$=z[Q].trim(),B.setAccTitle(this.$);break;case 17:case 18:this.$=z[Q].trim(),B.setAccDescription(this.$);break;case 19:this.$=z[Q-1];break;case 20:this.$=[Number(z[Q-2]),...z[Q]];break;case 21:this.$=[Number(z[Q])];break;case 22:B.setXAxisTitle(z[Q]);break;case 23:B.setXAxisTitle(z[Q-1]);break;case 24:B.setXAxisTitle({type:"text",text:""});break;case 25:B.setXAxisBand(z[Q]);break;case 26:B.setXAxisRangeData(Number(z[Q-2]),Number(z[Q]));break;case 27:this.$=z[Q-1];break;case 28:this.$=[z[Q-2],...z[Q]];break;case 29:this.$=[z[Q]];break;case 30:B.setYAxisTitle(z[Q]);break;case 31:B.setYAxisTitle(z[Q-1]);break;case 32:B.setYAxisTitle({type:"text",text:""});break;case 33:B.setYAxisRangeData(Number(z[Q-2]),Number(z[Q]));break;case 37:this.$={text:z[Q],type:"text"};break;case 38:this.$={text:z[Q],type:"text"};break;case 39:this.$={text:z[Q],type:"markdown"};break;case 40:this.$=z[Q];break;case 41:this.$=z[Q-1]+""+z[Q];break}},"anonymous"),table:[t(e,r,{3:1,4:2,7:4,5:n,34:i,35:a,36:s}),{1:[3]},t(e,r,{4:2,7:4,3:8,5:n,34:i,35:a,36:s}),t(e,r,{4:2,7:4,6:9,3:10,5:n,8:[1,11],34:i,35:a,36:s}),{1:[2,4],9:12,10:[1,13],12:[1,14],14:[1,15],16:[1,16],18:[1,17],19:[1,18],21:[1,19],23:[1,20]},t(l,[2,34]),t(l,[2,35]),t(l,[2,36]),{1:[2,1]},t(e,r,{4:2,7:4,3:21,5:n,34:i,35:a,36:s}),{1:[2,3]},t(l,[2,5]),t(e,[2,7],{4:22,34:i,35:a,36:s}),{11:23,37:24,38:u,39:h,40:27,41:f,42:d,43:p,44:m,45:g,46:y,47:v,48:x,49:b,50:w},{11:39,13:38,24:S,27:T,29:40,30:41,37:24,38:u,39:h,40:27,41:f,42:d,43:p,44:m,45:g,46:y,47:v,48:x,49:b,50:w},{11:45,15:44,27:E,33:46,37:24,38:u,39:h,40:27,41:f,42:d,43:p,44:m,45:g,46:y,47:v,48:x,49:b,50:w},{11:49,17:48,24:_,37:24,38:u,39:h,40:27,41:f,42:d,43:p,44:m,45:g,46:y,47:v,48:x,49:b,50:w},{11:52,17:51,24:_,37:24,38:u,39:h,40:27,41:f,42:d,43:p,44:m,45:g,46:y,47:v,48:x,49:b,50:w},{20:[1,53]},{22:[1,54]},t(A,[2,18]),{1:[2,2]},t(A,[2,8]),t(A,[2,9]),t(L,[2,37],{40:55,41:f,42:d,43:p,44:m,45:g,46:y,47:v,48:x,49:b,50:w}),t(L,[2,38]),t(L,[2,39]),t(M,[2,40]),t(M,[2,42]),t(M,[2,43]),t(M,[2,44]),t(M,[2,45]),t(M,[2,46]),t(M,[2,47]),t(M,[2,48]),t(M,[2,49]),t(M,[2,50]),t(M,[2,51]),t(A,[2,10]),t(A,[2,22],{30:41,29:56,24:S,27:T}),t(A,[2,24]),t(A,[2,25]),{31:[1,57]},{11:59,32:58,37:24,38:u,39:h,40:27,41:f,42:d,43:p,44:m,45:g,46:y,47:v,48:x,49:b,50:w},t(A,[2,11]),t(A,[2,30],{33:60,27:E}),t(A,[2,32]),{31:[1,61]},t(A,[2,12]),{17:62,24:_},{25:63,27:N},t(A,[2,14]),{17:65,24:_},t(A,[2,16]),t(A,[2,17]),t(M,[2,41]),t(A,[2,23]),{27:[1,66]},{26:[1,67]},{26:[2,29],28:[1,68]},t(A,[2,31]),{27:[1,69]},t(A,[2,13]),{26:[1,70]},{26:[2,21],28:[1,71]},t(A,[2,15]),t(A,[2,26]),t(A,[2,27]),{11:59,32:72,37:24,38:u,39:h,40:27,41:f,42:d,43:p,44:m,45:g,46:y,47:v,48:x,49:b,50:w},t(A,[2,33]),t(A,[2,19]),{25:73,27:N},{26:[2,28]},{26:[2,20]}],defaultActions:{8:[2,1],10:[2,3],21:[2,2],72:[2,28],73:[2,20]},parseError:o(function(D,P){if(P.recoverable)this.trace(D);else{var F=new Error(D);throw F.hash=P,F}},"parseError"),parse:o(function(D){var P=this,F=[0],B=[],$=[null],z=[],Y=this.table,Q="",X=0,ie=0,j=0,J=2,Z=1,H=z.slice.call(arguments,1),q=Object.create(this.lexer),K={yy:{}};for(var se in this.yy)Object.prototype.hasOwnProperty.call(this.yy,se)&&(K.yy[se]=this.yy[se]);q.setInput(D,K.yy),K.yy.lexer=q,K.yy.parser=this,typeof q.yylloc>"u"&&(q.yylloc={});var ce=q.yylloc;z.push(ce);var ue=q.options&&q.options.ranges;typeof K.yy.parseError=="function"?this.parseError=K.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function te(he){F.length=F.length-2*he,$.length=$.length-he,z.length=z.length-he}o(te,"popStack");function De(){var he;return he=B.pop()||q.lex()||Z,typeof he!="number"&&(he instanceof Array&&(B=he,he=B.pop()),he=P.symbols_[he]||he),he}o(De,"lex");for(var oe,ke,Ie,Se,Ue,Pe,_e={},me,W,fe,ge;;){if(Ie=F[F.length-1],this.defaultActions[Ie]?Se=this.defaultActions[Ie]:((oe===null||typeof oe>"u")&&(oe=De()),Se=Y[Ie]&&Y[Ie][oe]),typeof Se>"u"||!Se.length||!Se[0]){var re="";ge=[];for(me in Y[Ie])this.terminals_[me]&&me>J&&ge.push("'"+this.terminals_[me]+"'");q.showPosition?re="Parse error on line "+(X+1)+`: +`+q.showPosition()+` +Expecting `+ge.join(", ")+", got '"+(this.terminals_[oe]||oe)+"'":re="Parse error on line "+(X+1)+": Unexpected "+(oe==Z?"end of input":"'"+(this.terminals_[oe]||oe)+"'"),this.parseError(re,{text:q.match,token:this.terminals_[oe]||oe,line:q.yylineno,loc:ce,expected:ge})}if(Se[0]instanceof Array&&Se.length>1)throw new Error("Parse Error: multiple actions possible at state: "+Ie+", token: "+oe);switch(Se[0]){case 1:F.push(oe),$.push(q.yytext),z.push(q.yylloc),F.push(Se[1]),oe=null,ke?(oe=ke,ke=null):(ie=q.yyleng,Q=q.yytext,X=q.yylineno,ce=q.yylloc,j>0&&j--);break;case 2:if(W=this.productions_[Se[1]][1],_e.$=$[$.length-W],_e._$={first_line:z[z.length-(W||1)].first_line,last_line:z[z.length-1].last_line,first_column:z[z.length-(W||1)].first_column,last_column:z[z.length-1].last_column},ue&&(_e._$.range=[z[z.length-(W||1)].range[0],z[z.length-1].range[1]]),Pe=this.performAction.apply(_e,[Q,ie,X,K.yy,Se[1],$,z].concat(H)),typeof Pe<"u")return Pe;W&&(F=F.slice(0,-1*W*2),$=$.slice(0,-1*W),z=z.slice(0,-1*W)),F.push(this.productions_[Se[1]][0]),$.push(_e.$),z.push(_e._$),fe=Y[F[F.length-2]][F[F.length-1]],F.push(fe);break;case 3:return!0}}return!0},"parse")},I=function(){var O={EOF:1,parseError:o(function(P,F){if(this.yy.parser)this.yy.parser.parseError(P,F);else throw new Error(P)},"parseError"),setInput:o(function(D,P){return this.yy=P||this.yy||{},this._input=D,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:o(function(){var D=this._input[0];this.yytext+=D,this.yyleng++,this.offset++,this.match+=D,this.matched+=D;var P=D.match(/(?:\r\n?|\n).*/g);return P?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),D},"input"),unput:o(function(D){var P=D.length,F=D.split(/(?:\r\n?|\n)/g);this._input=D+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-P),this.offset-=P;var B=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),F.length-1&&(this.yylineno-=F.length-1);var $=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:F?(F.length===B.length?this.yylloc.first_column:0)+B[B.length-F.length].length-F[0].length:this.yylloc.first_column-P},this.options.ranges&&(this.yylloc.range=[$[0],$[0]+this.yyleng-P]),this.yyleng=this.yytext.length,this},"unput"),more:o(function(){return this._more=!0,this},"more"),reject:o(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:o(function(D){this.unput(this.match.slice(D))},"less"),pastInput:o(function(){var D=this.matched.substr(0,this.matched.length-this.match.length);return(D.length>20?"...":"")+D.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:o(function(){var D=this.match;return D.length<20&&(D+=this._input.substr(0,20-D.length)),(D.substr(0,20)+(D.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:o(function(){var D=this.pastInput(),P=new Array(D.length+1).join("-");return D+this.upcomingInput()+` +`+P+"^"},"showPosition"),test_match:o(function(D,P){var F,B,$;if(this.options.backtrack_lexer&&($={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&($.yylloc.range=this.yylloc.range.slice(0))),B=D[0].match(/(?:\r\n?|\n).*/g),B&&(this.yylineno+=B.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:B?B[B.length-1].length-B[B.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+D[0].length},this.yytext+=D[0],this.match+=D[0],this.matches=D,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(D[0].length),this.matched+=D[0],F=this.performAction.call(this,this.yy,this,P,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),F)return F;if(this._backtrack){for(var z in $)this[z]=$[z];return!1}return!1},"test_match"),next:o(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var D,P,F,B;this._more||(this.yytext="",this.match="");for(var $=this._currentRules(),z=0;z<$.length;z++)if(F=this._input.match(this.rules[$[z]]),F&&(!P||F[0].length>P[0].length)){if(P=F,B=z,this.options.backtrack_lexer){if(D=this.test_match(F,$[z]),D!==!1)return D;if(this._backtrack){P=!1;continue}else return!1}else if(!this.options.flex)break}return P?(D=this.test_match(P,$[B]),D!==!1?D:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:o(function(){var P=this.next();return P||this.lex()},"lex"),begin:o(function(P){this.conditionStack.push(P)},"begin"),popState:o(function(){var P=this.conditionStack.length-1;return P>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:o(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:o(function(P){return P=this.conditionStack.length-1-Math.abs(P||0),P>=0?this.conditionStack[P]:"INITIAL"},"topState"),pushState:o(function(P){this.begin(P)},"pushState"),stateStackSize:o(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:o(function(P,F,B,$){var z=$;switch(B){case 0:break;case 1:break;case 2:return this.popState(),34;break;case 3:return this.popState(),34;break;case 4:return 34;case 5:break;case 6:return 10;case 7:return this.pushState("acc_title"),19;break;case 8:return this.popState(),"acc_title_value";break;case 9:return this.pushState("acc_descr"),21;break;case 10:return this.popState(),"acc_descr_value";break;case 11:this.pushState("acc_descr_multiline");break;case 12:this.popState();break;case 13:return"acc_descr_multiline_value";case 14:return 5;case 15:return 8;case 16:return this.pushState("axis_data"),"X_AXIS";break;case 17:return this.pushState("axis_data"),"Y_AXIS";break;case 18:return this.pushState("axis_band_data"),24;break;case 19:return 31;case 20:return this.pushState("data"),16;break;case 21:return this.pushState("data"),18;break;case 22:return this.pushState("data_inner"),24;break;case 23:return 27;case 24:return this.popState(),26;break;case 25:this.popState();break;case 26:this.pushState("string");break;case 27:this.popState();break;case 28:return"STR";case 29:return 24;case 30:return 26;case 31:return 43;case 32:return"COLON";case 33:return 44;case 34:return 28;case 35:return 45;case 36:return 46;case 37:return 48;case 38:return 50;case 39:return 47;case 40:return 41;case 41:return 49;case 42:return 42;case 43:break;case 44:return 35;case 45:return 36}},"anonymous"),rules:[/^(?:%%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:(\r?\n))/i,/^(?:(\r?\n))/i,/^(?:[\n\r]+)/i,/^(?:%%[^\n]*)/i,/^(?:title\b)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:\{)/i,/^(?:[^\}]*)/i,/^(?:xychart-beta\b)/i,/^(?:(?:vertical|horizontal))/i,/^(?:x-axis\b)/i,/^(?:y-axis\b)/i,/^(?:\[)/i,/^(?:-->)/i,/^(?:line\b)/i,/^(?:bar\b)/i,/^(?:\[)/i,/^(?:[+-]?(?:\d+(?:\.\d+)?|\.\d+))/i,/^(?:\])/i,/^(?:(?:`\) \{ this\.pushState\(md_string\); \}\n\(\?:\(\?!`"\)\.\)\+ \{ return MD_STR; \}\n\(\?:`))/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:\[)/i,/^(?:\])/i,/^(?:[A-Za-z]+)/i,/^(?::)/i,/^(?:\+)/i,/^(?:,)/i,/^(?:=)/i,/^(?:\*)/i,/^(?:#)/i,/^(?:[\_])/i,/^(?:\.)/i,/^(?:&)/i,/^(?:-)/i,/^(?:[0-9]+)/i,/^(?:\s+)/i,/^(?:;)/i,/^(?:$)/i],conditions:{data_inner:{rules:[0,1,4,5,6,7,9,11,14,15,16,17,20,21,23,24,25,26,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45],inclusive:!0},data:{rules:[0,1,3,4,5,6,7,9,11,14,15,16,17,20,21,22,25,26,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45],inclusive:!0},axis_band_data:{rules:[0,1,4,5,6,7,9,11,14,15,16,17,20,21,24,25,26,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45],inclusive:!0},axis_data:{rules:[0,1,2,4,5,6,7,9,11,14,15,16,17,18,19,20,21,23,25,26,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45],inclusive:!0},acc_descr_multiline:{rules:[12,13],inclusive:!1},acc_descr:{rules:[10],inclusive:!1},acc_title:{rules:[8],inclusive:!1},title:{rules:[],inclusive:!1},md_string:{rules:[],inclusive:!1},string:{rules:[27,28],inclusive:!1},INITIAL:{rules:[0,1,4,5,6,7,9,11,14,15,16,17,20,21,25,26,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45],inclusive:!0}}};return O}();k.lexer=I;function C(){this.yy={}}return o(C,"Parser"),C.prototype=k,k.Parser=C,new C}();QI.parser=QI;tue=QI});function ZI(t){return t.type==="bar"}function hE(t){return t.type==="band"}function Ng(t){return t.type==="linear"}var fE=R(()=>{"use strict";o(ZI,"isBarPlot");o(hE,"isBandAxisData");o(Ng,"isLinearAxisData")});var Mg,JI=R(()=>{"use strict";Al();Mg=class{constructor(e){this.parentGroup=e}static{o(this,"TextDimensionCalculatorWithFont")}getMaxDimension(e,r){if(!this.parentGroup)return{width:e.reduce((a,s)=>Math.max(s.length,a),0)*r,height:r};let n={width:0,height:0},i=this.parentGroup.append("g").attr("visibility","hidden").attr("font-size",r);for(let a of e){let s=bj(i,1,a),l=s?s.width:a.length*r,u=s?s.height:r;n.width=Math.max(n.width,l),n.height=Math.max(n.height,u)}return i.remove(),n}}});var Ig,eO=R(()=>{"use strict";Ig=class{constructor(e,r,n,i){this.axisConfig=e;this.title=r;this.textDimensionCalculator=n;this.axisThemeConfig=i;this.boundingRect={x:0,y:0,width:0,height:0};this.axisPosition="left";this.showTitle=!1;this.showLabel=!1;this.showTick=!1;this.showAxisLine=!1;this.outerPadding=0;this.titleTextHeight=0;this.labelTextHeight=0;this.range=[0,10],this.boundingRect={x:0,y:0,width:0,height:0},this.axisPosition="left"}static{o(this,"BaseAxis")}setRange(e){this.range=e,this.axisPosition==="left"||this.axisPosition==="right"?this.boundingRect.height=e[1]-e[0]:this.boundingRect.width=e[1]-e[0],this.recalculateScale()}getRange(){return[this.range[0]+this.outerPadding,this.range[1]-this.outerPadding]}setAxisPosition(e){this.axisPosition=e,this.setRange(this.range)}getTickDistance(){let e=this.getRange();return Math.abs(e[0]-e[1])/this.getTickValues().length}getAxisOuterPadding(){return this.outerPadding}getLabelDimension(){return this.textDimensionCalculator.getMaxDimension(this.getTickValues().map(e=>e.toString()),this.axisConfig.labelFontSize)}recalculateOuterPaddingToDrawBar(){.7*this.getTickDistance()>this.outerPadding*2&&(this.outerPadding=Math.floor(.7*this.getTickDistance()/2)),this.recalculateScale()}calculateSpaceIfDrawnHorizontally(e){let r=e.height;if(this.axisConfig.showAxisLine&&r>this.axisConfig.axisLineWidth&&(r-=this.axisConfig.axisLineWidth,this.showAxisLine=!0),this.axisConfig.showLabel){let n=this.getLabelDimension(),i=.2*e.width;this.outerPadding=Math.min(n.width/2,i);let a=n.height+this.axisConfig.labelPadding*2;this.labelTextHeight=n.height,a<=r&&(r-=a,this.showLabel=!0)}if(this.axisConfig.showTick&&r>=this.axisConfig.tickLength&&(this.showTick=!0,r-=this.axisConfig.tickLength),this.axisConfig.showTitle&&this.title){let n=this.textDimensionCalculator.getMaxDimension([this.title],this.axisConfig.titleFontSize),i=n.height+this.axisConfig.titlePadding*2;this.titleTextHeight=n.height,i<=r&&(r-=i,this.showTitle=!0)}this.boundingRect.width=e.width,this.boundingRect.height=e.height-r}calculateSpaceIfDrawnVertical(e){let r=e.width;if(this.axisConfig.showAxisLine&&r>this.axisConfig.axisLineWidth&&(r-=this.axisConfig.axisLineWidth,this.showAxisLine=!0),this.axisConfig.showLabel){let n=this.getLabelDimension(),i=.2*e.height;this.outerPadding=Math.min(n.height/2,i);let a=n.width+this.axisConfig.labelPadding*2;a<=r&&(r-=a,this.showLabel=!0)}if(this.axisConfig.showTick&&r>=this.axisConfig.tickLength&&(this.showTick=!0,r-=this.axisConfig.tickLength),this.axisConfig.showTitle&&this.title){let n=this.textDimensionCalculator.getMaxDimension([this.title],this.axisConfig.titleFontSize),i=n.height+this.axisConfig.titlePadding*2;this.titleTextHeight=n.height,i<=r&&(r-=i,this.showTitle=!0)}this.boundingRect.width=e.width-r,this.boundingRect.height=e.height}calculateSpace(e){return this.axisPosition==="left"||this.axisPosition==="right"?this.calculateSpaceIfDrawnVertical(e):this.calculateSpaceIfDrawnHorizontally(e),this.recalculateScale(),{width:this.boundingRect.width,height:this.boundingRect.height}}setBoundingBoxXY(e){this.boundingRect.x=e.x,this.boundingRect.y=e.y}getDrawableElementsForLeftAxis(){let e=[];if(this.showAxisLine){let r=this.boundingRect.x+this.boundingRect.width-this.axisConfig.axisLineWidth/2;e.push({type:"path",groupTexts:["left-axis","axisl-line"],data:[{path:`M ${r},${this.boundingRect.y} L ${r},${this.boundingRect.y+this.boundingRect.height} `,strokeFill:this.axisThemeConfig.axisLineColor,strokeWidth:this.axisConfig.axisLineWidth}]})}if(this.showLabel&&e.push({type:"text",groupTexts:["left-axis","label"],data:this.getTickValues().map(r=>({text:r.toString(),x:this.boundingRect.x+this.boundingRect.width-(this.showLabel?this.axisConfig.labelPadding:0)-(this.showTick?this.axisConfig.tickLength:0)-(this.showAxisLine?this.axisConfig.axisLineWidth:0),y:this.getScaleValue(r),fill:this.axisThemeConfig.labelColor,fontSize:this.axisConfig.labelFontSize,rotation:0,verticalPos:"middle",horizontalPos:"right"}))}),this.showTick){let r=this.boundingRect.x+this.boundingRect.width-(this.showAxisLine?this.axisConfig.axisLineWidth:0);e.push({type:"path",groupTexts:["left-axis","ticks"],data:this.getTickValues().map(n=>({path:`M ${r},${this.getScaleValue(n)} L ${r-this.axisConfig.tickLength},${this.getScaleValue(n)}`,strokeFill:this.axisThemeConfig.tickColor,strokeWidth:this.axisConfig.tickWidth}))})}return this.showTitle&&e.push({type:"text",groupTexts:["left-axis","title"],data:[{text:this.title,x:this.boundingRect.x+this.axisConfig.titlePadding,y:this.boundingRect.y+this.boundingRect.height/2,fill:this.axisThemeConfig.titleColor,fontSize:this.axisConfig.titleFontSize,rotation:270,verticalPos:"top",horizontalPos:"center"}]}),e}getDrawableElementsForBottomAxis(){let e=[];if(this.showAxisLine){let r=this.boundingRect.y+this.axisConfig.axisLineWidth/2;e.push({type:"path",groupTexts:["bottom-axis","axis-line"],data:[{path:`M ${this.boundingRect.x},${r} L ${this.boundingRect.x+this.boundingRect.width},${r}`,strokeFill:this.axisThemeConfig.axisLineColor,strokeWidth:this.axisConfig.axisLineWidth}]})}if(this.showLabel&&e.push({type:"text",groupTexts:["bottom-axis","label"],data:this.getTickValues().map(r=>({text:r.toString(),x:this.getScaleValue(r),y:this.boundingRect.y+this.axisConfig.labelPadding+(this.showTick?this.axisConfig.tickLength:0)+(this.showAxisLine?this.axisConfig.axisLineWidth:0),fill:this.axisThemeConfig.labelColor,fontSize:this.axisConfig.labelFontSize,rotation:0,verticalPos:"top",horizontalPos:"center"}))}),this.showTick){let r=this.boundingRect.y+(this.showAxisLine?this.axisConfig.axisLineWidth:0);e.push({type:"path",groupTexts:["bottom-axis","ticks"],data:this.getTickValues().map(n=>({path:`M ${this.getScaleValue(n)},${r} L ${this.getScaleValue(n)},${r+this.axisConfig.tickLength}`,strokeFill:this.axisThemeConfig.tickColor,strokeWidth:this.axisConfig.tickWidth}))})}return this.showTitle&&e.push({type:"text",groupTexts:["bottom-axis","title"],data:[{text:this.title,x:this.range[0]+(this.range[1]-this.range[0])/2,y:this.boundingRect.y+this.boundingRect.height-this.axisConfig.titlePadding-this.titleTextHeight,fill:this.axisThemeConfig.titleColor,fontSize:this.axisConfig.titleFontSize,rotation:0,verticalPos:"top",horizontalPos:"center"}]}),e}getDrawableElementsForTopAxis(){let e=[];if(this.showAxisLine){let r=this.boundingRect.y+this.boundingRect.height-this.axisConfig.axisLineWidth/2;e.push({type:"path",groupTexts:["top-axis","axis-line"],data:[{path:`M ${this.boundingRect.x},${r} L ${this.boundingRect.x+this.boundingRect.width},${r}`,strokeFill:this.axisThemeConfig.axisLineColor,strokeWidth:this.axisConfig.axisLineWidth}]})}if(this.showLabel&&e.push({type:"text",groupTexts:["top-axis","label"],data:this.getTickValues().map(r=>({text:r.toString(),x:this.getScaleValue(r),y:this.boundingRect.y+(this.showTitle?this.titleTextHeight+this.axisConfig.titlePadding*2:0)+this.axisConfig.labelPadding,fill:this.axisThemeConfig.labelColor,fontSize:this.axisConfig.labelFontSize,rotation:0,verticalPos:"top",horizontalPos:"center"}))}),this.showTick){let r=this.boundingRect.y;e.push({type:"path",groupTexts:["top-axis","ticks"],data:this.getTickValues().map(n=>({path:`M ${this.getScaleValue(n)},${r+this.boundingRect.height-(this.showAxisLine?this.axisConfig.axisLineWidth:0)} L ${this.getScaleValue(n)},${r+this.boundingRect.height-this.axisConfig.tickLength-(this.showAxisLine?this.axisConfig.axisLineWidth:0)}`,strokeFill:this.axisThemeConfig.tickColor,strokeWidth:this.axisConfig.tickWidth}))})}return this.showTitle&&e.push({type:"text",groupTexts:["top-axis","title"],data:[{text:this.title,x:this.boundingRect.x+this.boundingRect.width/2,y:this.boundingRect.y+this.axisConfig.titlePadding,fill:this.axisThemeConfig.titleColor,fontSize:this.axisConfig.titleFontSize,rotation:0,verticalPos:"top",horizontalPos:"center"}]}),e}getDrawableElements(){if(this.axisPosition==="left")return this.getDrawableElementsForLeftAxis();if(this.axisPosition==="right")throw Error("Drawing of right axis is not implemented");return this.axisPosition==="bottom"?this.getDrawableElementsForBottomAxis():this.axisPosition==="top"?this.getDrawableElementsForTopAxis():[]}}});var dE,nue=R(()=>{"use strict";Zt();ut();eO();dE=class extends Ig{static{o(this,"BandAxis")}constructor(e,r,n,i,a){super(e,i,a,r),this.categories=n,this.scale=Op().domain(this.categories).range(this.getRange())}setRange(e){super.setRange(e)}recalculateScale(){this.scale=Op().domain(this.categories).range(this.getRange()).paddingInner(1).paddingOuter(0).align(.5),V.trace("BandAxis axis final categories, range: ",this.categories,this.getRange())}getTickValues(){return this.categories}getScaleValue(e){return this.scale(e)??this.getRange()[0]}}});var pE,iue=R(()=>{"use strict";Zt();eO();pE=class extends Ig{static{o(this,"LinearAxis")}constructor(e,r,n,i,a){super(e,i,a,r),this.domain=n,this.scale=gl().domain(this.domain).range(this.getRange())}getTickValues(){return this.scale.ticks()}recalculateScale(){let e=[...this.domain];this.axisPosition==="left"&&e.reverse(),this.scale=gl().domain(e).range(this.getRange())}getScaleValue(e){return this.scale(e)}}});function tO(t,e,r,n){let i=new Mg(n);return hE(t)?new dE(e,r,t.categories,t.title,i):new pE(e,r,[t.min,t.max],t.title,i)}var aue=R(()=>{"use strict";fE();JI();nue();iue();o(tO,"getAxis")});function sue(t,e,r,n){let i=new Mg(n);return new rO(i,t,e,r)}var rO,oue=R(()=>{"use strict";JI();rO=class{constructor(e,r,n,i){this.textDimensionCalculator=e;this.chartConfig=r;this.chartData=n;this.chartThemeConfig=i;this.boundingRect={x:0,y:0,width:0,height:0},this.showChartTitle=!1}static{o(this,"ChartTitle")}setBoundingBoxXY(e){this.boundingRect.x=e.x,this.boundingRect.y=e.y}calculateSpace(e){let r=this.textDimensionCalculator.getMaxDimension([this.chartData.title],this.chartConfig.titleFontSize),n=Math.max(r.width,e.width),i=r.height+2*this.chartConfig.titlePadding;return r.width<=n&&r.height<=i&&this.chartConfig.showTitle&&this.chartData.title&&(this.boundingRect.width=n,this.boundingRect.height=i,this.showChartTitle=!0),{width:this.boundingRect.width,height:this.boundingRect.height}}getDrawableElements(){let e=[];return this.showChartTitle&&e.push({groupTexts:["chart-title"],type:"text",data:[{fontSize:this.chartConfig.titleFontSize,text:this.chartData.title,verticalPos:"middle",horizontalPos:"center",x:this.boundingRect.x+this.boundingRect.width/2,y:this.boundingRect.y+this.boundingRect.height/2,fill:this.chartThemeConfig.titleColor,rotation:0}]}),e}};o(sue,"getChartTitleComponent")});var mE,lue=R(()=>{"use strict";Zt();mE=class{constructor(e,r,n,i,a){this.plotData=e;this.xAxis=r;this.yAxis=n;this.orientation=i;this.plotIndex=a}static{o(this,"LinePlot")}getDrawableElement(){let e=this.plotData.data.map(n=>[this.xAxis.getScaleValue(n[0]),this.yAxis.getScaleValue(n[1])]),r;return this.orientation==="horizontal"?r=ha().y(n=>n[0]).x(n=>n[1])(e):r=ha().x(n=>n[0]).y(n=>n[1])(e),r?[{groupTexts:["plot",`line-plot-${this.plotIndex}`],type:"path",data:[{path:r,strokeFill:this.plotData.strokeFill,strokeWidth:this.plotData.strokeWidth}]}]:[]}}});var gE,cue=R(()=>{"use strict";gE=class{constructor(e,r,n,i,a,s){this.barData=e;this.boundingRect=r;this.xAxis=n;this.yAxis=i;this.orientation=a;this.plotIndex=s}static{o(this,"BarPlot")}getDrawableElement(){let e=this.barData.data.map(a=>[this.xAxis.getScaleValue(a[0]),this.yAxis.getScaleValue(a[1])]),n=Math.min(this.xAxis.getAxisOuterPadding()*2,this.xAxis.getTickDistance())*(1-.05),i=n/2;return this.orientation==="horizontal"?[{groupTexts:["plot",`bar-plot-${this.plotIndex}`],type:"rect",data:e.map(a=>({x:this.boundingRect.x,y:a[0]-i,height:n,width:a[1]-this.boundingRect.x,fill:this.barData.fill,strokeWidth:0,strokeFill:this.barData.fill}))}]:[{groupTexts:["plot",`bar-plot-${this.plotIndex}`],type:"rect",data:e.map(a=>({x:a[0]-i,y:a[1],width:n,height:this.boundingRect.y+this.boundingRect.height-a[1],fill:this.barData.fill,strokeWidth:0,strokeFill:this.barData.fill}))}]}}});function uue(t,e,r){return new nO(t,e,r)}var nO,hue=R(()=>{"use strict";lue();cue();nO=class{constructor(e,r,n){this.chartConfig=e;this.chartData=r;this.chartThemeConfig=n;this.boundingRect={x:0,y:0,width:0,height:0}}static{o(this,"BasePlot")}setAxes(e,r){this.xAxis=e,this.yAxis=r}setBoundingBoxXY(e){this.boundingRect.x=e.x,this.boundingRect.y=e.y}calculateSpace(e){return this.boundingRect.width=e.width,this.boundingRect.height=e.height,{width:this.boundingRect.width,height:this.boundingRect.height}}getDrawableElements(){if(!(this.xAxis&&this.yAxis))throw Error("Axes must be passed to render Plots");let e=[];for(let[r,n]of this.chartData.plots.entries())switch(n.type){case"line":{let i=new mE(n,this.xAxis,this.yAxis,this.chartConfig.chartOrientation,r);e.push(...i.getDrawableElement())}break;case"bar":{let i=new gE(n,this.boundingRect,this.xAxis,this.yAxis,this.chartConfig.chartOrientation,r);e.push(...i.getDrawableElement())}break}return e}};o(uue,"getPlotComponent")});var yE,fue=R(()=>{"use strict";aue();oue();hue();fE();yE=class{constructor(e,r,n,i){this.chartConfig=e;this.chartData=r;this.componentStore={title:sue(e,r,n,i),plot:uue(e,r,n),xAxis:tO(r.xAxis,e.xAxis,{titleColor:n.xAxisTitleColor,labelColor:n.xAxisLabelColor,tickColor:n.xAxisTickColor,axisLineColor:n.xAxisLineColor},i),yAxis:tO(r.yAxis,e.yAxis,{titleColor:n.yAxisTitleColor,labelColor:n.yAxisLabelColor,tickColor:n.yAxisTickColor,axisLineColor:n.yAxisLineColor},i)}}static{o(this,"Orchestrator")}calculateVerticalSpace(){let e=this.chartConfig.width,r=this.chartConfig.height,n=0,i=0,a=Math.floor(e*this.chartConfig.plotReservedSpacePercent/100),s=Math.floor(r*this.chartConfig.plotReservedSpacePercent/100),l=this.componentStore.plot.calculateSpace({width:a,height:s});e-=l.width,r-=l.height,l=this.componentStore.title.calculateSpace({width:this.chartConfig.width,height:r}),i=l.height,r-=l.height,this.componentStore.xAxis.setAxisPosition("bottom"),l=this.componentStore.xAxis.calculateSpace({width:e,height:r}),r-=l.height,this.componentStore.yAxis.setAxisPosition("left"),l=this.componentStore.yAxis.calculateSpace({width:e,height:r}),n=l.width,e-=l.width,e>0&&(a+=e,e=0),r>0&&(s+=r,r=0),this.componentStore.plot.calculateSpace({width:a,height:s}),this.componentStore.plot.setBoundingBoxXY({x:n,y:i}),this.componentStore.xAxis.setRange([n,n+a]),this.componentStore.xAxis.setBoundingBoxXY({x:n,y:i+s}),this.componentStore.yAxis.setRange([i,i+s]),this.componentStore.yAxis.setBoundingBoxXY({x:0,y:i}),this.chartData.plots.some(u=>ZI(u))&&this.componentStore.xAxis.recalculateOuterPaddingToDrawBar()}calculateHorizontalSpace(){let e=this.chartConfig.width,r=this.chartConfig.height,n=0,i=0,a=0,s=Math.floor(e*this.chartConfig.plotReservedSpacePercent/100),l=Math.floor(r*this.chartConfig.plotReservedSpacePercent/100),u=this.componentStore.plot.calculateSpace({width:s,height:l});e-=u.width,r-=u.height,u=this.componentStore.title.calculateSpace({width:this.chartConfig.width,height:r}),n=u.height,r-=u.height,this.componentStore.xAxis.setAxisPosition("left"),u=this.componentStore.xAxis.calculateSpace({width:e,height:r}),e-=u.width,i=u.width,this.componentStore.yAxis.setAxisPosition("top"),u=this.componentStore.yAxis.calculateSpace({width:e,height:r}),r-=u.height,a=n+u.height,e>0&&(s+=e,e=0),r>0&&(l+=r,r=0),this.componentStore.plot.calculateSpace({width:s,height:l}),this.componentStore.plot.setBoundingBoxXY({x:i,y:a}),this.componentStore.yAxis.setRange([i,i+s]),this.componentStore.yAxis.setBoundingBoxXY({x:i,y:n}),this.componentStore.xAxis.setRange([a,a+l]),this.componentStore.xAxis.setBoundingBoxXY({x:0,y:a}),this.chartData.plots.some(h=>ZI(h))&&this.componentStore.xAxis.recalculateOuterPaddingToDrawBar()}calculateSpace(){this.chartConfig.chartOrientation==="horizontal"?this.calculateHorizontalSpace():this.calculateVerticalSpace()}getDrawableElement(){this.calculateSpace();let e=[];this.componentStore.plot.setAxes(this.componentStore.xAxis,this.componentStore.yAxis);for(let r of Object.values(this.componentStore))e.push(...r.getDrawableElements());return e}}});var vE,due=R(()=>{"use strict";fue();vE=class{static{o(this,"XYChartBuilder")}static build(e,r,n,i){return new yE(e,r,n,i).getDrawableElement()}}});function mue(){let t=hp(),e=Or();return Ts(t.xyChart,e.themeVariables.xyChart)}function gue(){let t=Or();return Ts(mr.xyChart,t.xyChart)}function yue(){return{yAxis:{type:"linear",title:"",min:1/0,max:-1/0},xAxis:{type:"band",title:"",categories:[]},title:"",plots:[]}}function sO(t){let e=Or();return qr(t.trim(),e)}function hze(t){pue=t}function fze(t){t==="horizontal"?px.chartOrientation="horizontal":px.chartOrientation="vertical"}function dze(t){sn.xAxis.title=sO(t.text)}function vue(t,e){sn.xAxis={type:"linear",title:sn.xAxis.title,min:t,max:e},xE=!0}function pze(t){sn.xAxis={type:"band",title:sn.xAxis.title,categories:t.map(e=>sO(e.text))},xE=!0}function mze(t){sn.yAxis.title=sO(t.text)}function gze(t,e){sn.yAxis={type:"linear",title:sn.yAxis.title,min:t,max:e},aO=!0}function yze(t){let e=Math.min(...t),r=Math.max(...t),n=Ng(sn.yAxis)?sn.yAxis.min:1/0,i=Ng(sn.yAxis)?sn.yAxis.max:-1/0;sn.yAxis={type:"linear",title:sn.yAxis.title,min:Math.min(n,e),max:Math.max(i,r)}}function xue(t){let e=[];if(t.length===0)return e;if(!xE){let r=Ng(sn.xAxis)?sn.xAxis.min:1/0,n=Ng(sn.xAxis)?sn.xAxis.max:-1/0;vue(Math.min(r,1),Math.max(n,t.length))}if(aO||yze(t),hE(sn.xAxis)&&(e=sn.xAxis.categories.map((r,n)=>[r,t[n]])),Ng(sn.xAxis)){let r=sn.xAxis.min,n=sn.xAxis.max,i=(n-r)/(t.length-1),a=[];for(let s=r;s<=n;s+=i)a.push(`${s}`);e=a.map((s,l)=>[s,t[l]])}return e}function bue(t){return iO[t===0?0:t%iO.length]}function vze(t,e){let r=xue(e);sn.plots.push({type:"line",strokeFill:bue(dx),strokeWidth:2,data:r}),dx++}function xze(t,e){let r=xue(e);sn.plots.push({type:"bar",fill:bue(dx),data:r}),dx++}function bze(){if(sn.plots.length===0)throw Error("No Plot to render, please provide a plot with some data");return sn.title=Xr(),vE.build(px,sn,mx,pue)}function wze(){return mx}function Tze(){return px}var dx,pue,px,mx,sn,iO,xE,aO,kze,wue,Tue=R(()=>{"use strict";qs();sl();jb();xr();rr();bi();due();fE();dx=0,px=gue(),mx=mue(),sn=yue(),iO=mx.plotColorPalette.split(",").map(t=>t.trim()),xE=!1,aO=!1;o(mue,"getChartDefaultThemeConfig");o(gue,"getChartDefaultConfig");o(yue,"getChartDefaultData");o(sO,"textSanitizer");o(hze,"setTmpSVGG");o(fze,"setOrientation");o(dze,"setXAxisTitle");o(vue,"setXAxisRangeData");o(pze,"setXAxisBand");o(mze,"setYAxisTitle");o(gze,"setYAxisRangeData");o(yze,"setYAxisRangeFromPlotData");o(xue,"transformDataWithoutCategory");o(bue,"getPlotColorFromPalette");o(vze,"setLineData");o(xze,"setBarData");o(bze,"getDrawableElem");o(wze,"getChartThemeConfig");o(Tze,"getChartConfig");kze=o(function(){vr(),dx=0,px=gue(),sn=yue(),mx=mue(),iO=mx.plotColorPalette.split(",").map(t=>t.trim()),xE=!1,aO=!1},"clear"),wue={getDrawableElem:bze,clear:kze,setAccTitle:kr,getAccTitle:Ar,setDiagramTitle:nn,getDiagramTitle:Xr,getAccDescription:Lr,setAccDescription:_r,setOrientation:fze,setXAxisTitle:dze,setXAxisRangeData:vue,setXAxisBand:pze,setYAxisTitle:mze,setYAxisRangeData:gze,setLineData:vze,setBarData:xze,setTmpSVGG:hze,getChartThemeConfig:wze,getChartConfig:Tze}});var Eze,kue,Eue=R(()=>{"use strict";ut();pf();Yn();Eze=o((t,e,r,n)=>{let i=n.db,a=i.getChartThemeConfig(),s=i.getChartConfig();function l(v){return v==="top"?"text-before-edge":"middle"}o(l,"getDominantBaseLine");function u(v){return v==="left"?"start":v==="right"?"end":"middle"}o(u,"getTextAnchor");function h(v){return`translate(${v.x}, ${v.y}) rotate(${v.rotation||0})`}o(h,"getTextTransformation"),V.debug(`Rendering xychart chart +`+t);let f=Ps(e),d=f.append("g").attr("class","main"),p=d.append("rect").attr("width",s.width).attr("height",s.height).attr("class","background");Sr(f,s.height,s.width,!0),f.attr("viewBox",`0 0 ${s.width} ${s.height}`),p.attr("fill",a.backgroundColor),i.setTmpSVGG(f.append("g").attr("class","mermaid-tmp-group"));let m=i.getDrawableElem(),g={};function y(v){let x=d,b="";for(let[w]of v.entries()){let S=d;w>0&&g[b]&&(S=g[b]),b+=v[w],x=g[b],x||(x=g[b]=S.append("g").attr("class",v[w]))}return x}o(y,"getGroup");for(let v of m){if(v.data.length===0)continue;let x=y(v.groupTexts);switch(v.type){case"rect":x.selectAll("rect").data(v.data).enter().append("rect").attr("x",b=>b.x).attr("y",b=>b.y).attr("width",b=>b.width).attr("height",b=>b.height).attr("fill",b=>b.fill).attr("stroke",b=>b.strokeFill).attr("stroke-width",b=>b.strokeWidth);break;case"text":x.selectAll("text").data(v.data).enter().append("text").attr("x",0).attr("y",0).attr("fill",b=>b.fill).attr("font-size",b=>b.fontSize).attr("dominant-baseline",b=>l(b.verticalPos)).attr("text-anchor",b=>u(b.horizontalPos)).attr("transform",b=>h(b)).text(b=>b.text);break;case"path":x.selectAll("path").data(v.data).enter().append("path").attr("d",b=>b.path).attr("fill",b=>b.fill?b.fill:"none").attr("stroke",b=>b.strokeFill).attr("stroke-width",b=>b.strokeWidth);break}}},"draw"),kue={draw:Eze}});var Cue={};hr(Cue,{diagram:()=>Cze});var Cze,Sue=R(()=>{"use strict";rue();Tue();Eue();Cze={parser:tue,db:wue,renderer:kue}});var oO,Lue,Due=R(()=>{"use strict";oO=function(){var t=o(function(ie,j,J,Z){for(J=J||{},Z=ie.length;Z--;J[ie[Z]]=j);return J},"o"),e=[1,3],r=[1,4],n=[1,5],i=[1,6],a=[5,6,8,9,11,13,31,32,33,34,35,36,44,62,63],s=[1,18],l=[2,7],u=[1,22],h=[1,23],f=[1,24],d=[1,25],p=[1,26],m=[1,27],g=[1,20],y=[1,28],v=[1,29],x=[62,63],b=[5,8,9,11,13,31,32,33,34,35,36,44,51,53,62,63],w=[1,47],S=[1,48],T=[1,49],E=[1,50],_=[1,51],A=[1,52],L=[1,53],M=[53,54],N=[1,64],k=[1,60],I=[1,61],C=[1,62],O=[1,63],D=[1,65],P=[1,69],F=[1,70],B=[1,67],$=[1,68],z=[5,8,9,11,13,31,32,33,34,35,36,44,62,63],Y={trace:o(function(){},"trace"),yy:{},symbols_:{error:2,start:3,directive:4,NEWLINE:5,RD:6,diagram:7,EOF:8,acc_title:9,acc_title_value:10,acc_descr:11,acc_descr_value:12,acc_descr_multiline_value:13,requirementDef:14,elementDef:15,relationshipDef:16,requirementType:17,requirementName:18,STRUCT_START:19,requirementBody:20,ID:21,COLONSEP:22,id:23,TEXT:24,text:25,RISK:26,riskLevel:27,VERIFYMTHD:28,verifyType:29,STRUCT_STOP:30,REQUIREMENT:31,FUNCTIONAL_REQUIREMENT:32,INTERFACE_REQUIREMENT:33,PERFORMANCE_REQUIREMENT:34,PHYSICAL_REQUIREMENT:35,DESIGN_CONSTRAINT:36,LOW_RISK:37,MED_RISK:38,HIGH_RISK:39,VERIFY_ANALYSIS:40,VERIFY_DEMONSTRATION:41,VERIFY_INSPECTION:42,VERIFY_TEST:43,ELEMENT:44,elementName:45,elementBody:46,TYPE:47,type:48,DOCREF:49,ref:50,END_ARROW_L:51,relationship:52,LINE:53,END_ARROW_R:54,CONTAINS:55,COPIES:56,DERIVES:57,SATISFIES:58,VERIFIES:59,REFINES:60,TRACES:61,unqString:62,qString:63,$accept:0,$end:1},terminals_:{2:"error",5:"NEWLINE",6:"RD",8:"EOF",9:"acc_title",10:"acc_title_value",11:"acc_descr",12:"acc_descr_value",13:"acc_descr_multiline_value",19:"STRUCT_START",21:"ID",22:"COLONSEP",24:"TEXT",26:"RISK",28:"VERIFYMTHD",30:"STRUCT_STOP",31:"REQUIREMENT",32:"FUNCTIONAL_REQUIREMENT",33:"INTERFACE_REQUIREMENT",34:"PERFORMANCE_REQUIREMENT",35:"PHYSICAL_REQUIREMENT",36:"DESIGN_CONSTRAINT",37:"LOW_RISK",38:"MED_RISK",39:"HIGH_RISK",40:"VERIFY_ANALYSIS",41:"VERIFY_DEMONSTRATION",42:"VERIFY_INSPECTION",43:"VERIFY_TEST",44:"ELEMENT",47:"TYPE",49:"DOCREF",51:"END_ARROW_L",53:"LINE",54:"END_ARROW_R",55:"CONTAINS",56:"COPIES",57:"DERIVES",58:"SATISFIES",59:"VERIFIES",60:"REFINES",61:"TRACES",62:"unqString",63:"qString"},productions_:[0,[3,3],[3,2],[3,4],[4,2],[4,2],[4,1],[7,0],[7,2],[7,2],[7,2],[7,2],[7,2],[14,5],[20,5],[20,5],[20,5],[20,5],[20,2],[20,1],[17,1],[17,1],[17,1],[17,1],[17,1],[17,1],[27,1],[27,1],[27,1],[29,1],[29,1],[29,1],[29,1],[15,5],[46,5],[46,5],[46,2],[46,1],[16,5],[16,5],[52,1],[52,1],[52,1],[52,1],[52,1],[52,1],[52,1],[18,1],[18,1],[23,1],[23,1],[25,1],[25,1],[45,1],[45,1],[48,1],[48,1],[50,1],[50,1]],performAction:o(function(j,J,Z,H,q,K,se){var ce=K.length-1;switch(q){case 4:this.$=K[ce].trim(),H.setAccTitle(this.$);break;case 5:case 6:this.$=K[ce].trim(),H.setAccDescription(this.$);break;case 7:this.$=[];break;case 13:H.addRequirement(K[ce-3],K[ce-4]);break;case 14:H.setNewReqId(K[ce-2]);break;case 15:H.setNewReqText(K[ce-2]);break;case 16:H.setNewReqRisk(K[ce-2]);break;case 17:H.setNewReqVerifyMethod(K[ce-2]);break;case 20:this.$=H.RequirementType.REQUIREMENT;break;case 21:this.$=H.RequirementType.FUNCTIONAL_REQUIREMENT;break;case 22:this.$=H.RequirementType.INTERFACE_REQUIREMENT;break;case 23:this.$=H.RequirementType.PERFORMANCE_REQUIREMENT;break;case 24:this.$=H.RequirementType.PHYSICAL_REQUIREMENT;break;case 25:this.$=H.RequirementType.DESIGN_CONSTRAINT;break;case 26:this.$=H.RiskLevel.LOW_RISK;break;case 27:this.$=H.RiskLevel.MED_RISK;break;case 28:this.$=H.RiskLevel.HIGH_RISK;break;case 29:this.$=H.VerifyType.VERIFY_ANALYSIS;break;case 30:this.$=H.VerifyType.VERIFY_DEMONSTRATION;break;case 31:this.$=H.VerifyType.VERIFY_INSPECTION;break;case 32:this.$=H.VerifyType.VERIFY_TEST;break;case 33:H.addElement(K[ce-3]);break;case 34:H.setNewElementType(K[ce-2]);break;case 35:H.setNewElementDocRef(K[ce-2]);break;case 38:H.addRelationship(K[ce-2],K[ce],K[ce-4]);break;case 39:H.addRelationship(K[ce-2],K[ce-4],K[ce]);break;case 40:this.$=H.Relationships.CONTAINS;break;case 41:this.$=H.Relationships.COPIES;break;case 42:this.$=H.Relationships.DERIVES;break;case 43:this.$=H.Relationships.SATISFIES;break;case 44:this.$=H.Relationships.VERIFIES;break;case 45:this.$=H.Relationships.REFINES;break;case 46:this.$=H.Relationships.TRACES;break}},"anonymous"),table:[{3:1,4:2,6:e,9:r,11:n,13:i},{1:[3]},{3:8,4:2,5:[1,7],6:e,9:r,11:n,13:i},{5:[1,9]},{10:[1,10]},{12:[1,11]},t(a,[2,6]),{3:12,4:2,6:e,9:r,11:n,13:i},{1:[2,2]},{4:17,5:s,7:13,8:l,9:r,11:n,13:i,14:14,15:15,16:16,17:19,23:21,31:u,32:h,33:f,34:d,35:p,36:m,44:g,62:y,63:v},t(a,[2,4]),t(a,[2,5]),{1:[2,1]},{8:[1,30]},{4:17,5:s,7:31,8:l,9:r,11:n,13:i,14:14,15:15,16:16,17:19,23:21,31:u,32:h,33:f,34:d,35:p,36:m,44:g,62:y,63:v},{4:17,5:s,7:32,8:l,9:r,11:n,13:i,14:14,15:15,16:16,17:19,23:21,31:u,32:h,33:f,34:d,35:p,36:m,44:g,62:y,63:v},{4:17,5:s,7:33,8:l,9:r,11:n,13:i,14:14,15:15,16:16,17:19,23:21,31:u,32:h,33:f,34:d,35:p,36:m,44:g,62:y,63:v},{4:17,5:s,7:34,8:l,9:r,11:n,13:i,14:14,15:15,16:16,17:19,23:21,31:u,32:h,33:f,34:d,35:p,36:m,44:g,62:y,63:v},{4:17,5:s,7:35,8:l,9:r,11:n,13:i,14:14,15:15,16:16,17:19,23:21,31:u,32:h,33:f,34:d,35:p,36:m,44:g,62:y,63:v},{18:36,62:[1,37],63:[1,38]},{45:39,62:[1,40],63:[1,41]},{51:[1,42],53:[1,43]},t(x,[2,20]),t(x,[2,21]),t(x,[2,22]),t(x,[2,23]),t(x,[2,24]),t(x,[2,25]),t(b,[2,49]),t(b,[2,50]),{1:[2,3]},{8:[2,8]},{8:[2,9]},{8:[2,10]},{8:[2,11]},{8:[2,12]},{19:[1,44]},{19:[2,47]},{19:[2,48]},{19:[1,45]},{19:[2,53]},{19:[2,54]},{52:46,55:w,56:S,57:T,58:E,59:_,60:A,61:L},{52:54,55:w,56:S,57:T,58:E,59:_,60:A,61:L},{5:[1,55]},{5:[1,56]},{53:[1,57]},t(M,[2,40]),t(M,[2,41]),t(M,[2,42]),t(M,[2,43]),t(M,[2,44]),t(M,[2,45]),t(M,[2,46]),{54:[1,58]},{5:N,20:59,21:k,24:I,26:C,28:O,30:D},{5:P,30:F,46:66,47:B,49:$},{23:71,62:y,63:v},{23:72,62:y,63:v},t(z,[2,13]),{22:[1,73]},{22:[1,74]},{22:[1,75]},{22:[1,76]},{5:N,20:77,21:k,24:I,26:C,28:O,30:D},t(z,[2,19]),t(z,[2,33]),{22:[1,78]},{22:[1,79]},{5:P,30:F,46:80,47:B,49:$},t(z,[2,37]),t(z,[2,38]),t(z,[2,39]),{23:81,62:y,63:v},{25:82,62:[1,83],63:[1,84]},{27:85,37:[1,86],38:[1,87],39:[1,88]},{29:89,40:[1,90],41:[1,91],42:[1,92],43:[1,93]},t(z,[2,18]),{48:94,62:[1,95],63:[1,96]},{50:97,62:[1,98],63:[1,99]},t(z,[2,36]),{5:[1,100]},{5:[1,101]},{5:[2,51]},{5:[2,52]},{5:[1,102]},{5:[2,26]},{5:[2,27]},{5:[2,28]},{5:[1,103]},{5:[2,29]},{5:[2,30]},{5:[2,31]},{5:[2,32]},{5:[1,104]},{5:[2,55]},{5:[2,56]},{5:[1,105]},{5:[2,57]},{5:[2,58]},{5:N,20:106,21:k,24:I,26:C,28:O,30:D},{5:N,20:107,21:k,24:I,26:C,28:O,30:D},{5:N,20:108,21:k,24:I,26:C,28:O,30:D},{5:N,20:109,21:k,24:I,26:C,28:O,30:D},{5:P,30:F,46:110,47:B,49:$},{5:P,30:F,46:111,47:B,49:$},t(z,[2,14]),t(z,[2,15]),t(z,[2,16]),t(z,[2,17]),t(z,[2,34]),t(z,[2,35])],defaultActions:{8:[2,2],12:[2,1],30:[2,3],31:[2,8],32:[2,9],33:[2,10],34:[2,11],35:[2,12],37:[2,47],38:[2,48],40:[2,53],41:[2,54],83:[2,51],84:[2,52],86:[2,26],87:[2,27],88:[2,28],90:[2,29],91:[2,30],92:[2,31],93:[2,32],95:[2,55],96:[2,56],98:[2,57],99:[2,58]},parseError:o(function(j,J){if(J.recoverable)this.trace(j);else{var Z=new Error(j);throw Z.hash=J,Z}},"parseError"),parse:o(function(j){var J=this,Z=[0],H=[],q=[null],K=[],se=this.table,ce="",ue=0,te=0,De=0,oe=2,ke=1,Ie=K.slice.call(arguments,1),Se=Object.create(this.lexer),Ue={yy:{}};for(var Pe in this.yy)Object.prototype.hasOwnProperty.call(this.yy,Pe)&&(Ue.yy[Pe]=this.yy[Pe]);Se.setInput(j,Ue.yy),Ue.yy.lexer=Se,Ue.yy.parser=this,typeof Se.yylloc>"u"&&(Se.yylloc={});var _e=Se.yylloc;K.push(_e);var me=Se.options&&Se.options.ranges;typeof Ue.yy.parseError=="function"?this.parseError=Ue.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function W(He){Z.length=Z.length-2*He,q.length=q.length-He,K.length=K.length-He}o(W,"popStack");function fe(){var He;return He=H.pop()||Se.lex()||ke,typeof He!="number"&&(He instanceof Array&&(H=He,He=H.pop()),He=J.symbols_[He]||He),He}o(fe,"lex");for(var ge,re,he,ne,ae,we,Te={},Ce,Ae,Ge,Me;;){if(he=Z[Z.length-1],this.defaultActions[he]?ne=this.defaultActions[he]:((ge===null||typeof ge>"u")&&(ge=fe()),ne=se[he]&&se[he][ge]),typeof ne>"u"||!ne.length||!ne[0]){var ye="";Me=[];for(Ce in se[he])this.terminals_[Ce]&&Ce>oe&&Me.push("'"+this.terminals_[Ce]+"'");Se.showPosition?ye="Parse error on line "+(ue+1)+`: +`+Se.showPosition()+` +Expecting `+Me.join(", ")+", got '"+(this.terminals_[ge]||ge)+"'":ye="Parse error on line "+(ue+1)+": Unexpected "+(ge==ke?"end of input":"'"+(this.terminals_[ge]||ge)+"'"),this.parseError(ye,{text:Se.match,token:this.terminals_[ge]||ge,line:Se.yylineno,loc:_e,expected:Me})}if(ne[0]instanceof Array&&ne.length>1)throw new Error("Parse Error: multiple actions possible at state: "+he+", token: "+ge);switch(ne[0]){case 1:Z.push(ge),q.push(Se.yytext),K.push(Se.yylloc),Z.push(ne[1]),ge=null,re?(ge=re,re=null):(te=Se.yyleng,ce=Se.yytext,ue=Se.yylineno,_e=Se.yylloc,De>0&&De--);break;case 2:if(Ae=this.productions_[ne[1]][1],Te.$=q[q.length-Ae],Te._$={first_line:K[K.length-(Ae||1)].first_line,last_line:K[K.length-1].last_line,first_column:K[K.length-(Ae||1)].first_column,last_column:K[K.length-1].last_column},me&&(Te._$.range=[K[K.length-(Ae||1)].range[0],K[K.length-1].range[1]]),we=this.performAction.apply(Te,[ce,te,ue,Ue.yy,ne[1],q,K].concat(Ie)),typeof we<"u")return we;Ae&&(Z=Z.slice(0,-1*Ae*2),q=q.slice(0,-1*Ae),K=K.slice(0,-1*Ae)),Z.push(this.productions_[ne[1]][0]),q.push(Te.$),K.push(Te._$),Ge=se[Z[Z.length-2]][Z[Z.length-1]],Z.push(Ge);break;case 3:return!0}}return!0},"parse")},Q=function(){var ie={EOF:1,parseError:o(function(J,Z){if(this.yy.parser)this.yy.parser.parseError(J,Z);else throw new Error(J)},"parseError"),setInput:o(function(j,J){return this.yy=J||this.yy||{},this._input=j,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:o(function(){var j=this._input[0];this.yytext+=j,this.yyleng++,this.offset++,this.match+=j,this.matched+=j;var J=j.match(/(?:\r\n?|\n).*/g);return J?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),j},"input"),unput:o(function(j){var J=j.length,Z=j.split(/(?:\r\n?|\n)/g);this._input=j+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-J),this.offset-=J;var H=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),Z.length-1&&(this.yylineno-=Z.length-1);var q=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:Z?(Z.length===H.length?this.yylloc.first_column:0)+H[H.length-Z.length].length-Z[0].length:this.yylloc.first_column-J},this.options.ranges&&(this.yylloc.range=[q[0],q[0]+this.yyleng-J]),this.yyleng=this.yytext.length,this},"unput"),more:o(function(){return this._more=!0,this},"more"),reject:o(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:o(function(j){this.unput(this.match.slice(j))},"less"),pastInput:o(function(){var j=this.matched.substr(0,this.matched.length-this.match.length);return(j.length>20?"...":"")+j.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:o(function(){var j=this.match;return j.length<20&&(j+=this._input.substr(0,20-j.length)),(j.substr(0,20)+(j.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:o(function(){var j=this.pastInput(),J=new Array(j.length+1).join("-");return j+this.upcomingInput()+` +`+J+"^"},"showPosition"),test_match:o(function(j,J){var Z,H,q;if(this.options.backtrack_lexer&&(q={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(q.yylloc.range=this.yylloc.range.slice(0))),H=j[0].match(/(?:\r\n?|\n).*/g),H&&(this.yylineno+=H.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:H?H[H.length-1].length-H[H.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+j[0].length},this.yytext+=j[0],this.match+=j[0],this.matches=j,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(j[0].length),this.matched+=j[0],Z=this.performAction.call(this,this.yy,this,J,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),Z)return Z;if(this._backtrack){for(var K in q)this[K]=q[K];return!1}return!1},"test_match"),next:o(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var j,J,Z,H;this._more||(this.yytext="",this.match="");for(var q=this._currentRules(),K=0;KJ[0].length)){if(J=Z,H=K,this.options.backtrack_lexer){if(j=this.test_match(Z,q[K]),j!==!1)return j;if(this._backtrack){J=!1;continue}else return!1}else if(!this.options.flex)break}return J?(j=this.test_match(J,q[H]),j!==!1?j:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:o(function(){var J=this.next();return J||this.lex()},"lex"),begin:o(function(J){this.conditionStack.push(J)},"begin"),popState:o(function(){var J=this.conditionStack.length-1;return J>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:o(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:o(function(J){return J=this.conditionStack.length-1-Math.abs(J||0),J>=0?this.conditionStack[J]:"INITIAL"},"topState"),pushState:o(function(J){this.begin(J)},"pushState"),stateStackSize:o(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:o(function(J,Z,H,q){var K=q;switch(H){case 0:return"title";case 1:return this.begin("acc_title"),9;break;case 2:return this.popState(),"acc_title_value";break;case 3:return this.begin("acc_descr"),11;break;case 4:return this.popState(),"acc_descr_value";break;case 5:this.begin("acc_descr_multiline");break;case 6:this.popState();break;case 7:return"acc_descr_multiline_value";case 8:return 5;case 9:break;case 10:break;case 11:break;case 12:return 8;case 13:return 6;case 14:return 19;case 15:return 30;case 16:return 22;case 17:return 21;case 18:return 24;case 19:return 26;case 20:return 28;case 21:return 31;case 22:return 32;case 23:return 33;case 24:return 34;case 25:return 35;case 26:return 36;case 27:return 37;case 28:return 38;case 29:return 39;case 30:return 40;case 31:return 41;case 32:return 42;case 33:return 43;case 34:return 44;case 35:return 55;case 36:return 56;case 37:return 57;case 38:return 58;case 39:return 59;case 40:return 60;case 41:return 61;case 42:return 47;case 43:return 49;case 44:return 51;case 45:return 54;case 46:return 53;case 47:this.begin("string");break;case 48:this.popState();break;case 49:return"qString";case 50:return Z.yytext=Z.yytext.trim(),62;break}},"anonymous"),rules:[/^(?:title\s[^#\n;]+)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:(\r?\n)+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:$)/i,/^(?:requirementDiagram\b)/i,/^(?:\{)/i,/^(?:\})/i,/^(?::)/i,/^(?:id\b)/i,/^(?:text\b)/i,/^(?:risk\b)/i,/^(?:verifyMethod\b)/i,/^(?:requirement\b)/i,/^(?:functionalRequirement\b)/i,/^(?:interfaceRequirement\b)/i,/^(?:performanceRequirement\b)/i,/^(?:physicalRequirement\b)/i,/^(?:designConstraint\b)/i,/^(?:low\b)/i,/^(?:medium\b)/i,/^(?:high\b)/i,/^(?:analysis\b)/i,/^(?:demonstration\b)/i,/^(?:inspection\b)/i,/^(?:test\b)/i,/^(?:element\b)/i,/^(?:contains\b)/i,/^(?:copies\b)/i,/^(?:derives\b)/i,/^(?:satisfies\b)/i,/^(?:verifies\b)/i,/^(?:refines\b)/i,/^(?:traces\b)/i,/^(?:type\b)/i,/^(?:docref\b)/i,/^(?:<-)/i,/^(?:->)/i,/^(?:-)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[\w][^\r\n\{\<\>\-\=]*)/i],conditions:{acc_descr_multiline:{rules:[6,7],inclusive:!1},acc_descr:{rules:[4],inclusive:!1},acc_title:{rules:[2],inclusive:!1},unqString:{rules:[],inclusive:!1},token:{rules:[],inclusive:!1},string:{rules:[48,49],inclusive:!1},INITIAL:{rules:[0,1,3,5,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,50],inclusive:!0}}};return ie}();Y.lexer=Q;function X(){this.yy={}}return o(X,"Parser"),X.prototype=Y,Y.Parser=X,new X}();oO.parser=oO;Lue=oO});var lO,Bs,gx,mf,yx,Lze,Dze,Rze,Nze,Mze,Ize,Oze,Pze,Bze,Fze,zze,Gze,$ze,Vze,Uze,Hze,Yze,Rue,Nue=R(()=>{"use strict";_t();ut();bi();lO=[],Bs={},gx=new Map,mf={},yx=new Map,Lze={REQUIREMENT:"Requirement",FUNCTIONAL_REQUIREMENT:"Functional Requirement",INTERFACE_REQUIREMENT:"Interface Requirement",PERFORMANCE_REQUIREMENT:"Performance Requirement",PHYSICAL_REQUIREMENT:"Physical Requirement",DESIGN_CONSTRAINT:"Design Constraint"},Dze={LOW_RISK:"Low",MED_RISK:"Medium",HIGH_RISK:"High"},Rze={VERIFY_ANALYSIS:"Analysis",VERIFY_DEMONSTRATION:"Demonstration",VERIFY_INSPECTION:"Inspection",VERIFY_TEST:"Test"},Nze={CONTAINS:"contains",COPIES:"copies",DERIVES:"derives",SATISFIES:"satisfies",VERIFIES:"verifies",REFINES:"refines",TRACES:"traces"},Mze=o((t,e)=>(gx.has(t)||gx.set(t,{name:t,type:e,id:Bs.id,text:Bs.text,risk:Bs.risk,verifyMethod:Bs.verifyMethod}),Bs={},gx.get(t)),"addRequirement"),Ize=o(()=>gx,"getRequirements"),Oze=o(t=>{Bs!==void 0&&(Bs.id=t)},"setNewReqId"),Pze=o(t=>{Bs!==void 0&&(Bs.text=t)},"setNewReqText"),Bze=o(t=>{Bs!==void 0&&(Bs.risk=t)},"setNewReqRisk"),Fze=o(t=>{Bs!==void 0&&(Bs.verifyMethod=t)},"setNewReqVerifyMethod"),zze=o(t=>(yx.has(t)||(yx.set(t,{name:t,type:mf.type,docRef:mf.docRef}),V.info("Added new requirement: ",t)),mf={},yx.get(t)),"addElement"),Gze=o(()=>yx,"getElements"),$ze=o(t=>{mf!==void 0&&(mf.type=t)},"setNewElementType"),Vze=o(t=>{mf!==void 0&&(mf.docRef=t)},"setNewElementDocRef"),Uze=o((t,e,r)=>{lO.push({type:t,src:e,dst:r})},"addRelationship"),Hze=o(()=>lO,"getRelationships"),Yze=o(()=>{lO=[],Bs={},gx=new Map,mf={},yx=new Map,vr()},"clear"),Rue={RequirementType:Lze,RiskLevel:Dze,VerifyType:Rze,Relationships:Nze,getConfig:o(()=>de().req,"getConfig"),addRequirement:Mze,getRequirements:Ize,setNewReqId:Oze,setNewReqText:Pze,setNewReqRisk:Bze,setNewReqVerifyMethod:Fze,setAccTitle:kr,getAccTitle:Ar,setAccDescription:_r,getAccDescription:Lr,addElement:zze,getElements:Gze,setNewElementType:$ze,setNewElementDocRef:Vze,addRelationship:Uze,getRelationships:Hze,clear:Yze}});var Wze,Mue,Iue=R(()=>{"use strict";Wze=o(t=>` + + marker { + fill: ${t.relationColor}; + stroke: ${t.relationColor}; + } + + marker.cross { + stroke: ${t.lineColor}; + } + + svg { + font-family: ${t.fontFamily}; + font-size: ${t.fontSize}; + } + + .reqBox { + fill: ${t.requirementBackground}; + fill-opacity: 1.0; + stroke: ${t.requirementBorderColor}; + stroke-width: ${t.requirementBorderSize}; + } + + .reqTitle, .reqLabel{ + fill: ${t.requirementTextColor}; + } + .reqLabelBox { + fill: ${t.relationLabelBackground}; + fill-opacity: 1.0; + } + + .req-title-line { + stroke: ${t.requirementBorderColor}; + stroke-width: ${t.requirementBorderSize}; + } + .relationshipLine { + stroke: ${t.relationColor}; + stroke-width: 1; + } + .relationshipLabel { + fill: ${t.relationLabelColor}; + } + +`,"getStyles"),Mue=Wze});var cO,qze,uO,Oue=R(()=>{"use strict";cO={CONTAINS:"contains",ARROW:"arrow"},qze=o((t,e)=>{let r=t.append("defs").append("marker").attr("id",cO.CONTAINS+"_line_ending").attr("refX",0).attr("refY",e.line_height/2).attr("markerWidth",e.line_height).attr("markerHeight",e.line_height).attr("orient","auto").append("g");r.append("circle").attr("cx",e.line_height/2).attr("cy",e.line_height/2).attr("r",e.line_height/2).attr("fill","none"),r.append("line").attr("x1",0).attr("x2",e.line_height).attr("y1",e.line_height/2).attr("y2",e.line_height/2).attr("stroke-width",1),r.append("line").attr("y1",0).attr("y2",e.line_height).attr("x1",e.line_height/2).attr("x2",e.line_height/2).attr("stroke-width",1),t.append("defs").append("marker").attr("id",cO.ARROW+"_line_ending").attr("refX",e.line_height).attr("refY",.5*e.line_height).attr("markerWidth",e.line_height).attr("markerHeight",e.line_height).attr("orient","auto").append("path").attr("d",`M0,0 + L${e.line_height},${e.line_height/2} + M${e.line_height},${e.line_height/2} + L0,${e.line_height}`).attr("stroke-width",1)},"insertLineEndings"),uO={ReqMarkers:cO,insertLineEndings:qze}});var ai,Pue,Bue,Fue,zue,Xze,jze,Kze,Qze,Zze,Jze,Og,eGe,Gue,$ue=R(()=>{"use strict";Zt();Vd();ya();_t();ut();Yn();rr();Oue();ai={},Pue=0,Bue=o((t,e)=>t.insert("rect","#"+e).attr("class","req reqBox").attr("x",0).attr("y",0).attr("width",ai.rect_min_width+"px").attr("height",ai.rect_min_height+"px"),"newRectNode"),Fue=o((t,e,r)=>{let n=ai.rect_min_width/2,i=t.append("text").attr("class","req reqLabel reqTitle").attr("id",e).attr("x",n).attr("y",ai.rect_padding).attr("dominant-baseline","hanging"),a=0;r.forEach(h=>{a==0?i.append("tspan").attr("text-anchor","middle").attr("x",ai.rect_min_width/2).attr("dy",0).text(h):i.append("tspan").attr("text-anchor","middle").attr("x",ai.rect_min_width/2).attr("dy",ai.line_height*.75).text(h),a++});let s=1.5*ai.rect_padding,l=a*ai.line_height*.75,u=s+l;return t.append("line").attr("class","req-title-line").attr("x1","0").attr("x2",ai.rect_min_width).attr("y1",u).attr("y2",u),{titleNode:i,y:u}},"newTitleNode"),zue=o((t,e,r,n)=>{let i=t.append("text").attr("class","req reqLabel").attr("id",e).attr("x",ai.rect_padding).attr("y",n).attr("dominant-baseline","hanging"),a=0,s=30,l=[];return r.forEach(u=>{let h=u.length;for(;h>s&&a<3;){let f=u.substring(0,s);u=u.substring(s,u.length),h=u.length,l[l.length]=f,a++}if(a==3){let f=l[l.length-1];l[l.length-1]=f.substring(0,f.length-4)+"..."}else l[l.length]=u;a=0}),l.forEach(u=>{i.append("tspan").attr("x",ai.rect_padding).attr("dy",ai.line_height).text(u)}),i},"newBodyNode"),Xze=o((t,e,r,n)=>{let i=e.node().getTotalLength(),a=e.node().getPointAtLength(i*.5),s="rel"+Pue;Pue++;let u=t.append("text").attr("class","req relationshipLabel").attr("id",s).attr("x",a.x).attr("y",a.y).attr("text-anchor","middle").attr("dominant-baseline","middle").text(n).node().getBBox();t.insert("rect","#"+s).attr("class","req reqLabelBox").attr("x",a.x-u.width/2).attr("y",a.y-u.height/2).attr("width",u.width).attr("height",u.height).attr("fill","white").attr("fill-opacity","85%")},"addEdgeLabel"),jze=o(function(t,e,r,n,i){let a=r.edge(Og(e.src),Og(e.dst)),s=ha().x(function(u){return u.x}).y(function(u){return u.y}),l=t.insert("path","#"+n).attr("class","er relationshipLine").attr("d",s(a.points)).attr("fill","none");e.type==i.db.Relationships.CONTAINS?l.attr("marker-start","url("+We.getUrl(ai.arrowMarkerAbsolute)+"#"+e.type+"_line_ending)"):(l.attr("stroke-dasharray","10,7"),l.attr("marker-end","url("+We.getUrl(ai.arrowMarkerAbsolute)+"#"+uO.ReqMarkers.ARROW+"_line_ending)")),Xze(t,l,ai,`<<${e.type}>>`)},"drawRelationshipFromLayout"),Kze=o((t,e,r)=>{t.forEach((n,i)=>{i=Og(i),V.info("Added new requirement: ",i);let a=r.append("g").attr("id",i),s="req-"+i,l=Bue(a,s),u=[],h=Fue(a,i+"_title",[`<<${n.type}>>`,`${n.name}`]);u.push(h.titleNode);let f=zue(a,i+"_body",[`Id: ${n.id}`,`Text: ${n.text}`,`Risk: ${n.risk}`,`Verification: ${n.verifyMethod}`],h.y);u.push(f);let d=l.node().getBBox();e.setNode(i,{width:d.width,height:d.height,shape:"rect",id:i})})},"drawReqs"),Qze=o((t,e,r)=>{t.forEach((n,i)=>{let a=Og(i),s=r.append("g").attr("id",a),l="element-"+a,u=Bue(s,l),h=[],f=Fue(s,l+"_title",["<>",`${i}`]);h.push(f.titleNode);let d=zue(s,l+"_body",[`Type: ${n.type||"Not Specified"}`,`Doc Ref: ${n.docRef||"None"}`],f.y);h.push(d);let p=u.node().getBBox();e.setNode(a,{width:p.width,height:p.height,shape:"rect",id:a})})},"drawElements"),Zze=o((t,e)=>(t.forEach(function(r){let n=Og(r.src),i=Og(r.dst);e.setEdge(n,i,{relationship:r})}),t),"addRelationships"),Jze=o(function(t,e){e.nodes().forEach(function(r){r!==void 0&&e.node(r)!==void 0&&(t.select("#"+r),t.select("#"+r).attr("transform","translate("+(e.node(r).x-e.node(r).width/2)+","+(e.node(r).y-e.node(r).height/2)+" )"))})},"adjustEntities"),Og=o(t=>t.replace(/\s/g,"").replace(/\./g,"_"),"elementString"),eGe=o((t,e,r,n)=>{ai=de().requirement;let i=ai.securityLevel,a;i==="sandbox"&&(a=$e("#i"+e));let l=(i==="sandbox"?$e(a.nodes()[0].contentDocument.body):$e("body")).select(`[id='${e}']`);uO.insertLineEndings(l,ai);let u=new lr({multigraph:!1,compound:!1,directed:!0}).setGraph({rankdir:ai.layoutDirection,marginx:20,marginy:20,nodesep:100,edgesep:100,ranksep:100}).setDefaultEdgeLabel(function(){return{}}),h=n.db.getRequirements(),f=n.db.getElements(),d=n.db.getRelationships();Kze(h,u,l),Qze(f,u,l),Zze(d,u),lo(u),Jze(l,u),d.forEach(function(v){jze(l,v,u,e,n)});let p=ai.rect_padding,m=l.node().getBBox(),g=m.width+p*2,y=m.height+p*2;Sr(l,y,g,ai.useMaxWidth),l.attr("viewBox",`${m.x-p} ${m.y-p} ${g} ${y}`)},"draw"),Gue={draw:eGe}});var Vue={};hr(Vue,{diagram:()=>tGe});var tGe,Uue=R(()=>{"use strict";Due();Nue();Iue();$ue();tGe={parser:Lue,db:Rue,renderer:Gue,styles:Mue}});var hO,Wue,que=R(()=>{"use strict";hO=function(){var t=o(function(H,q,K,se){for(K=K||{},se=H.length;se--;K[H[se]]=q);return K},"o"),e=[1,2],r=[1,3],n=[1,4],i=[2,4],a=[1,9],s=[1,11],l=[1,13],u=[1,14],h=[1,16],f=[1,17],d=[1,18],p=[1,24],m=[1,25],g=[1,26],y=[1,27],v=[1,28],x=[1,29],b=[1,30],w=[1,31],S=[1,32],T=[1,33],E=[1,34],_=[1,35],A=[1,36],L=[1,37],M=[1,38],N=[1,39],k=[1,41],I=[1,42],C=[1,43],O=[1,44],D=[1,45],P=[1,46],F=[1,4,5,13,14,16,18,21,23,29,30,31,33,35,36,37,38,39,41,43,44,46,47,48,49,50,52,53,54,59,60,61,62,70],B=[4,5,16,50,52,53],$=[4,5,13,14,16,18,21,23,29,30,31,33,35,36,37,38,39,41,43,44,46,50,52,53,54,59,60,61,62,70],z=[4,5,13,14,16,18,21,23,29,30,31,33,35,36,37,38,39,41,43,44,46,49,50,52,53,54,59,60,61,62,70],Y=[4,5,13,14,16,18,21,23,29,30,31,33,35,36,37,38,39,41,43,44,46,48,50,52,53,54,59,60,61,62,70],Q=[4,5,13,14,16,18,21,23,29,30,31,33,35,36,37,38,39,41,43,44,46,47,50,52,53,54,59,60,61,62,70],X=[68,69,70],ie=[1,122],j={trace:o(function(){},"trace"),yy:{},symbols_:{error:2,start:3,SPACE:4,NEWLINE:5,SD:6,document:7,line:8,statement:9,box_section:10,box_line:11,participant_statement:12,create:13,box:14,restOfLine:15,end:16,signal:17,autonumber:18,NUM:19,off:20,activate:21,actor:22,deactivate:23,note_statement:24,links_statement:25,link_statement:26,properties_statement:27,details_statement:28,title:29,legacy_title:30,acc_title:31,acc_title_value:32,acc_descr:33,acc_descr_value:34,acc_descr_multiline_value:35,loop:36,rect:37,opt:38,alt:39,else_sections:40,par:41,par_sections:42,par_over:43,critical:44,option_sections:45,break:46,option:47,and:48,else:49,participant:50,AS:51,participant_actor:52,destroy:53,note:54,placement:55,text2:56,over:57,actor_pair:58,links:59,link:60,properties:61,details:62,spaceList:63,",":64,left_of:65,right_of:66,signaltype:67,"+":68,"-":69,ACTOR:70,SOLID_OPEN_ARROW:71,DOTTED_OPEN_ARROW:72,SOLID_ARROW:73,BIDIRECTIONAL_SOLID_ARROW:74,DOTTED_ARROW:75,BIDIRECTIONAL_DOTTED_ARROW:76,SOLID_CROSS:77,DOTTED_CROSS:78,SOLID_POINT:79,DOTTED_POINT:80,TXT:81,$accept:0,$end:1},terminals_:{2:"error",4:"SPACE",5:"NEWLINE",6:"SD",13:"create",14:"box",15:"restOfLine",16:"end",18:"autonumber",19:"NUM",20:"off",21:"activate",23:"deactivate",29:"title",30:"legacy_title",31:"acc_title",32:"acc_title_value",33:"acc_descr",34:"acc_descr_value",35:"acc_descr_multiline_value",36:"loop",37:"rect",38:"opt",39:"alt",41:"par",43:"par_over",44:"critical",46:"break",47:"option",48:"and",49:"else",50:"participant",51:"AS",52:"participant_actor",53:"destroy",54:"note",57:"over",59:"links",60:"link",61:"properties",62:"details",64:",",65:"left_of",66:"right_of",68:"+",69:"-",70:"ACTOR",71:"SOLID_OPEN_ARROW",72:"DOTTED_OPEN_ARROW",73:"SOLID_ARROW",74:"BIDIRECTIONAL_SOLID_ARROW",75:"DOTTED_ARROW",76:"BIDIRECTIONAL_DOTTED_ARROW",77:"SOLID_CROSS",78:"DOTTED_CROSS",79:"SOLID_POINT",80:"DOTTED_POINT",81:"TXT"},productions_:[0,[3,2],[3,2],[3,2],[7,0],[7,2],[8,2],[8,1],[8,1],[10,0],[10,2],[11,2],[11,1],[11,1],[9,1],[9,2],[9,4],[9,2],[9,4],[9,3],[9,3],[9,2],[9,3],[9,3],[9,2],[9,2],[9,2],[9,2],[9,2],[9,1],[9,1],[9,2],[9,2],[9,1],[9,4],[9,4],[9,4],[9,4],[9,4],[9,4],[9,4],[9,4],[45,1],[45,4],[42,1],[42,4],[40,1],[40,4],[12,5],[12,3],[12,5],[12,3],[12,3],[24,4],[24,4],[25,3],[26,3],[27,3],[28,3],[63,2],[63,1],[58,3],[58,1],[55,1],[55,1],[17,5],[17,5],[17,4],[22,1],[67,1],[67,1],[67,1],[67,1],[67,1],[67,1],[67,1],[67,1],[67,1],[67,1],[56,1]],performAction:o(function(q,K,se,ce,ue,te,De){var oe=te.length-1;switch(ue){case 3:return ce.apply(te[oe]),te[oe];break;case 4:case 9:this.$=[];break;case 5:case 10:te[oe-1].push(te[oe]),this.$=te[oe-1];break;case 6:case 7:case 11:case 12:this.$=te[oe];break;case 8:case 13:this.$=[];break;case 15:te[oe].type="createParticipant",this.$=te[oe];break;case 16:te[oe-1].unshift({type:"boxStart",boxData:ce.parseBoxData(te[oe-2])}),te[oe-1].push({type:"boxEnd",boxText:te[oe-2]}),this.$=te[oe-1];break;case 18:this.$={type:"sequenceIndex",sequenceIndex:Number(te[oe-2]),sequenceIndexStep:Number(te[oe-1]),sequenceVisible:!0,signalType:ce.LINETYPE.AUTONUMBER};break;case 19:this.$={type:"sequenceIndex",sequenceIndex:Number(te[oe-1]),sequenceIndexStep:1,sequenceVisible:!0,signalType:ce.LINETYPE.AUTONUMBER};break;case 20:this.$={type:"sequenceIndex",sequenceVisible:!1,signalType:ce.LINETYPE.AUTONUMBER};break;case 21:this.$={type:"sequenceIndex",sequenceVisible:!0,signalType:ce.LINETYPE.AUTONUMBER};break;case 22:this.$={type:"activeStart",signalType:ce.LINETYPE.ACTIVE_START,actor:te[oe-1].actor};break;case 23:this.$={type:"activeEnd",signalType:ce.LINETYPE.ACTIVE_END,actor:te[oe-1].actor};break;case 29:ce.setDiagramTitle(te[oe].substring(6)),this.$=te[oe].substring(6);break;case 30:ce.setDiagramTitle(te[oe].substring(7)),this.$=te[oe].substring(7);break;case 31:this.$=te[oe].trim(),ce.setAccTitle(this.$);break;case 32:case 33:this.$=te[oe].trim(),ce.setAccDescription(this.$);break;case 34:te[oe-1].unshift({type:"loopStart",loopText:ce.parseMessage(te[oe-2]),signalType:ce.LINETYPE.LOOP_START}),te[oe-1].push({type:"loopEnd",loopText:te[oe-2],signalType:ce.LINETYPE.LOOP_END}),this.$=te[oe-1];break;case 35:te[oe-1].unshift({type:"rectStart",color:ce.parseMessage(te[oe-2]),signalType:ce.LINETYPE.RECT_START}),te[oe-1].push({type:"rectEnd",color:ce.parseMessage(te[oe-2]),signalType:ce.LINETYPE.RECT_END}),this.$=te[oe-1];break;case 36:te[oe-1].unshift({type:"optStart",optText:ce.parseMessage(te[oe-2]),signalType:ce.LINETYPE.OPT_START}),te[oe-1].push({type:"optEnd",optText:ce.parseMessage(te[oe-2]),signalType:ce.LINETYPE.OPT_END}),this.$=te[oe-1];break;case 37:te[oe-1].unshift({type:"altStart",altText:ce.parseMessage(te[oe-2]),signalType:ce.LINETYPE.ALT_START}),te[oe-1].push({type:"altEnd",signalType:ce.LINETYPE.ALT_END}),this.$=te[oe-1];break;case 38:te[oe-1].unshift({type:"parStart",parText:ce.parseMessage(te[oe-2]),signalType:ce.LINETYPE.PAR_START}),te[oe-1].push({type:"parEnd",signalType:ce.LINETYPE.PAR_END}),this.$=te[oe-1];break;case 39:te[oe-1].unshift({type:"parStart",parText:ce.parseMessage(te[oe-2]),signalType:ce.LINETYPE.PAR_OVER_START}),te[oe-1].push({type:"parEnd",signalType:ce.LINETYPE.PAR_END}),this.$=te[oe-1];break;case 40:te[oe-1].unshift({type:"criticalStart",criticalText:ce.parseMessage(te[oe-2]),signalType:ce.LINETYPE.CRITICAL_START}),te[oe-1].push({type:"criticalEnd",signalType:ce.LINETYPE.CRITICAL_END}),this.$=te[oe-1];break;case 41:te[oe-1].unshift({type:"breakStart",breakText:ce.parseMessage(te[oe-2]),signalType:ce.LINETYPE.BREAK_START}),te[oe-1].push({type:"breakEnd",optText:ce.parseMessage(te[oe-2]),signalType:ce.LINETYPE.BREAK_END}),this.$=te[oe-1];break;case 43:this.$=te[oe-3].concat([{type:"option",optionText:ce.parseMessage(te[oe-1]),signalType:ce.LINETYPE.CRITICAL_OPTION},te[oe]]);break;case 45:this.$=te[oe-3].concat([{type:"and",parText:ce.parseMessage(te[oe-1]),signalType:ce.LINETYPE.PAR_AND},te[oe]]);break;case 47:this.$=te[oe-3].concat([{type:"else",altText:ce.parseMessage(te[oe-1]),signalType:ce.LINETYPE.ALT_ELSE},te[oe]]);break;case 48:te[oe-3].draw="participant",te[oe-3].type="addParticipant",te[oe-3].description=ce.parseMessage(te[oe-1]),this.$=te[oe-3];break;case 49:te[oe-1].draw="participant",te[oe-1].type="addParticipant",this.$=te[oe-1];break;case 50:te[oe-3].draw="actor",te[oe-3].type="addParticipant",te[oe-3].description=ce.parseMessage(te[oe-1]),this.$=te[oe-3];break;case 51:te[oe-1].draw="actor",te[oe-1].type="addParticipant",this.$=te[oe-1];break;case 52:te[oe-1].type="destroyParticipant",this.$=te[oe-1];break;case 53:this.$=[te[oe-1],{type:"addNote",placement:te[oe-2],actor:te[oe-1].actor,text:te[oe]}];break;case 54:te[oe-2]=[].concat(te[oe-1],te[oe-1]).slice(0,2),te[oe-2][0]=te[oe-2][0].actor,te[oe-2][1]=te[oe-2][1].actor,this.$=[te[oe-1],{type:"addNote",placement:ce.PLACEMENT.OVER,actor:te[oe-2].slice(0,2),text:te[oe]}];break;case 55:this.$=[te[oe-1],{type:"addLinks",actor:te[oe-1].actor,text:te[oe]}];break;case 56:this.$=[te[oe-1],{type:"addALink",actor:te[oe-1].actor,text:te[oe]}];break;case 57:this.$=[te[oe-1],{type:"addProperties",actor:te[oe-1].actor,text:te[oe]}];break;case 58:this.$=[te[oe-1],{type:"addDetails",actor:te[oe-1].actor,text:te[oe]}];break;case 61:this.$=[te[oe-2],te[oe]];break;case 62:this.$=te[oe];break;case 63:this.$=ce.PLACEMENT.LEFTOF;break;case 64:this.$=ce.PLACEMENT.RIGHTOF;break;case 65:this.$=[te[oe-4],te[oe-1],{type:"addMessage",from:te[oe-4].actor,to:te[oe-1].actor,signalType:te[oe-3],msg:te[oe],activate:!0},{type:"activeStart",signalType:ce.LINETYPE.ACTIVE_START,actor:te[oe-1].actor}];break;case 66:this.$=[te[oe-4],te[oe-1],{type:"addMessage",from:te[oe-4].actor,to:te[oe-1].actor,signalType:te[oe-3],msg:te[oe]},{type:"activeEnd",signalType:ce.LINETYPE.ACTIVE_END,actor:te[oe-4].actor}];break;case 67:this.$=[te[oe-3],te[oe-1],{type:"addMessage",from:te[oe-3].actor,to:te[oe-1].actor,signalType:te[oe-2],msg:te[oe]}];break;case 68:this.$={type:"addParticipant",actor:te[oe]};break;case 69:this.$=ce.LINETYPE.SOLID_OPEN;break;case 70:this.$=ce.LINETYPE.DOTTED_OPEN;break;case 71:this.$=ce.LINETYPE.SOLID;break;case 72:this.$=ce.LINETYPE.BIDIRECTIONAL_SOLID;break;case 73:this.$=ce.LINETYPE.DOTTED;break;case 74:this.$=ce.LINETYPE.BIDIRECTIONAL_DOTTED;break;case 75:this.$=ce.LINETYPE.SOLID_CROSS;break;case 76:this.$=ce.LINETYPE.DOTTED_CROSS;break;case 77:this.$=ce.LINETYPE.SOLID_POINT;break;case 78:this.$=ce.LINETYPE.DOTTED_POINT;break;case 79:this.$=ce.parseMessage(te[oe].trim().substring(1));break}},"anonymous"),table:[{3:1,4:e,5:r,6:n},{1:[3]},{3:5,4:e,5:r,6:n},{3:6,4:e,5:r,6:n},t([1,4,5,13,14,18,21,23,29,30,31,33,35,36,37,38,39,41,43,44,46,50,52,53,54,59,60,61,62,70],i,{7:7}),{1:[2,1]},{1:[2,2]},{1:[2,3],4:a,5:s,8:8,9:10,12:12,13:l,14:u,17:15,18:h,21:f,22:40,23:d,24:19,25:20,26:21,27:22,28:23,29:p,30:m,31:g,33:y,35:v,36:x,37:b,38:w,39:S,41:T,43:E,44:_,46:A,50:L,52:M,53:N,54:k,59:I,60:C,61:O,62:D,70:P},t(F,[2,5]),{9:47,12:12,13:l,14:u,17:15,18:h,21:f,22:40,23:d,24:19,25:20,26:21,27:22,28:23,29:p,30:m,31:g,33:y,35:v,36:x,37:b,38:w,39:S,41:T,43:E,44:_,46:A,50:L,52:M,53:N,54:k,59:I,60:C,61:O,62:D,70:P},t(F,[2,7]),t(F,[2,8]),t(F,[2,14]),{12:48,50:L,52:M,53:N},{15:[1,49]},{5:[1,50]},{5:[1,53],19:[1,51],20:[1,52]},{22:54,70:P},{22:55,70:P},{5:[1,56]},{5:[1,57]},{5:[1,58]},{5:[1,59]},{5:[1,60]},t(F,[2,29]),t(F,[2,30]),{32:[1,61]},{34:[1,62]},t(F,[2,33]),{15:[1,63]},{15:[1,64]},{15:[1,65]},{15:[1,66]},{15:[1,67]},{15:[1,68]},{15:[1,69]},{15:[1,70]},{22:71,70:P},{22:72,70:P},{22:73,70:P},{67:74,71:[1,75],72:[1,76],73:[1,77],74:[1,78],75:[1,79],76:[1,80],77:[1,81],78:[1,82],79:[1,83],80:[1,84]},{55:85,57:[1,86],65:[1,87],66:[1,88]},{22:89,70:P},{22:90,70:P},{22:91,70:P},{22:92,70:P},t([5,51,64,71,72,73,74,75,76,77,78,79,80,81],[2,68]),t(F,[2,6]),t(F,[2,15]),t(B,[2,9],{10:93}),t(F,[2,17]),{5:[1,95],19:[1,94]},{5:[1,96]},t(F,[2,21]),{5:[1,97]},{5:[1,98]},t(F,[2,24]),t(F,[2,25]),t(F,[2,26]),t(F,[2,27]),t(F,[2,28]),t(F,[2,31]),t(F,[2,32]),t($,i,{7:99}),t($,i,{7:100}),t($,i,{7:101}),t(z,i,{40:102,7:103}),t(Y,i,{42:104,7:105}),t(Y,i,{7:105,42:106}),t(Q,i,{45:107,7:108}),t($,i,{7:109}),{5:[1,111],51:[1,110]},{5:[1,113],51:[1,112]},{5:[1,114]},{22:117,68:[1,115],69:[1,116],70:P},t(X,[2,69]),t(X,[2,70]),t(X,[2,71]),t(X,[2,72]),t(X,[2,73]),t(X,[2,74]),t(X,[2,75]),t(X,[2,76]),t(X,[2,77]),t(X,[2,78]),{22:118,70:P},{22:120,58:119,70:P},{70:[2,63]},{70:[2,64]},{56:121,81:ie},{56:123,81:ie},{56:124,81:ie},{56:125,81:ie},{4:[1,128],5:[1,130],11:127,12:129,16:[1,126],50:L,52:M,53:N},{5:[1,131]},t(F,[2,19]),t(F,[2,20]),t(F,[2,22]),t(F,[2,23]),{4:a,5:s,8:8,9:10,12:12,13:l,14:u,16:[1,132],17:15,18:h,21:f,22:40,23:d,24:19,25:20,26:21,27:22,28:23,29:p,30:m,31:g,33:y,35:v,36:x,37:b,38:w,39:S,41:T,43:E,44:_,46:A,50:L,52:M,53:N,54:k,59:I,60:C,61:O,62:D,70:P},{4:a,5:s,8:8,9:10,12:12,13:l,14:u,16:[1,133],17:15,18:h,21:f,22:40,23:d,24:19,25:20,26:21,27:22,28:23,29:p,30:m,31:g,33:y,35:v,36:x,37:b,38:w,39:S,41:T,43:E,44:_,46:A,50:L,52:M,53:N,54:k,59:I,60:C,61:O,62:D,70:P},{4:a,5:s,8:8,9:10,12:12,13:l,14:u,16:[1,134],17:15,18:h,21:f,22:40,23:d,24:19,25:20,26:21,27:22,28:23,29:p,30:m,31:g,33:y,35:v,36:x,37:b,38:w,39:S,41:T,43:E,44:_,46:A,50:L,52:M,53:N,54:k,59:I,60:C,61:O,62:D,70:P},{16:[1,135]},{4:a,5:s,8:8,9:10,12:12,13:l,14:u,16:[2,46],17:15,18:h,21:f,22:40,23:d,24:19,25:20,26:21,27:22,28:23,29:p,30:m,31:g,33:y,35:v,36:x,37:b,38:w,39:S,41:T,43:E,44:_,46:A,49:[1,136],50:L,52:M,53:N,54:k,59:I,60:C,61:O,62:D,70:P},{16:[1,137]},{4:a,5:s,8:8,9:10,12:12,13:l,14:u,16:[2,44],17:15,18:h,21:f,22:40,23:d,24:19,25:20,26:21,27:22,28:23,29:p,30:m,31:g,33:y,35:v,36:x,37:b,38:w,39:S,41:T,43:E,44:_,46:A,48:[1,138],50:L,52:M,53:N,54:k,59:I,60:C,61:O,62:D,70:P},{16:[1,139]},{16:[1,140]},{4:a,5:s,8:8,9:10,12:12,13:l,14:u,16:[2,42],17:15,18:h,21:f,22:40,23:d,24:19,25:20,26:21,27:22,28:23,29:p,30:m,31:g,33:y,35:v,36:x,37:b,38:w,39:S,41:T,43:E,44:_,46:A,47:[1,141],50:L,52:M,53:N,54:k,59:I,60:C,61:O,62:D,70:P},{4:a,5:s,8:8,9:10,12:12,13:l,14:u,16:[1,142],17:15,18:h,21:f,22:40,23:d,24:19,25:20,26:21,27:22,28:23,29:p,30:m,31:g,33:y,35:v,36:x,37:b,38:w,39:S,41:T,43:E,44:_,46:A,50:L,52:M,53:N,54:k,59:I,60:C,61:O,62:D,70:P},{15:[1,143]},t(F,[2,49]),{15:[1,144]},t(F,[2,51]),t(F,[2,52]),{22:145,70:P},{22:146,70:P},{56:147,81:ie},{56:148,81:ie},{56:149,81:ie},{64:[1,150],81:[2,62]},{5:[2,55]},{5:[2,79]},{5:[2,56]},{5:[2,57]},{5:[2,58]},t(F,[2,16]),t(B,[2,10]),{12:151,50:L,52:M,53:N},t(B,[2,12]),t(B,[2,13]),t(F,[2,18]),t(F,[2,34]),t(F,[2,35]),t(F,[2,36]),t(F,[2,37]),{15:[1,152]},t(F,[2,38]),{15:[1,153]},t(F,[2,39]),t(F,[2,40]),{15:[1,154]},t(F,[2,41]),{5:[1,155]},{5:[1,156]},{56:157,81:ie},{56:158,81:ie},{5:[2,67]},{5:[2,53]},{5:[2,54]},{22:159,70:P},t(B,[2,11]),t(z,i,{7:103,40:160}),t(Y,i,{7:105,42:161}),t(Q,i,{7:108,45:162}),t(F,[2,48]),t(F,[2,50]),{5:[2,65]},{5:[2,66]},{81:[2,61]},{16:[2,47]},{16:[2,45]},{16:[2,43]}],defaultActions:{5:[2,1],6:[2,2],87:[2,63],88:[2,64],121:[2,55],122:[2,79],123:[2,56],124:[2,57],125:[2,58],147:[2,67],148:[2,53],149:[2,54],157:[2,65],158:[2,66],159:[2,61],160:[2,47],161:[2,45],162:[2,43]},parseError:o(function(q,K){if(K.recoverable)this.trace(q);else{var se=new Error(q);throw se.hash=K,se}},"parseError"),parse:o(function(q){var K=this,se=[0],ce=[],ue=[null],te=[],De=this.table,oe="",ke=0,Ie=0,Se=0,Ue=2,Pe=1,_e=te.slice.call(arguments,1),me=Object.create(this.lexer),W={yy:{}};for(var fe in this.yy)Object.prototype.hasOwnProperty.call(this.yy,fe)&&(W.yy[fe]=this.yy[fe]);me.setInput(q,W.yy),W.yy.lexer=me,W.yy.parser=this,typeof me.yylloc>"u"&&(me.yylloc={});var ge=me.yylloc;te.push(ge);var re=me.options&&me.options.ranges;typeof W.yy.parseError=="function"?this.parseError=W.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function he(yt){se.length=se.length-2*yt,ue.length=ue.length-yt,te.length=te.length-yt}o(he,"popStack");function ne(){var yt;return yt=ce.pop()||me.lex()||Pe,typeof yt!="number"&&(yt instanceof Array&&(ce=yt,yt=ce.pop()),yt=K.symbols_[yt]||yt),yt}o(ne,"lex");for(var ae,we,Te,Ce,Ae,Ge,Me={},ye,He,ze,Ze;;){if(Te=se[se.length-1],this.defaultActions[Te]?Ce=this.defaultActions[Te]:((ae===null||typeof ae>"u")&&(ae=ne()),Ce=De[Te]&&De[Te][ae]),typeof Ce>"u"||!Ce.length||!Ce[0]){var gt="";Ze=[];for(ye in De[Te])this.terminals_[ye]&&ye>Ue&&Ze.push("'"+this.terminals_[ye]+"'");me.showPosition?gt="Parse error on line "+(ke+1)+`: +`+me.showPosition()+` +Expecting `+Ze.join(", ")+", got '"+(this.terminals_[ae]||ae)+"'":gt="Parse error on line "+(ke+1)+": Unexpected "+(ae==Pe?"end of input":"'"+(this.terminals_[ae]||ae)+"'"),this.parseError(gt,{text:me.match,token:this.terminals_[ae]||ae,line:me.yylineno,loc:ge,expected:Ze})}if(Ce[0]instanceof Array&&Ce.length>1)throw new Error("Parse Error: multiple actions possible at state: "+Te+", token: "+ae);switch(Ce[0]){case 1:se.push(ae),ue.push(me.yytext),te.push(me.yylloc),se.push(Ce[1]),ae=null,we?(ae=we,we=null):(Ie=me.yyleng,oe=me.yytext,ke=me.yylineno,ge=me.yylloc,Se>0&&Se--);break;case 2:if(He=this.productions_[Ce[1]][1],Me.$=ue[ue.length-He],Me._$={first_line:te[te.length-(He||1)].first_line,last_line:te[te.length-1].last_line,first_column:te[te.length-(He||1)].first_column,last_column:te[te.length-1].last_column},re&&(Me._$.range=[te[te.length-(He||1)].range[0],te[te.length-1].range[1]]),Ge=this.performAction.apply(Me,[oe,Ie,ke,W.yy,Ce[1],ue,te].concat(_e)),typeof Ge<"u")return Ge;He&&(se=se.slice(0,-1*He*2),ue=ue.slice(0,-1*He),te=te.slice(0,-1*He)),se.push(this.productions_[Ce[1]][0]),ue.push(Me.$),te.push(Me._$),ze=De[se[se.length-2]][se[se.length-1]],se.push(ze);break;case 3:return!0}}return!0},"parse")},J=function(){var H={EOF:1,parseError:o(function(K,se){if(this.yy.parser)this.yy.parser.parseError(K,se);else throw new Error(K)},"parseError"),setInput:o(function(q,K){return this.yy=K||this.yy||{},this._input=q,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:o(function(){var q=this._input[0];this.yytext+=q,this.yyleng++,this.offset++,this.match+=q,this.matched+=q;var K=q.match(/(?:\r\n?|\n).*/g);return K?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),q},"input"),unput:o(function(q){var K=q.length,se=q.split(/(?:\r\n?|\n)/g);this._input=q+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-K),this.offset-=K;var ce=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),se.length-1&&(this.yylineno-=se.length-1);var ue=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:se?(se.length===ce.length?this.yylloc.first_column:0)+ce[ce.length-se.length].length-se[0].length:this.yylloc.first_column-K},this.options.ranges&&(this.yylloc.range=[ue[0],ue[0]+this.yyleng-K]),this.yyleng=this.yytext.length,this},"unput"),more:o(function(){return this._more=!0,this},"more"),reject:o(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:o(function(q){this.unput(this.match.slice(q))},"less"),pastInput:o(function(){var q=this.matched.substr(0,this.matched.length-this.match.length);return(q.length>20?"...":"")+q.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:o(function(){var q=this.match;return q.length<20&&(q+=this._input.substr(0,20-q.length)),(q.substr(0,20)+(q.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:o(function(){var q=this.pastInput(),K=new Array(q.length+1).join("-");return q+this.upcomingInput()+` +`+K+"^"},"showPosition"),test_match:o(function(q,K){var se,ce,ue;if(this.options.backtrack_lexer&&(ue={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(ue.yylloc.range=this.yylloc.range.slice(0))),ce=q[0].match(/(?:\r\n?|\n).*/g),ce&&(this.yylineno+=ce.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:ce?ce[ce.length-1].length-ce[ce.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+q[0].length},this.yytext+=q[0],this.match+=q[0],this.matches=q,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(q[0].length),this.matched+=q[0],se=this.performAction.call(this,this.yy,this,K,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),se)return se;if(this._backtrack){for(var te in ue)this[te]=ue[te];return!1}return!1},"test_match"),next:o(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var q,K,se,ce;this._more||(this.yytext="",this.match="");for(var ue=this._currentRules(),te=0;teK[0].length)){if(K=se,ce=te,this.options.backtrack_lexer){if(q=this.test_match(se,ue[te]),q!==!1)return q;if(this._backtrack){K=!1;continue}else return!1}else if(!this.options.flex)break}return K?(q=this.test_match(K,ue[ce]),q!==!1?q:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:o(function(){var K=this.next();return K||this.lex()},"lex"),begin:o(function(K){this.conditionStack.push(K)},"begin"),popState:o(function(){var K=this.conditionStack.length-1;return K>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:o(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:o(function(K){return K=this.conditionStack.length-1-Math.abs(K||0),K>=0?this.conditionStack[K]:"INITIAL"},"topState"),pushState:o(function(K){this.begin(K)},"pushState"),stateStackSize:o(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:o(function(K,se,ce,ue){var te=ue;switch(ce){case 0:return 5;case 1:break;case 2:break;case 3:break;case 4:break;case 5:break;case 6:return 19;case 7:return this.begin("LINE"),14;break;case 8:return this.begin("ID"),50;break;case 9:return this.begin("ID"),52;break;case 10:return 13;case 11:return this.begin("ID"),53;break;case 12:return se.yytext=se.yytext.trim(),this.begin("ALIAS"),70;break;case 13:return this.popState(),this.popState(),this.begin("LINE"),51;break;case 14:return this.popState(),this.popState(),5;break;case 15:return this.begin("LINE"),36;break;case 16:return this.begin("LINE"),37;break;case 17:return this.begin("LINE"),38;break;case 18:return this.begin("LINE"),39;break;case 19:return this.begin("LINE"),49;break;case 20:return this.begin("LINE"),41;break;case 21:return this.begin("LINE"),43;break;case 22:return this.begin("LINE"),48;break;case 23:return this.begin("LINE"),44;break;case 24:return this.begin("LINE"),47;break;case 25:return this.begin("LINE"),46;break;case 26:return this.popState(),15;break;case 27:return 16;case 28:return 65;case 29:return 66;case 30:return 59;case 31:return 60;case 32:return 61;case 33:return 62;case 34:return 57;case 35:return 54;case 36:return this.begin("ID"),21;break;case 37:return this.begin("ID"),23;break;case 38:return 29;case 39:return 30;case 40:return this.begin("acc_title"),31;break;case 41:return this.popState(),"acc_title_value";break;case 42:return this.begin("acc_descr"),33;break;case 43:return this.popState(),"acc_descr_value";break;case 44:this.begin("acc_descr_multiline");break;case 45:this.popState();break;case 46:return"acc_descr_multiline_value";case 47:return 6;case 48:return 18;case 49:return 20;case 50:return 64;case 51:return 5;case 52:return se.yytext=se.yytext.trim(),70;break;case 53:return 73;case 54:return 74;case 55:return 75;case 56:return 76;case 57:return 71;case 58:return 72;case 59:return 77;case 60:return 78;case 61:return 79;case 62:return 80;case 63:return 81;case 64:return 68;case 65:return 69;case 66:return 5;case 67:return"INVALID"}},"anonymous"),rules:[/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:((?!\n)\s)+)/i,/^(?:#[^\n]*)/i,/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[0-9]+(?=[ \n]+))/i,/^(?:box\b)/i,/^(?:participant\b)/i,/^(?:actor\b)/i,/^(?:create\b)/i,/^(?:destroy\b)/i,/^(?:[^\<->\->:\n,;]+?([\-]*[^\<->\->:\n,;]+?)*?(?=((?!\n)\s)+as(?!\n)\s|[#\n;]|$))/i,/^(?:as\b)/i,/^(?:(?:))/i,/^(?:loop\b)/i,/^(?:rect\b)/i,/^(?:opt\b)/i,/^(?:alt\b)/i,/^(?:else\b)/i,/^(?:par\b)/i,/^(?:par_over\b)/i,/^(?:and\b)/i,/^(?:critical\b)/i,/^(?:option\b)/i,/^(?:break\b)/i,/^(?:(?:[:]?(?:no)?wrap)?[^#\n;]*)/i,/^(?:end\b)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:links\b)/i,/^(?:link\b)/i,/^(?:properties\b)/i,/^(?:details\b)/i,/^(?:over\b)/i,/^(?:note\b)/i,/^(?:activate\b)/i,/^(?:deactivate\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:title:\s[^#\n;]+)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:sequenceDiagram\b)/i,/^(?:autonumber\b)/i,/^(?:off\b)/i,/^(?:,)/i,/^(?:;)/i,/^(?:[^\+\<->\->:\n,;]+((?!(-x|--x|-\)|--\)))[\-]*[^\+\<->\->:\n,;]+)*)/i,/^(?:->>)/i,/^(?:<<->>)/i,/^(?:-->>)/i,/^(?:<<-->>)/i,/^(?:->)/i,/^(?:-->)/i,/^(?:-[x])/i,/^(?:--[x])/i,/^(?:-[\)])/i,/^(?:--[\)])/i,/^(?::(?:(?:no)?wrap)?[^#\n;]+)/i,/^(?:\+)/i,/^(?:-)/i,/^(?:$)/i,/^(?:.)/i],conditions:{acc_descr_multiline:{rules:[45,46],inclusive:!1},acc_descr:{rules:[43],inclusive:!1},acc_title:{rules:[41],inclusive:!1},ID:{rules:[2,3,12],inclusive:!1},ALIAS:{rules:[2,3,13,14],inclusive:!1},LINE:{rules:[2,3,26],inclusive:!1},INITIAL:{rules:[0,1,3,4,5,6,7,8,9,10,11,15,16,17,18,19,20,21,22,23,24,25,27,28,29,30,31,32,33,34,35,36,37,38,39,40,42,44,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67],inclusive:!0}}};return H}();j.lexer=J;function Z(){this.yy={}}return o(Z,"Parser"),Z.prototype=j,j.Parser=Z,new Z}();hO.parser=hO;Wue=hO});function dO(t,e){if(t.links==null)t.links=e;else for(let r in e)t.links[r]=e[r]}function Zue(t,e){if(t.properties==null)t.properties=e;else for(let r in e)t.properties[r]=e[r]}function SGe(){Mt.records.currentBox=void 0}var Mt,aGe,fO,sGe,oGe,pi,lGe,cGe,uGe,hGe,fGe,dGe,pGe,xx,mGe,gGe,yGe,vGe,xGe,Xue,A0,bGe,wGe,TGe,vx,kGe,EGe,jue,Kue,CGe,Que,Jue,AGe,ehe,pO,the=R(()=>{"use strict";_t();ut();Jk();rr();bi();Mt=new uf(()=>({prevActor:void 0,actors:new Map,createdActors:new Map,destroyedActors:new Map,boxes:[],messages:[],notes:[],sequenceNumbersEnabled:!1,wrapEnabled:void 0,currentBox:void 0,lastCreated:void 0,lastDestroyed:void 0})),aGe=o(function(t){Mt.records.boxes.push({name:t.text,wrap:t.wrap??A0(),fill:t.color,actorKeys:[]}),Mt.records.currentBox=Mt.records.boxes.slice(-1)[0]},"addBox"),fO=o(function(t,e,r,n){let i=Mt.records.currentBox,a=Mt.records.actors.get(t);if(a){if(Mt.records.currentBox&&a.box&&Mt.records.currentBox!==a.box)throw new Error(`A same participant should only be defined in one Box: ${a.name} can't be in '${a.box.name}' and in '${Mt.records.currentBox.name}' at the same time.`);if(i=a.box?a.box:Mt.records.currentBox,a.box=i,a&&e===a.name&&r==null)return}if(r?.text==null&&(r={text:e,type:n}),(n==null||r.text==null)&&(r={text:e,type:n}),Mt.records.actors.set(t,{box:i,name:e,description:r.text,wrap:r.wrap??A0(),prevActor:Mt.records.prevActor,links:{},properties:{},actorCnt:null,rectData:null,type:n??"participant"}),Mt.records.prevActor){let s=Mt.records.actors.get(Mt.records.prevActor);s&&(s.nextActor=t)}Mt.records.currentBox&&Mt.records.currentBox.actorKeys.push(t),Mt.records.prevActor=t},"addActor"),sGe=o(t=>{let e,r=0;if(!t)return 0;for(e=0;e>-",token:"->>-",line:"1",loc:{first_line:1,last_line:1,first_column:1,last_column:1},expected:["'ACTIVE_PARTICIPANT'"]},s}return Mt.records.messages.push({from:t,to:e,message:r?.text??"",wrap:r?.wrap??A0(),type:n,activate:i}),!0},"addSignal"),lGe=o(function(){return Mt.records.boxes.length>0},"hasAtLeastOneBox"),cGe=o(function(){return Mt.records.boxes.some(t=>t.name)},"hasAtLeastOneBoxWithTitle"),uGe=o(function(){return Mt.records.messages},"getMessages"),hGe=o(function(){return Mt.records.boxes},"getBoxes"),fGe=o(function(){return Mt.records.actors},"getActors"),dGe=o(function(){return Mt.records.createdActors},"getCreatedActors"),pGe=o(function(){return Mt.records.destroyedActors},"getDestroyedActors"),xx=o(function(t){return Mt.records.actors.get(t)},"getActor"),mGe=o(function(){return[...Mt.records.actors.keys()]},"getActorKeys"),gGe=o(function(){Mt.records.sequenceNumbersEnabled=!0},"enableSequenceNumbers"),yGe=o(function(){Mt.records.sequenceNumbersEnabled=!1},"disableSequenceNumbers"),vGe=o(()=>Mt.records.sequenceNumbersEnabled,"showSequenceNumbers"),xGe=o(function(t){Mt.records.wrapEnabled=t},"setWrap"),Xue=o(t=>{if(t===void 0)return{};t=t.trim();let e=/^:?wrap:/.exec(t)!==null?!0:/^:?nowrap:/.exec(t)!==null?!1:void 0;return{cleanedText:(e===void 0?t:t.replace(/^:?(?:no)?wrap:/,"")).trim(),wrap:e}},"extractWrap"),A0=o(()=>Mt.records.wrapEnabled!==void 0?Mt.records.wrapEnabled:de().sequence?.wrap??!1,"autoWrap"),bGe=o(function(){Mt.reset(),vr()},"clear"),wGe=o(function(t){let e=t.trim(),{wrap:r,cleanedText:n}=Xue(e),i={text:n,wrap:r};return V.debug(`parseMessage: ${JSON.stringify(i)}`),i},"parseMessage"),TGe=o(function(t){let e=/^((?:rgba?|hsla?)\s*\(.*\)|\w*)(.*)$/.exec(t),r=e?.[1]?e[1].trim():"transparent",n=e?.[2]?e[2].trim():void 0;if(window?.CSS)window.CSS.supports("color",r)||(r="transparent",n=t.trim());else{let s=new Option().style;s.color=r,s.color!==r&&(r="transparent",n=t.trim())}let{wrap:i,cleanedText:a}=Xue(n);return{text:a?qr(a,de()):void 0,color:r,wrap:i}},"parseBoxData"),vx={SOLID:0,DOTTED:1,NOTE:2,SOLID_CROSS:3,DOTTED_CROSS:4,SOLID_OPEN:5,DOTTED_OPEN:6,LOOP_START:10,LOOP_END:11,ALT_START:12,ALT_ELSE:13,ALT_END:14,OPT_START:15,OPT_END:16,ACTIVE_START:17,ACTIVE_END:18,PAR_START:19,PAR_AND:20,PAR_END:21,RECT_START:22,RECT_END:23,SOLID_POINT:24,DOTTED_POINT:25,AUTONUMBER:26,CRITICAL_START:27,CRITICAL_OPTION:28,CRITICAL_END:29,BREAK_START:30,BREAK_END:31,PAR_OVER_START:32,BIDIRECTIONAL_SOLID:33,BIDIRECTIONAL_DOTTED:34},kGe={FILLED:0,OPEN:1},EGe={LEFTOF:0,RIGHTOF:1,OVER:2},jue=o(function(t,e,r){let n={actor:t,placement:e,message:r.text,wrap:r.wrap??A0()},i=[].concat(t,t);Mt.records.notes.push(n),Mt.records.messages.push({from:i[0],to:i[1],message:r.text,wrap:r.wrap??A0(),type:vx.NOTE,placement:e})},"addNote"),Kue=o(function(t,e){let r=xx(t);try{let n=qr(e.text,de());n=n.replace(/&/g,"&"),n=n.replace(/=/g,"=");let i=JSON.parse(n);dO(r,i)}catch(n){V.error("error while parsing actor link text",n)}},"addLinks"),CGe=o(function(t,e){let r=xx(t);try{let n={},i=qr(e.text,de()),a=i.indexOf("@");i=i.replace(/&/g,"&"),i=i.replace(/=/g,"=");let s=i.slice(0,a-1).trim(),l=i.slice(a+1).trim();n[s]=l,dO(r,n)}catch(n){V.error("error while parsing actor link text",n)}},"addALink");o(dO,"insertLinks");Que=o(function(t,e){let r=xx(t);try{let n=qr(e.text,de()),i=JSON.parse(n);Zue(r,i)}catch(n){V.error("error while parsing actor properties text",n)}},"addProperties");o(Zue,"insertProperties");o(SGe,"boxEnd");Jue=o(function(t,e){let r=xx(t),n=document.getElementById(e.text);try{let i=n.innerHTML,a=JSON.parse(i);a.properties&&Zue(r,a.properties),a.links&&dO(r,a.links)}catch(i){V.error("error while parsing actor details text",i)}},"addDetails"),AGe=o(function(t,e){if(t?.properties!==void 0)return t.properties[e]},"getActorProperty"),ehe=o(function(t){if(Array.isArray(t))t.forEach(function(e){ehe(e)});else switch(t.type){case"sequenceIndex":Mt.records.messages.push({from:void 0,to:void 0,message:{start:t.sequenceIndex,step:t.sequenceIndexStep,visible:t.sequenceVisible},wrap:!1,type:t.signalType});break;case"addParticipant":fO(t.actor,t.actor,t.description,t.draw);break;case"createParticipant":if(Mt.records.actors.has(t.actor))throw new Error("It is not possible to have actors with the same id, even if one is destroyed before the next is created. Use 'AS' aliases to simulate the behavior");Mt.records.lastCreated=t.actor,fO(t.actor,t.actor,t.description,t.draw),Mt.records.createdActors.set(t.actor,Mt.records.messages.length);break;case"destroyParticipant":Mt.records.lastDestroyed=t.actor,Mt.records.destroyedActors.set(t.actor,Mt.records.messages.length);break;case"activeStart":pi(t.actor,void 0,void 0,t.signalType);break;case"activeEnd":pi(t.actor,void 0,void 0,t.signalType);break;case"addNote":jue(t.actor,t.placement,t.text);break;case"addLinks":Kue(t.actor,t.text);break;case"addALink":CGe(t.actor,t.text);break;case"addProperties":Que(t.actor,t.text);break;case"addDetails":Jue(t.actor,t.text);break;case"addMessage":if(Mt.records.lastCreated){if(t.to!==Mt.records.lastCreated)throw new Error("The created participant "+Mt.records.lastCreated.name+" does not have an associated creating message after its declaration. Please check the sequence diagram.");Mt.records.lastCreated=void 0}else if(Mt.records.lastDestroyed){if(t.to!==Mt.records.lastDestroyed&&t.from!==Mt.records.lastDestroyed)throw new Error("The destroyed participant "+Mt.records.lastDestroyed.name+" does not have an associated destroying message after its declaration. Please check the sequence diagram.");Mt.records.lastDestroyed=void 0}pi(t.from,t.to,t.msg,t.signalType,t.activate);break;case"boxStart":aGe(t.boxData);break;case"boxEnd":SGe();break;case"loopStart":pi(void 0,void 0,t.loopText,t.signalType);break;case"loopEnd":pi(void 0,void 0,void 0,t.signalType);break;case"rectStart":pi(void 0,void 0,t.color,t.signalType);break;case"rectEnd":pi(void 0,void 0,void 0,t.signalType);break;case"optStart":pi(void 0,void 0,t.optText,t.signalType);break;case"optEnd":pi(void 0,void 0,void 0,t.signalType);break;case"altStart":pi(void 0,void 0,t.altText,t.signalType);break;case"else":pi(void 0,void 0,t.altText,t.signalType);break;case"altEnd":pi(void 0,void 0,void 0,t.signalType);break;case"setAccTitle":kr(t.text);break;case"parStart":pi(void 0,void 0,t.parText,t.signalType);break;case"and":pi(void 0,void 0,t.parText,t.signalType);break;case"parEnd":pi(void 0,void 0,void 0,t.signalType);break;case"criticalStart":pi(void 0,void 0,t.criticalText,t.signalType);break;case"option":pi(void 0,void 0,t.optionText,t.signalType);break;case"criticalEnd":pi(void 0,void 0,void 0,t.signalType);break;case"breakStart":pi(void 0,void 0,t.breakText,t.signalType);break;case"breakEnd":pi(void 0,void 0,void 0,t.signalType);break}},"apply"),pO={addActor:fO,addMessage:oGe,addSignal:pi,addLinks:Kue,addDetails:Jue,addProperties:Que,autoWrap:A0,setWrap:xGe,enableSequenceNumbers:gGe,disableSequenceNumbers:yGe,showSequenceNumbers:vGe,getMessages:uGe,getActors:fGe,getCreatedActors:dGe,getDestroyedActors:pGe,getActor:xx,getActorKeys:mGe,getActorProperty:AGe,getAccTitle:Ar,getBoxes:hGe,getDiagramTitle:Xr,setDiagramTitle:nn,getConfig:o(()=>de().sequence,"getConfig"),clear:bGe,parseMessage:wGe,parseBoxData:TGe,LINETYPE:vx,ARROWTYPE:kGe,PLACEMENT:EGe,addNote:jue,setAccTitle:kr,apply:ehe,setAccDescription:_r,getAccDescription:Lr,hasAtLeastOneBox:lGe,hasAtLeastOneBoxWithTitle:cGe}});var _Ge,rhe,nhe=R(()=>{"use strict";_Ge=o(t=>`.actor { + stroke: ${t.actorBorder}; + fill: ${t.actorBkg}; + } + + text.actor > tspan { + fill: ${t.actorTextColor}; + stroke: none; + } + + .actor-line { + stroke: ${t.actorLineColor}; + } + + .messageLine0 { + stroke-width: 1.5; + stroke-dasharray: none; + stroke: ${t.signalColor}; + } + + .messageLine1 { + stroke-width: 1.5; + stroke-dasharray: 2, 2; + stroke: ${t.signalColor}; + } + + #arrowhead path { + fill: ${t.signalColor}; + stroke: ${t.signalColor}; + } + + .sequenceNumber { + fill: ${t.sequenceNumberColor}; + } + + #sequencenumber { + fill: ${t.signalColor}; + } + + #crosshead path { + fill: ${t.signalColor}; + stroke: ${t.signalColor}; + } + + .messageText { + fill: ${t.signalTextColor}; + stroke: none; + } + + .labelBox { + stroke: ${t.labelBoxBorderColor}; + fill: ${t.labelBoxBkgColor}; + } + + .labelText, .labelText > tspan { + fill: ${t.labelTextColor}; + stroke: none; + } + + .loopText, .loopText > tspan { + fill: ${t.loopTextColor}; + stroke: none; + } + + .loopLine { + stroke-width: 2px; + stroke-dasharray: 2, 2; + stroke: ${t.labelBoxBorderColor}; + fill: ${t.labelBoxBorderColor}; + } + + .note { + //stroke: #decc93; + stroke: ${t.noteBorderColor}; + fill: ${t.noteBkgColor}; + } + + .noteText, .noteText > tspan { + fill: ${t.noteTextColor}; + stroke: none; + } + + .activation0 { + fill: ${t.activationBkgColor}; + stroke: ${t.activationBorderColor}; + } + + .activation1 { + fill: ${t.activationBkgColor}; + stroke: ${t.activationBorderColor}; + } + + .activation2 { + fill: ${t.activationBkgColor}; + stroke: ${t.activationBorderColor}; + } + + .actorPopupMenu { + position: absolute; + } + + .actorPopupMenuPanel { + position: absolute; + fill: ${t.actorBkg}; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + filter: drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4)); +} + .actor-man line { + stroke: ${t.actorBorder}; + fill: ${t.actorBkg}; + } + .actor-man circle, line { + stroke: ${t.actorBorder}; + fill: ${t.actorBkg}; + stroke-width: 2px; + } +`,"getStyles"),rhe=_Ge});var mO,gf,ahe,she,LGe,ihe,gO,DGe,RGe,bx,_0,ohe,Bc,yO,NGe,MGe,IGe,OGe,PGe,BGe,FGe,lhe,zGe,GGe,$Ge,VGe,UGe,HGe,YGe,che,WGe,vO,qGe,si,uhe=R(()=>{"use strict";rr();Qy();xr();mO=Xi(Up(),1);qs();gf=18*2,ahe="actor-top",she="actor-bottom",LGe="actor-box",ihe="actor-man",gO=o(function(t,e){return yd(t,e)},"drawRect"),DGe=o(function(t,e,r,n,i){if(e.links===void 0||e.links===null||Object.keys(e.links).length===0)return{height:0,width:0};let a=e.links,s=e.actorCnt,l=e.rectData;var u="none";i&&(u="block !important");let h=t.append("g");h.attr("id","actor"+s+"_popup"),h.attr("class","actorPopupMenu"),h.attr("display",u);var f="";l.class!==void 0&&(f=" "+l.class);let d=l.width>r?l.width:r,p=h.append("rect");if(p.attr("class","actorPopupMenuPanel"+f),p.attr("x",l.x),p.attr("y",l.height),p.attr("fill",l.fill),p.attr("stroke",l.stroke),p.attr("width",d),p.attr("height",l.height),p.attr("rx",l.rx),p.attr("ry",l.ry),a!=null){var m=20;for(let v in a){var g=h.append("a"),y=(0,mO.sanitizeUrl)(a[v]);g.attr("xlink:href",y),g.attr("target","_blank"),qGe(n)(v,g,l.x+10,l.height+m,d,20,{class:"actor"},n),m+=30}}return p.attr("height",m),{height:l.height+m,width:d}},"drawPopup"),RGe=o(function(t){return"var pu = document.getElementById('"+t+"'); if (pu != null) { pu.style.display = pu.style.display == 'block' ? 'none' : 'block'; }"},"popupMenuToggle"),bx=o(async function(t,e,r=null){let n=t.append("foreignObject"),i=await yh(e.text,Or()),s=n.append("xhtml:div").attr("style","width: fit-content;").attr("xmlns","http://www.w3.org/1999/xhtml").html(i).node().getBoundingClientRect();if(n.attr("height",Math.round(s.height)).attr("width",Math.round(s.width)),e.class==="noteText"){let l=t.node().firstChild;l.setAttribute("height",s.height+2*e.textMargin);let u=l.getBBox();n.attr("x",Math.round(u.x+u.width/2-s.width/2)).attr("y",Math.round(u.y+u.height/2-s.height/2))}else if(r){let{startx:l,stopx:u,starty:h}=r;if(l>u){let f=l;l=u,u=f}n.attr("x",Math.round(l+Math.abs(l-u)/2-s.width/2)),e.class==="loopText"?n.attr("y",Math.round(h)):n.attr("y",Math.round(h-s.height))}return[n]},"drawKatex"),_0=o(function(t,e){let r=0,n=0,i=e.text.split(We.lineBreakRegex),[a,s]=mc(e.fontSize),l=[],u=0,h=o(()=>e.y,"yfunc");if(e.valign!==void 0&&e.textMargin!==void 0&&e.textMargin>0)switch(e.valign){case"top":case"start":h=o(()=>Math.round(e.y+e.textMargin),"yfunc");break;case"middle":case"center":h=o(()=>Math.round(e.y+(r+n+e.textMargin)/2),"yfunc");break;case"bottom":case"end":h=o(()=>Math.round(e.y+(r+n+2*e.textMargin)-e.textMargin),"yfunc");break}if(e.anchor!==void 0&&e.textMargin!==void 0&&e.width!==void 0)switch(e.anchor){case"left":case"start":e.x=Math.round(e.x+e.textMargin),e.anchor="start",e.dominantBaseline="middle",e.alignmentBaseline="middle";break;case"middle":case"center":e.x=Math.round(e.x+e.width/2),e.anchor="middle",e.dominantBaseline="middle",e.alignmentBaseline="middle";break;case"right":case"end":e.x=Math.round(e.x+e.width-e.textMargin),e.anchor="end",e.dominantBaseline="middle",e.alignmentBaseline="middle";break}for(let[f,d]of i.entries()){e.textMargin!==void 0&&e.textMargin===0&&a!==void 0&&(u=f*a);let p=t.append("text");p.attr("x",e.x),p.attr("y",h()),e.anchor!==void 0&&p.attr("text-anchor",e.anchor).attr("dominant-baseline",e.dominantBaseline).attr("alignment-baseline",e.alignmentBaseline),e.fontFamily!==void 0&&p.style("font-family",e.fontFamily),s!==void 0&&p.style("font-size",s),e.fontWeight!==void 0&&p.style("font-weight",e.fontWeight),e.fill!==void 0&&p.attr("fill",e.fill),e.class!==void 0&&p.attr("class",e.class),e.dy!==void 0?p.attr("dy",e.dy):u!==0&&p.attr("dy",u);let m=d||K_;if(e.tspan){let g=p.append("tspan");g.attr("x",e.x),e.fill!==void 0&&g.attr("fill",e.fill),g.text(m)}else p.text(m);e.valign!==void 0&&e.textMargin!==void 0&&e.textMargin>0&&(n+=(p._groups||p)[0][0].getBBox().height,r=n),l.push(p)}return l},"drawText"),ohe=o(function(t,e){function r(i,a,s,l,u){return i+","+a+" "+(i+s)+","+a+" "+(i+s)+","+(a+l-u)+" "+(i+s-u*1.2)+","+(a+l)+" "+i+","+(a+l)}o(r,"genPoints");let n=t.append("polygon");return n.attr("points",r(e.x,e.y,e.width,e.height,7)),n.attr("class","labelBox"),e.y=e.y+e.height/2,_0(t,e),n},"drawLabel"),Bc=-1,yO=o((t,e,r,n)=>{t.select&&r.forEach(i=>{let a=e.get(i),s=t.select("#actor"+a.actorCnt);!n.mirrorActors&&a.stopy?s.attr("y2",a.stopy+a.height/2):n.mirrorActors&&s.attr("y2",a.stopy)})},"fixLifeLineHeights"),NGe=o(function(t,e,r,n){let i=n?e.stopy:e.starty,a=e.x+e.width/2,s=i+5,l=t.append("g").lower();var u=l;n||(Bc++,Object.keys(e.links||{}).length&&!r.forceMenus&&u.attr("onclick",RGe(`actor${Bc}_popup`)).attr("cursor","pointer"),u.append("line").attr("id","actor"+Bc).attr("x1",a).attr("y1",s).attr("x2",a).attr("y2",2e3).attr("class","actor-line 200").attr("stroke-width","0.5px").attr("stroke","#999").attr("name",e.name),u=l.append("g"),e.actorCnt=Bc,e.links!=null&&u.attr("id","root-"+Bc));let h=wl();var f="actor";e.properties?.class?f=e.properties.class:h.fill="#eaeaea",n?f+=` ${she}`:f+=` ${ahe}`,h.x=e.x,h.y=i,h.width=e.width,h.height=e.height,h.class=f,h.rx=3,h.ry=3,h.name=e.name;let d=gO(u,h);if(e.rectData=h,e.properties?.icon){let m=e.properties.icon.trim();m.charAt(0)==="@"?EW(u,h.x+h.width-20,h.y+10,m.substr(1)):kW(u,h.x+h.width-20,h.y+10,m)}vO(r,Ni(e.description))(e.description,u,h.x,h.y,h.width,h.height,{class:`actor ${LGe}`},r);let p=e.height;if(d.node){let m=d.node().getBBox();e.height=m.height,p=m.height}return p},"drawActorTypeParticipant"),MGe=o(function(t,e,r,n){let i=n?e.stopy:e.starty,a=e.x+e.width/2,s=i+80,l=t.append("g").lower();n||(Bc++,l.append("line").attr("id","actor"+Bc).attr("x1",a).attr("y1",s).attr("x2",a).attr("y2",2e3).attr("class","actor-line 200").attr("stroke-width","0.5px").attr("stroke","#999").attr("name",e.name),e.actorCnt=Bc);let u=t.append("g"),h=ihe;n?h+=` ${she}`:h+=` ${ahe}`,u.attr("class",h),u.attr("name",e.name);let f=wl();f.x=e.x,f.y=i,f.fill="#eaeaea",f.width=e.width,f.height=e.height,f.class="actor",f.rx=3,f.ry=3,u.append("line").attr("id","actor-man-torso"+Bc).attr("x1",a).attr("y1",i+25).attr("x2",a).attr("y2",i+45),u.append("line").attr("id","actor-man-arms"+Bc).attr("x1",a-gf/2).attr("y1",i+33).attr("x2",a+gf/2).attr("y2",i+33),u.append("line").attr("x1",a-gf/2).attr("y1",i+60).attr("x2",a).attr("y2",i+45),u.append("line").attr("x1",a).attr("y1",i+45).attr("x2",a+gf/2-2).attr("y2",i+60);let d=u.append("circle");d.attr("cx",e.x+e.width/2),d.attr("cy",i+10),d.attr("r",15),d.attr("width",e.width),d.attr("height",e.height);let p=u.node().getBBox();return e.height=p.height,vO(r,Ni(e.description))(e.description,u,f.x,f.y+35,f.width,f.height,{class:`actor ${ihe}`},r),e.height},"drawActorTypeActor"),IGe=o(async function(t,e,r,n){switch(e.type){case"actor":return await MGe(t,e,r,n);case"participant":return await NGe(t,e,r,n)}},"drawActor"),OGe=o(function(t,e,r){let i=t.append("g");lhe(i,e),e.name&&vO(r)(e.name,i,e.x,e.y+(e.textMaxHeight||0)/2,e.width,0,{class:"text"},r),i.lower()},"drawBox"),PGe=o(function(t){return t.append("g")},"anchorElement"),BGe=o(function(t,e,r,n,i){let a=wl(),s=e.anchored;a.x=e.startx,a.y=e.starty,a.class="activation"+i%3,a.width=e.stopx-e.startx,a.height=r-e.starty,gO(s,a)},"drawActivation"),FGe=o(async function(t,e,r,n){let{boxMargin:i,boxTextMargin:a,labelBoxHeight:s,labelBoxWidth:l,messageFontFamily:u,messageFontSize:h,messageFontWeight:f}=n,d=t.append("g"),p=o(function(y,v,x,b){return d.append("line").attr("x1",y).attr("y1",v).attr("x2",x).attr("y2",b).attr("class","loopLine")},"drawLoopLine");p(e.startx,e.starty,e.stopx,e.starty),p(e.stopx,e.starty,e.stopx,e.stopy),p(e.startx,e.stopy,e.stopx,e.stopy),p(e.startx,e.starty,e.startx,e.stopy),e.sections!==void 0&&e.sections.forEach(function(y){p(e.startx,y.y,e.stopx,y.y).style("stroke-dasharray","3, 3")});let m=Ky();m.text=r,m.x=e.startx,m.y=e.starty,m.fontFamily=u,m.fontSize=h,m.fontWeight=f,m.anchor="middle",m.valign="middle",m.tspan=!1,m.width=l||50,m.height=s||20,m.textMargin=a,m.class="labelText",ohe(d,m),m=che(),m.text=e.title,m.x=e.startx+l/2+(e.stopx-e.startx)/2,m.y=e.starty+i+a,m.anchor="middle",m.valign="middle",m.textMargin=a,m.class="loopText",m.fontFamily=u,m.fontSize=h,m.fontWeight=f,m.wrap=!0;let g=Ni(m.text)?await bx(d,m,e):_0(d,m);if(e.sectionTitles!==void 0){for(let[y,v]of Object.entries(e.sectionTitles))if(v.message){m.text=v.message,m.x=e.startx+(e.stopx-e.startx)/2,m.y=e.sections[y].y+i+a,m.class="loopText",m.anchor="middle",m.valign="middle",m.tspan=!1,m.fontFamily=u,m.fontSize=h,m.fontWeight=f,m.wrap=e.wrap,Ni(m.text)?(e.starty=e.sections[y].y,await bx(d,m,e)):_0(d,m);let x=Math.round(g.map(b=>(b._groups||b)[0][0].getBBox().height).reduce((b,w)=>b+w));e.sections[y].height+=x-(i+a)}}return e.height=Math.round(e.stopy-e.starty),d},"drawLoop"),lhe=o(function(t,e){j3(t,e)},"drawBackgroundRect"),zGe=o(function(t){t.append("defs").append("symbol").attr("id","database").attr("fill-rule","evenodd").attr("clip-rule","evenodd").append("path").attr("transform","scale(.5)").attr("d","M12.258.001l.256.004.255.005.253.008.251.01.249.012.247.015.246.016.242.019.241.02.239.023.236.024.233.027.231.028.229.031.225.032.223.034.22.036.217.038.214.04.211.041.208.043.205.045.201.046.198.048.194.05.191.051.187.053.183.054.18.056.175.057.172.059.168.06.163.061.16.063.155.064.15.066.074.033.073.033.071.034.07.034.069.035.068.035.067.035.066.035.064.036.064.036.062.036.06.036.06.037.058.037.058.037.055.038.055.038.053.038.052.038.051.039.05.039.048.039.047.039.045.04.044.04.043.04.041.04.04.041.039.041.037.041.036.041.034.041.033.042.032.042.03.042.029.042.027.042.026.043.024.043.023.043.021.043.02.043.018.044.017.043.015.044.013.044.012.044.011.045.009.044.007.045.006.045.004.045.002.045.001.045v17l-.001.045-.002.045-.004.045-.006.045-.007.045-.009.044-.011.045-.012.044-.013.044-.015.044-.017.043-.018.044-.02.043-.021.043-.023.043-.024.043-.026.043-.027.042-.029.042-.03.042-.032.042-.033.042-.034.041-.036.041-.037.041-.039.041-.04.041-.041.04-.043.04-.044.04-.045.04-.047.039-.048.039-.05.039-.051.039-.052.038-.053.038-.055.038-.055.038-.058.037-.058.037-.06.037-.06.036-.062.036-.064.036-.064.036-.066.035-.067.035-.068.035-.069.035-.07.034-.071.034-.073.033-.074.033-.15.066-.155.064-.16.063-.163.061-.168.06-.172.059-.175.057-.18.056-.183.054-.187.053-.191.051-.194.05-.198.048-.201.046-.205.045-.208.043-.211.041-.214.04-.217.038-.22.036-.223.034-.225.032-.229.031-.231.028-.233.027-.236.024-.239.023-.241.02-.242.019-.246.016-.247.015-.249.012-.251.01-.253.008-.255.005-.256.004-.258.001-.258-.001-.256-.004-.255-.005-.253-.008-.251-.01-.249-.012-.247-.015-.245-.016-.243-.019-.241-.02-.238-.023-.236-.024-.234-.027-.231-.028-.228-.031-.226-.032-.223-.034-.22-.036-.217-.038-.214-.04-.211-.041-.208-.043-.204-.045-.201-.046-.198-.048-.195-.05-.19-.051-.187-.053-.184-.054-.179-.056-.176-.057-.172-.059-.167-.06-.164-.061-.159-.063-.155-.064-.151-.066-.074-.033-.072-.033-.072-.034-.07-.034-.069-.035-.068-.035-.067-.035-.066-.035-.064-.036-.063-.036-.062-.036-.061-.036-.06-.037-.058-.037-.057-.037-.056-.038-.055-.038-.053-.038-.052-.038-.051-.039-.049-.039-.049-.039-.046-.039-.046-.04-.044-.04-.043-.04-.041-.04-.04-.041-.039-.041-.037-.041-.036-.041-.034-.041-.033-.042-.032-.042-.03-.042-.029-.042-.027-.042-.026-.043-.024-.043-.023-.043-.021-.043-.02-.043-.018-.044-.017-.043-.015-.044-.013-.044-.012-.044-.011-.045-.009-.044-.007-.045-.006-.045-.004-.045-.002-.045-.001-.045v-17l.001-.045.002-.045.004-.045.006-.045.007-.045.009-.044.011-.045.012-.044.013-.044.015-.044.017-.043.018-.044.02-.043.021-.043.023-.043.024-.043.026-.043.027-.042.029-.042.03-.042.032-.042.033-.042.034-.041.036-.041.037-.041.039-.041.04-.041.041-.04.043-.04.044-.04.046-.04.046-.039.049-.039.049-.039.051-.039.052-.038.053-.038.055-.038.056-.038.057-.037.058-.037.06-.037.061-.036.062-.036.063-.036.064-.036.066-.035.067-.035.068-.035.069-.035.07-.034.072-.034.072-.033.074-.033.151-.066.155-.064.159-.063.164-.061.167-.06.172-.059.176-.057.179-.056.184-.054.187-.053.19-.051.195-.05.198-.048.201-.046.204-.045.208-.043.211-.041.214-.04.217-.038.22-.036.223-.034.226-.032.228-.031.231-.028.234-.027.236-.024.238-.023.241-.02.243-.019.245-.016.247-.015.249-.012.251-.01.253-.008.255-.005.256-.004.258-.001.258.001zm-9.258 20.499v.01l.001.021.003.021.004.022.005.021.006.022.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.023.018.024.019.024.021.024.022.025.023.024.024.025.052.049.056.05.061.051.066.051.07.051.075.051.079.052.084.052.088.052.092.052.097.052.102.051.105.052.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.048.144.049.147.047.152.047.155.047.16.045.163.045.167.043.171.043.176.041.178.041.183.039.187.039.19.037.194.035.197.035.202.033.204.031.209.03.212.029.216.027.219.025.222.024.226.021.23.02.233.018.236.016.24.015.243.012.246.01.249.008.253.005.256.004.259.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.021.224-.024.22-.026.216-.027.212-.028.21-.031.205-.031.202-.034.198-.034.194-.036.191-.037.187-.039.183-.04.179-.04.175-.042.172-.043.168-.044.163-.045.16-.046.155-.046.152-.047.148-.048.143-.049.139-.049.136-.05.131-.05.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.053.083-.051.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.05.023-.024.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.023.01-.022.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.127l-.077.055-.08.053-.083.054-.085.053-.087.052-.09.052-.093.051-.095.05-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.045-.118.044-.12.043-.122.042-.124.042-.126.041-.128.04-.13.04-.132.038-.134.038-.135.037-.138.037-.139.035-.142.035-.143.034-.144.033-.147.032-.148.031-.15.03-.151.03-.153.029-.154.027-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.01-.179.008-.179.008-.181.006-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.006-.179-.008-.179-.008-.178-.01-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.027-.153-.029-.151-.03-.15-.03-.148-.031-.146-.032-.145-.033-.143-.034-.141-.035-.14-.035-.137-.037-.136-.037-.134-.038-.132-.038-.13-.04-.128-.04-.126-.041-.124-.042-.122-.042-.12-.044-.117-.043-.116-.045-.113-.045-.112-.046-.109-.047-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.05-.093-.052-.09-.051-.087-.052-.085-.053-.083-.054-.08-.054-.077-.054v4.127zm0-5.654v.011l.001.021.003.021.004.021.005.022.006.022.007.022.009.022.01.022.011.023.012.023.013.023.015.024.016.023.017.024.018.024.019.024.021.024.022.024.023.025.024.024.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.052.11.051.114.051.119.052.123.05.127.051.131.05.135.049.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.044.171.042.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.022.23.02.233.018.236.016.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.012.241-.015.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.048.139-.05.136-.049.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.051.051-.049.023-.025.023-.024.021-.025.02-.024.019-.024.018-.024.017-.024.015-.023.014-.023.013-.024.012-.022.01-.023.01-.023.008-.022.006-.022.006-.022.004-.021.004-.022.001-.021.001-.021v-4.139l-.077.054-.08.054-.083.054-.085.052-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.044-.118.044-.12.044-.122.042-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.035-.143.033-.144.033-.147.033-.148.031-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.009-.179.009-.179.007-.181.007-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.007-.179-.007-.179-.009-.178-.009-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.031-.146-.033-.145-.033-.143-.033-.141-.035-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.04-.126-.041-.124-.042-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.051-.093-.051-.09-.051-.087-.053-.085-.052-.083-.054-.08-.054-.077-.054v4.139zm0-5.666v.011l.001.02.003.022.004.021.005.022.006.021.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.024.018.023.019.024.021.025.022.024.023.024.024.025.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.051.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.043.171.043.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.021.23.02.233.018.236.017.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.013.241-.014.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.049.139-.049.136-.049.131-.051.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.049.023-.025.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.022.01-.023.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.153l-.077.054-.08.054-.083.053-.085.053-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.048-.105.048-.106.048-.109.046-.111.046-.114.046-.115.044-.118.044-.12.043-.122.043-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.034-.143.034-.144.033-.147.032-.148.032-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.024-.161.024-.162.023-.163.023-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.01-.178.01-.179.009-.179.007-.181.006-.182.006-.182.004-.184.003-.184.001-.185.001-.185-.001-.184-.001-.184-.003-.182-.004-.182-.006-.181-.006-.179-.007-.179-.009-.178-.01-.176-.01-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.023-.162-.023-.161-.024-.159-.024-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.032-.146-.032-.145-.033-.143-.034-.141-.034-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.041-.126-.041-.124-.041-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.048-.105-.048-.102-.048-.1-.05-.097-.049-.095-.051-.093-.051-.09-.052-.087-.052-.085-.053-.083-.053-.08-.054-.077-.054v4.153zm8.74-8.179l-.257.004-.254.005-.25.008-.247.011-.244.012-.241.014-.237.016-.233.018-.231.021-.226.022-.224.023-.22.026-.216.027-.212.028-.21.031-.205.032-.202.033-.198.034-.194.036-.191.038-.187.038-.183.04-.179.041-.175.042-.172.043-.168.043-.163.045-.16.046-.155.046-.152.048-.148.048-.143.048-.139.049-.136.05-.131.05-.126.051-.123.051-.118.051-.114.052-.11.052-.106.052-.101.052-.096.052-.092.052-.088.052-.083.052-.079.052-.074.051-.07.052-.065.051-.06.05-.056.05-.051.05-.023.025-.023.024-.021.024-.02.025-.019.024-.018.024-.017.023-.015.024-.014.023-.013.023-.012.023-.01.023-.01.022-.008.022-.006.023-.006.021-.004.022-.004.021-.001.021-.001.021.001.021.001.021.004.021.004.022.006.021.006.023.008.022.01.022.01.023.012.023.013.023.014.023.015.024.017.023.018.024.019.024.02.025.021.024.023.024.023.025.051.05.056.05.06.05.065.051.07.052.074.051.079.052.083.052.088.052.092.052.096.052.101.052.106.052.11.052.114.052.118.051.123.051.126.051.131.05.136.05.139.049.143.048.148.048.152.048.155.046.16.046.163.045.168.043.172.043.175.042.179.041.183.04.187.038.191.038.194.036.198.034.202.033.205.032.21.031.212.028.216.027.22.026.224.023.226.022.231.021.233.018.237.016.241.014.244.012.247.011.25.008.254.005.257.004.26.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.022.224-.023.22-.026.216-.027.212-.028.21-.031.205-.032.202-.033.198-.034.194-.036.191-.038.187-.038.183-.04.179-.041.175-.042.172-.043.168-.043.163-.045.16-.046.155-.046.152-.048.148-.048.143-.048.139-.049.136-.05.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.05.051-.05.023-.025.023-.024.021-.024.02-.025.019-.024.018-.024.017-.023.015-.024.014-.023.013-.023.012-.023.01-.023.01-.022.008-.022.006-.023.006-.021.004-.022.004-.021.001-.021.001-.021-.001-.021-.001-.021-.004-.021-.004-.022-.006-.021-.006-.023-.008-.022-.01-.022-.01-.023-.012-.023-.013-.023-.014-.023-.015-.024-.017-.023-.018-.024-.019-.024-.02-.025-.021-.024-.023-.024-.023-.025-.051-.05-.056-.05-.06-.05-.065-.051-.07-.052-.074-.051-.079-.052-.083-.052-.088-.052-.092-.052-.096-.052-.101-.052-.106-.052-.11-.052-.114-.052-.118-.051-.123-.051-.126-.051-.131-.05-.136-.05-.139-.049-.143-.048-.148-.048-.152-.048-.155-.046-.16-.046-.163-.045-.168-.043-.172-.043-.175-.042-.179-.041-.183-.04-.187-.038-.191-.038-.194-.036-.198-.034-.202-.033-.205-.032-.21-.031-.212-.028-.216-.027-.22-.026-.224-.023-.226-.022-.231-.021-.233-.018-.237-.016-.241-.014-.244-.012-.247-.011-.25-.008-.254-.005-.257-.004-.26-.001-.26.001z")},"insertDatabaseIcon"),GGe=o(function(t){t.append("defs").append("symbol").attr("id","computer").attr("width","24").attr("height","24").append("path").attr("transform","scale(.5)").attr("d","M2 2v13h20v-13h-20zm18 11h-16v-9h16v9zm-10.228 6l.466-1h3.524l.467 1h-4.457zm14.228 3h-24l2-6h2.104l-1.33 4h18.45l-1.297-4h2.073l2 6zm-5-10h-14v-7h14v7z")},"insertComputerIcon"),$Ge=o(function(t){t.append("defs").append("symbol").attr("id","clock").attr("width","24").attr("height","24").append("path").attr("transform","scale(.5)").attr("d","M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm5.848 12.459c.202.038.202.333.001.372-1.907.361-6.045 1.111-6.547 1.111-.719 0-1.301-.582-1.301-1.301 0-.512.77-5.447 1.125-7.445.034-.192.312-.181.343.014l.985 6.238 5.394 1.011z")},"insertClockIcon"),VGe=o(function(t){t.append("defs").append("marker").attr("id","arrowhead").attr("refX",7.9).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",12).attr("markerHeight",12).attr("orient","auto-start-reverse").append("path").attr("d","M -1 0 L 10 5 L 0 10 z")},"insertArrowHead"),UGe=o(function(t){t.append("defs").append("marker").attr("id","filled-head").attr("refX",15.5).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L14,7 L9,1 Z")},"insertArrowFilledHead"),HGe=o(function(t){t.append("defs").append("marker").attr("id","sequencenumber").attr("refX",15).attr("refY",15).attr("markerWidth",60).attr("markerHeight",40).attr("orient","auto").append("circle").attr("cx",15).attr("cy",15).attr("r",6)},"insertSequenceNumber"),YGe=o(function(t){t.append("defs").append("marker").attr("id","crosshead").attr("markerWidth",15).attr("markerHeight",8).attr("orient","auto").attr("refX",4).attr("refY",4.5).append("path").attr("fill","none").attr("stroke","#000000").style("stroke-dasharray","0, 0").attr("stroke-width","1pt").attr("d","M 1,2 L 6,7 M 6,2 L 1,7")},"insertArrowCrossHead"),che=o(function(){return{x:0,y:0,fill:void 0,anchor:void 0,style:"#666",width:void 0,height:void 0,textMargin:0,rx:0,ry:0,tspan:!0,valign:void 0}},"getTextObj"),WGe=o(function(){return{x:0,y:0,fill:"#EDF2AE",stroke:"#666",width:100,anchor:"start",height:100,rx:0,ry:0}},"getNoteRect"),vO=function(){function t(a,s,l,u,h,f,d){let p=s.append("text").attr("x",l+h/2).attr("y",u+f/2+5).style("text-anchor","middle").text(a);i(p,d)}o(t,"byText");function e(a,s,l,u,h,f,d,p){let{actorFontSize:m,actorFontFamily:g,actorFontWeight:y}=p,[v,x]=mc(m),b=a.split(We.lineBreakRegex);for(let w=0;w{let s=L0(Ne),l=a.actorKeys.reduce((f,d)=>f+=t.get(d).width+(t.get(d).margin||0),0);l-=2*Ne.boxTextMargin,a.wrap&&(a.name=Lt.wrapLabel(a.name,l-2*Ne.wrapPadding,s));let u=Lt.calculateTextDimensions(a.name,s);i=We.getMax(u.height,i);let h=We.getMax(l,u.width+2*Ne.wrapPadding);if(a.margin=Ne.boxTextMargin,la.textMaxHeight=i),We.getMax(n,Ne.height)}var Ne,Ke,XGe,L0,Pg,xO,KGe,QGe,bO,fhe,dhe,bE,hhe,JGe,t$e,n$e,i$e,a$e,phe,mhe=R(()=>{"use strict";Zt();uhe();ut();rr();Qy();_t();cp();xr();Yn();Ne={},Ke={data:{startx:void 0,stopx:void 0,starty:void 0,stopy:void 0},verticalPos:0,sequenceItems:[],activations:[],models:{getHeight:o(function(){return Math.max.apply(null,this.actors.length===0?[0]:this.actors.map(t=>t.height||0))+(this.loops.length===0?0:this.loops.map(t=>t.height||0).reduce((t,e)=>t+e))+(this.messages.length===0?0:this.messages.map(t=>t.height||0).reduce((t,e)=>t+e))+(this.notes.length===0?0:this.notes.map(t=>t.height||0).reduce((t,e)=>t+e))},"getHeight"),clear:o(function(){this.actors=[],this.boxes=[],this.loops=[],this.messages=[],this.notes=[]},"clear"),addBox:o(function(t){this.boxes.push(t)},"addBox"),addActor:o(function(t){this.actors.push(t)},"addActor"),addLoop:o(function(t){this.loops.push(t)},"addLoop"),addMessage:o(function(t){this.messages.push(t)},"addMessage"),addNote:o(function(t){this.notes.push(t)},"addNote"),lastActor:o(function(){return this.actors[this.actors.length-1]},"lastActor"),lastLoop:o(function(){return this.loops[this.loops.length-1]},"lastLoop"),lastMessage:o(function(){return this.messages[this.messages.length-1]},"lastMessage"),lastNote:o(function(){return this.notes[this.notes.length-1]},"lastNote"),actors:[],boxes:[],loops:[],messages:[],notes:[]},init:o(function(){this.sequenceItems=[],this.activations=[],this.models.clear(),this.data={startx:void 0,stopx:void 0,starty:void 0,stopy:void 0},this.verticalPos=0,dhe(de())},"init"),updateVal:o(function(t,e,r,n){t[e]===void 0?t[e]=r:t[e]=n(r,t[e])},"updateVal"),updateBounds:o(function(t,e,r,n){let i=this,a=0;function s(l){return o(function(h){a++;let f=i.sequenceItems.length-a+1;i.updateVal(h,"starty",e-f*Ne.boxMargin,Math.min),i.updateVal(h,"stopy",n+f*Ne.boxMargin,Math.max),i.updateVal(Ke.data,"startx",t-f*Ne.boxMargin,Math.min),i.updateVal(Ke.data,"stopx",r+f*Ne.boxMargin,Math.max),l!=="activation"&&(i.updateVal(h,"startx",t-f*Ne.boxMargin,Math.min),i.updateVal(h,"stopx",r+f*Ne.boxMargin,Math.max),i.updateVal(Ke.data,"starty",e-f*Ne.boxMargin,Math.min),i.updateVal(Ke.data,"stopy",n+f*Ne.boxMargin,Math.max))},"updateItemBounds")}o(s,"updateFn"),this.sequenceItems.forEach(s()),this.activations.forEach(s("activation"))},"updateBounds"),insert:o(function(t,e,r,n){let i=We.getMin(t,r),a=We.getMax(t,r),s=We.getMin(e,n),l=We.getMax(e,n);this.updateVal(Ke.data,"startx",i,Math.min),this.updateVal(Ke.data,"starty",s,Math.min),this.updateVal(Ke.data,"stopx",a,Math.max),this.updateVal(Ke.data,"stopy",l,Math.max),this.updateBounds(i,s,a,l)},"insert"),newActivation:o(function(t,e,r){let n=r.get(t.from),i=bE(t.from).length||0,a=n.x+n.width/2+(i-1)*Ne.activationWidth/2;this.activations.push({startx:a,starty:this.verticalPos+2,stopx:a+Ne.activationWidth,stopy:void 0,actor:t.from,anchored:si.anchorElement(e)})},"newActivation"),endActivation:o(function(t){let e=this.activations.map(function(r){return r.actor}).lastIndexOf(t.from);return this.activations.splice(e,1)[0]},"endActivation"),createLoop:o(function(t={message:void 0,wrap:!1,width:void 0},e){return{startx:void 0,starty:this.verticalPos,stopx:void 0,stopy:void 0,title:t.message,wrap:t.wrap,width:t.width,height:0,fill:e}},"createLoop"),newLoop:o(function(t={message:void 0,wrap:!1,width:void 0},e){this.sequenceItems.push(this.createLoop(t,e))},"newLoop"),endLoop:o(function(){return this.sequenceItems.pop()},"endLoop"),isLoopOverlap:o(function(){return this.sequenceItems.length?this.sequenceItems[this.sequenceItems.length-1].overlap:!1},"isLoopOverlap"),addSectionToLoop:o(function(t){let e=this.sequenceItems.pop();e.sections=e.sections||[],e.sectionTitles=e.sectionTitles||[],e.sections.push({y:Ke.getVerticalPos(),height:0}),e.sectionTitles.push(t),this.sequenceItems.push(e)},"addSectionToLoop"),saveVerticalPos:o(function(){this.isLoopOverlap()&&(this.savedVerticalPos=this.verticalPos)},"saveVerticalPos"),resetVerticalPos:o(function(){this.isLoopOverlap()&&(this.verticalPos=this.savedVerticalPos)},"resetVerticalPos"),bumpVerticalPos:o(function(t){this.verticalPos=this.verticalPos+t,this.data.stopy=We.getMax(this.data.stopy,this.verticalPos)},"bumpVerticalPos"),getVerticalPos:o(function(){return this.verticalPos},"getVerticalPos"),getBounds:o(function(){return{bounds:this.data,models:this.models}},"getBounds")},XGe=o(async function(t,e){Ke.bumpVerticalPos(Ne.boxMargin),e.height=Ne.boxMargin,e.starty=Ke.getVerticalPos();let r=wl();r.x=e.startx,r.y=e.starty,r.width=e.width||Ne.width,r.class="note";let n=t.append("g"),i=si.drawRect(n,r),a=Ky();a.x=e.startx,a.y=e.starty,a.width=r.width,a.dy="1em",a.text=e.message,a.class="noteText",a.fontFamily=Ne.noteFontFamily,a.fontSize=Ne.noteFontSize,a.fontWeight=Ne.noteFontWeight,a.anchor=Ne.noteAlign,a.textMargin=Ne.noteMargin,a.valign="center";let s=Ni(a.text)?await bx(n,a):_0(n,a),l=Math.round(s.map(u=>(u._groups||u)[0][0].getBBox().height).reduce((u,h)=>u+h));i.attr("height",l+2*Ne.noteMargin),e.height+=l+2*Ne.noteMargin,Ke.bumpVerticalPos(l+2*Ne.noteMargin),e.stopy=e.starty+l+2*Ne.noteMargin,e.stopx=e.startx+r.width,Ke.insert(e.startx,e.starty,e.stopx,e.stopy),Ke.models.addNote(e)},"drawNote"),L0=o(t=>({fontFamily:t.messageFontFamily,fontSize:t.messageFontSize,fontWeight:t.messageFontWeight}),"messageFont"),Pg=o(t=>({fontFamily:t.noteFontFamily,fontSize:t.noteFontSize,fontWeight:t.noteFontWeight}),"noteFont"),xO=o(t=>({fontFamily:t.actorFontFamily,fontSize:t.actorFontSize,fontWeight:t.actorFontWeight}),"actorFont");o(jGe,"boundMessage");KGe=o(async function(t,e,r,n){let{startx:i,stopx:a,starty:s,message:l,type:u,sequenceIndex:h,sequenceVisible:f}=e,d=Lt.calculateTextDimensions(l,L0(Ne)),p=Ky();p.x=i,p.y=s+10,p.width=a-i,p.class="messageText",p.dy="1em",p.text=l,p.fontFamily=Ne.messageFontFamily,p.fontSize=Ne.messageFontSize,p.fontWeight=Ne.messageFontWeight,p.anchor=Ne.messageAlign,p.valign="center",p.textMargin=Ne.wrapPadding,p.tspan=!1,Ni(p.text)?await bx(t,p,{startx:i,stopx:a,starty:r}):_0(t,p);let m=d.width,g;i===a?Ne.rightAngles?g=t.append("path").attr("d",`M ${i},${r} H ${i+We.getMax(Ne.width/2,m/2)} V ${r+25} H ${i}`):g=t.append("path").attr("d","M "+i+","+r+" C "+(i+60)+","+(r-10)+" "+(i+60)+","+(r+30)+" "+i+","+(r+20)):(g=t.append("line"),g.attr("x1",i),g.attr("y1",r),g.attr("x2",a),g.attr("y2",r)),u===n.db.LINETYPE.DOTTED||u===n.db.LINETYPE.DOTTED_CROSS||u===n.db.LINETYPE.DOTTED_POINT||u===n.db.LINETYPE.DOTTED_OPEN||u===n.db.LINETYPE.BIDIRECTIONAL_DOTTED?(g.style("stroke-dasharray","3, 3"),g.attr("class","messageLine1")):g.attr("class","messageLine0");let y="";Ne.arrowMarkerAbsolute&&(y=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search,y=y.replace(/\(/g,"\\("),y=y.replace(/\)/g,"\\)")),g.attr("stroke-width",2),g.attr("stroke","none"),g.style("fill","none"),(u===n.db.LINETYPE.SOLID||u===n.db.LINETYPE.DOTTED)&&g.attr("marker-end","url("+y+"#arrowhead)"),(u===n.db.LINETYPE.BIDIRECTIONAL_SOLID||u===n.db.LINETYPE.BIDIRECTIONAL_DOTTED)&&(g.attr("marker-start","url("+y+"#arrowhead)"),g.attr("marker-end","url("+y+"#arrowhead)")),(u===n.db.LINETYPE.SOLID_POINT||u===n.db.LINETYPE.DOTTED_POINT)&&g.attr("marker-end","url("+y+"#filled-head)"),(u===n.db.LINETYPE.SOLID_CROSS||u===n.db.LINETYPE.DOTTED_CROSS)&&g.attr("marker-end","url("+y+"#crosshead)"),(f||Ne.showSequenceNumbers)&&(g.attr("marker-start","url("+y+"#sequencenumber)"),t.append("text").attr("x",i).attr("y",r+4).attr("font-family","sans-serif").attr("font-size","12px").attr("text-anchor","middle").attr("class","sequenceNumber").text(h))},"drawMessage"),QGe=o(function(t,e,r,n,i,a,s){let l=0,u=0,h,f=0;for(let d of n){let p=e.get(d),m=p.box;h&&h!=m&&(s||Ke.models.addBox(h),u+=Ne.boxMargin+h.margin),m&&m!=h&&(s||(m.x=l+u,m.y=i),u+=m.margin),p.width=p.width||Ne.width,p.height=We.getMax(p.height||Ne.height,Ne.height),p.margin=p.margin||Ne.actorMargin,f=We.getMax(f,p.height),r.get(p.name)&&(u+=p.width/2),p.x=l+u,p.starty=Ke.getVerticalPos(),Ke.insert(p.x,i,p.x+p.width,p.height),l+=p.width+u,p.box&&(p.box.width=l+m.margin-p.box.x),u=p.margin,h=p.box,Ke.models.addActor(p)}h&&!s&&Ke.models.addBox(h),Ke.bumpVerticalPos(f)},"addActorRenderingData"),bO=o(async function(t,e,r,n){if(n){let i=0;Ke.bumpVerticalPos(Ne.boxMargin*2);for(let a of r){let s=e.get(a);s.stopy||(s.stopy=Ke.getVerticalPos());let l=await si.drawActor(t,s,Ne,!0);i=We.getMax(i,l)}Ke.bumpVerticalPos(i+Ne.boxMargin)}else for(let i of r){let a=e.get(i);await si.drawActor(t,a,Ne,!1)}},"drawActors"),fhe=o(function(t,e,r,n){let i=0,a=0;for(let s of r){let l=e.get(s),u=t$e(l),h=si.drawPopup(t,l,u,Ne,Ne.forceMenus,n);h.height>i&&(i=h.height),h.width+l.x>a&&(a=h.width+l.x)}return{maxHeight:i,maxWidth:a}},"drawActorsPopup"),dhe=o(function(t){On(Ne,t),t.fontFamily&&(Ne.actorFontFamily=Ne.noteFontFamily=Ne.messageFontFamily=t.fontFamily),t.fontSize&&(Ne.actorFontSize=Ne.noteFontSize=Ne.messageFontSize=t.fontSize),t.fontWeight&&(Ne.actorFontWeight=Ne.noteFontWeight=Ne.messageFontWeight=t.fontWeight)},"setConf"),bE=o(function(t){return Ke.activations.filter(function(e){return e.actor===t})},"actorActivations"),hhe=o(function(t,e){let r=e.get(t),n=bE(t),i=n.reduce(function(s,l){return We.getMin(s,l.startx)},r.x+r.width/2-1),a=n.reduce(function(s,l){return We.getMax(s,l.stopx)},r.x+r.width/2+1);return[i,a]},"activationBounds");o(Fc,"adjustLoopHeightForWrap");o(ZGe,"adjustCreatedDestroyedData");JGe=o(async function(t,e,r,n){let{securityLevel:i,sequence:a}=de();Ne=a;let s;i==="sandbox"&&(s=$e("#i"+e));let l=i==="sandbox"?$e(s.nodes()[0].contentDocument.body):$e("body"),u=i==="sandbox"?s.nodes()[0].contentDocument:document;Ke.init(),V.debug(n.db);let h=i==="sandbox"?l.select(`[id="${e}"]`):$e(`[id="${e}"]`),f=n.db.getActors(),d=n.db.getCreatedActors(),p=n.db.getDestroyedActors(),m=n.db.getBoxes(),g=n.db.getActorKeys(),y=n.db.getMessages(),v=n.db.getDiagramTitle(),x=n.db.hasAtLeastOneBox(),b=n.db.hasAtLeastOneBoxWithTitle(),w=await e$e(f,y,n);if(Ne.height=await r$e(f,w,m),si.insertComputerIcon(h),si.insertDatabaseIcon(h),si.insertClockIcon(h),x&&(Ke.bumpVerticalPos(Ne.boxMargin),b&&Ke.bumpVerticalPos(m[0].textMaxHeight)),Ne.hideUnusedParticipants===!0){let F=new Set;y.forEach(B=>{F.add(B.from),F.add(B.to)}),g=g.filter(B=>F.has(B))}QGe(h,f,d,g,0,y,!1);let S=await a$e(y,f,w,n);si.insertArrowHead(h),si.insertArrowCrossHead(h),si.insertArrowFilledHead(h),si.insertSequenceNumber(h);function T(F,B){let $=Ke.endActivation(F);$.starty+18>B&&($.starty=B-6,B+=12),si.drawActivation(h,$,B,Ne,bE(F.from).length),Ke.insert($.startx,B-10,$.stopx,B)}o(T,"activeEnd");let E=1,_=1,A=[],L=[],M=0;for(let F of y){let B,$,z;switch(F.type){case n.db.LINETYPE.NOTE:Ke.resetVerticalPos(),$=F.noteModel,await XGe(h,$);break;case n.db.LINETYPE.ACTIVE_START:Ke.newActivation(F,h,f);break;case n.db.LINETYPE.ACTIVE_END:T(F,Ke.getVerticalPos());break;case n.db.LINETYPE.LOOP_START:Fc(S,F,Ne.boxMargin,Ne.boxMargin+Ne.boxTextMargin,Y=>Ke.newLoop(Y));break;case n.db.LINETYPE.LOOP_END:B=Ke.endLoop(),await si.drawLoop(h,B,"loop",Ne),Ke.bumpVerticalPos(B.stopy-Ke.getVerticalPos()),Ke.models.addLoop(B);break;case n.db.LINETYPE.RECT_START:Fc(S,F,Ne.boxMargin,Ne.boxMargin,Y=>Ke.newLoop(void 0,Y.message));break;case n.db.LINETYPE.RECT_END:B=Ke.endLoop(),L.push(B),Ke.models.addLoop(B),Ke.bumpVerticalPos(B.stopy-Ke.getVerticalPos());break;case n.db.LINETYPE.OPT_START:Fc(S,F,Ne.boxMargin,Ne.boxMargin+Ne.boxTextMargin,Y=>Ke.newLoop(Y));break;case n.db.LINETYPE.OPT_END:B=Ke.endLoop(),await si.drawLoop(h,B,"opt",Ne),Ke.bumpVerticalPos(B.stopy-Ke.getVerticalPos()),Ke.models.addLoop(B);break;case n.db.LINETYPE.ALT_START:Fc(S,F,Ne.boxMargin,Ne.boxMargin+Ne.boxTextMargin,Y=>Ke.newLoop(Y));break;case n.db.LINETYPE.ALT_ELSE:Fc(S,F,Ne.boxMargin+Ne.boxTextMargin,Ne.boxMargin,Y=>Ke.addSectionToLoop(Y));break;case n.db.LINETYPE.ALT_END:B=Ke.endLoop(),await si.drawLoop(h,B,"alt",Ne),Ke.bumpVerticalPos(B.stopy-Ke.getVerticalPos()),Ke.models.addLoop(B);break;case n.db.LINETYPE.PAR_START:case n.db.LINETYPE.PAR_OVER_START:Fc(S,F,Ne.boxMargin,Ne.boxMargin+Ne.boxTextMargin,Y=>Ke.newLoop(Y)),Ke.saveVerticalPos();break;case n.db.LINETYPE.PAR_AND:Fc(S,F,Ne.boxMargin+Ne.boxTextMargin,Ne.boxMargin,Y=>Ke.addSectionToLoop(Y));break;case n.db.LINETYPE.PAR_END:B=Ke.endLoop(),await si.drawLoop(h,B,"par",Ne),Ke.bumpVerticalPos(B.stopy-Ke.getVerticalPos()),Ke.models.addLoop(B);break;case n.db.LINETYPE.AUTONUMBER:E=F.message.start||E,_=F.message.step||_,F.message.visible?n.db.enableSequenceNumbers():n.db.disableSequenceNumbers();break;case n.db.LINETYPE.CRITICAL_START:Fc(S,F,Ne.boxMargin,Ne.boxMargin+Ne.boxTextMargin,Y=>Ke.newLoop(Y));break;case n.db.LINETYPE.CRITICAL_OPTION:Fc(S,F,Ne.boxMargin+Ne.boxTextMargin,Ne.boxMargin,Y=>Ke.addSectionToLoop(Y));break;case n.db.LINETYPE.CRITICAL_END:B=Ke.endLoop(),await si.drawLoop(h,B,"critical",Ne),Ke.bumpVerticalPos(B.stopy-Ke.getVerticalPos()),Ke.models.addLoop(B);break;case n.db.LINETYPE.BREAK_START:Fc(S,F,Ne.boxMargin,Ne.boxMargin+Ne.boxTextMargin,Y=>Ke.newLoop(Y));break;case n.db.LINETYPE.BREAK_END:B=Ke.endLoop(),await si.drawLoop(h,B,"break",Ne),Ke.bumpVerticalPos(B.stopy-Ke.getVerticalPos()),Ke.models.addLoop(B);break;default:try{z=F.msgModel,z.starty=Ke.getVerticalPos(),z.sequenceIndex=E,z.sequenceVisible=n.db.showSequenceNumbers();let Y=await jGe(h,z);ZGe(F,z,Y,M,f,d,p),A.push({messageModel:z,lineStartY:Y}),Ke.models.addMessage(z)}catch(Y){V.error("error while drawing message",Y)}}[n.db.LINETYPE.SOLID_OPEN,n.db.LINETYPE.DOTTED_OPEN,n.db.LINETYPE.SOLID,n.db.LINETYPE.DOTTED,n.db.LINETYPE.SOLID_CROSS,n.db.LINETYPE.DOTTED_CROSS,n.db.LINETYPE.SOLID_POINT,n.db.LINETYPE.DOTTED_POINT,n.db.LINETYPE.BIDIRECTIONAL_SOLID,n.db.LINETYPE.BIDIRECTIONAL_DOTTED].includes(F.type)&&(E=E+_),M++}V.debug("createdActors",d),V.debug("destroyedActors",p),await bO(h,f,g,!1);for(let F of A)await KGe(h,F.messageModel,F.lineStartY,n);Ne.mirrorActors&&await bO(h,f,g,!0),L.forEach(F=>si.drawBackgroundRect(h,F)),yO(h,f,g,Ne);for(let F of Ke.models.boxes)F.height=Ke.getVerticalPos()-F.y,Ke.insert(F.x,F.y,F.x+F.width,F.height),F.startx=F.x,F.starty=F.y,F.stopx=F.startx+F.width,F.stopy=F.starty+F.height,F.stroke="rgb(0,0,0, 0.5)",si.drawBox(h,F,Ne);x&&Ke.bumpVerticalPos(Ne.boxMargin);let N=fhe(h,f,g,u),{bounds:k}=Ke.getBounds();k.startx===void 0&&(k.startx=0),k.starty===void 0&&(k.starty=0),k.stopx===void 0&&(k.stopx=0),k.stopy===void 0&&(k.stopy=0);let I=k.stopy-k.starty;I2,d=o(y=>l?-y:y,"adjustValue");t.from===t.to?h=u:(t.activate&&!f&&(h+=d(Ne.activationWidth/2-1)),[r.db.LINETYPE.SOLID_OPEN,r.db.LINETYPE.DOTTED_OPEN].includes(t.type)||(h+=d(3)),[r.db.LINETYPE.BIDIRECTIONAL_SOLID,r.db.LINETYPE.BIDIRECTIONAL_DOTTED].includes(t.type)&&(u-=d(3)));let p=[n,i,a,s],m=Math.abs(u-h);t.wrap&&t.message&&(t.message=Lt.wrapLabel(t.message,We.getMax(m+2*Ne.wrapPadding,Ne.width),L0(Ne)));let g=Lt.calculateTextDimensions(t.message,L0(Ne));return{width:We.getMax(t.wrap?0:g.width+2*Ne.wrapPadding,m+2*Ne.wrapPadding,Ne.width),height:0,startx:u,stopx:h,starty:0,stopy:0,message:t.message,type:t.type,wrap:t.wrap,fromBounds:Math.min.apply(null,p),toBounds:Math.max.apply(null,p)}},"buildMessageModel"),a$e=o(async function(t,e,r,n){let i={},a=[],s,l,u;for(let h of t){switch(h.id=Lt.random({length:10}),h.type){case n.db.LINETYPE.LOOP_START:case n.db.LINETYPE.ALT_START:case n.db.LINETYPE.OPT_START:case n.db.LINETYPE.PAR_START:case n.db.LINETYPE.PAR_OVER_START:case n.db.LINETYPE.CRITICAL_START:case n.db.LINETYPE.BREAK_START:a.push({id:h.id,msg:h.message,from:Number.MAX_SAFE_INTEGER,to:Number.MIN_SAFE_INTEGER,width:0});break;case n.db.LINETYPE.ALT_ELSE:case n.db.LINETYPE.PAR_AND:case n.db.LINETYPE.CRITICAL_OPTION:h.message&&(s=a.pop(),i[s.id]=s,i[h.id]=s,a.push(s));break;case n.db.LINETYPE.LOOP_END:case n.db.LINETYPE.ALT_END:case n.db.LINETYPE.OPT_END:case n.db.LINETYPE.PAR_END:case n.db.LINETYPE.CRITICAL_END:case n.db.LINETYPE.BREAK_END:s=a.pop(),i[s.id]=s;break;case n.db.LINETYPE.ACTIVE_START:{let d=e.get(h.from?h.from:h.to.actor),p=bE(h.from?h.from:h.to.actor).length,m=d.x+d.width/2+(p-1)*Ne.activationWidth/2,g={startx:m,stopx:m+Ne.activationWidth,actor:h.from,enabled:!0};Ke.activations.push(g)}break;case n.db.LINETYPE.ACTIVE_END:{let d=Ke.activations.map(p=>p.actor).lastIndexOf(h.from);Ke.activations.splice(d,1).splice(0,1)}break}h.placement!==void 0?(l=await n$e(h,e,n),h.noteModel=l,a.forEach(d=>{s=d,s.from=We.getMin(s.from,l.startx),s.to=We.getMax(s.to,l.startx+l.width),s.width=We.getMax(s.width,Math.abs(s.from-s.to))-Ne.labelBoxWidth})):(u=i$e(h,e,n),h.msgModel=u,u.startx&&u.stopx&&a.length>0&&a.forEach(d=>{if(s=d,u.startx===u.stopx){let p=e.get(h.from),m=e.get(h.to);s.from=We.getMin(p.x-u.width/2,p.x-p.width/2,s.from),s.to=We.getMax(m.x+u.width/2,m.x+p.width/2,s.to),s.width=We.getMax(s.width,Math.abs(s.to-s.from))-Ne.labelBoxWidth}else s.from=We.getMin(u.startx,s.from),s.to=We.getMax(u.stopx,s.to),s.width=We.getMax(s.width,u.width)-Ne.labelBoxWidth}))}return Ke.activations=[],V.debug("Loop type widths:",i),i},"calculateLoopBounds"),phe={bounds:Ke,drawActors:bO,drawActorsPopup:fhe,setConf:dhe,draw:JGe}});var ghe={};hr(ghe,{diagram:()=>s$e});var s$e,yhe=R(()=>{"use strict";que();the();nhe();mhe();s$e={parser:Wue,db:pO,renderer:phe,styles:rhe,init:o(({wrap:t})=>{pO.setWrap(t)},"init")}});var wO,wE,TO=R(()=>{"use strict";wO=function(){var t=o(function(Pe,_e,me,W){for(me=me||{},W=Pe.length;W--;me[Pe[W]]=_e);return me},"o"),e=[1,17],r=[1,18],n=[1,19],i=[1,39],a=[1,40],s=[1,25],l=[1,23],u=[1,24],h=[1,31],f=[1,32],d=[1,33],p=[1,34],m=[1,35],g=[1,36],y=[1,26],v=[1,27],x=[1,28],b=[1,29],w=[1,43],S=[1,30],T=[1,42],E=[1,44],_=[1,41],A=[1,45],L=[1,9],M=[1,8,9],N=[1,56],k=[1,57],I=[1,58],C=[1,59],O=[1,60],D=[1,61],P=[1,62],F=[1,8,9,39],B=[1,74],$=[1,8,9,12,13,21,37,39,42,59,60,61,62,63,64,65,70,72],z=[1,8,9,12,13,19,21,37,39,42,46,59,60,61,62,63,64,65,70,72,74,80,95,97,98],Y=[13,74,80,95,97,98],Q=[13,64,65,74,80,95,97,98],X=[13,59,60,61,62,63,74,80,95,97,98],ie=[1,93],j=[1,110],J=[1,108],Z=[1,102],H=[1,103],q=[1,104],K=[1,105],se=[1,106],ce=[1,107],ue=[1,109],te=[1,8,9,37,39,42],De=[1,8,9,21],oe=[1,8,9,78],ke=[1,8,9,21,73,74,78,80,81,82,83,84,85],Ie={trace:o(function(){},"trace"),yy:{},symbols_:{error:2,start:3,mermaidDoc:4,statements:5,graphConfig:6,CLASS_DIAGRAM:7,NEWLINE:8,EOF:9,statement:10,classLabel:11,SQS:12,STR:13,SQE:14,namespaceName:15,alphaNumToken:16,className:17,classLiteralName:18,GENERICTYPE:19,relationStatement:20,LABEL:21,namespaceStatement:22,classStatement:23,memberStatement:24,annotationStatement:25,clickStatement:26,styleStatement:27,cssClassStatement:28,noteStatement:29,direction:30,acc_title:31,acc_title_value:32,acc_descr:33,acc_descr_value:34,acc_descr_multiline_value:35,namespaceIdentifier:36,STRUCT_START:37,classStatements:38,STRUCT_STOP:39,NAMESPACE:40,classIdentifier:41,STYLE_SEPARATOR:42,members:43,CLASS:44,ANNOTATION_START:45,ANNOTATION_END:46,MEMBER:47,SEPARATOR:48,relation:49,NOTE_FOR:50,noteText:51,NOTE:52,direction_tb:53,direction_bt:54,direction_rl:55,direction_lr:56,relationType:57,lineType:58,AGGREGATION:59,EXTENSION:60,COMPOSITION:61,DEPENDENCY:62,LOLLIPOP:63,LINE:64,DOTTED_LINE:65,CALLBACK:66,LINK:67,LINK_TARGET:68,CLICK:69,CALLBACK_NAME:70,CALLBACK_ARGS:71,HREF:72,STYLE:73,ALPHA:74,stylesOpt:75,CSSCLASS:76,style:77,COMMA:78,styleComponent:79,NUM:80,COLON:81,UNIT:82,SPACE:83,BRKT:84,PCT:85,commentToken:86,textToken:87,graphCodeTokens:88,textNoTagsToken:89,TAGSTART:90,TAGEND:91,"==":92,"--":93,DEFAULT:94,MINUS:95,keywords:96,UNICODE_TEXT:97,BQUOTE_STR:98,$accept:0,$end:1},terminals_:{2:"error",7:"CLASS_DIAGRAM",8:"NEWLINE",9:"EOF",12:"SQS",13:"STR",14:"SQE",19:"GENERICTYPE",21:"LABEL",31:"acc_title",32:"acc_title_value",33:"acc_descr",34:"acc_descr_value",35:"acc_descr_multiline_value",37:"STRUCT_START",39:"STRUCT_STOP",40:"NAMESPACE",42:"STYLE_SEPARATOR",44:"CLASS",45:"ANNOTATION_START",46:"ANNOTATION_END",47:"MEMBER",48:"SEPARATOR",50:"NOTE_FOR",52:"NOTE",53:"direction_tb",54:"direction_bt",55:"direction_rl",56:"direction_lr",59:"AGGREGATION",60:"EXTENSION",61:"COMPOSITION",62:"DEPENDENCY",63:"LOLLIPOP",64:"LINE",65:"DOTTED_LINE",66:"CALLBACK",67:"LINK",68:"LINK_TARGET",69:"CLICK",70:"CALLBACK_NAME",71:"CALLBACK_ARGS",72:"HREF",73:"STYLE",74:"ALPHA",76:"CSSCLASS",78:"COMMA",80:"NUM",81:"COLON",82:"UNIT",83:"SPACE",84:"BRKT",85:"PCT",88:"graphCodeTokens",90:"TAGSTART",91:"TAGEND",92:"==",93:"--",94:"DEFAULT",95:"MINUS",96:"keywords",97:"UNICODE_TEXT",98:"BQUOTE_STR"},productions_:[0,[3,1],[3,1],[4,1],[6,4],[5,1],[5,2],[5,3],[11,3],[15,1],[15,2],[17,1],[17,1],[17,2],[17,2],[17,2],[10,1],[10,2],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,2],[10,2],[10,1],[22,4],[22,5],[36,2],[38,1],[38,2],[38,3],[23,1],[23,3],[23,4],[23,6],[41,2],[41,3],[25,4],[43,1],[43,2],[24,1],[24,2],[24,1],[24,1],[20,3],[20,4],[20,4],[20,5],[29,3],[29,2],[30,1],[30,1],[30,1],[30,1],[49,3],[49,2],[49,2],[49,1],[57,1],[57,1],[57,1],[57,1],[57,1],[58,1],[58,1],[26,3],[26,4],[26,3],[26,4],[26,4],[26,5],[26,3],[26,4],[26,4],[26,5],[26,4],[26,5],[26,5],[26,6],[27,3],[28,3],[75,1],[75,3],[77,1],[77,2],[79,1],[79,1],[79,1],[79,1],[79,1],[79,1],[79,1],[79,1],[79,1],[86,1],[86,1],[87,1],[87,1],[87,1],[87,1],[87,1],[87,1],[87,1],[89,1],[89,1],[89,1],[89,1],[16,1],[16,1],[16,1],[16,1],[18,1],[51,1]],performAction:o(function(_e,me,W,fe,ge,re,he){var ne=re.length-1;switch(ge){case 8:this.$=re[ne-1];break;case 9:case 11:case 12:this.$=re[ne];break;case 10:case 13:this.$=re[ne-1]+re[ne];break;case 14:case 15:this.$=re[ne-1]+"~"+re[ne]+"~";break;case 16:fe.addRelation(re[ne]);break;case 17:re[ne-1].title=fe.cleanupLabel(re[ne]),fe.addRelation(re[ne-1]);break;case 27:this.$=re[ne].trim(),fe.setAccTitle(this.$);break;case 28:case 29:this.$=re[ne].trim(),fe.setAccDescription(this.$);break;case 30:fe.addClassesToNamespace(re[ne-3],re[ne-1]);break;case 31:fe.addClassesToNamespace(re[ne-4],re[ne-1]);break;case 32:this.$=re[ne],fe.addNamespace(re[ne]);break;case 33:this.$=[re[ne]];break;case 34:this.$=[re[ne-1]];break;case 35:re[ne].unshift(re[ne-2]),this.$=re[ne];break;case 37:fe.setCssClass(re[ne-2],re[ne]);break;case 38:fe.addMembers(re[ne-3],re[ne-1]);break;case 39:fe.setCssClass(re[ne-5],re[ne-3]),fe.addMembers(re[ne-5],re[ne-1]);break;case 40:this.$=re[ne],fe.addClass(re[ne]);break;case 41:this.$=re[ne-1],fe.addClass(re[ne-1]),fe.setClassLabel(re[ne-1],re[ne]);break;case 42:fe.addAnnotation(re[ne],re[ne-2]);break;case 43:this.$=[re[ne]];break;case 44:re[ne].push(re[ne-1]),this.$=re[ne];break;case 45:break;case 46:fe.addMember(re[ne-1],fe.cleanupLabel(re[ne]));break;case 47:break;case 48:break;case 49:this.$={id1:re[ne-2],id2:re[ne],relation:re[ne-1],relationTitle1:"none",relationTitle2:"none"};break;case 50:this.$={id1:re[ne-3],id2:re[ne],relation:re[ne-1],relationTitle1:re[ne-2],relationTitle2:"none"};break;case 51:this.$={id1:re[ne-3],id2:re[ne],relation:re[ne-2],relationTitle1:"none",relationTitle2:re[ne-1]};break;case 52:this.$={id1:re[ne-4],id2:re[ne],relation:re[ne-2],relationTitle1:re[ne-3],relationTitle2:re[ne-1]};break;case 53:fe.addNote(re[ne],re[ne-1]);break;case 54:fe.addNote(re[ne]);break;case 55:fe.setDirection("TB");break;case 56:fe.setDirection("BT");break;case 57:fe.setDirection("RL");break;case 58:fe.setDirection("LR");break;case 59:this.$={type1:re[ne-2],type2:re[ne],lineType:re[ne-1]};break;case 60:this.$={type1:"none",type2:re[ne],lineType:re[ne-1]};break;case 61:this.$={type1:re[ne-1],type2:"none",lineType:re[ne]};break;case 62:this.$={type1:"none",type2:"none",lineType:re[ne]};break;case 63:this.$=fe.relationType.AGGREGATION;break;case 64:this.$=fe.relationType.EXTENSION;break;case 65:this.$=fe.relationType.COMPOSITION;break;case 66:this.$=fe.relationType.DEPENDENCY;break;case 67:this.$=fe.relationType.LOLLIPOP;break;case 68:this.$=fe.lineType.LINE;break;case 69:this.$=fe.lineType.DOTTED_LINE;break;case 70:case 76:this.$=re[ne-2],fe.setClickEvent(re[ne-1],re[ne]);break;case 71:case 77:this.$=re[ne-3],fe.setClickEvent(re[ne-2],re[ne-1]),fe.setTooltip(re[ne-2],re[ne]);break;case 72:this.$=re[ne-2],fe.setLink(re[ne-1],re[ne]);break;case 73:this.$=re[ne-3],fe.setLink(re[ne-2],re[ne-1],re[ne]);break;case 74:this.$=re[ne-3],fe.setLink(re[ne-2],re[ne-1]),fe.setTooltip(re[ne-2],re[ne]);break;case 75:this.$=re[ne-4],fe.setLink(re[ne-3],re[ne-2],re[ne]),fe.setTooltip(re[ne-3],re[ne-1]);break;case 78:this.$=re[ne-3],fe.setClickEvent(re[ne-2],re[ne-1],re[ne]);break;case 79:this.$=re[ne-4],fe.setClickEvent(re[ne-3],re[ne-2],re[ne-1]),fe.setTooltip(re[ne-3],re[ne]);break;case 80:this.$=re[ne-3],fe.setLink(re[ne-2],re[ne]);break;case 81:this.$=re[ne-4],fe.setLink(re[ne-3],re[ne-1],re[ne]);break;case 82:this.$=re[ne-4],fe.setLink(re[ne-3],re[ne-1]),fe.setTooltip(re[ne-3],re[ne]);break;case 83:this.$=re[ne-5],fe.setLink(re[ne-4],re[ne-2],re[ne]),fe.setTooltip(re[ne-4],re[ne-1]);break;case 84:this.$=re[ne-2],fe.setCssStyle(re[ne-1],re[ne]);break;case 85:fe.setCssClass(re[ne-1],re[ne]);break;case 86:this.$=[re[ne]];break;case 87:re[ne-2].push(re[ne]),this.$=re[ne-2];break;case 89:this.$=re[ne-1]+re[ne];break}},"anonymous"),table:[{3:1,4:2,5:3,6:4,7:[1,6],10:5,16:37,17:20,18:38,20:7,22:8,23:9,24:10,25:11,26:12,27:13,28:14,29:15,30:16,31:e,33:r,35:n,36:21,40:i,41:22,44:a,45:s,47:l,48:u,50:h,52:f,53:d,54:p,55:m,56:g,66:y,67:v,69:x,73:b,74:w,76:S,80:T,95:E,97:_,98:A},{1:[3]},{1:[2,1]},{1:[2,2]},{1:[2,3]},t(L,[2,5],{8:[1,46]}),{8:[1,47]},t(M,[2,16],{21:[1,48]}),t(M,[2,18]),t(M,[2,19]),t(M,[2,20]),t(M,[2,21]),t(M,[2,22]),t(M,[2,23]),t(M,[2,24]),t(M,[2,25]),t(M,[2,26]),{32:[1,49]},{34:[1,50]},t(M,[2,29]),t(M,[2,45],{49:51,57:54,58:55,13:[1,52],21:[1,53],59:N,60:k,61:I,62:C,63:O,64:D,65:P}),{37:[1,63]},t(F,[2,36],{37:[1,65],42:[1,64]}),t(M,[2,47]),t(M,[2,48]),{16:66,74:w,80:T,95:E,97:_},{16:37,17:67,18:38,74:w,80:T,95:E,97:_,98:A},{16:37,17:68,18:38,74:w,80:T,95:E,97:_,98:A},{16:37,17:69,18:38,74:w,80:T,95:E,97:_,98:A},{74:[1,70]},{13:[1,71]},{16:37,17:72,18:38,74:w,80:T,95:E,97:_,98:A},{13:B,51:73},t(M,[2,55]),t(M,[2,56]),t(M,[2,57]),t(M,[2,58]),t($,[2,11],{16:37,18:38,17:75,19:[1,76],74:w,80:T,95:E,97:_,98:A}),t($,[2,12],{19:[1,77]}),{15:78,16:79,74:w,80:T,95:E,97:_},{16:37,17:80,18:38,74:w,80:T,95:E,97:_,98:A},t(z,[2,112]),t(z,[2,113]),t(z,[2,114]),t(z,[2,115]),t([1,8,9,12,13,19,21,37,39,42,59,60,61,62,63,64,65,70,72],[2,116]),t(L,[2,6],{10:5,20:7,22:8,23:9,24:10,25:11,26:12,27:13,28:14,29:15,30:16,17:20,36:21,41:22,16:37,18:38,5:81,31:e,33:r,35:n,40:i,44:a,45:s,47:l,48:u,50:h,52:f,53:d,54:p,55:m,56:g,66:y,67:v,69:x,73:b,74:w,76:S,80:T,95:E,97:_,98:A}),{5:82,10:5,16:37,17:20,18:38,20:7,22:8,23:9,24:10,25:11,26:12,27:13,28:14,29:15,30:16,31:e,33:r,35:n,36:21,40:i,41:22,44:a,45:s,47:l,48:u,50:h,52:f,53:d,54:p,55:m,56:g,66:y,67:v,69:x,73:b,74:w,76:S,80:T,95:E,97:_,98:A},t(M,[2,17]),t(M,[2,27]),t(M,[2,28]),{13:[1,84],16:37,17:83,18:38,74:w,80:T,95:E,97:_,98:A},{49:85,57:54,58:55,59:N,60:k,61:I,62:C,63:O,64:D,65:P},t(M,[2,46]),{58:86,64:D,65:P},t(Y,[2,62],{57:87,59:N,60:k,61:I,62:C,63:O}),t(Q,[2,63]),t(Q,[2,64]),t(Q,[2,65]),t(Q,[2,66]),t(Q,[2,67]),t(X,[2,68]),t(X,[2,69]),{8:[1,89],23:90,38:88,41:22,44:a},{16:91,74:w,80:T,95:E,97:_},{43:92,47:ie},{46:[1,94]},{13:[1,95]},{13:[1,96]},{70:[1,97],72:[1,98]},{21:j,73:J,74:Z,75:99,77:100,79:101,80:H,81:q,82:K,83:se,84:ce,85:ue},{74:[1,111]},{13:B,51:112},t(M,[2,54]),t(M,[2,117]),t($,[2,13]),t($,[2,14]),t($,[2,15]),{37:[2,32]},{15:113,16:79,37:[2,9],74:w,80:T,95:E,97:_},t(te,[2,40],{11:114,12:[1,115]}),t(L,[2,7]),{9:[1,116]},t(De,[2,49]),{16:37,17:117,18:38,74:w,80:T,95:E,97:_,98:A},{13:[1,119],16:37,17:118,18:38,74:w,80:T,95:E,97:_,98:A},t(Y,[2,61],{57:120,59:N,60:k,61:I,62:C,63:O}),t(Y,[2,60]),{39:[1,121]},{23:90,38:122,41:22,44:a},{8:[1,123],39:[2,33]},t(F,[2,37],{37:[1,124]}),{39:[1,125]},{39:[2,43],43:126,47:ie},{16:37,17:127,18:38,74:w,80:T,95:E,97:_,98:A},t(M,[2,70],{13:[1,128]}),t(M,[2,72],{13:[1,130],68:[1,129]}),t(M,[2,76],{13:[1,131],71:[1,132]}),{13:[1,133]},t(M,[2,84],{78:[1,134]}),t(oe,[2,86],{79:135,21:j,73:J,74:Z,80:H,81:q,82:K,83:se,84:ce,85:ue}),t(ke,[2,88]),t(ke,[2,90]),t(ke,[2,91]),t(ke,[2,92]),t(ke,[2,93]),t(ke,[2,94]),t(ke,[2,95]),t(ke,[2,96]),t(ke,[2,97]),t(ke,[2,98]),t(M,[2,85]),t(M,[2,53]),{37:[2,10]},t(te,[2,41]),{13:[1,136]},{1:[2,4]},t(De,[2,51]),t(De,[2,50]),{16:37,17:137,18:38,74:w,80:T,95:E,97:_,98:A},t(Y,[2,59]),t(M,[2,30]),{39:[1,138]},{23:90,38:139,39:[2,34],41:22,44:a},{43:140,47:ie},t(F,[2,38]),{39:[2,44]},t(M,[2,42]),t(M,[2,71]),t(M,[2,73]),t(M,[2,74],{68:[1,141]}),t(M,[2,77]),t(M,[2,78],{13:[1,142]}),t(M,[2,80],{13:[1,144],68:[1,143]}),{21:j,73:J,74:Z,77:145,79:101,80:H,81:q,82:K,83:se,84:ce,85:ue},t(ke,[2,89]),{14:[1,146]},t(De,[2,52]),t(M,[2,31]),{39:[2,35]},{39:[1,147]},t(M,[2,75]),t(M,[2,79]),t(M,[2,81]),t(M,[2,82],{68:[1,148]}),t(oe,[2,87],{79:135,21:j,73:J,74:Z,80:H,81:q,82:K,83:se,84:ce,85:ue}),t(te,[2,8]),t(F,[2,39]),t(M,[2,83])],defaultActions:{2:[2,1],3:[2,2],4:[2,3],78:[2,32],113:[2,10],116:[2,4],126:[2,44],139:[2,35]},parseError:o(function(_e,me){if(me.recoverable)this.trace(_e);else{var W=new Error(_e);throw W.hash=me,W}},"parseError"),parse:o(function(_e){var me=this,W=[0],fe=[],ge=[null],re=[],he=this.table,ne="",ae=0,we=0,Te=0,Ce=2,Ae=1,Ge=re.slice.call(arguments,1),Me=Object.create(this.lexer),ye={yy:{}};for(var He in this.yy)Object.prototype.hasOwnProperty.call(this.yy,He)&&(ye.yy[He]=this.yy[He]);Me.setInput(_e,ye.yy),ye.yy.lexer=Me,ye.yy.parser=this,typeof Me.yylloc>"u"&&(Me.yylloc={});var ze=Me.yylloc;re.push(ze);var Ze=Me.options&&Me.options.ranges;typeof ye.yy.parseError=="function"?this.parseError=ye.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function gt(mt){W.length=W.length-2*mt,ge.length=ge.length-mt,re.length=re.length-mt}o(gt,"popStack");function yt(){var mt;return mt=fe.pop()||Me.lex()||Ae,typeof mt!="number"&&(mt instanceof Array&&(fe=mt,mt=fe.pop()),mt=me.symbols_[mt]||mt),mt}o(yt,"lex");for(var tt,Ye,Je,Ve,je,kt,at={},xt,it,dt,lt;;){if(Je=W[W.length-1],this.defaultActions[Je]?Ve=this.defaultActions[Je]:((tt===null||typeof tt>"u")&&(tt=yt()),Ve=he[Je]&&he[Je][tt]),typeof Ve>"u"||!Ve.length||!Ve[0]){var It="";lt=[];for(xt in he[Je])this.terminals_[xt]&&xt>Ce&<.push("'"+this.terminals_[xt]+"'");Me.showPosition?It="Parse error on line "+(ae+1)+`: +`+Me.showPosition()+` +Expecting `+lt.join(", ")+", got '"+(this.terminals_[tt]||tt)+"'":It="Parse error on line "+(ae+1)+": Unexpected "+(tt==Ae?"end of input":"'"+(this.terminals_[tt]||tt)+"'"),this.parseError(It,{text:Me.match,token:this.terminals_[tt]||tt,line:Me.yylineno,loc:ze,expected:lt})}if(Ve[0]instanceof Array&&Ve.length>1)throw new Error("Parse Error: multiple actions possible at state: "+Je+", token: "+tt);switch(Ve[0]){case 1:W.push(tt),ge.push(Me.yytext),re.push(Me.yylloc),W.push(Ve[1]),tt=null,Ye?(tt=Ye,Ye=null):(we=Me.yyleng,ne=Me.yytext,ae=Me.yylineno,ze=Me.yylloc,Te>0&&Te--);break;case 2:if(it=this.productions_[Ve[1]][1],at.$=ge[ge.length-it],at._$={first_line:re[re.length-(it||1)].first_line,last_line:re[re.length-1].last_line,first_column:re[re.length-(it||1)].first_column,last_column:re[re.length-1].last_column},Ze&&(at._$.range=[re[re.length-(it||1)].range[0],re[re.length-1].range[1]]),kt=this.performAction.apply(at,[ne,we,ae,ye.yy,Ve[1],ge,re].concat(Ge)),typeof kt<"u")return kt;it&&(W=W.slice(0,-1*it*2),ge=ge.slice(0,-1*it),re=re.slice(0,-1*it)),W.push(this.productions_[Ve[1]][0]),ge.push(at.$),re.push(at._$),dt=he[W[W.length-2]][W[W.length-1]],W.push(dt);break;case 3:return!0}}return!0},"parse")},Se=function(){var Pe={EOF:1,parseError:o(function(me,W){if(this.yy.parser)this.yy.parser.parseError(me,W);else throw new Error(me)},"parseError"),setInput:o(function(_e,me){return this.yy=me||this.yy||{},this._input=_e,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:o(function(){var _e=this._input[0];this.yytext+=_e,this.yyleng++,this.offset++,this.match+=_e,this.matched+=_e;var me=_e.match(/(?:\r\n?|\n).*/g);return me?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),_e},"input"),unput:o(function(_e){var me=_e.length,W=_e.split(/(?:\r\n?|\n)/g);this._input=_e+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-me),this.offset-=me;var fe=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),W.length-1&&(this.yylineno-=W.length-1);var ge=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:W?(W.length===fe.length?this.yylloc.first_column:0)+fe[fe.length-W.length].length-W[0].length:this.yylloc.first_column-me},this.options.ranges&&(this.yylloc.range=[ge[0],ge[0]+this.yyleng-me]),this.yyleng=this.yytext.length,this},"unput"),more:o(function(){return this._more=!0,this},"more"),reject:o(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:o(function(_e){this.unput(this.match.slice(_e))},"less"),pastInput:o(function(){var _e=this.matched.substr(0,this.matched.length-this.match.length);return(_e.length>20?"...":"")+_e.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:o(function(){var _e=this.match;return _e.length<20&&(_e+=this._input.substr(0,20-_e.length)),(_e.substr(0,20)+(_e.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:o(function(){var _e=this.pastInput(),me=new Array(_e.length+1).join("-");return _e+this.upcomingInput()+` +`+me+"^"},"showPosition"),test_match:o(function(_e,me){var W,fe,ge;if(this.options.backtrack_lexer&&(ge={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(ge.yylloc.range=this.yylloc.range.slice(0))),fe=_e[0].match(/(?:\r\n?|\n).*/g),fe&&(this.yylineno+=fe.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:fe?fe[fe.length-1].length-fe[fe.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+_e[0].length},this.yytext+=_e[0],this.match+=_e[0],this.matches=_e,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(_e[0].length),this.matched+=_e[0],W=this.performAction.call(this,this.yy,this,me,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),W)return W;if(this._backtrack){for(var re in ge)this[re]=ge[re];return!1}return!1},"test_match"),next:o(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var _e,me,W,fe;this._more||(this.yytext="",this.match="");for(var ge=this._currentRules(),re=0;reme[0].length)){if(me=W,fe=re,this.options.backtrack_lexer){if(_e=this.test_match(W,ge[re]),_e!==!1)return _e;if(this._backtrack){me=!1;continue}else return!1}else if(!this.options.flex)break}return me?(_e=this.test_match(me,ge[fe]),_e!==!1?_e:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:o(function(){var me=this.next();return me||this.lex()},"lex"),begin:o(function(me){this.conditionStack.push(me)},"begin"),popState:o(function(){var me=this.conditionStack.length-1;return me>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:o(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:o(function(me){return me=this.conditionStack.length-1-Math.abs(me||0),me>=0?this.conditionStack[me]:"INITIAL"},"topState"),pushState:o(function(me){this.begin(me)},"pushState"),stateStackSize:o(function(){return this.conditionStack.length},"stateStackSize"),options:{},performAction:o(function(me,W,fe,ge){var re=ge;switch(fe){case 0:return 53;case 1:return 54;case 2:return 55;case 3:return 56;case 4:break;case 5:break;case 6:return this.begin("acc_title"),31;break;case 7:return this.popState(),"acc_title_value";break;case 8:return this.begin("acc_descr"),33;break;case 9:return this.popState(),"acc_descr_value";break;case 10:this.begin("acc_descr_multiline");break;case 11:this.popState();break;case 12:return"acc_descr_multiline_value";case 13:return 8;case 14:break;case 15:return 7;case 16:return 7;case 17:return"EDGE_STATE";case 18:this.begin("callback_name");break;case 19:this.popState();break;case 20:this.popState(),this.begin("callback_args");break;case 21:return 70;case 22:this.popState();break;case 23:return 71;case 24:this.popState();break;case 25:return"STR";case 26:this.begin("string");break;case 27:return 73;case 28:return this.begin("namespace"),40;break;case 29:return this.popState(),8;break;case 30:break;case 31:return this.begin("namespace-body"),37;break;case 32:return this.popState(),39;break;case 33:return"EOF_IN_STRUCT";case 34:return 8;case 35:break;case 36:return"EDGE_STATE";case 37:return this.begin("class"),44;break;case 38:return this.popState(),8;break;case 39:break;case 40:return this.popState(),this.popState(),39;break;case 41:return this.begin("class-body"),37;break;case 42:return this.popState(),39;break;case 43:return"EOF_IN_STRUCT";case 44:return"EDGE_STATE";case 45:return"OPEN_IN_STRUCT";case 46:break;case 47:return"MEMBER";case 48:return 76;case 49:return 66;case 50:return 67;case 51:return 69;case 52:return 50;case 53:return 52;case 54:return 45;case 55:return 46;case 56:return 72;case 57:this.popState();break;case 58:return"GENERICTYPE";case 59:this.begin("generic");break;case 60:this.popState();break;case 61:return"BQUOTE_STR";case 62:this.begin("bqstring");break;case 63:return 68;case 64:return 68;case 65:return 68;case 66:return 68;case 67:return 60;case 68:return 60;case 69:return 62;case 70:return 62;case 71:return 61;case 72:return 59;case 73:return 63;case 74:return 64;case 75:return 65;case 76:return 21;case 77:return 42;case 78:return 95;case 79:return"DOT";case 80:return"PLUS";case 81:return 81;case 82:return 78;case 83:return 84;case 84:return 84;case 85:return 85;case 86:return"EQUALS";case 87:return"EQUALS";case 88:return 74;case 89:return 12;case 90:return 14;case 91:return"PUNCTUATION";case 92:return 80;case 93:return 97;case 94:return 83;case 95:return 83;case 96:return 9}},"anonymous"),rules:[/^(?:.*direction\s+TB[^\n]*)/,/^(?:.*direction\s+BT[^\n]*)/,/^(?:.*direction\s+RL[^\n]*)/,/^(?:.*direction\s+LR[^\n]*)/,/^(?:%%(?!\{)*[^\n]*(\r?\n?)+)/,/^(?:%%[^\n]*(\r?\n)*)/,/^(?:accTitle\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*\{\s*)/,/^(?:[\}])/,/^(?:[^\}]*)/,/^(?:\s*(\r?\n)+)/,/^(?:\s+)/,/^(?:classDiagram-v2\b)/,/^(?:classDiagram\b)/,/^(?:\[\*\])/,/^(?:call[\s]+)/,/^(?:\([\s]*\))/,/^(?:\()/,/^(?:[^(]*)/,/^(?:\))/,/^(?:[^)]*)/,/^(?:["])/,/^(?:[^"]*)/,/^(?:["])/,/^(?:style\b)/,/^(?:namespace\b)/,/^(?:\s*(\r?\n)+)/,/^(?:\s+)/,/^(?:[{])/,/^(?:[}])/,/^(?:$)/,/^(?:\s*(\r?\n)+)/,/^(?:\s+)/,/^(?:\[\*\])/,/^(?:class\b)/,/^(?:\s*(\r?\n)+)/,/^(?:\s+)/,/^(?:[}])/,/^(?:[{])/,/^(?:[}])/,/^(?:$)/,/^(?:\[\*\])/,/^(?:[{])/,/^(?:[\n])/,/^(?:[^{}\n]*)/,/^(?:cssClass\b)/,/^(?:callback\b)/,/^(?:link\b)/,/^(?:click\b)/,/^(?:note for\b)/,/^(?:note\b)/,/^(?:<<)/,/^(?:>>)/,/^(?:href\b)/,/^(?:[~])/,/^(?:[^~]*)/,/^(?:~)/,/^(?:[`])/,/^(?:[^`]+)/,/^(?:[`])/,/^(?:_self\b)/,/^(?:_blank\b)/,/^(?:_parent\b)/,/^(?:_top\b)/,/^(?:\s*<\|)/,/^(?:\s*\|>)/,/^(?:\s*>)/,/^(?:\s*<)/,/^(?:\s*\*)/,/^(?:\s*o\b)/,/^(?:\s*\(\))/,/^(?:--)/,/^(?:\.\.)/,/^(?::{1}[^:\n;]+)/,/^(?::{3})/,/^(?:-)/,/^(?:\.)/,/^(?:\+)/,/^(?::)/,/^(?:,)/,/^(?:#)/,/^(?:#)/,/^(?:%)/,/^(?:=)/,/^(?:=)/,/^(?:\w+)/,/^(?:\[)/,/^(?:\])/,/^(?:[!"#$%&'*+,-.`?\\/])/,/^(?:[0-9]+)/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\s)/,/^(?:\s)/,/^(?:$)/],conditions:{"namespace-body":{rules:[26,32,33,34,35,36,37,48,49,50,51,52,53,54,55,56,59,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,85,86,87,88,89,90,91,92,93,94,96],inclusive:!1},namespace:{rules:[26,28,29,30,31,48,49,50,51,52,53,54,55,56,59,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,85,86,87,88,89,90,91,92,93,94,96],inclusive:!1},"class-body":{rules:[26,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,59,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,85,86,87,88,89,90,91,92,93,94,96],inclusive:!1},class:{rules:[26,38,39,40,41,48,49,50,51,52,53,54,55,56,59,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,85,86,87,88,89,90,91,92,93,94,96],inclusive:!1},acc_descr_multiline:{rules:[11,12,26,48,49,50,51,52,53,54,55,56,59,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,85,86,87,88,89,90,91,92,93,94,96],inclusive:!1},acc_descr:{rules:[9,26,48,49,50,51,52,53,54,55,56,59,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,85,86,87,88,89,90,91,92,93,94,96],inclusive:!1},acc_title:{rules:[7,26,48,49,50,51,52,53,54,55,56,59,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,85,86,87,88,89,90,91,92,93,94,96],inclusive:!1},callback_args:{rules:[22,23,26,48,49,50,51,52,53,54,55,56,59,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,85,86,87,88,89,90,91,92,93,94,96],inclusive:!1},callback_name:{rules:[19,20,21,26,48,49,50,51,52,53,54,55,56,59,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,85,86,87,88,89,90,91,92,93,94,96],inclusive:!1},href:{rules:[26,48,49,50,51,52,53,54,55,56,59,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,85,86,87,88,89,90,91,92,93,94,96],inclusive:!1},struct:{rules:[26,48,49,50,51,52,53,54,55,56,59,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,85,86,87,88,89,90,91,92,93,94,96],inclusive:!1},generic:{rules:[26,48,49,50,51,52,53,54,55,56,57,58,59,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,85,86,87,88,89,90,91,92,93,94,96],inclusive:!1},bqstring:{rules:[26,48,49,50,51,52,53,54,55,56,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,85,86,87,88,89,90,91,92,93,94,96],inclusive:!1},string:{rules:[24,25,26,48,49,50,51,52,53,54,55,56,59,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,85,86,87,88,89,90,91,92,93,94,96],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,8,10,13,14,15,16,17,18,26,27,28,37,48,49,50,51,52,53,54,55,56,59,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96],inclusive:!0}}};return Pe}();Ie.lexer=Se;function Ue(){this.yy={}}return o(Ue,"Parser"),Ue.prototype=Ie,Ie.Parser=Ue,new Ue}();wO.parser=wO;wE=wO});var bhe,wx,whe=R(()=>{"use strict";_t();rr();bhe=["#","+","~","-",""],wx=class{static{o(this,"ClassMember")}constructor(e,r){this.memberType=r,this.visibility="",this.classifier="";let n=qr(e,de());this.parseMember(n)}getDisplayDetails(){let e=this.visibility+gh(this.id);this.memberType==="method"&&(e+=`(${gh(this.parameters.trim())})`,this.returnType&&(e+=" : "+gh(this.returnType))),e=e.trim();let r=this.parseClassifier();return{displayText:e,cssStyle:r}}parseMember(e){let r="";if(this.memberType==="method"){let i=/([#+~-])?(.+)\((.*)\)([\s$*])?(.*)([$*])?/.exec(e);if(i){let a=i[1]?i[1].trim():"";if(bhe.includes(a)&&(this.visibility=a),this.id=i[2].trim(),this.parameters=i[3]?i[3].trim():"",r=i[4]?i[4].trim():"",this.returnType=i[5]?i[5].trim():"",r===""){let s=this.returnType.substring(this.returnType.length-1);/[$*]/.exec(s)&&(r=s,this.returnType=this.returnType.substring(0,this.returnType.length-1))}}}else{let n=e.length,i=e.substring(0,1),a=e.substring(n-1);bhe.includes(i)&&(this.visibility=i),/[$*]/.exec(a)&&(r=a),this.id=e.substring(this.visibility===""?0:1,r===""?n:n-1)}this.classifier=r}parseClassifier(){switch(this.classifier){case"*":return"font-style:italic;";case"$":return"text-decoration:underline;";default:return""}}}});var EE,EO,Gi,TE,The,qu,kO,Tx,D0,R0,u$e,kE,khe,h$e,f$e,d$e,p$e,m$e,g$e,y$e,Ehe,v$e,x$e,b$e,CO,w$e,T$e,k$e,E$e,C$e,S$e,A$e,_$e,Che,SO,L$e,D$e,R$e,N$e,M$e,I$e,O$e,Bg,AO=R(()=>{"use strict";Zt();ut();_t();rr();xr();bi();whe();EE="classId-",EO=[],Gi=new Map,TE=[],The=0,qu=new Map,kO=0,Tx=[],D0=o(t=>We.sanitizeText(t,de()),"sanitizeText"),R0=o(function(t){let e=We.sanitizeText(t,de()),r="",n=e;if(e.indexOf("~")>0){let i=e.split("~");n=D0(i[0]),r=D0(i[1])}return{className:n,type:r}},"splitClassNameAndType"),u$e=o(function(t,e){let r=We.sanitizeText(t,de());e&&(e=D0(e));let{className:n}=R0(r);Gi.get(n).label=e},"setClassLabel"),kE=o(function(t){let e=We.sanitizeText(t,de()),{className:r,type:n}=R0(e);if(Gi.has(r))return;let i=We.sanitizeText(r,de());Gi.set(i,{id:i,type:n,label:i,cssClasses:[],methods:[],members:[],annotations:[],styles:[],domId:EE+i+"-"+The}),The++},"addClass"),khe=o(function(t){let e=We.sanitizeText(t,de());if(Gi.has(e))return Gi.get(e).domId;throw new Error("Class not found: "+e)},"lookUpDomId"),h$e=o(function(){EO=[],Gi=new Map,TE=[],Tx=[],Tx.push(Che),qu=new Map,kO=0,SO="TB",vr()},"clear"),f$e=o(function(t){return Gi.get(t)},"getClass"),d$e=o(function(){return Gi},"getClasses"),p$e=o(function(){return EO},"getRelations"),m$e=o(function(){return TE},"getNotes"),g$e=o(function(t){V.debug("Adding relation: "+JSON.stringify(t)),kE(t.id1),kE(t.id2),t.id1=R0(t.id1).className,t.id2=R0(t.id2).className,t.relationTitle1=We.sanitizeText(t.relationTitle1.trim(),de()),t.relationTitle2=We.sanitizeText(t.relationTitle2.trim(),de()),EO.push(t)},"addRelation"),y$e=o(function(t,e){let r=R0(t).className;Gi.get(r).annotations.push(e)},"addAnnotation"),Ehe=o(function(t,e){kE(t);let r=R0(t).className,n=Gi.get(r);if(typeof e=="string"){let i=e.trim();i.startsWith("<<")&&i.endsWith(">>")?n.annotations.push(D0(i.substring(2,i.length-2))):i.indexOf(")")>0?n.methods.push(new wx(i,"method")):i&&n.members.push(new wx(i,"attribute"))}},"addMember"),v$e=o(function(t,e){Array.isArray(e)&&(e.reverse(),e.forEach(r=>Ehe(t,r)))},"addMembers"),x$e=o(function(t,e){let r={id:`note${TE.length}`,class:e,text:t};TE.push(r)},"addNote"),b$e=o(function(t){return t.startsWith(":")&&(t=t.substring(1)),D0(t.trim())},"cleanupLabel"),CO=o(function(t,e){t.split(",").forEach(function(r){let n=r;/\d/.exec(r[0])&&(n=EE+n);let i=Gi.get(n);i&&i.cssClasses.push(e)})},"setCssClass"),w$e=o(function(t,e){t.split(",").forEach(function(r){e!==void 0&&(Gi.get(r).tooltip=D0(e))})},"setTooltip"),T$e=o(function(t,e){return e&&qu.has(e)?qu.get(e).classes.get(t).tooltip:Gi.get(t).tooltip},"getTooltip"),k$e=o(function(t,e,r){let n=de();t.split(",").forEach(function(i){let a=i;/\d/.exec(i[0])&&(a=EE+a);let s=Gi.get(a);s&&(s.link=Lt.formatUrl(e,n),n.securityLevel==="sandbox"?s.linkTarget="_top":typeof r=="string"?s.linkTarget=D0(r):s.linkTarget="_blank")}),CO(t,"clickable")},"setLink"),E$e=o(function(t,e,r){t.split(",").forEach(function(n){C$e(n,e,r),Gi.get(n).haveCallback=!0}),CO(t,"clickable")},"setClickEvent"),C$e=o(function(t,e,r){let n=We.sanitizeText(t,de());if(de().securityLevel!=="loose"||e===void 0)return;let a=n;if(Gi.has(a)){let s=khe(a),l=[];if(typeof r=="string"){l=r.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);for(let u=0;u")),i.classed("hover",!0)}).on("mouseout",function(){e.transition().duration(500).style("opacity",0),$e(this).classed("hover",!1)})},"setupToolTips");Tx.push(Che);SO="TB",L$e=o(()=>SO,"getDirection"),D$e=o(t=>{SO=t},"setDirection"),R$e=o(function(t){qu.has(t)||(qu.set(t,{id:t,classes:new Map,children:{},domId:EE+t+"-"+kO}),kO++)},"addNamespace"),N$e=o(function(t){return qu.get(t)},"getNamespace"),M$e=o(function(){return qu},"getNamespaces"),I$e=o(function(t,e){if(qu.has(t))for(let r of e){let{className:n}=R0(r);Gi.get(n).parent=t,qu.get(t).classes.set(n,Gi.get(n))}},"addClassesToNamespace"),O$e=o(function(t,e){let r=Gi.get(t);if(!(!e||!r))for(let n of e)n.includes(",")?r.styles.push(...n.split(",")):r.styles.push(n)},"setCssStyle"),Bg={setAccTitle:kr,getAccTitle:Ar,getAccDescription:Lr,setAccDescription:_r,getConfig:o(()=>de().class,"getConfig"),addClass:kE,bindFunctions:S$e,clear:h$e,getClass:f$e,getClasses:d$e,getNotes:m$e,addAnnotation:y$e,addNote:x$e,getRelations:p$e,addRelation:g$e,getDirection:L$e,setDirection:D$e,addMember:Ehe,addMembers:v$e,cleanupLabel:b$e,lineType:A$e,relationType:_$e,setClickEvent:E$e,setCssClass:CO,setLink:k$e,getTooltip:T$e,setTooltip:w$e,lookUpDomId:khe,setDiagramTitle:nn,getDiagramTitle:Xr,setClassLabel:u$e,addNamespace:R$e,addClassesToNamespace:I$e,getNamespace:N$e,getNamespaces:M$e,setCssStyle:O$e}});var P$e,CE,_O=R(()=>{"use strict";P$e=o(t=>`g.classGroup text { + fill: ${t.nodeBorder||t.classText}; + stroke: none; + font-family: ${t.fontFamily}; + font-size: 10px; + + .title { + font-weight: bolder; + } + +} + +.nodeLabel, .edgeLabel { + color: ${t.classText}; +} +.edgeLabel .label rect { + fill: ${t.mainBkg}; +} +.label text { + fill: ${t.classText}; +} +.edgeLabel .label span { + background: ${t.mainBkg}; +} + +.classTitle { + font-weight: bolder; +} +.node rect, + .node circle, + .node ellipse, + .node polygon, + .node path { + fill: ${t.mainBkg}; + stroke: ${t.nodeBorder}; + stroke-width: 1px; + } + + +.divider { + stroke: ${t.nodeBorder}; + stroke-width: 1; +} + +g.clickable { + cursor: pointer; +} + +g.classGroup rect { + fill: ${t.mainBkg}; + stroke: ${t.nodeBorder}; +} + +g.classGroup line { + stroke: ${t.nodeBorder}; + stroke-width: 1; +} + +.classLabel .box { + stroke: none; + stroke-width: 0; + fill: ${t.mainBkg}; + opacity: 0.5; +} + +.classLabel .label { + fill: ${t.nodeBorder}; + font-size: 10px; +} + +.relation { + stroke: ${t.lineColor}; + stroke-width: 1; + fill: none; +} + +.dashed-line{ + stroke-dasharray: 3; +} + +.dotted-line{ + stroke-dasharray: 1 2; +} + +#compositionStart, .composition { + fill: ${t.lineColor} !important; + stroke: ${t.lineColor} !important; + stroke-width: 1; +} + +#compositionEnd, .composition { + fill: ${t.lineColor} !important; + stroke: ${t.lineColor} !important; + stroke-width: 1; +} + +#dependencyStart, .dependency { + fill: ${t.lineColor} !important; + stroke: ${t.lineColor} !important; + stroke-width: 1; +} + +#dependencyStart, .dependency { + fill: ${t.lineColor} !important; + stroke: ${t.lineColor} !important; + stroke-width: 1; +} + +#extensionStart, .extension { + fill: transparent !important; + stroke: ${t.lineColor} !important; + stroke-width: 1; +} + +#extensionEnd, .extension { + fill: transparent !important; + stroke: ${t.lineColor} !important; + stroke-width: 1; +} + +#aggregationStart, .aggregation { + fill: transparent !important; + stroke: ${t.lineColor} !important; + stroke-width: 1; +} + +#aggregationEnd, .aggregation { + fill: transparent !important; + stroke: ${t.lineColor} !important; + stroke-width: 1; +} + +#lollipopStart, .lollipop { + fill: ${t.mainBkg} !important; + stroke: ${t.lineColor} !important; + stroke-width: 1; +} + +#lollipopEnd, .lollipop { + fill: ${t.mainBkg} !important; + stroke: ${t.lineColor} !important; + stroke-width: 1; +} + +.edgeTerminals { + font-size: 11px; + line-height: initial; +} + +.classTitleText { + text-anchor: middle; + font-size: 18px; + fill: ${t.textColor}; +} +`,"getStyles"),CE=P$e});var She,B$e,F$e,_he,z$e,Ahe,SE,Lhe=R(()=>{"use strict";Zt();xr();ut();rr();She=0,B$e=o(function(t,e,r,n,i){let a=o(function(b){switch(b){case i.db.relationType.AGGREGATION:return"aggregation";case i.db.relationType.EXTENSION:return"extension";case i.db.relationType.COMPOSITION:return"composition";case i.db.relationType.DEPENDENCY:return"dependency";case i.db.relationType.LOLLIPOP:return"lollipop"}},"getRelationType");e.points=e.points.filter(b=>!Number.isNaN(b.y));let s=e.points,l=ha().x(function(b){return b.x}).y(function(b){return b.y}).curve(vs),u=t.append("path").attr("d",l(s)).attr("id","edge"+She).attr("class","relation"),h="";n.arrowMarkerAbsolute&&(h=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search,h=h.replace(/\(/g,"\\("),h=h.replace(/\)/g,"\\)")),r.relation.lineType==1&&u.attr("class","relation dashed-line"),r.relation.lineType==10&&u.attr("class","relation dotted-line"),r.relation.type1!=="none"&&u.attr("marker-start","url("+h+"#"+a(r.relation.type1)+"Start)"),r.relation.type2!=="none"&&u.attr("marker-end","url("+h+"#"+a(r.relation.type2)+"End)");let f,d,p=e.points.length,m=Lt.calcLabelPosition(e.points);f=m.x,d=m.y;let g,y,v,x;if(p%2!==0&&p>1){let b=Lt.calcCardinalityPosition(r.relation.type1!=="none",e.points,e.points[0]),w=Lt.calcCardinalityPosition(r.relation.type2!=="none",e.points,e.points[p-1]);V.debug("cardinality_1_point "+JSON.stringify(b)),V.debug("cardinality_2_point "+JSON.stringify(w)),g=b.x,y=b.y,v=w.x,x=w.y}if(r.title!==void 0){let b=t.append("g").attr("class","classLabel"),w=b.append("text").attr("class","label").attr("x",f).attr("y",d).attr("fill","red").attr("text-anchor","middle").text(r.title);window.label=w;let S=w.node().getBBox();b.insert("rect",":first-child").attr("class","box").attr("x",S.x-n.padding/2).attr("y",S.y-n.padding/2).attr("width",S.width+n.padding).attr("height",S.height+n.padding)}V.info("Rendering relation "+JSON.stringify(r)),r.relationTitle1!==void 0&&r.relationTitle1!=="none"&&t.append("g").attr("class","cardinality").append("text").attr("class","type1").attr("x",g).attr("y",y).attr("fill","black").attr("font-size","6").text(r.relationTitle1),r.relationTitle2!==void 0&&r.relationTitle2!=="none"&&t.append("g").attr("class","cardinality").append("text").attr("class","type2").attr("x",v).attr("y",x).attr("fill","black").attr("font-size","6").text(r.relationTitle2),She++},"drawEdge"),F$e=o(function(t,e,r,n){V.debug("Rendering class ",e,r);let i=e.id,a={id:i,label:e.id,width:0,height:0},s=t.append("g").attr("id",n.db.lookUpDomId(i)).attr("class","classGroup"),l;e.link?l=s.append("svg:a").attr("xlink:href",e.link).attr("target",e.linkTarget).append("text").attr("y",r.textHeight+r.padding).attr("x",0):l=s.append("text").attr("y",r.textHeight+r.padding).attr("x",0);let u=!0;e.annotations.forEach(function(w){let S=l.append("tspan").text("\xAB"+w+"\xBB");u||S.attr("dy",r.textHeight),u=!1});let h=_he(e),f=l.append("tspan").text(h).attr("class","title");u||f.attr("dy",r.textHeight);let d=l.node().getBBox().height,p,m,g;if(e.members.length>0){p=s.append("line").attr("x1",0).attr("y1",r.padding+d+r.dividerMargin/2).attr("y2",r.padding+d+r.dividerMargin/2);let w=s.append("text").attr("x",r.padding).attr("y",d+r.dividerMargin+r.textHeight).attr("fill","white").attr("class","classText");u=!0,e.members.forEach(function(S){Ahe(w,S,u,r),u=!1}),m=w.node().getBBox()}if(e.methods.length>0){g=s.append("line").attr("x1",0).attr("y1",r.padding+d+r.dividerMargin+m.height).attr("y2",r.padding+d+r.dividerMargin+m.height);let w=s.append("text").attr("x",r.padding).attr("y",d+2*r.dividerMargin+m.height+r.textHeight).attr("fill","white").attr("class","classText");u=!0,e.methods.forEach(function(S){Ahe(w,S,u,r),u=!1})}let y=s.node().getBBox();var v=" ";e.cssClasses.length>0&&(v=v+e.cssClasses.join(" "));let b=s.insert("rect",":first-child").attr("x",0).attr("y",0).attr("width",y.width+2*r.padding).attr("height",y.height+r.padding+.5*r.dividerMargin).attr("class",v).node().getBBox().width;return l.node().childNodes.forEach(function(w){w.setAttribute("x",(b-w.getBBox().width)/2)}),e.tooltip&&l.insert("title").text(e.tooltip),p&&p.attr("x2",b),g&&g.attr("x2",b),a.width=b,a.height=y.height+r.padding+.5*r.dividerMargin,a},"drawClass"),_he=o(function(t){let e=t.id;return t.type&&(e+="<"+gh(t.type)+">"),e},"getClassTitleString"),z$e=o(function(t,e,r,n){V.debug("Rendering note ",e,r);let i=e.id,a={id:i,text:e.text,width:0,height:0},s=t.append("g").attr("id",i).attr("class","classGroup"),l=s.append("text").attr("y",r.textHeight+r.padding).attr("x",0),u=JSON.parse(`"${e.text}"`).split(` +`);u.forEach(function(p){V.debug(`Adding line: ${p}`),l.append("tspan").text(p).attr("class","title").attr("dy",r.textHeight)});let h=s.node().getBBox(),d=s.insert("rect",":first-child").attr("x",0).attr("y",0).attr("width",h.width+2*r.padding).attr("height",h.height+u.length*r.textHeight+r.padding+.5*r.dividerMargin).node().getBBox().width;return l.node().childNodes.forEach(function(p){p.setAttribute("x",(d-p.getBBox().width)/2)}),a.width=d,a.height=h.height+u.length*r.textHeight+r.padding+.5*r.dividerMargin,a},"drawNote"),Ahe=o(function(t,e,r,n){let{displayText:i,cssStyle:a}=e.getDisplayDetails(),s=t.append("tspan").attr("x",n.padding).text(i);a!==""&&s.attr("style",e.cssStyle),r||s.attr("dy",n.textHeight)},"addTspan"),SE={getClassTitleString:_he,drawClass:F$e,drawEdge:B$e,drawNote:z$e}});var _E,AE,kx,G$e,$$e,Dhe,Rhe=R(()=>{"use strict";Zt();Vd();ya();ut();Lhe();Yn();_t();_E={},AE=20,kx=o(function(t){let e=Object.entries(_E).find(r=>r[1].label===t);if(e)return e[0]},"getGraphId"),G$e=o(function(t){t.append("defs").append("marker").attr("id","extensionStart").attr("class","extension").attr("refX",0).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 1,7 L18,13 V 1 Z"),t.append("defs").append("marker").attr("id","extensionEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 1,1 V 13 L18,7 Z"),t.append("defs").append("marker").attr("id","compositionStart").attr("class","extension").attr("refX",0).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),t.append("defs").append("marker").attr("id","compositionEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),t.append("defs").append("marker").attr("id","aggregationStart").attr("class","extension").attr("refX",0).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),t.append("defs").append("marker").attr("id","aggregationEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),t.append("defs").append("marker").attr("id","dependencyStart").attr("class","extension").attr("refX",0).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 5,7 L9,13 L1,7 L9,1 Z"),t.append("defs").append("marker").attr("id","dependencyEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L14,7 L9,1 Z")},"insertMarkers"),$$e=o(function(t,e,r,n){let i=de().class;_E={},V.info("Rendering diagram "+t);let a=de().securityLevel,s;a==="sandbox"&&(s=$e("#i"+e));let l=a==="sandbox"?$e(s.nodes()[0].contentDocument.body):$e("body"),u=l.select(`[id='${e}']`);G$e(u);let h=new lr({multigraph:!0});h.setGraph({isMultiGraph:!0}),h.setDefaultEdgeLabel(function(){return{}});let f=n.db.getClasses(),d=[...f.keys()];for(let b of d){let w=f.get(b),S=SE.drawClass(u,w,i,n);_E[S.id]=S,h.setNode(S.id,S),V.info("Org height: "+S.height)}n.db.getRelations().forEach(function(b){V.info("tjoho"+kx(b.id1)+kx(b.id2)+JSON.stringify(b)),h.setEdge(kx(b.id1),kx(b.id2),{relation:b},b.title||"DEFAULT")}),n.db.getNotes().forEach(function(b){V.debug(`Adding note: ${JSON.stringify(b)}`);let w=SE.drawNote(u,b,i,n);_E[w.id]=w,h.setNode(w.id,w),b.class&&f.has(b.class)&&h.setEdge(b.id,kx(b.class),{relation:{id1:b.id,id2:b.class,relation:{type1:"none",type2:"none",lineType:10}}},"DEFAULT")}),lo(h),h.nodes().forEach(function(b){b!==void 0&&h.node(b)!==void 0&&(V.debug("Node "+b+": "+JSON.stringify(h.node(b))),l.select("#"+(n.db.lookUpDomId(b)||b)).attr("transform","translate("+(h.node(b).x-h.node(b).width/2)+","+(h.node(b).y-h.node(b).height/2)+" )"))}),h.edges().forEach(function(b){b!==void 0&&h.edge(b)!==void 0&&(V.debug("Edge "+b.v+" -> "+b.w+": "+JSON.stringify(h.edge(b))),SE.drawEdge(u,h.edge(b),h.edge(b).relation,i,n))});let g=u.node().getBBox(),y=g.width+AE*2,v=g.height+AE*2;Sr(u,v,y,i.useMaxWidth);let x=`${g.x-AE} ${g.y-AE} ${y} ${v}`;V.debug(`viewBox ${x}`),u.attr("viewBox",x)},"draw"),Dhe={draw:$$e}});var Nhe={};hr(Nhe,{diagram:()=>V$e});var V$e,Mhe=R(()=>{"use strict";TO();AO();_O();Rhe();V$e={parser:wE,db:Bg,renderer:Dhe,styles:CE,init:o(t=>{t.class||(t.class={}),t.class.arrowMarkerAbsolute=t.arrowMarkerAbsolute,Bg.clear()},"init")}});var W$e,q$e,X$e,j$e,K$e,Q$e,Z$e,J$e,eVe,tVe,rVe,LE,LO=R(()=>{"use strict";ut();W$e=o((t,e,r,n)=>{e.forEach(i=>{rVe[i](t,r,n)})},"insertMarkers"),q$e=o((t,e,r)=>{V.trace("Making markers for ",r),t.append("defs").append("marker").attr("id",r+"_"+e+"-extensionStart").attr("class","marker extension "+e).attr("refX",18).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 1,7 L18,13 V 1 Z"),t.append("defs").append("marker").attr("id",r+"_"+e+"-extensionEnd").attr("class","marker extension "+e).attr("refX",1).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 1,1 V 13 L18,7 Z")},"extension"),X$e=o((t,e,r)=>{t.append("defs").append("marker").attr("id",r+"_"+e+"-compositionStart").attr("class","marker composition "+e).attr("refX",18).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),t.append("defs").append("marker").attr("id",r+"_"+e+"-compositionEnd").attr("class","marker composition "+e).attr("refX",1).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z")},"composition"),j$e=o((t,e,r)=>{t.append("defs").append("marker").attr("id",r+"_"+e+"-aggregationStart").attr("class","marker aggregation "+e).attr("refX",18).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z"),t.append("defs").append("marker").attr("id",r+"_"+e+"-aggregationEnd").attr("class","marker aggregation "+e).attr("refX",1).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L1,7 L9,1 Z")},"aggregation"),K$e=o((t,e,r)=>{t.append("defs").append("marker").attr("id",r+"_"+e+"-dependencyStart").attr("class","marker dependency "+e).attr("refX",6).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("path").attr("d","M 5,7 L9,13 L1,7 L9,1 Z"),t.append("defs").append("marker").attr("id",r+"_"+e+"-dependencyEnd").attr("class","marker dependency "+e).attr("refX",13).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 18,7 L9,13 L14,7 L9,1 Z")},"dependency"),Q$e=o((t,e,r)=>{t.append("defs").append("marker").attr("id",r+"_"+e+"-lollipopStart").attr("class","marker lollipop "+e).attr("refX",13).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("circle").attr("stroke","black").attr("fill","transparent").attr("cx",7).attr("cy",7).attr("r",6),t.append("defs").append("marker").attr("id",r+"_"+e+"-lollipopEnd").attr("class","marker lollipop "+e).attr("refX",1).attr("refY",7).attr("markerWidth",190).attr("markerHeight",240).attr("orient","auto").append("circle").attr("stroke","black").attr("fill","transparent").attr("cx",7).attr("cy",7).attr("r",6)},"lollipop"),Z$e=o((t,e,r)=>{t.append("marker").attr("id",r+"_"+e+"-pointEnd").attr("class","marker "+e).attr("viewBox","0 0 10 10").attr("refX",6).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",12).attr("markerHeight",12).attr("orient","auto").append("path").attr("d","M 0 0 L 10 5 L 0 10 z").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0"),t.append("marker").attr("id",r+"_"+e+"-pointStart").attr("class","marker "+e).attr("viewBox","0 0 10 10").attr("refX",4.5).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",12).attr("markerHeight",12).attr("orient","auto").append("path").attr("d","M 0 5 L 10 10 L 10 0 z").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0")},"point"),J$e=o((t,e,r)=>{t.append("marker").attr("id",r+"_"+e+"-circleEnd").attr("class","marker "+e).attr("viewBox","0 0 10 10").attr("refX",11).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("circle").attr("cx","5").attr("cy","5").attr("r","5").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0"),t.append("marker").attr("id",r+"_"+e+"-circleStart").attr("class","marker "+e).attr("viewBox","0 0 10 10").attr("refX",-1).attr("refY",5).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("circle").attr("cx","5").attr("cy","5").attr("r","5").attr("class","arrowMarkerPath").style("stroke-width",1).style("stroke-dasharray","1,0")},"circle"),eVe=o((t,e,r)=>{t.append("marker").attr("id",r+"_"+e+"-crossEnd").attr("class","marker cross "+e).attr("viewBox","0 0 11 11").attr("refX",12).attr("refY",5.2).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("path").attr("d","M 1,1 l 9,9 M 10,1 l -9,9").attr("class","arrowMarkerPath").style("stroke-width",2).style("stroke-dasharray","1,0"),t.append("marker").attr("id",r+"_"+e+"-crossStart").attr("class","marker cross "+e).attr("viewBox","0 0 11 11").attr("refX",-1).attr("refY",5.2).attr("markerUnits","userSpaceOnUse").attr("markerWidth",11).attr("markerHeight",11).attr("orient","auto").append("path").attr("d","M 1,1 l 9,9 M 10,1 l -9,9").attr("class","arrowMarkerPath").style("stroke-width",2).style("stroke-dasharray","1,0")},"cross"),tVe=o((t,e,r)=>{t.append("defs").append("marker").attr("id",r+"_"+e+"-barbEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",14).attr("markerUnits","strokeWidth").attr("orient","auto").append("path").attr("d","M 19,7 L9,13 L14,7 L9,1 Z")},"barb"),rVe={extension:q$e,composition:X$e,aggregation:j$e,dependency:K$e,lollipop:Q$e,point:Z$e,circle:J$e,cross:eVe,barb:tVe},LE=W$e});var tr,zl,Phe,Bhe,RE,nVe,Fhe,zhe,Fg,DE,Ghe,$he,Vhe,Uhe,Hhe=R(()=>{"use strict";ut();Pv();ya();tr={},zl={},Phe={},Bhe=o(()=>{zl={},Phe={},tr={}},"clear"),RE=o((t,e)=>(V.trace("In isDescendant",e," ",t," = ",zl[e].includes(t)),!!zl[e].includes(t)),"isDescendant"),nVe=o((t,e)=>(V.info("Descendants of ",e," is ",zl[e]),V.info("Edge is ",t),t.v===e||t.w===e?!1:zl[e]?zl[e].includes(t.v)||RE(t.v,e)||RE(t.w,e)||zl[e].includes(t.w):(V.debug("Tilt, ",e,",not in descendants"),!1)),"edgeInCluster"),Fhe=o((t,e,r,n)=>{V.warn("Copying children of ",t,"root",n,"data",e.node(t),n);let i=e.children(t)||[];t!==n&&i.push(t),V.warn("Copying (nodes) clusterId",t,"nodes",i),i.forEach(a=>{if(e.children(a).length>0)Fhe(a,e,r,n);else{let s=e.node(a);V.info("cp ",a," to ",n," with parent ",t),r.setNode(a,s),n!==e.parent(a)&&(V.warn("Setting parent",a,e.parent(a)),r.setParent(a,e.parent(a))),t!==n&&a!==t?(V.debug("Setting parent",a,t),r.setParent(a,t)):(V.info("In copy ",t,"root",n,"data",e.node(t),n),V.debug("Not Setting parent for node=",a,"cluster!==rootId",t!==n,"node!==clusterId",a!==t));let l=e.edges(a);V.debug("Copying Edges",l),l.forEach(u=>{V.info("Edge",u);let h=e.edge(u.v,u.w,u.name);V.info("Edge data",h,n);try{nVe(u,n)?(V.info("Copying as ",u.v,u.w,h,u.name),r.setEdge(u.v,u.w,h,u.name),V.info("newGraph edges ",r.edges(),r.edge(r.edges()[0]))):V.info("Skipping copy of edge ",u.v,"-->",u.w," rootId: ",n," clusterId:",t)}catch(f){V.error(f)}})}V.debug("Removing node",a),e.removeNode(a)})},"copy"),zhe=o((t,e)=>{let r=e.children(t),n=[...r];for(let i of r)Phe[i]=t,n=[...n,...zhe(i,e)];return n},"extractDescendants"),Fg=o((t,e)=>{V.trace("Searching",t);let r=e.children(t);if(V.trace("Searching children of id ",t,r),r.length<1)return V.trace("This is a valid node",t),t;for(let n of r){let i=Fg(n,e);if(i)return V.trace("Found replacement for",t," => ",i),i}},"findNonClusterChild"),DE=o(t=>!tr[t]||!tr[t].externalConnections?t:tr[t]?tr[t].id:t,"getAnchorId"),Ghe=o((t,e)=>{if(!t||e>10){V.debug("Opting out, no graph ");return}else V.debug("Opting in, graph ");t.nodes().forEach(function(r){t.children(r).length>0&&(V.warn("Cluster identified",r," Replacement id in edges: ",Fg(r,t)),zl[r]=zhe(r,t),tr[r]={id:Fg(r,t),clusterData:t.node(r)})}),t.nodes().forEach(function(r){let n=t.children(r),i=t.edges();n.length>0?(V.debug("Cluster identified",r,zl),i.forEach(a=>{if(a.v!==r&&a.w!==r){let s=RE(a.v,r),l=RE(a.w,r);s^l&&(V.warn("Edge: ",a," leaves cluster ",r),V.warn("Descendants of XXX ",r,": ",zl[r]),tr[r].externalConnections=!0)}})):V.debug("Not a cluster ",r,zl)});for(let r of Object.keys(tr)){let n=tr[r].id,i=t.parent(n);i!==r&&tr[i]&&!tr[i].externalConnections&&(tr[r].id=i)}t.edges().forEach(function(r){let n=t.edge(r);V.warn("Edge "+r.v+" -> "+r.w+": "+JSON.stringify(r)),V.warn("Edge "+r.v+" -> "+r.w+": "+JSON.stringify(t.edge(r)));let i=r.v,a=r.w;if(V.warn("Fix XXX",tr,"ids:",r.v,r.w,"Translating: ",tr[r.v]," --- ",tr[r.w]),tr[r.v]&&tr[r.w]&&tr[r.v]===tr[r.w]){V.warn("Fixing and trixing link to self - removing XXX",r.v,r.w,r.name),V.warn("Fixing and trixing - removing XXX",r.v,r.w,r.name),i=DE(r.v),a=DE(r.w),t.removeEdge(r.v,r.w,r.name);let s=r.w+"---"+r.v;t.setNode(s,{domId:s,id:s,labelStyle:"",labelText:n.label,padding:0,shape:"labelRect",style:""});let l=structuredClone(n),u=structuredClone(n);l.label="",l.arrowTypeEnd="none",u.label="",l.fromCluster=r.v,u.toCluster=r.v,t.setEdge(i,s,l,r.name+"-cyclic-special"),t.setEdge(s,a,u,r.name+"-cyclic-special")}else if(tr[r.v]||tr[r.w]){if(V.warn("Fixing and trixing - removing XXX",r.v,r.w,r.name),i=DE(r.v),a=DE(r.w),t.removeEdge(r.v,r.w,r.name),i!==r.v){let s=t.parent(i);tr[s].externalConnections=!0,n.fromCluster=r.v}if(a!==r.w){let s=t.parent(a);tr[s].externalConnections=!0,n.toCluster=r.w}V.warn("Fix Replacing with XXX",i,a,r.name),t.setEdge(i,a,n,r.name)}}),V.warn("Adjusted Graph",zn(t)),$he(t,0),V.trace(tr)},"adjustClustersAndEdges"),$he=o((t,e)=>{if(V.warn("extractor - ",e,zn(t),t.children("D")),e>10){V.error("Bailing out");return}let r=t.nodes(),n=!1;for(let i of r){let a=t.children(i);n=n||a.length>0}if(!n){V.debug("Done, no node has children",t.nodes());return}V.debug("Nodes = ",r,e);for(let i of r)if(V.debug("Extracting node",i,tr,tr[i]&&!tr[i].externalConnections,!t.parent(i),t.node(i),t.children("D")," Depth ",e),!tr[i])V.debug("Not a cluster",i,e);else if(!tr[i].externalConnections&&t.children(i)&&t.children(i).length>0){V.warn("Cluster without external connections, without a parent and with children",i,e);let s=t.graph().rankdir==="TB"?"LR":"TB";tr[i]?.clusterData?.dir&&(s=tr[i].clusterData.dir,V.warn("Fixing dir",tr[i].clusterData.dir,s));let l=new lr({multigraph:!0,compound:!0}).setGraph({rankdir:s,nodesep:50,ranksep:50,marginx:8,marginy:8}).setDefaultEdgeLabel(function(){return{}});V.warn("Old graph before copy",zn(t)),Fhe(i,t,l,i),t.setNode(i,{clusterNode:!0,id:i,clusterData:tr[i].clusterData,labelText:tr[i].labelText,graph:l}),V.warn("New graph after copy node: (",i,")",zn(l)),V.debug("Old graph after copy",zn(t))}else V.warn("Cluster ** ",i," **not meeting the criteria !externalConnections:",!tr[i].externalConnections," no parent: ",!t.parent(i)," children ",t.children(i)&&t.children(i).length>0,t.children("D"),e),V.debug(tr);r=t.nodes(),V.warn("New list of nodes",r);for(let i of r){let a=t.node(i);V.warn(" Now next level",i,a),a.clusterNode&&$he(a.graph,e+1)}},"extractor"),Vhe=o((t,e)=>{if(e.length===0)return[];let r=Object.assign(e);return e.forEach(n=>{let i=t.children(n),a=Vhe(t,i);r=[...r,...a]}),r},"sorter"),Uhe=o(t=>Vhe(t,t.children()),"sortNodesByHierarchy")});var iVe,aVe,sVe,oVe,lVe,Yhe,Whe,qhe,Xhe=R(()=>{"use strict";S9();ut();bv();Al();Zt();_t();rr();_d();iVe=o((t,e)=>{V.info("Creating subgraph rect for ",e.id,e);let r=de(),n=t.insert("g").attr("class","cluster"+(e.class?" "+e.class:"")).attr("id",e.id),i=n.insert("rect",":first-child"),a=yr(r.flowchart.htmlLabels),s=n.insert("g").attr("class","cluster-label"),l=e.labelType==="markdown"?ta(s,e.labelText,{style:e.labelStyle,useHtmlLabels:a},r):s.node().appendChild(ra(e.labelText,e.labelStyle,void 0,!0)),u=l.getBBox();if(yr(r.flowchart.htmlLabels)){let g=l.children[0],y=$e(l);u=g.getBoundingClientRect(),y.attr("width",u.width),y.attr("height",u.height)}let h=0*e.padding,f=h/2,d=e.width<=u.width+h?u.width+h:e.width;e.width<=u.width+h?e.diff=(u.width-e.width)/2-e.padding/2:e.diff=-e.padding/2,V.trace("Data ",e,JSON.stringify(e)),i.attr("style",e.style).attr("rx",e.rx).attr("ry",e.ry).attr("x",e.x-d/2).attr("y",e.y-e.height/2-f).attr("width",d).attr("height",e.height+h);let{subGraphTitleTopMargin:p}=io(r);a?s.attr("transform",`translate(${e.x-u.width/2}, ${e.y-e.height/2+p})`):s.attr("transform",`translate(${e.x}, ${e.y-e.height/2+p})`);let m=i.node().getBBox();return e.width=m.width,e.height=m.height,e.intersect=function(g){return Ad(e,g)},n},"rect"),aVe=o((t,e)=>{let r=t.insert("g").attr("class","note-cluster").attr("id",e.id),n=r.insert("rect",":first-child"),i=0*e.padding,a=i/2;n.attr("rx",e.rx).attr("ry",e.ry).attr("x",e.x-e.width/2-a).attr("y",e.y-e.height/2-a).attr("width",e.width+i).attr("height",e.height+i).attr("fill","none");let s=n.node().getBBox();return e.width=s.width,e.height=s.height,e.intersect=function(l){return Ad(e,l)},r},"noteGroup"),sVe=o((t,e)=>{let r=de(),n=t.insert("g").attr("class",e.classes).attr("id",e.id),i=n.insert("rect",":first-child"),a=n.insert("g").attr("class","cluster-label"),s=n.append("rect"),l=a.node().appendChild(ra(e.labelText,e.labelStyle,void 0,!0)),u=l.getBBox();if(yr(r.flowchart.htmlLabels)){let g=l.children[0],y=$e(l);u=g.getBoundingClientRect(),y.attr("width",u.width),y.attr("height",u.height)}u=l.getBBox();let h=0*e.padding,f=h/2,d=e.width<=u.width+e.padding?u.width+e.padding:e.width;e.width<=u.width+e.padding?e.diff=(u.width+e.padding*0-e.width)/2:e.diff=-e.padding/2,i.attr("class","outer").attr("x",e.x-d/2-f).attr("y",e.y-e.height/2-f).attr("width",d+h).attr("height",e.height+h),s.attr("class","inner").attr("x",e.x-d/2-f).attr("y",e.y-e.height/2-f+u.height-1).attr("width",d+h).attr("height",e.height+h-u.height-3);let{subGraphTitleTopMargin:p}=io(r);a.attr("transform",`translate(${e.x-u.width/2}, ${e.y-e.height/2-e.padding/3+(yr(r.flowchart.htmlLabels)?5:3)+p})`);let m=i.node().getBBox();return e.height=m.height,e.intersect=function(g){return Ad(e,g)},n},"roundedWithTitle"),oVe=o((t,e)=>{let r=t.insert("g").attr("class",e.classes).attr("id",e.id),n=r.insert("rect",":first-child"),i=0*e.padding,a=i/2;n.attr("class","divider").attr("x",e.x-e.width/2-a).attr("y",e.y-e.height/2).attr("width",e.width+i).attr("height",e.height+i);let s=n.node().getBBox();return e.width=s.width,e.height=s.height,e.diff=-e.padding/2,e.intersect=function(l){return Ad(e,l)},r},"divider"),lVe={rect:iVe,roundedWithTitle:sVe,noteGroup:aVe,divider:oVe},Yhe={},Whe=o((t,e)=>{V.trace("Inserting cluster");let r=e.shape||"rect";Yhe[e.id]=lVe[r](t,e)},"insertCluster"),qhe=o(()=>{Yhe={}},"clear")});var Khe,cVe,jhe,Qhe=R(()=>{"use strict";ut();Khe=o((t,e,r,n,i)=>{e.arrowTypeStart&&jhe(t,"start",e.arrowTypeStart,r,n,i),e.arrowTypeEnd&&jhe(t,"end",e.arrowTypeEnd,r,n,i)},"addEdgeMarkers"),cVe={arrow_cross:"cross",arrow_point:"point",arrow_barb:"barb",arrow_circle:"circle",aggregation:"aggregation",extension:"extension",composition:"composition",dependency:"dependency",lollipop:"lollipop"},jhe=o((t,e,r,n,i,a)=>{let s=cVe[r];if(!s){V.warn(`Unknown arrow type: ${r}`);return}let l=e==="start"?"Start":"End";t.attr(`marker-${e}`,`url(${n}#${i}_${a}-${s}${l})`)},"addEdgeMarker")});function NE(t,e){de().flowchart.htmlLabels&&t&&(t.style.width=e.length*9+"px",t.style.height="12px")}var ME,Ta,Jhe,IE,OE,uVe,hVe,Zhe,PE,DO=R(()=>{"use strict";ut();bv();Al();Zt();_t();xr();rr();j9();_d();Qhe();ME={},Ta={},Jhe=o(()=>{ME={},Ta={}},"clear"),IE=o((t,e)=>{let r=de(),n=yr(r.flowchart.htmlLabels),i=e.labelType==="markdown"?ta(t,e.label,{style:e.labelStyle,useHtmlLabels:n,addSvgBackground:!0},r):ra(e.label,e.labelStyle),a=t.insert("g").attr("class","edgeLabel"),s=a.insert("g").attr("class","label");s.node().appendChild(i);let l=i.getBBox();if(n){let h=i.children[0],f=$e(i);l=h.getBoundingClientRect(),f.attr("width",l.width),f.attr("height",l.height)}s.attr("transform","translate("+-l.width/2+", "+-l.height/2+")"),ME[e.id]=a,e.width=l.width,e.height=l.height;let u;if(e.startLabelLeft){let h=ra(e.startLabelLeft,e.labelStyle),f=t.insert("g").attr("class","edgeTerminals"),d=f.insert("g").attr("class","inner");u=d.node().appendChild(h);let p=h.getBBox();d.attr("transform","translate("+-p.width/2+", "+-p.height/2+")"),Ta[e.id]||(Ta[e.id]={}),Ta[e.id].startLeft=f,NE(u,e.startLabelLeft)}if(e.startLabelRight){let h=ra(e.startLabelRight,e.labelStyle),f=t.insert("g").attr("class","edgeTerminals"),d=f.insert("g").attr("class","inner");u=f.node().appendChild(h),d.node().appendChild(h);let p=h.getBBox();d.attr("transform","translate("+-p.width/2+", "+-p.height/2+")"),Ta[e.id]||(Ta[e.id]={}),Ta[e.id].startRight=f,NE(u,e.startLabelRight)}if(e.endLabelLeft){let h=ra(e.endLabelLeft,e.labelStyle),f=t.insert("g").attr("class","edgeTerminals"),d=f.insert("g").attr("class","inner");u=d.node().appendChild(h);let p=h.getBBox();d.attr("transform","translate("+-p.width/2+", "+-p.height/2+")"),f.node().appendChild(h),Ta[e.id]||(Ta[e.id]={}),Ta[e.id].endLeft=f,NE(u,e.endLabelLeft)}if(e.endLabelRight){let h=ra(e.endLabelRight,e.labelStyle),f=t.insert("g").attr("class","edgeTerminals"),d=f.insert("g").attr("class","inner");u=d.node().appendChild(h);let p=h.getBBox();d.attr("transform","translate("+-p.width/2+", "+-p.height/2+")"),f.node().appendChild(h),Ta[e.id]||(Ta[e.id]={}),Ta[e.id].endRight=f,NE(u,e.endLabelRight)}return i},"insertEdgeLabel");o(NE,"setTerminalWidth");OE=o((t,e)=>{V.debug("Moving label abc88 ",t.id,t.label,ME[t.id],e);let r=e.updatedPath?e.updatedPath:e.originalPath,n=de(),{subGraphTitleTotalMargin:i}=io(n);if(t.label){let a=ME[t.id],s=t.x,l=t.y;if(r){let u=Lt.calcLabelPosition(r);V.debug("Moving label "+t.label+" from (",s,",",l,") to (",u.x,",",u.y,") abc88"),e.updatedPath&&(s=u.x,l=u.y)}a.attr("transform",`translate(${s}, ${l+i/2})`)}if(t.startLabelLeft){let a=Ta[t.id].startLeft,s=t.x,l=t.y;if(r){let u=Lt.calcTerminalLabelPosition(t.arrowTypeStart?10:0,"start_left",r);s=u.x,l=u.y}a.attr("transform",`translate(${s}, ${l})`)}if(t.startLabelRight){let a=Ta[t.id].startRight,s=t.x,l=t.y;if(r){let u=Lt.calcTerminalLabelPosition(t.arrowTypeStart?10:0,"start_right",r);s=u.x,l=u.y}a.attr("transform",`translate(${s}, ${l})`)}if(t.endLabelLeft){let a=Ta[t.id].endLeft,s=t.x,l=t.y;if(r){let u=Lt.calcTerminalLabelPosition(t.arrowTypeEnd?10:0,"end_left",r);s=u.x,l=u.y}a.attr("transform",`translate(${s}, ${l})`)}if(t.endLabelRight){let a=Ta[t.id].endRight,s=t.x,l=t.y;if(r){let u=Lt.calcTerminalLabelPosition(t.arrowTypeEnd?10:0,"end_right",r);s=u.x,l=u.y}a.attr("transform",`translate(${s}, ${l})`)}},"positionEdgeLabel"),uVe=o((t,e)=>{let r=t.x,n=t.y,i=Math.abs(e.x-r),a=Math.abs(e.y-n),s=t.width/2,l=t.height/2;return i>=s||a>=l},"outsideNode"),hVe=o((t,e,r)=>{V.debug(`intersection calc abc89: + outsidePoint: ${JSON.stringify(e)} + insidePoint : ${JSON.stringify(r)} + node : x:${t.x} y:${t.y} w:${t.width} h:${t.height}`);let n=t.x,i=t.y,a=Math.abs(n-r.x),s=t.width/2,l=r.xMath.abs(n-e.x)*u){let d=r.y{V.debug("abc88 cutPathAtIntersect",t,e);let r=[],n=t[0],i=!1;return t.forEach(a=>{if(!uVe(e,a)&&!i){let s=hVe(e,n,a),l=!1;r.forEach(u=>{l=l||u.x===s.x&&u.y===s.y}),r.some(u=>u.x===s.x&&u.y===s.y)||r.push(s),i=!0}else n=a,i||r.push(a)}),r},"cutPathAtIntersect"),PE=o(function(t,e,r,n,i,a,s){let l=r.points;V.debug("abc88 InsertEdge: edge=",r,"e=",e);let u=!1,h=a.node(e.v);var f=a.node(e.w);f?.intersect&&h?.intersect&&(l=l.slice(1,r.points.length-1),l.unshift(h.intersect(l[0])),l.push(f.intersect(l[l.length-1]))),r.toCluster&&(V.debug("to cluster abc88",n[r.toCluster]),l=Zhe(r.points,n[r.toCluster].node),u=!0),r.fromCluster&&(V.debug("from cluster abc88",n[r.fromCluster]),l=Zhe(l.reverse(),n[r.fromCluster].node).reverse(),u=!0);let d=l.filter(S=>!Number.isNaN(S.y)),p=vs;r.curve&&(i==="graph"||i==="flowchart")&&(p=r.curve);let{x:m,y:g}=X5(r),y=ha().x(m).y(g).curve(p),v;switch(r.thickness){case"normal":v="edge-thickness-normal";break;case"thick":v="edge-thickness-thick";break;case"invisible":v="edge-thickness-thick";break;default:v=""}switch(r.pattern){case"solid":v+=" edge-pattern-solid";break;case"dotted":v+=" edge-pattern-dotted";break;case"dashed":v+=" edge-pattern-dashed";break}let x=t.append("path").attr("d",y(d)).attr("id",r.id).attr("class"," "+v+(r.classes?" "+r.classes:"")).attr("style",r.style),b="";(de().flowchart.arrowMarkerAbsolute||de().state.arrowMarkerAbsolute)&&(b=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search,b=b.replace(/\(/g,"\\("),b=b.replace(/\)/g,"\\)")),Khe(x,r,b,s,i);let w={};return u&&(w.updatedPath=l),w.originalPath=r.points,w},"insertEdge")});var efe,tfe,rfe=R(()=>{"use strict";Vd();Pv();LO();N5();Hhe();M5();Xhe();DO();ut();_d();_t();efe=o(async(t,e,r,n,i,a)=>{V.info("Graph in recursive render: XXX",zn(e),i);let s=e.graph().rankdir;V.trace("Dir in recursive render - dir:",s);let l=t.insert("g").attr("class","root");e.nodes()?V.info("Recursive render XXX",e.nodes()):V.info("No nodes found for",e),e.edges().length>0&&V.trace("Recursive edges",e.edge(e.edges()[0]));let u=l.insert("g").attr("class","clusters"),h=l.insert("g").attr("class","edgePaths"),f=l.insert("g").attr("class","edgeLabels"),d=l.insert("g").attr("class","nodes");await Promise.all(e.nodes().map(async function(g){let y=e.node(g);if(i!==void 0){let v=JSON.parse(JSON.stringify(i.clusterData));V.info("Setting data for cluster XXX (",g,") ",v,i),e.setNode(i.id,v),e.parent(g)||(V.trace("Setting parent",g,i.id),e.setParent(g,i.id,v))}if(V.info("(Insert) Node XXX"+g+": "+JSON.stringify(e.node(g))),y?.clusterNode){V.info("Cluster identified",g,y.width,e.node(g));let{ranksep:v,nodesep:x}=e.graph();y.graph.setGraph({...y.graph.graph(),ranksep:v,nodesep:x});let b=await efe(d,y.graph,r,n,e.node(g),a),w=b.elem;kn(y,w),y.diff=b.diff||0,V.info("Node bounds (abc123)",g,y,y.width,y.x,y.y),Bj(w,y),V.warn("Recursive render complete ",w,y)}else e.children(g).length>0?(V.info("Cluster - the non recursive path XXX",g,y.id,y,e),V.info(Fg(y.id,e)),tr[y.id]={id:Fg(y.id,e),node:y}):(V.info("Node - the non recursive path",g,y.id,y),await pm(d,e.node(g),s))})),e.edges().forEach(async function(g){let y=e.edge(g.v,g.w,g.name);V.info("Edge "+g.v+" -> "+g.w+": "+JSON.stringify(g)),V.info("Edge "+g.v+" -> "+g.w+": ",g," ",JSON.stringify(e.edge(g))),V.info("Fix",tr,"ids:",g.v,g.w,"Translating: ",tr[g.v],tr[g.w]),await IE(f,y)}),e.edges().forEach(function(g){V.info("Edge "+g.v+" -> "+g.w+": "+JSON.stringify(g))}),V.info("Graph before layout:",JSON.stringify(zn(e))),V.info("#############################################"),V.info("### Layout ###"),V.info("#############################################"),V.info(e),lo(e),V.info("Graph after layout:",JSON.stringify(zn(e)));let p=0,{subGraphTitleTotalMargin:m}=io(a);return Uhe(e).forEach(function(g){let y=e.node(g);V.info("Position "+g+": "+JSON.stringify(e.node(g))),V.info("Position "+g+": ("+y.x,","+y.y,") width: ",y.width," height: ",y.height),y?.clusterNode?(y.y+=m,wv(y)):e.children(g).length>0?(y.height+=m,Whe(u,y),tr[y.id].node=y):(y.y+=m/2,wv(y))}),e.edges().forEach(function(g){let y=e.edge(g);V.info("Edge "+g.v+" -> "+g.w+": "+JSON.stringify(y),y),y.points.forEach(x=>x.y+=m/2);let v=PE(h,g,y,tr,r,e,n);OE(y,v)}),e.nodes().forEach(function(g){let y=e.node(g);V.info(g,y.type,y.diff),y.type==="group"&&(p=y.diff)}),{elem:l,diff:p}},"recursiveRender"),tfe=o(async(t,e,r,n,i)=>{LE(t,r,n,i),Fj(),Jhe(),qhe(),Bhe(),V.warn("Graph at first:",JSON.stringify(zn(e))),Ghe(e),V.warn("Graph after:",JSON.stringify(zn(e)));let a=de();await efe(t,e,n,i,void 0,a)},"render")});function nfe(t){let e;switch(t){case 0:e="aggregation";break;case 1:e="extension";break;case 2:e="composition";break;case 3:e="dependency";break;case 4:e="lollipop";break;default:e="none"}return e}var NO,RO,fVe,ife,dVe,pVe,mVe,gVe,afe,sfe=R(()=>{"use strict";Zt();ya();ut();_t();rfe();xr();xr();Yn();rr();NO=o(t=>We.sanitizeText(t,de()),"sanitizeText"),RO={dividerMargin:10,padding:5,textHeight:10,curve:void 0},fVe=o(function(t,e,r,n){V.info("keys:",[...t.keys()]),V.info(t),t.forEach(function(i){let s={shape:"rect",id:i.id,domId:i.domId,labelText:NO(i.id),labelStyle:"",style:"fill: none; stroke: black",padding:de().flowchart?.padding??de().class?.padding};e.setNode(i.id,s),ife(i.classes,e,r,n,i.id),V.info("setNode",s)})},"addNamespaces"),ife=o(function(t,e,r,n,i){V.info("keys:",[...t.keys()]),V.info(t),[...t.values()].filter(a=>a.parent===i).forEach(function(a){let s=a.cssClasses.join(" "),l=lm(a.styles),u=a.label??a.id,h=0,d={labelStyle:l.labelStyle,shape:"class_box",labelText:NO(u),classData:a,rx:h,ry:h,class:s,style:l.style,id:a.id,domId:a.domId,tooltip:n.db.getTooltip(a.id,i)||"",haveCallback:a.haveCallback,link:a.link,width:a.type==="group"?500:void 0,type:a.type,padding:de().flowchart?.padding??de().class?.padding};e.setNode(a.id,d),i&&e.setParent(a.id,i),V.info("setNode",d)})},"addClasses"),dVe=o(function(t,e,r,n){V.info(t),t.forEach(function(i,a){let s=i,l="",u={labelStyle:"",style:""},h=s.text,f=0,p={labelStyle:u.labelStyle,shape:"note",labelText:NO(h),noteData:s,rx:f,ry:f,class:l,style:u.style,id:s.id,domId:s.id,tooltip:"",type:"note",padding:de().flowchart?.padding??de().class?.padding};if(e.setNode(s.id,p),V.info("setNode",p),!s.class||!n.has(s.class))return;let m=r+a,g={id:`edgeNote${m}`,classes:"relation",pattern:"dotted",arrowhead:"none",startLabelRight:"",endLabelLeft:"",arrowTypeStart:"none",arrowTypeEnd:"none",style:"fill:none",labelStyle:"",curve:om(RO.curve,xu)};e.setEdge(s.id,s.class,g,m)})},"addNotes"),pVe=o(function(t,e){let r=de().flowchart,n=0;t.forEach(function(i){n++;let a={classes:"relation",pattern:i.relation.lineType==1?"dashed":"solid",id:y5(i.id1,i.id2,{prefix:"id",counter:n}),arrowhead:i.type==="arrow_open"?"none":"normal",startLabelRight:i.relationTitle1==="none"?"":i.relationTitle1,endLabelLeft:i.relationTitle2==="none"?"":i.relationTitle2,arrowTypeStart:nfe(i.relation.type1),arrowTypeEnd:nfe(i.relation.type2),style:"fill:none",labelStyle:"",curve:om(r?.curve,xu)};if(V.info(a,i),i.style!==void 0){let s=lm(i.style);a.style=s.style,a.labelStyle=s.labelStyle}i.text=i.title,i.text===void 0?i.style!==void 0&&(a.arrowheadStyle="fill: #333"):(a.arrowheadStyle="fill: #333",a.labelpos="c",de().flowchart?.htmlLabels??de().htmlLabels?(a.labelType="html",a.label=''+i.text+""):(a.labelType="text",a.label=i.text.replace(We.lineBreakRegex,` +`),i.style===void 0&&(a.style=a.style||"stroke: #333; stroke-width: 1.5px;fill:none"),a.labelStyle=a.labelStyle.replace("color:","fill:"))),e.setEdge(i.id1,i.id2,a,n)})},"addRelations"),mVe=o(function(t){RO={...RO,...t}},"setConf"),gVe=o(async function(t,e,r,n){V.info("Drawing class - ",e);let i=de().flowchart??de().class,a=de().securityLevel;V.info("config:",i);let s=i?.nodeSpacing??50,l=i?.rankSpacing??50,u=new lr({multigraph:!0,compound:!0}).setGraph({rankdir:n.db.getDirection(),nodesep:s,ranksep:l,marginx:8,marginy:8}).setDefaultEdgeLabel(function(){return{}}),h=n.db.getNamespaces(),f=n.db.getClasses(),d=n.db.getRelations(),p=n.db.getNotes();V.info(d),fVe(h,u,e,n),ife(f,u,e,n),pVe(d,u),dVe(p,u,d.length+1,f);let m;a==="sandbox"&&(m=$e("#i"+e));let g=a==="sandbox"?$e(m.nodes()[0].contentDocument.body):$e("body"),y=g.select(`[id="${e}"]`),v=g.select("#"+e+" g");if(await tfe(v,u,["aggregation","extension","composition","dependency","lollipop"],"classDiagram",e),Lt.insertTitle(y,"classTitleText",i?.titleTopMargin??5,n.db.getDiagramTitle()),Lo(u,y,i?.diagramPadding,i?.useMaxWidth),!i?.htmlLabels){let x=a==="sandbox"?m.nodes()[0].contentDocument:document,b=x.querySelectorAll('[id="'+e+'"] .edgeLabel .label');for(let w of b){let S=w.getBBox(),T=x.createElementNS("http://www.w3.org/2000/svg","rect");T.setAttribute("rx",0),T.setAttribute("ry",0),T.setAttribute("width",S.width),T.setAttribute("height",S.height),w.insertBefore(T,w.firstChild)}}},"draw");o(nfe,"getArrowMarker");afe={setConf:mVe,draw:gVe}});var ofe={};hr(ofe,{diagram:()=>yVe});var yVe,lfe=R(()=>{"use strict";TO();AO();_O();sfe();yVe={parser:wE,db:Bg,renderer:afe,styles:CE,init:o(t=>{t.class||(t.class={}),t.class.arrowMarkerAbsolute=t.arrowMarkerAbsolute,Bg.clear()},"init")}});var MO,BE,IO=R(()=>{"use strict";MO=function(){var t=o(function(F,B,$,z){for($=$||{},z=F.length;z--;$[F[z]]=B);return $},"o"),e=[1,2],r=[1,3],n=[1,4],i=[2,4],a=[1,9],s=[1,11],l=[1,16],u=[1,17],h=[1,18],f=[1,19],d=[1,32],p=[1,20],m=[1,21],g=[1,22],y=[1,23],v=[1,24],x=[1,26],b=[1,27],w=[1,28],S=[1,29],T=[1,30],E=[1,31],_=[1,34],A=[1,35],L=[1,36],M=[1,37],N=[1,33],k=[1,4,5,16,17,19,21,22,24,25,26,27,28,29,33,35,37,38,42,45,48,49,50,51,54],I=[1,4,5,14,15,16,17,19,21,22,24,25,26,27,28,29,33,35,37,38,42,45,48,49,50,51,54],C=[4,5,16,17,19,21,22,24,25,26,27,28,29,33,35,37,38,42,45,48,49,50,51,54],O={trace:o(function(){},"trace"),yy:{},symbols_:{error:2,start:3,SPACE:4,NL:5,SD:6,document:7,line:8,statement:9,classDefStatement:10,styleStatement:11,cssClassStatement:12,idStatement:13,DESCR:14,"-->":15,HIDE_EMPTY:16,scale:17,WIDTH:18,COMPOSIT_STATE:19,STRUCT_START:20,STRUCT_STOP:21,STATE_DESCR:22,AS:23,ID:24,FORK:25,JOIN:26,CHOICE:27,CONCURRENT:28,note:29,notePosition:30,NOTE_TEXT:31,direction:32,acc_title:33,acc_title_value:34,acc_descr:35,acc_descr_value:36,acc_descr_multiline_value:37,classDef:38,CLASSDEF_ID:39,CLASSDEF_STYLEOPTS:40,DEFAULT:41,style:42,STYLE_IDS:43,STYLEDEF_STYLEOPTS:44,class:45,CLASSENTITY_IDS:46,STYLECLASS:47,direction_tb:48,direction_bt:49,direction_rl:50,direction_lr:51,eol:52,";":53,EDGE_STATE:54,STYLE_SEPARATOR:55,left_of:56,right_of:57,$accept:0,$end:1},terminals_:{2:"error",4:"SPACE",5:"NL",6:"SD",14:"DESCR",15:"-->",16:"HIDE_EMPTY",17:"scale",18:"WIDTH",19:"COMPOSIT_STATE",20:"STRUCT_START",21:"STRUCT_STOP",22:"STATE_DESCR",23:"AS",24:"ID",25:"FORK",26:"JOIN",27:"CHOICE",28:"CONCURRENT",29:"note",31:"NOTE_TEXT",33:"acc_title",34:"acc_title_value",35:"acc_descr",36:"acc_descr_value",37:"acc_descr_multiline_value",38:"classDef",39:"CLASSDEF_ID",40:"CLASSDEF_STYLEOPTS",41:"DEFAULT",42:"style",43:"STYLE_IDS",44:"STYLEDEF_STYLEOPTS",45:"class",46:"CLASSENTITY_IDS",47:"STYLECLASS",48:"direction_tb",49:"direction_bt",50:"direction_rl",51:"direction_lr",53:";",54:"EDGE_STATE",55:"STYLE_SEPARATOR",56:"left_of",57:"right_of"},productions_:[0,[3,2],[3,2],[3,2],[7,0],[7,2],[8,2],[8,1],[8,1],[9,1],[9,1],[9,1],[9,1],[9,2],[9,3],[9,4],[9,1],[9,2],[9,1],[9,4],[9,3],[9,6],[9,1],[9,1],[9,1],[9,1],[9,4],[9,4],[9,1],[9,2],[9,2],[9,1],[10,3],[10,3],[11,3],[12,3],[32,1],[32,1],[32,1],[32,1],[52,1],[52,1],[13,1],[13,1],[13,3],[13,3],[30,1],[30,1]],performAction:o(function(B,$,z,Y,Q,X,ie){var j=X.length-1;switch(Q){case 3:return Y.setRootDoc(X[j]),X[j];break;case 4:this.$=[];break;case 5:X[j]!="nl"&&(X[j-1].push(X[j]),this.$=X[j-1]);break;case 6:case 7:this.$=X[j];break;case 8:this.$="nl";break;case 12:this.$=X[j];break;case 13:let q=X[j-1];q.description=Y.trimColon(X[j]),this.$=q;break;case 14:this.$={stmt:"relation",state1:X[j-2],state2:X[j]};break;case 15:let K=Y.trimColon(X[j]);this.$={stmt:"relation",state1:X[j-3],state2:X[j-1],description:K};break;case 19:this.$={stmt:"state",id:X[j-3],type:"default",description:"",doc:X[j-1]};break;case 20:var J=X[j],Z=X[j-2].trim();if(X[j].match(":")){var H=X[j].split(":");J=H[0],Z=[Z,H[1]]}this.$={stmt:"state",id:J,type:"default",description:Z};break;case 21:this.$={stmt:"state",id:X[j-3],type:"default",description:X[j-5],doc:X[j-1]};break;case 22:this.$={stmt:"state",id:X[j],type:"fork"};break;case 23:this.$={stmt:"state",id:X[j],type:"join"};break;case 24:this.$={stmt:"state",id:X[j],type:"choice"};break;case 25:this.$={stmt:"state",id:Y.getDividerId(),type:"divider"};break;case 26:this.$={stmt:"state",id:X[j-1].trim(),note:{position:X[j-2].trim(),text:X[j].trim()}};break;case 29:this.$=X[j].trim(),Y.setAccTitle(this.$);break;case 30:case 31:this.$=X[j].trim(),Y.setAccDescription(this.$);break;case 32:case 33:this.$={stmt:"classDef",id:X[j-1].trim(),classes:X[j].trim()};break;case 34:this.$={stmt:"style",id:X[j-1].trim(),styleClass:X[j].trim()};break;case 35:this.$={stmt:"applyClass",id:X[j-1].trim(),styleClass:X[j].trim()};break;case 36:Y.setDirection("TB"),this.$={stmt:"dir",value:"TB"};break;case 37:Y.setDirection("BT"),this.$={stmt:"dir",value:"BT"};break;case 38:Y.setDirection("RL"),this.$={stmt:"dir",value:"RL"};break;case 39:Y.setDirection("LR"),this.$={stmt:"dir",value:"LR"};break;case 42:case 43:this.$={stmt:"state",id:X[j].trim(),type:"default",description:""};break;case 44:this.$={stmt:"state",id:X[j-2].trim(),classes:[X[j].trim()],type:"default",description:""};break;case 45:this.$={stmt:"state",id:X[j-2].trim(),classes:[X[j].trim()],type:"default",description:""};break}},"anonymous"),table:[{3:1,4:e,5:r,6:n},{1:[3]},{3:5,4:e,5:r,6:n},{3:6,4:e,5:r,6:n},t([1,4,5,16,17,19,22,24,25,26,27,28,29,33,35,37,38,42,45,48,49,50,51,54],i,{7:7}),{1:[2,1]},{1:[2,2]},{1:[2,3],4:a,5:s,8:8,9:10,10:12,11:13,12:14,13:15,16:l,17:u,19:h,22:f,24:d,25:p,26:m,27:g,28:y,29:v,32:25,33:x,35:b,37:w,38:S,42:T,45:E,48:_,49:A,50:L,51:M,54:N},t(k,[2,5]),{9:38,10:12,11:13,12:14,13:15,16:l,17:u,19:h,22:f,24:d,25:p,26:m,27:g,28:y,29:v,32:25,33:x,35:b,37:w,38:S,42:T,45:E,48:_,49:A,50:L,51:M,54:N},t(k,[2,7]),t(k,[2,8]),t(k,[2,9]),t(k,[2,10]),t(k,[2,11]),t(k,[2,12],{14:[1,39],15:[1,40]}),t(k,[2,16]),{18:[1,41]},t(k,[2,18],{20:[1,42]}),{23:[1,43]},t(k,[2,22]),t(k,[2,23]),t(k,[2,24]),t(k,[2,25]),{30:44,31:[1,45],56:[1,46],57:[1,47]},t(k,[2,28]),{34:[1,48]},{36:[1,49]},t(k,[2,31]),{39:[1,50],41:[1,51]},{43:[1,52]},{46:[1,53]},t(I,[2,42],{55:[1,54]}),t(I,[2,43],{55:[1,55]}),t(k,[2,36]),t(k,[2,37]),t(k,[2,38]),t(k,[2,39]),t(k,[2,6]),t(k,[2,13]),{13:56,24:d,54:N},t(k,[2,17]),t(C,i,{7:57}),{24:[1,58]},{24:[1,59]},{23:[1,60]},{24:[2,46]},{24:[2,47]},t(k,[2,29]),t(k,[2,30]),{40:[1,61]},{40:[1,62]},{44:[1,63]},{47:[1,64]},{24:[1,65]},{24:[1,66]},t(k,[2,14],{14:[1,67]}),{4:a,5:s,8:8,9:10,10:12,11:13,12:14,13:15,16:l,17:u,19:h,21:[1,68],22:f,24:d,25:p,26:m,27:g,28:y,29:v,32:25,33:x,35:b,37:w,38:S,42:T,45:E,48:_,49:A,50:L,51:M,54:N},t(k,[2,20],{20:[1,69]}),{31:[1,70]},{24:[1,71]},t(k,[2,32]),t(k,[2,33]),t(k,[2,34]),t(k,[2,35]),t(I,[2,44]),t(I,[2,45]),t(k,[2,15]),t(k,[2,19]),t(C,i,{7:72}),t(k,[2,26]),t(k,[2,27]),{4:a,5:s,8:8,9:10,10:12,11:13,12:14,13:15,16:l,17:u,19:h,21:[1,73],22:f,24:d,25:p,26:m,27:g,28:y,29:v,32:25,33:x,35:b,37:w,38:S,42:T,45:E,48:_,49:A,50:L,51:M,54:N},t(k,[2,21])],defaultActions:{5:[2,1],6:[2,2],46:[2,46],47:[2,47]},parseError:o(function(B,$){if($.recoverable)this.trace(B);else{var z=new Error(B);throw z.hash=$,z}},"parseError"),parse:o(function(B){var $=this,z=[0],Y=[],Q=[null],X=[],ie=this.table,j="",J=0,Z=0,H=0,q=2,K=1,se=X.slice.call(arguments,1),ce=Object.create(this.lexer),ue={yy:{}};for(var te in this.yy)Object.prototype.hasOwnProperty.call(this.yy,te)&&(ue.yy[te]=this.yy[te]);ce.setInput(B,ue.yy),ue.yy.lexer=ce,ue.yy.parser=this,typeof ce.yylloc>"u"&&(ce.yylloc={});var De=ce.yylloc;X.push(De);var oe=ce.options&&ce.options.ranges;typeof ue.yy.parseError=="function"?this.parseError=ue.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function ke(we){z.length=z.length-2*we,Q.length=Q.length-we,X.length=X.length-we}o(ke,"popStack");function Ie(){var we;return we=Y.pop()||ce.lex()||K,typeof we!="number"&&(we instanceof Array&&(Y=we,we=Y.pop()),we=$.symbols_[we]||we),we}o(Ie,"lex");for(var Se,Ue,Pe,_e,me,W,fe={},ge,re,he,ne;;){if(Pe=z[z.length-1],this.defaultActions[Pe]?_e=this.defaultActions[Pe]:((Se===null||typeof Se>"u")&&(Se=Ie()),_e=ie[Pe]&&ie[Pe][Se]),typeof _e>"u"||!_e.length||!_e[0]){var ae="";ne=[];for(ge in ie[Pe])this.terminals_[ge]&&ge>q&&ne.push("'"+this.terminals_[ge]+"'");ce.showPosition?ae="Parse error on line "+(J+1)+`: +`+ce.showPosition()+` +Expecting `+ne.join(", ")+", got '"+(this.terminals_[Se]||Se)+"'":ae="Parse error on line "+(J+1)+": Unexpected "+(Se==K?"end of input":"'"+(this.terminals_[Se]||Se)+"'"),this.parseError(ae,{text:ce.match,token:this.terminals_[Se]||Se,line:ce.yylineno,loc:De,expected:ne})}if(_e[0]instanceof Array&&_e.length>1)throw new Error("Parse Error: multiple actions possible at state: "+Pe+", token: "+Se);switch(_e[0]){case 1:z.push(Se),Q.push(ce.yytext),X.push(ce.yylloc),z.push(_e[1]),Se=null,Ue?(Se=Ue,Ue=null):(Z=ce.yyleng,j=ce.yytext,J=ce.yylineno,De=ce.yylloc,H>0&&H--);break;case 2:if(re=this.productions_[_e[1]][1],fe.$=Q[Q.length-re],fe._$={first_line:X[X.length-(re||1)].first_line,last_line:X[X.length-1].last_line,first_column:X[X.length-(re||1)].first_column,last_column:X[X.length-1].last_column},oe&&(fe._$.range=[X[X.length-(re||1)].range[0],X[X.length-1].range[1]]),W=this.performAction.apply(fe,[j,Z,J,ue.yy,_e[1],Q,X].concat(se)),typeof W<"u")return W;re&&(z=z.slice(0,-1*re*2),Q=Q.slice(0,-1*re),X=X.slice(0,-1*re)),z.push(this.productions_[_e[1]][0]),Q.push(fe.$),X.push(fe._$),he=ie[z[z.length-2]][z[z.length-1]],z.push(he);break;case 3:return!0}}return!0},"parse")},D=function(){var F={EOF:1,parseError:o(function($,z){if(this.yy.parser)this.yy.parser.parseError($,z);else throw new Error($)},"parseError"),setInput:o(function(B,$){return this.yy=$||this.yy||{},this._input=B,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:o(function(){var B=this._input[0];this.yytext+=B,this.yyleng++,this.offset++,this.match+=B,this.matched+=B;var $=B.match(/(?:\r\n?|\n).*/g);return $?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),B},"input"),unput:o(function(B){var $=B.length,z=B.split(/(?:\r\n?|\n)/g);this._input=B+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-$),this.offset-=$;var Y=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),z.length-1&&(this.yylineno-=z.length-1);var Q=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:z?(z.length===Y.length?this.yylloc.first_column:0)+Y[Y.length-z.length].length-z[0].length:this.yylloc.first_column-$},this.options.ranges&&(this.yylloc.range=[Q[0],Q[0]+this.yyleng-$]),this.yyleng=this.yytext.length,this},"unput"),more:o(function(){return this._more=!0,this},"more"),reject:o(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:o(function(B){this.unput(this.match.slice(B))},"less"),pastInput:o(function(){var B=this.matched.substr(0,this.matched.length-this.match.length);return(B.length>20?"...":"")+B.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:o(function(){var B=this.match;return B.length<20&&(B+=this._input.substr(0,20-B.length)),(B.substr(0,20)+(B.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:o(function(){var B=this.pastInput(),$=new Array(B.length+1).join("-");return B+this.upcomingInput()+` +`+$+"^"},"showPosition"),test_match:o(function(B,$){var z,Y,Q;if(this.options.backtrack_lexer&&(Q={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(Q.yylloc.range=this.yylloc.range.slice(0))),Y=B[0].match(/(?:\r\n?|\n).*/g),Y&&(this.yylineno+=Y.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:Y?Y[Y.length-1].length-Y[Y.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+B[0].length},this.yytext+=B[0],this.match+=B[0],this.matches=B,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(B[0].length),this.matched+=B[0],z=this.performAction.call(this,this.yy,this,$,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),z)return z;if(this._backtrack){for(var X in Q)this[X]=Q[X];return!1}return!1},"test_match"),next:o(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var B,$,z,Y;this._more||(this.yytext="",this.match="");for(var Q=this._currentRules(),X=0;X$[0].length)){if($=z,Y=X,this.options.backtrack_lexer){if(B=this.test_match(z,Q[X]),B!==!1)return B;if(this._backtrack){$=!1;continue}else return!1}else if(!this.options.flex)break}return $?(B=this.test_match($,Q[Y]),B!==!1?B:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:o(function(){var $=this.next();return $||this.lex()},"lex"),begin:o(function($){this.conditionStack.push($)},"begin"),popState:o(function(){var $=this.conditionStack.length-1;return $>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:o(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:o(function($){return $=this.conditionStack.length-1-Math.abs($||0),$>=0?this.conditionStack[$]:"INITIAL"},"topState"),pushState:o(function($){this.begin($)},"pushState"),stateStackSize:o(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:o(function($,z,Y,Q){var X=Q;switch(Y){case 0:return 41;case 1:return 48;case 2:return 49;case 3:return 50;case 4:return 51;case 5:break;case 6:break;case 7:return 5;case 8:break;case 9:break;case 10:break;case 11:break;case 12:return this.pushState("SCALE"),17;break;case 13:return 18;case 14:this.popState();break;case 15:return this.begin("acc_title"),33;break;case 16:return this.popState(),"acc_title_value";break;case 17:return this.begin("acc_descr"),35;break;case 18:return this.popState(),"acc_descr_value";break;case 19:this.begin("acc_descr_multiline");break;case 20:this.popState();break;case 21:return"acc_descr_multiline_value";case 22:return this.pushState("CLASSDEF"),38;break;case 23:return this.popState(),this.pushState("CLASSDEFID"),"DEFAULT_CLASSDEF_ID";break;case 24:return this.popState(),this.pushState("CLASSDEFID"),39;break;case 25:return this.popState(),40;break;case 26:return this.pushState("CLASS"),45;break;case 27:return this.popState(),this.pushState("CLASS_STYLE"),46;break;case 28:return this.popState(),47;break;case 29:return this.pushState("STYLE"),42;break;case 30:return this.popState(),this.pushState("STYLEDEF_STYLES"),43;break;case 31:return this.popState(),44;break;case 32:return this.pushState("SCALE"),17;break;case 33:return 18;case 34:this.popState();break;case 35:this.pushState("STATE");break;case 36:return this.popState(),z.yytext=z.yytext.slice(0,-8).trim(),25;break;case 37:return this.popState(),z.yytext=z.yytext.slice(0,-8).trim(),26;break;case 38:return this.popState(),z.yytext=z.yytext.slice(0,-10).trim(),27;break;case 39:return this.popState(),z.yytext=z.yytext.slice(0,-8).trim(),25;break;case 40:return this.popState(),z.yytext=z.yytext.slice(0,-8).trim(),26;break;case 41:return this.popState(),z.yytext=z.yytext.slice(0,-10).trim(),27;break;case 42:return 48;case 43:return 49;case 44:return 50;case 45:return 51;case 46:this.pushState("STATE_STRING");break;case 47:return this.pushState("STATE_ID"),"AS";break;case 48:return this.popState(),"ID";break;case 49:this.popState();break;case 50:return"STATE_DESCR";case 51:return 19;case 52:this.popState();break;case 53:return this.popState(),this.pushState("struct"),20;break;case 54:break;case 55:return this.popState(),21;break;case 56:break;case 57:return this.begin("NOTE"),29;break;case 58:return this.popState(),this.pushState("NOTE_ID"),56;break;case 59:return this.popState(),this.pushState("NOTE_ID"),57;break;case 60:this.popState(),this.pushState("FLOATING_NOTE");break;case 61:return this.popState(),this.pushState("FLOATING_NOTE_ID"),"AS";break;case 62:break;case 63:return"NOTE_TEXT";case 64:return this.popState(),"ID";break;case 65:return this.popState(),this.pushState("NOTE_TEXT"),24;break;case 66:return this.popState(),z.yytext=z.yytext.substr(2).trim(),31;break;case 67:return this.popState(),z.yytext=z.yytext.slice(0,-8).trim(),31;break;case 68:return 6;case 69:return 6;case 70:return 16;case 71:return 54;case 72:return 24;case 73:return z.yytext=z.yytext.trim(),14;break;case 74:return 15;case 75:return 28;case 76:return 55;case 77:return 5;case 78:return"INVALID"}},"anonymous"),rules:[/^(?:default\b)/i,/^(?:.*direction\s+TB[^\n]*)/i,/^(?:.*direction\s+BT[^\n]*)/i,/^(?:.*direction\s+RL[^\n]*)/i,/^(?:.*direction\s+LR[^\n]*)/i,/^(?:%%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:[\s]+)/i,/^(?:((?!\n)\s)+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:scale\s+)/i,/^(?:\d+)/i,/^(?:\s+width\b)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:classDef\s+)/i,/^(?:DEFAULT\s+)/i,/^(?:\w+\s+)/i,/^(?:[^\n]*)/i,/^(?:class\s+)/i,/^(?:(\w+)+((,\s*\w+)*))/i,/^(?:[^\n]*)/i,/^(?:style\s+)/i,/^(?:[\w,]+\s+)/i,/^(?:[^\n]*)/i,/^(?:scale\s+)/i,/^(?:\d+)/i,/^(?:\s+width\b)/i,/^(?:state\s+)/i,/^(?:.*<>)/i,/^(?:.*<>)/i,/^(?:.*<>)/i,/^(?:.*\[\[fork\]\])/i,/^(?:.*\[\[join\]\])/i,/^(?:.*\[\[choice\]\])/i,/^(?:.*direction\s+TB[^\n]*)/i,/^(?:.*direction\s+BT[^\n]*)/i,/^(?:.*direction\s+RL[^\n]*)/i,/^(?:.*direction\s+LR[^\n]*)/i,/^(?:["])/i,/^(?:\s*as\s+)/i,/^(?:[^\n\{]*)/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[^\n\s\{]+)/i,/^(?:\n)/i,/^(?:\{)/i,/^(?:%%(?!\{)[^\n]*)/i,/^(?:\})/i,/^(?:[\n])/i,/^(?:note\s+)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:")/i,/^(?:\s*as\s*)/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[^\n]*)/i,/^(?:\s*[^:\n\s\-]+)/i,/^(?:\s*:[^:\n;]+)/i,/^(?:[\s\S]*?end note\b)/i,/^(?:stateDiagram\s+)/i,/^(?:stateDiagram-v2\s+)/i,/^(?:hide empty description\b)/i,/^(?:\[\*\])/i,/^(?:[^:\n\s\-\{]+)/i,/^(?:\s*:[^:\n;]+)/i,/^(?:-->)/i,/^(?:--)/i,/^(?::::)/i,/^(?:$)/i,/^(?:.)/i],conditions:{LINE:{rules:[9,10],inclusive:!1},struct:{rules:[9,10,22,26,29,35,42,43,44,45,54,55,56,57,71,72,73,74,75],inclusive:!1},FLOATING_NOTE_ID:{rules:[64],inclusive:!1},FLOATING_NOTE:{rules:[61,62,63],inclusive:!1},NOTE_TEXT:{rules:[66,67],inclusive:!1},NOTE_ID:{rules:[65],inclusive:!1},NOTE:{rules:[58,59,60],inclusive:!1},STYLEDEF_STYLEOPTS:{rules:[],inclusive:!1},STYLEDEF_STYLES:{rules:[31],inclusive:!1},STYLE_IDS:{rules:[],inclusive:!1},STYLE:{rules:[30],inclusive:!1},CLASS_STYLE:{rules:[28],inclusive:!1},CLASS:{rules:[27],inclusive:!1},CLASSDEFID:{rules:[25],inclusive:!1},CLASSDEF:{rules:[23,24],inclusive:!1},acc_descr_multiline:{rules:[20,21],inclusive:!1},acc_descr:{rules:[18],inclusive:!1},acc_title:{rules:[16],inclusive:!1},SCALE:{rules:[13,14,33,34],inclusive:!1},ALIAS:{rules:[],inclusive:!1},STATE_ID:{rules:[48],inclusive:!1},STATE_STRING:{rules:[49,50],inclusive:!1},FORK_STATE:{rules:[],inclusive:!1},STATE:{rules:[9,10,36,37,38,39,40,41,46,47,51,52,53],inclusive:!1},ID:{rules:[9,10],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,7,8,10,11,12,15,17,19,22,26,29,32,35,53,57,68,69,70,71,72,73,74,76,77,78],inclusive:!0}}};return F}();O.lexer=D;function P(){this.yy={}}return o(P,"Parser"),P.prototype=O,O.Parser=P,new P}();MO.parser=MO;BE=MO});var hfe,FE,zg,Ex,ffe,dfe,pfe,N0,zE,OO,PO,BO,FO,zO,GE,$E,mfe,gfe,GO,$O,yfe,vfe,Gg,wVe,xfe,VO,TVe,kVe,bfe,wfe,EVe,Tfe,CVe,kfe,UO,HO,Efe,VE,Cfe,YO,UE=R(()=>{"use strict";hfe="LR",FE="TB",zg="state",Ex="relation",ffe="classDef",dfe="style",pfe="applyClass",N0="default",zE="divider",OO="fill:none",PO="fill: #333",BO="c",FO="text",zO="normal",GE="rect",$E="rectWithTitle",mfe="stateStart",gfe="stateEnd",GO="divider",$O="roundedWithTitle",yfe="note",vfe="noteGroup",Gg="statediagram",wVe="state",xfe=`${Gg}-${wVe}`,VO="transition",TVe="note",kVe="note-edge",bfe=`${VO} ${kVe}`,wfe=`${Gg}-${TVe}`,EVe="cluster",Tfe=`${Gg}-${EVe}`,CVe="cluster-alt",kfe=`${Gg}-${CVe}`,UO="parent",HO="note",Efe="state",VE="----",Cfe=`${VE}${HO}`,YO=`${VE}${UO}`});function WO(t="",e=0,r="",n=VE){let i=r!==null&&r.length>0?`${n}${r}`:"";return`${Efe}-${t}${i}-${e}`}function HE(t,e,r){if(!e.id||e.id===""||e.id==="")return;e.cssClasses&&(Array.isArray(e.cssCompiledStyles)||(e.cssCompiledStyles=[]),e.cssClasses.split(" ").forEach(i=>{if(r.get(i)){let a=r.get(i);e.cssCompiledStyles=[...e.cssCompiledStyles,...a.styles]}}));let n=t.find(i=>i.id===e.id);n?Object.assign(n,e):t.push(e)}function AVe(t){return t?.classes?.join(" ")??""}function _Ve(t){return t?.styles??[]}var YE,yf,SVe,Sfe,$g,Afe,_fe=R(()=>{"use strict";_t();ut();rr();UE();YE=new Map,yf=0;o(WO,"stateDomId");SVe=o((t,e,r,n,i,a,s,l)=>{V.trace("items",e),e.forEach(u=>{switch(u.stmt){case zg:$g(t,u,r,n,i,a,s,l);break;case N0:$g(t,u,r,n,i,a,s,l);break;case Ex:{$g(t,u.state1,r,n,i,a,s,l),$g(t,u.state2,r,n,i,a,s,l);let h={id:"edge"+yf,start:u.state1.id,end:u.state2.id,arrowhead:"normal",arrowTypeEnd:"arrow_barb",style:OO,labelStyle:"",label:We.sanitizeText(u.description,de()),arrowheadStyle:PO,labelpos:BO,labelType:FO,thickness:zO,classes:VO,look:s};i.push(h),yf++}break}})},"setupDoc"),Sfe=o((t,e=FE)=>{let r=e;if(t.doc)for(let n of t.doc)n.stmt==="dir"&&(r=n.value);return r},"getDir");o(HE,"insertOrUpdateNode");o(AVe,"getClassesFromDbInfo");o(_Ve,"getStylesFromDbInfo");$g=o((t,e,r,n,i,a,s,l)=>{let u=e.id,h=r.get(u),f=AVe(h),d=_Ve(h);if(V.info("dataFetcher parsedItem",e,h,d),u!=="root"){let p=GE;e.start===!0?p=mfe:e.start===!1&&(p=gfe),e.type!==N0&&(p=e.type),YE.get(u)||YE.set(u,{id:u,shape:p,description:We.sanitizeText(u,de()),cssClasses:`${f} ${xfe}`,cssStyles:d});let m=YE.get(u);e.description&&(Array.isArray(m.description)?(m.shape=$E,m.description.push(e.description)):m.description?.length>0?(m.shape=$E,m.description===u?m.description=[e.description]:m.description=[m.description,e.description]):(m.shape=GE,m.description=e.description),m.description=We.sanitizeTextOrArray(m.description,de())),m.description?.length===1&&m.shape===$E&&(m.type==="group"?m.shape=$O:m.shape=GE),!m.type&&e.doc&&(V.info("Setting cluster for XCX",u,Sfe(e)),m.type="group",m.isGroup=!0,m.dir=Sfe(e),m.shape=e.type===zE?GO:$O,m.cssClasses=`${m.cssClasses} ${Tfe} ${a?kfe:""}`);let g={labelStyle:"",shape:m.shape,label:m.description,cssClasses:m.cssClasses,cssCompiledStyles:[],cssStyles:m.cssStyles,id:u,dir:m.dir,domId:WO(u,yf),type:m.type,isGroup:m.type==="group",padding:8,rx:10,ry:10,look:s};if(g.shape===GO&&(g.label=""),t&&t.id!=="root"&&(V.trace("Setting node ",u," to be child of its parent ",t.id),g.parentId=t.id),g.centerLabel=!0,e.note){let y={labelStyle:"",shape:yfe,label:e.note.text,cssClasses:wfe,cssStyles:[],cssCompilesStyles:[],id:u+Cfe+"-"+yf,domId:WO(u,yf,HO),type:m.type,isGroup:m.type==="group",padding:de().flowchart.padding,look:s,position:e.note.position},v=u+YO,x={labelStyle:"",shape:vfe,label:e.note.text,cssClasses:m.cssClasses,cssStyles:[],id:u+YO,domId:WO(u,yf,UO),type:"group",isGroup:!0,padding:16,look:s,position:e.note.position};yf++,x.id=v,y.parentId=v,HE(n,x,l),HE(n,y,l),HE(n,g,l);let b=u,w=y.id;e.note.position==="left of"&&(b=y.id,w=u),i.push({id:b+"-"+w,start:b,end:w,arrowhead:"none",arrowTypeEnd:"",style:OO,labelStyle:"",classes:bfe,arrowheadStyle:PO,labelpos:BO,labelType:FO,thickness:zO,look:s})}else HE(n,g,l)}e.doc&&(V.trace("Adding nodes children "),SVe(e,e.doc,r,n,i,!a,s,l))},"dataFetcher"),Afe=o(()=>{YE.clear(),yf=0},"reset")});var qO,LVe,DVe,Lfe,XO=R(()=>{"use strict";_t();ut();L9();oT();yD();xr();UE();qO=o((t,e=FE)=>{if(!t.doc)return e;let r=e;for(let n of t.doc)n.stmt==="dir"&&(r=n.value);return r},"getDir"),LVe=o(function(t,e){return e.db.extract(e.db.getRootDocV2()),e.db.getClasses()},"getClasses"),DVe=o(async function(t,e,r,n){V.info("REF0:"),V.info("Drawing state diagram (v2)",e);let{securityLevel:i,state:a,layout:s}=de();n.db.extract(n.db.getRootDocV2());let l=n.db.getData(),u=I5(e,i);l.type=n.type,l.layoutAlgorithm=s,l.nodeSpacing=a?.nodeSpacing||50,l.rankSpacing=a?.rankSpacing||50,l.markers=["barb"],l.diagramId=e,await sT(l,u);let h=8;Lt.insertTitle(u,"statediagramTitleText",a?.titleTopMargin??25,n.db.getDiagramTitle()),lT(u,h,Gg,a?.useMaxWidth??!0)},"draw"),Lfe={getClasses:LVe,draw:DVe,getDir:qO}});function Pfe(){return new Map}function jO(t=""){let e=t;return t===ZO&&(Cx++,e=`${Mfe}${Cx}`),e}function KO(t="",e=N0){return t===ZO?Mfe:e}function GVe(t=""){let e=t;return t===Ife&&(Cx++,e=`${Ofe}${Cx}`),e}function $Ve(t="",e=N0){return t===Ife?Ofe:e}function VVe(t,e,r){let n=jO(t.id.trim()),i=KO(t.id.trim(),t.type),a=jO(e.id.trim()),s=KO(e.id.trim(),e.type);vf(n,i,t.doc,t.description,t.note,t.classes,t.styles,t.textStyles),vf(a,s,e.doc,e.description,e.note,e.classes,e.styles,e.textStyles),Fs.relations.push({id1:n,id2:a,relationTitle:We.sanitizeText(r,de())})}var ZO,Mfe,Ife,Ofe,Dfe,Rfe,RVe,NVe,XE,JO,Bfe,jE,Vg,Ffe,KE,Fs,Cx,Nfe,MVe,IVe,WE,OVe,PVe,qE,eP,BVe,vf,zfe,M0,Gfe,FVe,zVe,$fe,QO,UVe,HVe,Vfe,YVe,tP,WVe,qVe,XVe,jVe,KVe,QVe,Qo,QE=R(()=>{"use strict";ut();xr();rr();_t();bi();_fe();XO();UE();ZO="[*]",Mfe="start",Ife=ZO,Ofe="end",Dfe="color",Rfe="fill",RVe="bgFill",NVe=",";o(Pfe,"newClassesList");XE=[],JO=[],Bfe=hfe,jE=[],Vg=Pfe(),Ffe=o(()=>({relations:[],states:new Map,documents:{}}),"newDoc"),KE={root:Ffe()},Fs=KE.root,Cx=0,Nfe=0,MVe={LINE:0,DOTTED_LINE:1},IVe={AGGREGATION:0,EXTENSION:1,COMPOSITION:2,DEPENDENCY:3},WE=o(t=>JSON.parse(JSON.stringify(t)),"clone"),OVe=o(t=>{V.info("Setting root doc",t),jE=t},"setRootDoc"),PVe=o(()=>jE,"getRootDoc"),qE=o((t,e,r)=>{if(e.stmt===Ex)qE(t,e.state1,!0),qE(t,e.state2,!1);else if(e.stmt===zg&&(e.id==="[*]"?(e.id=r?t.id+"_start":t.id+"_end",e.start=r):e.id=e.id.trim()),e.doc){let n=[],i=[],a;for(a=0;a0&&i.length>0){let s={stmt:zg,id:Z_(),type:"divider",doc:WE(i)};n.push(WE(s)),e.doc=n}e.doc.forEach(s=>qE(e,s,!0))}},"docTranslator"),eP=o(()=>(qE({id:"root"},{id:"root",doc:jE},!0),{id:"root",doc:jE}),"getRootDocV2"),BVe=o(t=>{let e;t.doc?e=t.doc:e=t,V.info(e),zfe(!0),V.info("Extract initial document:",e),e.forEach(a=>{switch(V.warn("Statement",a.stmt),a.stmt){case zg:vf(a.id.trim(),a.type,a.doc,a.description,a.note,a.classes,a.styles,a.textStyles);break;case Ex:$fe(a.state1,a.state2,a.description);break;case ffe:Vfe(a.id.trim(),a.classes);break;case dfe:{let s=a.id.trim().split(","),l=a.styleClass.split(",");s.forEach(u=>{let h=M0(u);if(h===void 0){let f=u.trim();vf(f),h=M0(f)}h.styles=l.map(f=>f.replace(/;/g,"")?.trim())})}break;case pfe:tP(a.id.trim(),a.styleClass);break}});let r=Gfe(),i=de().look;Afe(),$g(void 0,eP(),r,XE,JO,!0,i,Vg),XE.forEach(a=>{if(Array.isArray(a.label)){if(a.description=a.label.slice(1),a.isGroup&&a.description.length>0)throw new Error("Group nodes can only have label. Remove the additional description for node ["+a.id+"]");a.label=a.label[0]}})},"extract"),vf=o(function(t,e=N0,r=null,n=null,i=null,a=null,s=null,l=null){let u=t?.trim();if(Fs.states.has(u)?(Fs.states.get(u).doc||(Fs.states.get(u).doc=r),Fs.states.get(u).type||(Fs.states.get(u).type=e)):(V.info("Adding state ",u,n),Fs.states.set(u,{id:u,descriptions:[],type:e,doc:r,note:i,classes:[],styles:[],textStyles:[]})),n&&(V.info("Setting state description",u,n),typeof n=="string"&&QO(u,n.trim()),typeof n=="object"&&n.forEach(h=>QO(u,h.trim()))),i){let h=Fs.states.get(u);h.note=i,h.note.text=We.sanitizeText(h.note.text,de())}a&&(V.info("Setting state classes",u,a),(typeof a=="string"?[a]:a).forEach(f=>tP(u,f.trim()))),s&&(V.info("Setting state styles",u,s),(typeof s=="string"?[s]:s).forEach(f=>WVe(u,f.trim()))),l&&(V.info("Setting state styles",u,s),(typeof l=="string"?[l]:l).forEach(f=>qVe(u,f.trim())))},"addState"),zfe=o(function(t){XE=[],JO=[],KE={root:Ffe()},Fs=KE.root,Cx=0,Vg=Pfe(),t||vr()},"clear"),M0=o(function(t){return Fs.states.get(t)},"getState"),Gfe=o(function(){return Fs.states},"getStates"),FVe=o(function(){V.info("Documents = ",KE)},"logDocuments"),zVe=o(function(){return Fs.relations},"getRelations");o(jO,"startIdIfNeeded");o(KO,"startTypeIfNeeded");o(GVe,"endIdIfNeeded");o($Ve,"endTypeIfNeeded");o(VVe,"addRelationObjs");$fe=o(function(t,e,r){if(typeof t=="object")VVe(t,e,r);else{let n=jO(t.trim()),i=KO(t),a=GVe(e.trim()),s=$Ve(e);vf(n,i),vf(a,s),Fs.relations.push({id1:n,id2:a,title:We.sanitizeText(r,de())})}},"addRelation"),QO=o(function(t,e){let r=Fs.states.get(t),n=e.startsWith(":")?e.replace(":","").trim():e;r.descriptions.push(We.sanitizeText(n,de()))},"addDescription"),UVe=o(function(t){return t.substring(0,1)===":"?t.substr(2).trim():t.trim()},"cleanupLabel"),HVe=o(()=>(Nfe++,"divider-id-"+Nfe),"getDividerId"),Vfe=o(function(t,e=""){Vg.has(t)||Vg.set(t,{id:t,styles:[],textStyles:[]});let r=Vg.get(t);e?.split(NVe).forEach(n=>{let i=n.replace(/([^;]*);/,"$1").trim();if(RegExp(Dfe).exec(n)){let s=i.replace(Rfe,RVe).replace(Dfe,Rfe);r.textStyles.push(s)}r.styles.push(i)})},"addStyleClass"),YVe=o(function(){return Vg},"getClasses"),tP=o(function(t,e){t.split(",").forEach(function(r){let n=M0(r);if(n===void 0){let i=r.trim();vf(i),n=M0(i)}n.classes.push(e)})},"setCssClass"),WVe=o(function(t,e){let r=M0(t);r!==void 0&&r.styles.push(e)},"setStyle"),qVe=o(function(t,e){let r=M0(t);r!==void 0&&r.textStyles.push(e)},"setTextStyle"),XVe=o(()=>Bfe,"getDirection"),jVe=o(t=>{Bfe=t},"setDirection"),KVe=o(t=>t&&t[0]===":"?t.substr(1).trim():t.trim(),"trimColon"),QVe=o(()=>{let t=de();return{nodes:XE,edges:JO,other:{},config:t,direction:qO(eP())}},"getData"),Qo={getConfig:o(()=>de().state,"getConfig"),getData:QVe,addState:vf,clear:zfe,getState:M0,getStates:Gfe,getRelations:zVe,getClasses:YVe,getDirection:XVe,addRelation:$fe,getDividerId:HVe,setDirection:jVe,cleanupLabel:UVe,lineType:MVe,relationType:IVe,logDocuments:FVe,getRootDoc:PVe,setRootDoc:OVe,getRootDocV2:eP,extract:BVe,trimColon:KVe,getAccTitle:Ar,setAccTitle:kr,getAccDescription:Lr,setAccDescription:_r,addStyleClass:Vfe,setCssClass:tP,addDescription:QO,setDiagramTitle:nn,getDiagramTitle:Xr}});var ZVe,ZE,rP=R(()=>{"use strict";ZVe=o(t=>` +defs #statediagram-barbEnd { + fill: ${t.transitionColor}; + stroke: ${t.transitionColor}; + } +g.stateGroup text { + fill: ${t.nodeBorder}; + stroke: none; + font-size: 10px; +} +g.stateGroup text { + fill: ${t.textColor}; + stroke: none; + font-size: 10px; + +} +g.stateGroup .state-title { + font-weight: bolder; + fill: ${t.stateLabelColor}; +} + +g.stateGroup rect { + fill: ${t.mainBkg}; + stroke: ${t.nodeBorder}; +} + +g.stateGroup line { + stroke: ${t.lineColor}; + stroke-width: 1; +} + +.transition { + stroke: ${t.transitionColor}; + stroke-width: 1; + fill: none; +} + +.stateGroup .composit { + fill: ${t.background}; + border-bottom: 1px +} + +.stateGroup .alt-composit { + fill: #e0e0e0; + border-bottom: 1px +} + +.state-note { + stroke: ${t.noteBorderColor}; + fill: ${t.noteBkgColor}; + + text { + fill: ${t.noteTextColor}; + stroke: none; + font-size: 10px; + } +} + +.stateLabel .box { + stroke: none; + stroke-width: 0; + fill: ${t.mainBkg}; + opacity: 0.5; +} + +.edgeLabel .label rect { + fill: ${t.labelBackgroundColor}; + opacity: 0.5; +} +.edgeLabel { + background-color: ${t.edgeLabelBackground}; + p { + background-color: ${t.edgeLabelBackground}; + } + rect { + opacity: 0.5; + background-color: ${t.edgeLabelBackground}; + fill: ${t.edgeLabelBackground}; + } + text-align: center; +} +.edgeLabel .label text { + fill: ${t.transitionLabelColor||t.tertiaryTextColor}; +} +.label div .edgeLabel { + color: ${t.transitionLabelColor||t.tertiaryTextColor}; +} + +.stateLabel text { + fill: ${t.stateLabelColor}; + font-size: 10px; + font-weight: bold; +} + +.node circle.state-start { + fill: ${t.specialStateColor}; + stroke: ${t.specialStateColor}; +} + +.node .fork-join { + fill: ${t.specialStateColor}; + stroke: ${t.specialStateColor}; +} + +.node circle.state-end { + fill: ${t.innerEndBackground}; + stroke: ${t.background}; + stroke-width: 1.5 +} +.end-state-inner { + fill: ${t.compositeBackground||t.background}; + // stroke: ${t.background}; + stroke-width: 1.5 +} + +.node rect { + fill: ${t.stateBkg||t.mainBkg}; + stroke: ${t.stateBorder||t.nodeBorder}; + stroke-width: 1px; +} +.node polygon { + fill: ${t.mainBkg}; + stroke: ${t.stateBorder||t.nodeBorder};; + stroke-width: 1px; +} +#statediagram-barbEnd { + fill: ${t.lineColor}; +} + +.statediagram-cluster rect { + fill: ${t.compositeTitleBackground}; + stroke: ${t.stateBorder||t.nodeBorder}; + stroke-width: 1px; +} + +.cluster-label, .nodeLabel { + color: ${t.stateLabelColor}; + // line-height: 1; +} + +.statediagram-cluster rect.outer { + rx: 5px; + ry: 5px; +} +.statediagram-state .divider { + stroke: ${t.stateBorder||t.nodeBorder}; +} + +.statediagram-state .title-state { + rx: 5px; + ry: 5px; +} +.statediagram-cluster.statediagram-cluster .inner { + fill: ${t.compositeBackground||t.background}; +} +.statediagram-cluster.statediagram-cluster-alt .inner { + fill: ${t.altBackground?t.altBackground:"#efefef"}; +} + +.statediagram-cluster .inner { + rx:0; + ry:0; +} + +.statediagram-state rect.basic { + rx: 5px; + ry: 5px; +} +.statediagram-state rect.divider { + stroke-dasharray: 10,10; + fill: ${t.altBackground?t.altBackground:"#efefef"}; +} + +.note-edge { + stroke-dasharray: 5; +} + +.statediagram-note rect { + fill: ${t.noteBkgColor}; + stroke: ${t.noteBorderColor}; + stroke-width: 1px; + rx: 0; + ry: 0; +} +.statediagram-note rect { + fill: ${t.noteBkgColor}; + stroke: ${t.noteBorderColor}; + stroke-width: 1px; + rx: 0; + ry: 0; +} + +.statediagram-note text { + fill: ${t.noteTextColor}; +} + +.statediagram-note .nodeLabel { + color: ${t.noteTextColor}; +} +.statediagram .edgeLabel { + color: red; // ${t.noteTextColor}; +} + +#dependencyStart, #dependencyEnd { + fill: ${t.lineColor}; + stroke: ${t.lineColor}; + stroke-width: 1; +} + +.statediagramTitleText { + text-anchor: middle; + font-size: 18px; + fill: ${t.textColor}; +} +`,"getStyles"),ZE=ZVe});var nP,JVe,eUe,Ufe,tUe,Hfe,Yfe=R(()=>{"use strict";nP={},JVe=o((t,e)=>{nP[t]=e},"set"),eUe=o(t=>nP[t],"get"),Ufe=o(()=>Object.keys(nP),"keys"),tUe=o(()=>Ufe().length,"size"),Hfe={get:eUe,set:JVe,keys:Ufe,size:tUe}});var rUe,nUe,iUe,aUe,qfe,sUe,oUe,lUe,cUe,iP,Wfe,Xfe,jfe=R(()=>{"use strict";Zt();Yfe();QE();xr();rr();_t();ut();rUe=o(t=>t.append("circle").attr("class","start-state").attr("r",de().state.sizeUnit).attr("cx",de().state.padding+de().state.sizeUnit).attr("cy",de().state.padding+de().state.sizeUnit),"drawStartState"),nUe=o(t=>t.append("line").style("stroke","grey").style("stroke-dasharray","3").attr("x1",de().state.textHeight).attr("class","divider").attr("x2",de().state.textHeight*2).attr("y1",0).attr("y2",0),"drawDivider"),iUe=o((t,e)=>{let r=t.append("text").attr("x",2*de().state.padding).attr("y",de().state.textHeight+2*de().state.padding).attr("font-size",de().state.fontSize).attr("class","state-title").text(e.id),n=r.node().getBBox();return t.insert("rect",":first-child").attr("x",de().state.padding).attr("y",de().state.padding).attr("width",n.width+2*de().state.padding).attr("height",n.height+2*de().state.padding).attr("rx",de().state.radius),r},"drawSimpleState"),aUe=o((t,e)=>{let r=o(function(p,m,g){let y=p.append("tspan").attr("x",2*de().state.padding).text(m);g||y.attr("dy",de().state.textHeight)},"addTspan"),i=t.append("text").attr("x",2*de().state.padding).attr("y",de().state.textHeight+1.3*de().state.padding).attr("font-size",de().state.fontSize).attr("class","state-title").text(e.descriptions[0]).node().getBBox(),a=i.height,s=t.append("text").attr("x",de().state.padding).attr("y",a+de().state.padding*.4+de().state.dividerMargin+de().state.textHeight).attr("class","state-description"),l=!0,u=!0;e.descriptions.forEach(function(p){l||(r(s,p,u),u=!1),l=!1});let h=t.append("line").attr("x1",de().state.padding).attr("y1",de().state.padding+a+de().state.dividerMargin/2).attr("y2",de().state.padding+a+de().state.dividerMargin/2).attr("class","descr-divider"),f=s.node().getBBox(),d=Math.max(f.width,i.width);return h.attr("x2",d+3*de().state.padding),t.insert("rect",":first-child").attr("x",de().state.padding).attr("y",de().state.padding).attr("width",d+2*de().state.padding).attr("height",f.height+a+2*de().state.padding).attr("rx",de().state.radius),t},"drawDescrState"),qfe=o((t,e,r)=>{let n=de().state.padding,i=2*de().state.padding,a=t.node().getBBox(),s=a.width,l=a.x,u=t.append("text").attr("x",0).attr("y",de().state.titleShift).attr("font-size",de().state.fontSize).attr("class","state-title").text(e.id),f=u.node().getBBox().width+i,d=Math.max(f,s);d===s&&(d=d+i);let p,m=t.node().getBBox();e.doc,p=l-n,f>s&&(p=(s-d)/2+n),Math.abs(l-m.x)s&&(p=l-(f-s)/2);let g=1-de().state.textHeight;return t.insert("rect",":first-child").attr("x",p).attr("y",g).attr("class",r?"alt-composit":"composit").attr("width",d).attr("height",m.height+de().state.textHeight+de().state.titleShift+1).attr("rx","0"),u.attr("x",p+n),f<=s&&u.attr("x",l+(d-i)/2-f/2+n),t.insert("rect",":first-child").attr("x",p).attr("y",de().state.titleShift-de().state.textHeight-de().state.padding).attr("width",d).attr("height",de().state.textHeight*3).attr("rx",de().state.radius),t.insert("rect",":first-child").attr("x",p).attr("y",de().state.titleShift-de().state.textHeight-de().state.padding).attr("width",d).attr("height",m.height+3+2*de().state.textHeight).attr("rx",de().state.radius),t},"addTitleAndBox"),sUe=o(t=>(t.append("circle").attr("class","end-state-outer").attr("r",de().state.sizeUnit+de().state.miniPadding).attr("cx",de().state.padding+de().state.sizeUnit+de().state.miniPadding).attr("cy",de().state.padding+de().state.sizeUnit+de().state.miniPadding),t.append("circle").attr("class","end-state-inner").attr("r",de().state.sizeUnit).attr("cx",de().state.padding+de().state.sizeUnit+2).attr("cy",de().state.padding+de().state.sizeUnit+2)),"drawEndState"),oUe=o((t,e)=>{let r=de().state.forkWidth,n=de().state.forkHeight;if(e.parentId){let i=r;r=n,n=i}return t.append("rect").style("stroke","black").style("fill","black").attr("width",r).attr("height",n).attr("x",de().state.padding).attr("y",de().state.padding)},"drawForkJoinState"),lUe=o((t,e,r,n)=>{let i=0,a=n.append("text");a.style("text-anchor","start"),a.attr("class","noteText");let s=t.replace(/\r\n/g,"
    ");s=s.replace(/\n/g,"
    ");let l=s.split(We.lineBreakRegex),u=1.25*de().state.noteMargin;for(let h of l){let f=h.trim();if(f.length>0){let d=a.append("tspan");if(d.text(f),u===0){let p=d.node().getBBox();u+=p.height}i+=u,d.attr("x",e+de().state.noteMargin),d.attr("y",r+i+1.25*de().state.noteMargin)}}return{textWidth:a.node().getBBox().width,textHeight:i}},"_drawLongText"),cUe=o((t,e)=>{e.attr("class","state-note");let r=e.append("rect").attr("x",0).attr("y",de().state.padding),n=e.append("g"),{textWidth:i,textHeight:a}=lUe(t,0,0,n);return r.attr("height",a+2*de().state.noteMargin),r.attr("width",i+de().state.noteMargin*2),r},"drawNote"),iP=o(function(t,e){let r=e.id,n={id:r,label:e.id,width:0,height:0},i=t.append("g").attr("id",r).attr("class","stateGroup");e.type==="start"&&rUe(i),e.type==="end"&&sUe(i),(e.type==="fork"||e.type==="join")&&oUe(i,e),e.type==="note"&&cUe(e.note.text,i),e.type==="divider"&&nUe(i),e.type==="default"&&e.descriptions.length===0&&iUe(i,e),e.type==="default"&&e.descriptions.length>0&&aUe(i,e);let a=i.node().getBBox();return n.width=a.width+2*de().state.padding,n.height=a.height+2*de().state.padding,Hfe.set(r,n),n},"drawState"),Wfe=0,Xfe=o(function(t,e,r){let n=o(function(u){switch(u){case Qo.relationType.AGGREGATION:return"aggregation";case Qo.relationType.EXTENSION:return"extension";case Qo.relationType.COMPOSITION:return"composition";case Qo.relationType.DEPENDENCY:return"dependency"}},"getRelationType");e.points=e.points.filter(u=>!Number.isNaN(u.y));let i=e.points,a=ha().x(function(u){return u.x}).y(function(u){return u.y}).curve(vs),s=t.append("path").attr("d",a(i)).attr("id","edge"+Wfe).attr("class","transition"),l="";if(de().state.arrowMarkerAbsolute&&(l=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search,l=l.replace(/\(/g,"\\("),l=l.replace(/\)/g,"\\)")),s.attr("marker-end","url("+l+"#"+n(Qo.relationType.DEPENDENCY)+"End)"),r.title!==void 0){let u=t.append("g").attr("class","stateLabel"),{x:h,y:f}=Lt.calcLabelPosition(e.points),d=We.getRows(r.title),p=0,m=[],g=0,y=0;for(let b=0;b<=d.length;b++){let w=u.append("text").attr("text-anchor","middle").text(d[b]).attr("x",h).attr("y",f+p),S=w.node().getBBox();g=Math.max(g,S.width),y=Math.min(y,S.x),V.info(S.x,h,f+p),p===0&&(p=w.node().getBBox().height,V.info("Title height",p,f)),m.push(w)}let v=p*d.length;if(d.length>1){let b=(d.length-1)*p*.5;m.forEach((w,S)=>w.attr("y",f+S*p-b)),v=p*d.length}let x=u.node().getBBox();u.insert("rect",":first-child").attr("class","box").attr("x",h-g/2-de().state.padding/2).attr("y",f-v/2-de().state.padding/2-3.5).attr("width",g+de().state.padding).attr("height",v+de().state.padding),V.info(x)}Wfe++},"drawEdge")});var vo,aP,uUe,hUe,fUe,dUe,Kfe,Qfe,Zfe=R(()=>{"use strict";Zt();Vd();ya();ut();rr();jfe();_t();Yn();aP={},uUe=o(function(){},"setConf"),hUe=o(function(t){t.append("defs").append("marker").attr("id","dependencyEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 19,7 L9,13 L14,7 L9,1 Z")},"insertMarkers"),fUe=o(function(t,e,r,n){vo=de().state;let i=de().securityLevel,a;i==="sandbox"&&(a=$e("#i"+e));let s=i==="sandbox"?$e(a.nodes()[0].contentDocument.body):$e("body"),l=i==="sandbox"?a.nodes()[0].contentDocument:document;V.debug("Rendering diagram "+t);let u=s.select(`[id='${e}']`);hUe(u);let h=n.db.getRootDoc();Kfe(h,u,void 0,!1,s,l,n);let f=vo.padding,d=u.node().getBBox(),p=d.width+f*2,m=d.height+f*2,g=p*1.75;Sr(u,m,g,vo.useMaxWidth),u.attr("viewBox",`${d.x-vo.padding} ${d.y-vo.padding} `+p+" "+m)},"draw"),dUe=o(t=>t?t.length*vo.fontSizeFactor:1,"getLabelWidth"),Kfe=o((t,e,r,n,i,a,s)=>{let l=new lr({compound:!0,multigraph:!0}),u,h=!0;for(u=0;u{let T=S.parentElement,E=0,_=0;T&&(T.parentElement&&(E=T.parentElement.getBBox().width),_=parseInt(T.getAttribute("data-x-shift"),10),Number.isNaN(_)&&(_=0)),S.setAttribute("x1",0-_+8),S.setAttribute("x2",E-_-8)})):V.debug("No Node "+b+": "+JSON.stringify(l.node(b)))});let v=y.getBBox();l.edges().forEach(function(b){b!==void 0&&l.edge(b)!==void 0&&(V.debug("Edge "+b.v+" -> "+b.w+": "+JSON.stringify(l.edge(b))),Xfe(e,l.edge(b),l.edge(b).relation))}),v=y.getBBox();let x={id:r||"root",label:r||"root",width:0,height:0};return x.width=v.width+2*vo.padding,x.height=v.height+2*vo.padding,V.debug("Doc rendered",x,l),x},"renderDoc"),Qfe={setConf:uUe,draw:fUe}});var Jfe={};hr(Jfe,{diagram:()=>pUe});var pUe,ede=R(()=>{"use strict";IO();QE();rP();Zfe();pUe={parser:BE,db:Qo,renderer:Qfe,styles:ZE,init:o(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute,Qo.clear()},"init")}});var nde={};hr(nde,{diagram:()=>vUe});var vUe,ide=R(()=>{"use strict";IO();QE();rP();XO();vUe={parser:BE,db:Qo,renderer:Lfe,styles:ZE,init:o(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute,Qo.clear()},"init")}});var sP,ode,lde=R(()=>{"use strict";sP=function(){var t=o(function(d,p,m,g){for(m=m||{},g=d.length;g--;m[d[g]]=p);return m},"o"),e=[6,8,10,11,12,14,16,17,18],r=[1,9],n=[1,10],i=[1,11],a=[1,12],s=[1,13],l=[1,14],u={trace:o(function(){},"trace"),yy:{},symbols_:{error:2,start:3,journey:4,document:5,EOF:6,line:7,SPACE:8,statement:9,NEWLINE:10,title:11,acc_title:12,acc_title_value:13,acc_descr:14,acc_descr_value:15,acc_descr_multiline_value:16,section:17,taskName:18,taskData:19,$accept:0,$end:1},terminals_:{2:"error",4:"journey",6:"EOF",8:"SPACE",10:"NEWLINE",11:"title",12:"acc_title",13:"acc_title_value",14:"acc_descr",15:"acc_descr_value",16:"acc_descr_multiline_value",17:"section",18:"taskName",19:"taskData"},productions_:[0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[9,1],[9,2],[9,2],[9,1],[9,1],[9,2]],performAction:o(function(p,m,g,y,v,x,b){var w=x.length-1;switch(v){case 1:return x[w-1];case 2:this.$=[];break;case 3:x[w-1].push(x[w]),this.$=x[w-1];break;case 4:case 5:this.$=x[w];break;case 6:case 7:this.$=[];break;case 8:y.setDiagramTitle(x[w].substr(6)),this.$=x[w].substr(6);break;case 9:this.$=x[w].trim(),y.setAccTitle(this.$);break;case 10:case 11:this.$=x[w].trim(),y.setAccDescription(this.$);break;case 12:y.addSection(x[w].substr(8)),this.$=x[w].substr(8);break;case 13:y.addTask(x[w-1],x[w]),this.$="task";break}},"anonymous"),table:[{3:1,4:[1,2]},{1:[3]},t(e,[2,2],{5:3}),{6:[1,4],7:5,8:[1,6],9:7,10:[1,8],11:r,12:n,14:i,16:a,17:s,18:l},t(e,[2,7],{1:[2,1]}),t(e,[2,3]),{9:15,11:r,12:n,14:i,16:a,17:s,18:l},t(e,[2,5]),t(e,[2,6]),t(e,[2,8]),{13:[1,16]},{15:[1,17]},t(e,[2,11]),t(e,[2,12]),{19:[1,18]},t(e,[2,4]),t(e,[2,9]),t(e,[2,10]),t(e,[2,13])],defaultActions:{},parseError:o(function(p,m){if(m.recoverable)this.trace(p);else{var g=new Error(p);throw g.hash=m,g}},"parseError"),parse:o(function(p){var m=this,g=[0],y=[],v=[null],x=[],b=this.table,w="",S=0,T=0,E=0,_=2,A=1,L=x.slice.call(arguments,1),M=Object.create(this.lexer),N={yy:{}};for(var k in this.yy)Object.prototype.hasOwnProperty.call(this.yy,k)&&(N.yy[k]=this.yy[k]);M.setInput(p,N.yy),N.yy.lexer=M,N.yy.parser=this,typeof M.yylloc>"u"&&(M.yylloc={});var I=M.yylloc;x.push(I);var C=M.options&&M.options.ranges;typeof N.yy.parseError=="function"?this.parseError=N.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function O(H){g.length=g.length-2*H,v.length=v.length-H,x.length=x.length-H}o(O,"popStack");function D(){var H;return H=y.pop()||M.lex()||A,typeof H!="number"&&(H instanceof Array&&(y=H,H=y.pop()),H=m.symbols_[H]||H),H}o(D,"lex");for(var P,F,B,$,z,Y,Q={},X,ie,j,J;;){if(B=g[g.length-1],this.defaultActions[B]?$=this.defaultActions[B]:((P===null||typeof P>"u")&&(P=D()),$=b[B]&&b[B][P]),typeof $>"u"||!$.length||!$[0]){var Z="";J=[];for(X in b[B])this.terminals_[X]&&X>_&&J.push("'"+this.terminals_[X]+"'");M.showPosition?Z="Parse error on line "+(S+1)+`: +`+M.showPosition()+` +Expecting `+J.join(", ")+", got '"+(this.terminals_[P]||P)+"'":Z="Parse error on line "+(S+1)+": Unexpected "+(P==A?"end of input":"'"+(this.terminals_[P]||P)+"'"),this.parseError(Z,{text:M.match,token:this.terminals_[P]||P,line:M.yylineno,loc:I,expected:J})}if($[0]instanceof Array&&$.length>1)throw new Error("Parse Error: multiple actions possible at state: "+B+", token: "+P);switch($[0]){case 1:g.push(P),v.push(M.yytext),x.push(M.yylloc),g.push($[1]),P=null,F?(P=F,F=null):(T=M.yyleng,w=M.yytext,S=M.yylineno,I=M.yylloc,E>0&&E--);break;case 2:if(ie=this.productions_[$[1]][1],Q.$=v[v.length-ie],Q._$={first_line:x[x.length-(ie||1)].first_line,last_line:x[x.length-1].last_line,first_column:x[x.length-(ie||1)].first_column,last_column:x[x.length-1].last_column},C&&(Q._$.range=[x[x.length-(ie||1)].range[0],x[x.length-1].range[1]]),Y=this.performAction.apply(Q,[w,T,S,N.yy,$[1],v,x].concat(L)),typeof Y<"u")return Y;ie&&(g=g.slice(0,-1*ie*2),v=v.slice(0,-1*ie),x=x.slice(0,-1*ie)),g.push(this.productions_[$[1]][0]),v.push(Q.$),x.push(Q._$),j=b[g[g.length-2]][g[g.length-1]],g.push(j);break;case 3:return!0}}return!0},"parse")},h=function(){var d={EOF:1,parseError:o(function(m,g){if(this.yy.parser)this.yy.parser.parseError(m,g);else throw new Error(m)},"parseError"),setInput:o(function(p,m){return this.yy=m||this.yy||{},this._input=p,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:o(function(){var p=this._input[0];this.yytext+=p,this.yyleng++,this.offset++,this.match+=p,this.matched+=p;var m=p.match(/(?:\r\n?|\n).*/g);return m?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),p},"input"),unput:o(function(p){var m=p.length,g=p.split(/(?:\r\n?|\n)/g);this._input=p+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-m),this.offset-=m;var y=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),g.length-1&&(this.yylineno-=g.length-1);var v=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:g?(g.length===y.length?this.yylloc.first_column:0)+y[y.length-g.length].length-g[0].length:this.yylloc.first_column-m},this.options.ranges&&(this.yylloc.range=[v[0],v[0]+this.yyleng-m]),this.yyleng=this.yytext.length,this},"unput"),more:o(function(){return this._more=!0,this},"more"),reject:o(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:o(function(p){this.unput(this.match.slice(p))},"less"),pastInput:o(function(){var p=this.matched.substr(0,this.matched.length-this.match.length);return(p.length>20?"...":"")+p.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:o(function(){var p=this.match;return p.length<20&&(p+=this._input.substr(0,20-p.length)),(p.substr(0,20)+(p.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:o(function(){var p=this.pastInput(),m=new Array(p.length+1).join("-");return p+this.upcomingInput()+` +`+m+"^"},"showPosition"),test_match:o(function(p,m){var g,y,v;if(this.options.backtrack_lexer&&(v={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(v.yylloc.range=this.yylloc.range.slice(0))),y=p[0].match(/(?:\r\n?|\n).*/g),y&&(this.yylineno+=y.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:y?y[y.length-1].length-y[y.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+p[0].length},this.yytext+=p[0],this.match+=p[0],this.matches=p,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(p[0].length),this.matched+=p[0],g=this.performAction.call(this,this.yy,this,m,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),g)return g;if(this._backtrack){for(var x in v)this[x]=v[x];return!1}return!1},"test_match"),next:o(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var p,m,g,y;this._more||(this.yytext="",this.match="");for(var v=this._currentRules(),x=0;xm[0].length)){if(m=g,y=x,this.options.backtrack_lexer){if(p=this.test_match(g,v[x]),p!==!1)return p;if(this._backtrack){m=!1;continue}else return!1}else if(!this.options.flex)break}return m?(p=this.test_match(m,v[y]),p!==!1?p:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:o(function(){var m=this.next();return m||this.lex()},"lex"),begin:o(function(m){this.conditionStack.push(m)},"begin"),popState:o(function(){var m=this.conditionStack.length-1;return m>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:o(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:o(function(m){return m=this.conditionStack.length-1-Math.abs(m||0),m>=0?this.conditionStack[m]:"INITIAL"},"topState"),pushState:o(function(m){this.begin(m)},"pushState"),stateStackSize:o(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:o(function(m,g,y,v){var x=v;switch(y){case 0:break;case 1:break;case 2:return 10;case 3:break;case 4:break;case 5:return 4;case 6:return 11;case 7:return this.begin("acc_title"),12;break;case 8:return this.popState(),"acc_title_value";break;case 9:return this.begin("acc_descr"),14;break;case 10:return this.popState(),"acc_descr_value";break;case 11:this.begin("acc_descr_multiline");break;case 12:this.popState();break;case 13:return"acc_descr_multiline_value";case 14:return 17;case 15:return 18;case 16:return 19;case 17:return":";case 18:return 6;case 19:return"INVALID"}},"anonymous"),rules:[/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:journey\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:section\s[^#:\n;]+)/i,/^(?:[^#:\n;]+)/i,/^(?::[^#\n;]+)/i,/^(?::)/i,/^(?:$)/i,/^(?:.)/i],conditions:{acc_descr_multiline:{rules:[12,13],inclusive:!1},acc_descr:{rules:[10],inclusive:!1},acc_title:{rules:[8],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,7,9,11,14,15,16,17,18,19],inclusive:!0}}};return d}();u.lexer=h;function f(){this.yy={}}return o(f,"Parser"),f.prototype=u,u.Parser=f,new f}();sP.parser=sP;ode=sP});var Ug,oP,Sx,Ax,TUe,kUe,EUe,CUe,SUe,AUe,_Ue,cde,LUe,lP,ude=R(()=>{"use strict";_t();bi();Ug="",oP=[],Sx=[],Ax=[],TUe=o(function(){oP.length=0,Sx.length=0,Ug="",Ax.length=0,vr()},"clear"),kUe=o(function(t){Ug=t,oP.push(t)},"addSection"),EUe=o(function(){return oP},"getSections"),CUe=o(function(){let t=cde(),e=100,r=0;for(;!t&&r{r.people&&t.push(...r.people)}),[...new Set(t)].sort()},"updateActors"),AUe=o(function(t,e){let r=e.substr(1).split(":"),n=0,i=[];r.length===1?(n=Number(r[0]),i=[]):(n=Number(r[0]),i=r[1].split(","));let a=i.map(l=>l.trim()),s={section:Ug,type:Ug,people:a,task:t,score:n};Ax.push(s)},"addTask"),_Ue=o(function(t){let e={section:Ug,type:Ug,description:t,task:t,classes:[]};Sx.push(e)},"addTaskOrg"),cde=o(function(){let t=o(function(r){return Ax[r].processed},"compileTask"),e=!0;for(let[r,n]of Ax.entries())t(r),e=e&&n.processed;return e},"compileTasks"),LUe=o(function(){return SUe()},"getActors"),lP={getConfig:o(()=>de().journey,"getConfig"),clear:TUe,setDiagramTitle:nn,getDiagramTitle:Xr,setAccTitle:kr,getAccTitle:Ar,setAccDescription:_r,getAccDescription:Lr,addSection:kUe,getSections:EUe,getTasks:CUe,addTask:AUe,addTaskOrg:_Ue,getActors:LUe}});var DUe,hde,fde=R(()=>{"use strict";DUe=o(t=>`.label { + font-family: 'trebuchet ms', verdana, arial, sans-serif; + font-family: var(--mermaid-font-family); + color: ${t.textColor}; + } + .mouth { + stroke: #666; + } + + line { + stroke: ${t.textColor} + } + + .legend { + fill: ${t.textColor}; + } + + .label text { + fill: #333; + } + .label { + color: ${t.textColor} + } + + .face { + ${t.faceColor?`fill: ${t.faceColor}`:"fill: #FFF8DC"}; + stroke: #999; + } + + .node rect, + .node circle, + .node ellipse, + .node polygon, + .node path { + fill: ${t.mainBkg}; + stroke: ${t.nodeBorder}; + stroke-width: 1px; + } + + .node .label { + text-align: center; + } + .node.clickable { + cursor: pointer; + } + + .arrowheadPath { + fill: ${t.arrowheadColor}; + } + + .edgePath .path { + stroke: ${t.lineColor}; + stroke-width: 1.5px; + } + + .flowchart-link { + stroke: ${t.lineColor}; + fill: none; + } + + .edgeLabel { + background-color: ${t.edgeLabelBackground}; + rect { + opacity: 0.5; + } + text-align: center; + } + + .cluster rect { + } + + .cluster text { + fill: ${t.titleColor}; + } + + div.mermaidTooltip { + position: absolute; + text-align: center; + max-width: 200px; + padding: 2px; + font-family: 'trebuchet ms', verdana, arial, sans-serif; + font-family: var(--mermaid-font-family); + font-size: 12px; + background: ${t.tertiaryColor}; + border: 1px solid ${t.border2}; + border-radius: 2px; + pointer-events: none; + z-index: 100; + } + + .task-type-0, .section-type-0 { + ${t.fillType0?`fill: ${t.fillType0}`:""}; + } + .task-type-1, .section-type-1 { + ${t.fillType0?`fill: ${t.fillType1}`:""}; + } + .task-type-2, .section-type-2 { + ${t.fillType0?`fill: ${t.fillType2}`:""}; + } + .task-type-3, .section-type-3 { + ${t.fillType0?`fill: ${t.fillType3}`:""}; + } + .task-type-4, .section-type-4 { + ${t.fillType0?`fill: ${t.fillType4}`:""}; + } + .task-type-5, .section-type-5 { + ${t.fillType0?`fill: ${t.fillType5}`:""}; + } + .task-type-6, .section-type-6 { + ${t.fillType0?`fill: ${t.fillType6}`:""}; + } + .task-type-7, .section-type-7 { + ${t.fillType0?`fill: ${t.fillType7}`:""}; + } + + .actor-0 { + ${t.actor0?`fill: ${t.actor0}`:""}; + } + .actor-1 { + ${t.actor1?`fill: ${t.actor1}`:""}; + } + .actor-2 { + ${t.actor2?`fill: ${t.actor2}`:""}; + } + .actor-3 { + ${t.actor3?`fill: ${t.actor3}`:""}; + } + .actor-4 { + ${t.actor4?`fill: ${t.actor4}`:""}; + } + .actor-5 { + ${t.actor5?`fill: ${t.actor5}`:""}; + } +`,"getStyles"),hde=DUe});var cP,RUe,pde,mde,NUe,MUe,dde,IUe,OUe,gde,PUe,Hg,yde=R(()=>{"use strict";Zt();Qy();cP=o(function(t,e){return yd(t,e)},"drawRect"),RUe=o(function(t,e){let n=t.append("circle").attr("cx",e.cx).attr("cy",e.cy).attr("class","face").attr("r",15).attr("stroke-width",2).attr("overflow","visible"),i=t.append("g");i.append("circle").attr("cx",e.cx-15/3).attr("cy",e.cy-15/3).attr("r",1.5).attr("stroke-width",2).attr("fill","#666").attr("stroke","#666"),i.append("circle").attr("cx",e.cx+15/3).attr("cy",e.cy-15/3).attr("r",1.5).attr("stroke-width",2).attr("fill","#666").attr("stroke","#666");function a(u){let h=bl().startAngle(Math.PI/2).endAngle(3*(Math.PI/2)).innerRadius(7.5).outerRadius(6.8181818181818175);u.append("path").attr("class","mouth").attr("d",h).attr("transform","translate("+e.cx+","+(e.cy+2)+")")}o(a,"smile");function s(u){let h=bl().startAngle(3*Math.PI/2).endAngle(5*(Math.PI/2)).innerRadius(7.5).outerRadius(6.8181818181818175);u.append("path").attr("class","mouth").attr("d",h).attr("transform","translate("+e.cx+","+(e.cy+7)+")")}o(s,"sad");function l(u){u.append("line").attr("class","mouth").attr("stroke",2).attr("x1",e.cx-5).attr("y1",e.cy+7).attr("x2",e.cx+5).attr("y2",e.cy+7).attr("class","mouth").attr("stroke-width","1px").attr("stroke","#666")}return o(l,"ambivalent"),e.score>3?a(i):e.score<3?s(i):l(i),n},"drawFace"),pde=o(function(t,e){let r=t.append("circle");return r.attr("cx",e.cx),r.attr("cy",e.cy),r.attr("class","actor-"+e.pos),r.attr("fill",e.fill),r.attr("stroke",e.stroke),r.attr("r",e.r),r.class!==void 0&&r.attr("class",r.class),e.title!==void 0&&r.append("title").text(e.title),r},"drawCircle"),mde=o(function(t,e){return TW(t,e)},"drawText"),NUe=o(function(t,e){function r(i,a,s,l,u){return i+","+a+" "+(i+s)+","+a+" "+(i+s)+","+(a+l-u)+" "+(i+s-u*1.2)+","+(a+l)+" "+i+","+(a+l)}o(r,"genPoints");let n=t.append("polygon");n.attr("points",r(e.x,e.y,50,20,7)),n.attr("class","labelBox"),e.y=e.y+e.labelMargin,e.x=e.x+.5*e.labelMargin,mde(t,e)},"drawLabel"),MUe=o(function(t,e,r){let n=t.append("g"),i=wl();i.x=e.x,i.y=e.y,i.fill=e.fill,i.width=r.width*e.taskCount+r.diagramMarginX*(e.taskCount-1),i.height=r.height,i.class="journey-section section-type-"+e.num,i.rx=3,i.ry=3,cP(n,i),gde(r)(e.text,n,i.x,i.y,i.width,i.height,{class:"journey-section section-type-"+e.num},r,e.colour)},"drawSection"),dde=-1,IUe=o(function(t,e,r){let n=e.x+r.width/2,i=t.append("g");dde++;let a=300+5*30;i.append("line").attr("id","task"+dde).attr("x1",n).attr("y1",e.y).attr("x2",n).attr("y2",a).attr("class","task-line").attr("stroke-width","1px").attr("stroke-dasharray","4 2").attr("stroke","#666"),RUe(i,{cx:n,cy:300+(5-e.score)*30,score:e.score});let s=wl();s.x=e.x,s.y=e.y,s.fill=e.fill,s.width=r.width,s.height=r.height,s.class="task task-type-"+e.num,s.rx=3,s.ry=3,cP(i,s);let l=e.x+14;e.people.forEach(u=>{let h=e.actors[u].color,f={cx:l,cy:e.y,r:7,fill:h,stroke:"#000",title:u,pos:e.actors[u].position};pde(i,f),l+=10}),gde(r)(e.task,i,s.x,s.y,s.width,s.height,{class:"task"},r,e.colour)},"drawTask"),OUe=o(function(t,e){j3(t,e)},"drawBackgroundRect"),gde=function(){function t(i,a,s,l,u,h,f,d){let p=a.append("text").attr("x",s+u/2).attr("y",l+h/2+5).style("font-color",d).style("text-anchor","middle").text(i);n(p,f)}o(t,"byText");function e(i,a,s,l,u,h,f,d,p){let{taskFontSize:m,taskFontFamily:g}=d,y=i.split(//gi);for(let v=0;v{let i=Xu[n].color,a={cx:20,cy:r,r:7,fill:i,stroke:"#000",pos:Xu[n].position};Hg.drawCircle(t,a);let s={x:40,y:r+7,fill:"#666",text:n,textMargin:e.boxTextMargin|5};Hg.drawText(t,s),r+=20})}var BUe,Xu,JE,I0,zUe,Zo,uP,vde,GUe,hP,xde=R(()=>{"use strict";Zt();yde();_t();Yn();BUe=o(function(t){Object.keys(t).forEach(function(r){JE[r]=t[r]})},"setConf"),Xu={};o(FUe,"drawActorLegend");JE=de().journey,I0=JE.leftMargin,zUe=o(function(t,e,r,n){let i=de().journey,a=de().securityLevel,s;a==="sandbox"&&(s=$e("#i"+e));let l=a==="sandbox"?$e(s.nodes()[0].contentDocument.body):$e("body");Zo.init();let u=l.select("#"+e);Hg.initGraphics(u);let h=n.db.getTasks(),f=n.db.getDiagramTitle(),d=n.db.getActors();for(let x in Xu)delete Xu[x];let p=0;d.forEach(x=>{Xu[x]={color:i.actorColours[p%i.actorColours.length],position:p},p++}),FUe(u),Zo.insert(0,0,I0,Object.keys(Xu).length*50),GUe(u,h,0);let m=Zo.getBounds();f&&u.append("text").text(f).attr("x",I0).attr("font-size","4ex").attr("font-weight","bold").attr("y",25);let g=m.stopy-m.starty+2*i.diagramMarginY,y=I0+m.stopx+2*i.diagramMarginX;Sr(u,g,y,i.useMaxWidth),u.append("line").attr("x1",I0).attr("y1",i.height*4).attr("x2",y-I0-4).attr("y2",i.height*4).attr("stroke-width",4).attr("stroke","black").attr("marker-end","url(#arrowhead)");let v=f?70:0;u.attr("viewBox",`${m.startx} -25 ${y} ${g+v}`),u.attr("preserveAspectRatio","xMinYMin meet"),u.attr("height",g+v+25)},"draw"),Zo={data:{startx:void 0,stopx:void 0,starty:void 0,stopy:void 0},verticalPos:0,sequenceItems:[],init:o(function(){this.sequenceItems=[],this.data={startx:void 0,stopx:void 0,starty:void 0,stopy:void 0},this.verticalPos=0},"init"),updateVal:o(function(t,e,r,n){t[e]===void 0?t[e]=r:t[e]=n(r,t[e])},"updateVal"),updateBounds:o(function(t,e,r,n){let i=de().journey,a=this,s=0;function l(u){return o(function(f){s++;let d=a.sequenceItems.length-s+1;a.updateVal(f,"starty",e-d*i.boxMargin,Math.min),a.updateVal(f,"stopy",n+d*i.boxMargin,Math.max),a.updateVal(Zo.data,"startx",t-d*i.boxMargin,Math.min),a.updateVal(Zo.data,"stopx",r+d*i.boxMargin,Math.max),u!=="activation"&&(a.updateVal(f,"startx",t-d*i.boxMargin,Math.min),a.updateVal(f,"stopx",r+d*i.boxMargin,Math.max),a.updateVal(Zo.data,"starty",e-d*i.boxMargin,Math.min),a.updateVal(Zo.data,"stopy",n+d*i.boxMargin,Math.max))},"updateItemBounds")}o(l,"updateFn"),this.sequenceItems.forEach(l())},"updateBounds"),insert:o(function(t,e,r,n){let i=Math.min(t,r),a=Math.max(t,r),s=Math.min(e,n),l=Math.max(e,n);this.updateVal(Zo.data,"startx",i,Math.min),this.updateVal(Zo.data,"starty",s,Math.min),this.updateVal(Zo.data,"stopx",a,Math.max),this.updateVal(Zo.data,"stopy",l,Math.max),this.updateBounds(i,s,a,l)},"insert"),bumpVerticalPos:o(function(t){this.verticalPos=this.verticalPos+t,this.data.stopy=this.verticalPos},"bumpVerticalPos"),getVerticalPos:o(function(){return this.verticalPos},"getVerticalPos"),getBounds:o(function(){return this.data},"getBounds")},uP=JE.sectionFills,vde=JE.sectionColours,GUe=o(function(t,e,r){let n=de().journey,i="",a=n.height*2+n.diagramMarginY,s=r+a,l=0,u="#CCC",h="black",f=0;for(let[d,p]of e.entries()){if(i!==p.section){u=uP[l%uP.length],f=l%uP.length,h=vde[l%vde.length];let g=0,y=p.section;for(let x=d;x(Xu[y]&&(g[y]=Xu[y]),g),{});p.x=d*n.taskMargin+d*n.width+I0,p.y=s,p.width=n.diagramMarginX,p.height=n.diagramMarginY,p.colour=h,p.fill=u,p.num=f,p.actors=m,Hg.drawTask(t,p,n),Zo.insert(p.x,p.y,p.x+p.width+n.taskMargin,300+5*30)}},"drawTasks"),hP={setConf:BUe,draw:zUe}});var bde={};hr(bde,{diagram:()=>$Ue});var $Ue,wde=R(()=>{"use strict";lde();ude();fde();xde();$Ue={parser:ode,db:lP,renderer:hP,styles:hde,init:o(t=>{hP.setConf(t.journey),lP.clear()},"init")}});var dP,_de,Lde=R(()=>{"use strict";dP=function(){var t=o(function(p,m,g,y){for(g=g||{},y=p.length;y--;g[p[y]]=m);return g},"o"),e=[6,8,10,11,12,14,16,17,20,21],r=[1,9],n=[1,10],i=[1,11],a=[1,12],s=[1,13],l=[1,16],u=[1,17],h={trace:o(function(){},"trace"),yy:{},symbols_:{error:2,start:3,timeline:4,document:5,EOF:6,line:7,SPACE:8,statement:9,NEWLINE:10,title:11,acc_title:12,acc_title_value:13,acc_descr:14,acc_descr_value:15,acc_descr_multiline_value:16,section:17,period_statement:18,event_statement:19,period:20,event:21,$accept:0,$end:1},terminals_:{2:"error",4:"timeline",6:"EOF",8:"SPACE",10:"NEWLINE",11:"title",12:"acc_title",13:"acc_title_value",14:"acc_descr",15:"acc_descr_value",16:"acc_descr_multiline_value",17:"section",20:"period",21:"event"},productions_:[0,[3,3],[5,0],[5,2],[7,2],[7,1],[7,1],[7,1],[9,1],[9,2],[9,2],[9,1],[9,1],[9,1],[9,1],[18,1],[19,1]],performAction:o(function(m,g,y,v,x,b,w){var S=b.length-1;switch(x){case 1:return b[S-1];case 2:this.$=[];break;case 3:b[S-1].push(b[S]),this.$=b[S-1];break;case 4:case 5:this.$=b[S];break;case 6:case 7:this.$=[];break;case 8:v.getCommonDb().setDiagramTitle(b[S].substr(6)),this.$=b[S].substr(6);break;case 9:this.$=b[S].trim(),v.getCommonDb().setAccTitle(this.$);break;case 10:case 11:this.$=b[S].trim(),v.getCommonDb().setAccDescription(this.$);break;case 12:v.addSection(b[S].substr(8)),this.$=b[S].substr(8);break;case 15:v.addTask(b[S],0,""),this.$=b[S];break;case 16:v.addEvent(b[S].substr(2)),this.$=b[S];break}},"anonymous"),table:[{3:1,4:[1,2]},{1:[3]},t(e,[2,2],{5:3}),{6:[1,4],7:5,8:[1,6],9:7,10:[1,8],11:r,12:n,14:i,16:a,17:s,18:14,19:15,20:l,21:u},t(e,[2,7],{1:[2,1]}),t(e,[2,3]),{9:18,11:r,12:n,14:i,16:a,17:s,18:14,19:15,20:l,21:u},t(e,[2,5]),t(e,[2,6]),t(e,[2,8]),{13:[1,19]},{15:[1,20]},t(e,[2,11]),t(e,[2,12]),t(e,[2,13]),t(e,[2,14]),t(e,[2,15]),t(e,[2,16]),t(e,[2,4]),t(e,[2,9]),t(e,[2,10])],defaultActions:{},parseError:o(function(m,g){if(g.recoverable)this.trace(m);else{var y=new Error(m);throw y.hash=g,y}},"parseError"),parse:o(function(m){var g=this,y=[0],v=[],x=[null],b=[],w=this.table,S="",T=0,E=0,_=0,A=2,L=1,M=b.slice.call(arguments,1),N=Object.create(this.lexer),k={yy:{}};for(var I in this.yy)Object.prototype.hasOwnProperty.call(this.yy,I)&&(k.yy[I]=this.yy[I]);N.setInput(m,k.yy),k.yy.lexer=N,k.yy.parser=this,typeof N.yylloc>"u"&&(N.yylloc={});var C=N.yylloc;b.push(C);var O=N.options&&N.options.ranges;typeof k.yy.parseError=="function"?this.parseError=k.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function D(q){y.length=y.length-2*q,x.length=x.length-q,b.length=b.length-q}o(D,"popStack");function P(){var q;return q=v.pop()||N.lex()||L,typeof q!="number"&&(q instanceof Array&&(v=q,q=v.pop()),q=g.symbols_[q]||q),q}o(P,"lex");for(var F,B,$,z,Y,Q,X={},ie,j,J,Z;;){if($=y[y.length-1],this.defaultActions[$]?z=this.defaultActions[$]:((F===null||typeof F>"u")&&(F=P()),z=w[$]&&w[$][F]),typeof z>"u"||!z.length||!z[0]){var H="";Z=[];for(ie in w[$])this.terminals_[ie]&&ie>A&&Z.push("'"+this.terminals_[ie]+"'");N.showPosition?H="Parse error on line "+(T+1)+`: +`+N.showPosition()+` +Expecting `+Z.join(", ")+", got '"+(this.terminals_[F]||F)+"'":H="Parse error on line "+(T+1)+": Unexpected "+(F==L?"end of input":"'"+(this.terminals_[F]||F)+"'"),this.parseError(H,{text:N.match,token:this.terminals_[F]||F,line:N.yylineno,loc:C,expected:Z})}if(z[0]instanceof Array&&z.length>1)throw new Error("Parse Error: multiple actions possible at state: "+$+", token: "+F);switch(z[0]){case 1:y.push(F),x.push(N.yytext),b.push(N.yylloc),y.push(z[1]),F=null,B?(F=B,B=null):(E=N.yyleng,S=N.yytext,T=N.yylineno,C=N.yylloc,_>0&&_--);break;case 2:if(j=this.productions_[z[1]][1],X.$=x[x.length-j],X._$={first_line:b[b.length-(j||1)].first_line,last_line:b[b.length-1].last_line,first_column:b[b.length-(j||1)].first_column,last_column:b[b.length-1].last_column},O&&(X._$.range=[b[b.length-(j||1)].range[0],b[b.length-1].range[1]]),Q=this.performAction.apply(X,[S,E,T,k.yy,z[1],x,b].concat(M)),typeof Q<"u")return Q;j&&(y=y.slice(0,-1*j*2),x=x.slice(0,-1*j),b=b.slice(0,-1*j)),y.push(this.productions_[z[1]][0]),x.push(X.$),b.push(X._$),J=w[y[y.length-2]][y[y.length-1]],y.push(J);break;case 3:return!0}}return!0},"parse")},f=function(){var p={EOF:1,parseError:o(function(g,y){if(this.yy.parser)this.yy.parser.parseError(g,y);else throw new Error(g)},"parseError"),setInput:o(function(m,g){return this.yy=g||this.yy||{},this._input=m,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:o(function(){var m=this._input[0];this.yytext+=m,this.yyleng++,this.offset++,this.match+=m,this.matched+=m;var g=m.match(/(?:\r\n?|\n).*/g);return g?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),m},"input"),unput:o(function(m){var g=m.length,y=m.split(/(?:\r\n?|\n)/g);this._input=m+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-g),this.offset-=g;var v=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),y.length-1&&(this.yylineno-=y.length-1);var x=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:y?(y.length===v.length?this.yylloc.first_column:0)+v[v.length-y.length].length-y[0].length:this.yylloc.first_column-g},this.options.ranges&&(this.yylloc.range=[x[0],x[0]+this.yyleng-g]),this.yyleng=this.yytext.length,this},"unput"),more:o(function(){return this._more=!0,this},"more"),reject:o(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:o(function(m){this.unput(this.match.slice(m))},"less"),pastInput:o(function(){var m=this.matched.substr(0,this.matched.length-this.match.length);return(m.length>20?"...":"")+m.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:o(function(){var m=this.match;return m.length<20&&(m+=this._input.substr(0,20-m.length)),(m.substr(0,20)+(m.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:o(function(){var m=this.pastInput(),g=new Array(m.length+1).join("-");return m+this.upcomingInput()+` +`+g+"^"},"showPosition"),test_match:o(function(m,g){var y,v,x;if(this.options.backtrack_lexer&&(x={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(x.yylloc.range=this.yylloc.range.slice(0))),v=m[0].match(/(?:\r\n?|\n).*/g),v&&(this.yylineno+=v.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:v?v[v.length-1].length-v[v.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+m[0].length},this.yytext+=m[0],this.match+=m[0],this.matches=m,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(m[0].length),this.matched+=m[0],y=this.performAction.call(this,this.yy,this,g,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),y)return y;if(this._backtrack){for(var b in x)this[b]=x[b];return!1}return!1},"test_match"),next:o(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var m,g,y,v;this._more||(this.yytext="",this.match="");for(var x=this._currentRules(),b=0;bg[0].length)){if(g=y,v=b,this.options.backtrack_lexer){if(m=this.test_match(y,x[b]),m!==!1)return m;if(this._backtrack){g=!1;continue}else return!1}else if(!this.options.flex)break}return g?(m=this.test_match(g,x[v]),m!==!1?m:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:o(function(){var g=this.next();return g||this.lex()},"lex"),begin:o(function(g){this.conditionStack.push(g)},"begin"),popState:o(function(){var g=this.conditionStack.length-1;return g>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:o(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:o(function(g){return g=this.conditionStack.length-1-Math.abs(g||0),g>=0?this.conditionStack[g]:"INITIAL"},"topState"),pushState:o(function(g){this.begin(g)},"pushState"),stateStackSize:o(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:o(function(g,y,v,x){var b=x;switch(v){case 0:break;case 1:break;case 2:return 10;case 3:break;case 4:break;case 5:return 4;case 6:return 11;case 7:return this.begin("acc_title"),12;break;case 8:return this.popState(),"acc_title_value";break;case 9:return this.begin("acc_descr"),14;break;case 10:return this.popState(),"acc_descr_value";break;case 11:this.begin("acc_descr_multiline");break;case 12:this.popState();break;case 13:return"acc_descr_multiline_value";case 14:return 17;case 15:return 21;case 16:return 20;case 17:return 6;case 18:return"INVALID"}},"anonymous"),rules:[/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:timeline\b)/i,/^(?:title\s[^\n]+)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:section\s[^:\n]+)/i,/^(?::\s[^:\n]+)/i,/^(?:[^#:\n]+)/i,/^(?:$)/i,/^(?:.)/i],conditions:{acc_descr_multiline:{rules:[12,13],inclusive:!1},acc_descr:{rules:[10],inclusive:!1},acc_title:{rules:[8],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,7,9,11,14,15,16,17,18],inclusive:!0}}};return p}();h.lexer=f;function d(){this.yy={}}return o(d,"Parser"),d.prototype=h,h.Parser=d,new d}();dP.parser=dP;_de=dP});var mP={};hr(mP,{addEvent:()=>Fde,addSection:()=>Ide,addTask:()=>Bde,addTaskOrg:()=>zde,clear:()=>Mde,default:()=>KUe,getCommonDb:()=>Nde,getSections:()=>Ode,getTasks:()=>Pde});var Yg,Rde,pP,e6,Wg,Nde,Mde,Ide,Ode,Pde,Bde,Fde,zde,Dde,KUe,Gde=R(()=>{"use strict";bi();Yg="",Rde=0,pP=[],e6=[],Wg=[],Nde=o(()=>ly,"getCommonDb"),Mde=o(function(){pP.length=0,e6.length=0,Yg="",Wg.length=0,vr()},"clear"),Ide=o(function(t){Yg=t,pP.push(t)},"addSection"),Ode=o(function(){return pP},"getSections"),Pde=o(function(){let t=Dde(),e=100,r=0;for(;!t&&rr.id===Rde-1).events.push(t)},"addEvent"),zde=o(function(t){let e={section:Yg,type:Yg,description:t,task:t,classes:[]};e6.push(e)},"addTaskOrg"),Dde=o(function(){let t=o(function(r){return Wg[r].processed},"compileTask"),e=!0;for(let[r,n]of Wg.entries())t(r),e=e&&n.processed;return e},"compileTasks"),KUe={clear:Mde,getCommonDb:Nde,addSection:Ide,getSections:Ode,getTasks:Pde,addTask:Bde,addTaskOrg:zde,addEvent:Fde}});function Hde(t,e){t.each(function(){var r=$e(this),n=r.text().split(/(\s+|
    )/).reverse(),i,a=[],s=1.1,l=r.attr("y"),u=parseFloat(r.attr("dy")),h=r.text(null).append("tspan").attr("x",0).attr("y",l).attr("dy",u+"em");for(let f=0;fe||i==="
    ")&&(a.pop(),h.text(a.join(" ").trim()),i==="
    "?a=[""]:a=[i],h=r.append("tspan").attr("x",0).attr("y",l).attr("dy",s+"em").text(i))})}var QUe,t6,ZUe,JUe,Vde,eHe,tHe,$de,rHe,nHe,iHe,gP,Ude,aHe,sHe,oHe,lHe,xf,Yde=R(()=>{"use strict";Zt();QUe=12,t6=o(function(t,e){let r=t.append("rect");return r.attr("x",e.x),r.attr("y",e.y),r.attr("fill",e.fill),r.attr("stroke",e.stroke),r.attr("width",e.width),r.attr("height",e.height),r.attr("rx",e.rx),r.attr("ry",e.ry),e.class!==void 0&&r.attr("class",e.class),r},"drawRect"),ZUe=o(function(t,e){let n=t.append("circle").attr("cx",e.cx).attr("cy",e.cy).attr("class","face").attr("r",15).attr("stroke-width",2).attr("overflow","visible"),i=t.append("g");i.append("circle").attr("cx",e.cx-15/3).attr("cy",e.cy-15/3).attr("r",1.5).attr("stroke-width",2).attr("fill","#666").attr("stroke","#666"),i.append("circle").attr("cx",e.cx+15/3).attr("cy",e.cy-15/3).attr("r",1.5).attr("stroke-width",2).attr("fill","#666").attr("stroke","#666");function a(u){let h=bl().startAngle(Math.PI/2).endAngle(3*(Math.PI/2)).innerRadius(7.5).outerRadius(6.8181818181818175);u.append("path").attr("class","mouth").attr("d",h).attr("transform","translate("+e.cx+","+(e.cy+2)+")")}o(a,"smile");function s(u){let h=bl().startAngle(3*Math.PI/2).endAngle(5*(Math.PI/2)).innerRadius(7.5).outerRadius(6.8181818181818175);u.append("path").attr("class","mouth").attr("d",h).attr("transform","translate("+e.cx+","+(e.cy+7)+")")}o(s,"sad");function l(u){u.append("line").attr("class","mouth").attr("stroke",2).attr("x1",e.cx-5).attr("y1",e.cy+7).attr("x2",e.cx+5).attr("y2",e.cy+7).attr("class","mouth").attr("stroke-width","1px").attr("stroke","#666")}return o(l,"ambivalent"),e.score>3?a(i):e.score<3?s(i):l(i),n},"drawFace"),JUe=o(function(t,e){let r=t.append("circle");return r.attr("cx",e.cx),r.attr("cy",e.cy),r.attr("class","actor-"+e.pos),r.attr("fill",e.fill),r.attr("stroke",e.stroke),r.attr("r",e.r),r.class!==void 0&&r.attr("class",r.class),e.title!==void 0&&r.append("title").text(e.title),r},"drawCircle"),Vde=o(function(t,e){let r=e.text.replace(//gi," "),n=t.append("text");n.attr("x",e.x),n.attr("y",e.y),n.attr("class","legend"),n.style("text-anchor",e.anchor),e.class!==void 0&&n.attr("class",e.class);let i=n.append("tspan");return i.attr("x",e.x+e.textMargin*2),i.text(r),n},"drawText"),eHe=o(function(t,e){function r(i,a,s,l,u){return i+","+a+" "+(i+s)+","+a+" "+(i+s)+","+(a+l-u)+" "+(i+s-u*1.2)+","+(a+l)+" "+i+","+(a+l)}o(r,"genPoints");let n=t.append("polygon");n.attr("points",r(e.x,e.y,50,20,7)),n.attr("class","labelBox"),e.y=e.y+e.labelMargin,e.x=e.x+.5*e.labelMargin,Vde(t,e)},"drawLabel"),tHe=o(function(t,e,r){let n=t.append("g"),i=gP();i.x=e.x,i.y=e.y,i.fill=e.fill,i.width=r.width,i.height=r.height,i.class="journey-section section-type-"+e.num,i.rx=3,i.ry=3,t6(n,i),Ude(r)(e.text,n,i.x,i.y,i.width,i.height,{class:"journey-section section-type-"+e.num},r,e.colour)},"drawSection"),$de=-1,rHe=o(function(t,e,r){let n=e.x+r.width/2,i=t.append("g");$de++;let a=300+5*30;i.append("line").attr("id","task"+$de).attr("x1",n).attr("y1",e.y).attr("x2",n).attr("y2",a).attr("class","task-line").attr("stroke-width","1px").attr("stroke-dasharray","4 2").attr("stroke","#666"),ZUe(i,{cx:n,cy:300+(5-e.score)*30,score:e.score});let s=gP();s.x=e.x,s.y=e.y,s.fill=e.fill,s.width=r.width,s.height=r.height,s.class="task task-type-"+e.num,s.rx=3,s.ry=3,t6(i,s),Ude(r)(e.task,i,s.x,s.y,s.width,s.height,{class:"task"},r,e.colour)},"drawTask"),nHe=o(function(t,e){t6(t,{x:e.startx,y:e.starty,width:e.stopx-e.startx,height:e.stopy-e.starty,fill:e.fill,class:"rect"}).lower()},"drawBackgroundRect"),iHe=o(function(){return{x:0,y:0,fill:void 0,"text-anchor":"start",width:100,height:100,textMargin:0,rx:0,ry:0}},"getTextObj"),gP=o(function(){return{x:0,y:0,width:100,anchor:"start",height:100,rx:0,ry:0}},"getNoteRect"),Ude=function(){function t(i,a,s,l,u,h,f,d){let p=a.append("text").attr("x",s+u/2).attr("y",l+h/2+5).style("font-color",d).style("text-anchor","middle").text(i);n(p,f)}o(t,"byText");function e(i,a,s,l,u,h,f,d,p){let{taskFontSize:m,taskFontFamily:g}=d,y=i.split(//gi);for(let v=0;v{"use strict";Zt();Yde();ut();_t();Yn();cHe=o(function(t,e,r,n){let i=de(),a=i.leftMargin??50;V.debug("timeline",n.db);let s=i.securityLevel,l;s==="sandbox"&&(l=$e("#i"+e));let h=(s==="sandbox"?$e(l.nodes()[0].contentDocument.body):$e("body")).select("#"+e);h.append("g");let f=n.db.getTasks(),d=n.db.getCommonDb().getDiagramTitle();V.debug("task",f),xf.initGraphics(h);let p=n.db.getSections();V.debug("sections",p);let m=0,g=0,y=0,v=0,x=50+a,b=50;v=50;let w=0,S=!0;p.forEach(function(L){let M={number:w,descr:L,section:w,width:150,padding:20,maxHeight:m},N=xf.getVirtualNodeHeight(h,M,i);V.debug("sectionHeight before draw",N),m=Math.max(m,N+20)});let T=0,E=0;V.debug("tasks.length",f.length);for(let[L,M]of f.entries()){let N={number:L,descr:M,section:M.section,width:150,padding:20,maxHeight:g},k=xf.getVirtualNodeHeight(h,N,i);V.debug("taskHeight before draw",k),g=Math.max(g,k+20),T=Math.max(T,M.events.length);let I=0;for(let C of M.events){let O={descr:C,section:M.section,number:M.section,width:150,padding:20,maxHeight:50};I+=xf.getVirtualNodeHeight(h,O,i)}E=Math.max(E,I)}V.debug("maxSectionHeight before draw",m),V.debug("maxTaskHeight before draw",g),p&&p.length>0?p.forEach(L=>{let M=f.filter(C=>C.section===L),N={number:w,descr:L,section:w,width:200*Math.max(M.length,1)-50,padding:20,maxHeight:m};V.debug("sectionNode",N);let k=h.append("g"),I=xf.drawNode(k,N,w,i);V.debug("sectionNode output",I),k.attr("transform",`translate(${x}, ${v})`),b+=m+50,M.length>0&&Wde(h,M,w,x,b,g,i,T,E,m,!1),x+=200*Math.max(M.length,1),b=v,w++}):(S=!1,Wde(h,f,w,x,b,g,i,T,E,m,!0));let _=h.node().getBBox();V.debug("bounds",_),d&&h.append("text").text(d).attr("x",_.width/2-a).attr("font-size","4ex").attr("font-weight","bold").attr("y",20),y=S?m+g+150:g+100,h.append("g").attr("class","lineWrapper").append("line").attr("x1",a).attr("y1",y).attr("x2",_.width+3*a).attr("y2",y).attr("stroke-width",4).attr("stroke","black").attr("marker-end","url(#arrowhead)"),Lo(void 0,h,i.timeline?.padding??50,i.timeline?.useMaxWidth??!1)},"draw"),Wde=o(function(t,e,r,n,i,a,s,l,u,h,f){for(let d of e){let p={descr:d.task,section:r,number:r,width:150,padding:20,maxHeight:a};V.debug("taskNode",p);let m=t.append("g").attr("class","taskWrapper"),y=xf.drawNode(m,p,r,s).height;if(V.debug("taskHeight after draw",y),m.attr("transform",`translate(${n}, ${i})`),a=Math.max(a,y),d.events){let v=t.append("g").attr("class","lineWrapper"),x=a;i+=100,x=x+uHe(t,d.events,r,n,i,s),i-=100,v.append("line").attr("x1",n+190/2).attr("y1",i+a).attr("x2",n+190/2).attr("y2",i+a+(f?a:h)+u+120).attr("stroke-width",2).attr("stroke","black").attr("marker-end","url(#arrowhead)").attr("stroke-dasharray","5,5")}n=n+200,f&&!s.timeline?.disableMulticolor&&r++}i=i-10},"drawTasks"),uHe=o(function(t,e,r,n,i,a){let s=0,l=i;i=i+100;for(let u of e){let h={descr:u,section:r,number:r,width:150,padding:20,maxHeight:50};V.debug("eventNode",h);let f=t.append("g").attr("class","eventWrapper"),p=xf.drawNode(f,h,r,a).height;s=s+p,f.attr("transform",`translate(${n}, ${i})`),i=i+10+p}return i=l,s},"drawEvents"),qde={setConf:o(()=>{},"setConf"),draw:cHe}});var hHe,fHe,jde,Kde=R(()=>{"use strict";al();hHe=o(t=>{let e="";for(let r=0;r` + .edge { + stroke-width: 3; + } + ${hHe(t)} + .section-root rect, .section-root path, .section-root circle { + fill: ${t.git0}; + } + .section-root text { + fill: ${t.gitBranchLabel0}; + } + .icon-container { + height:100%; + display: flex; + justify-content: center; + align-items: center; + } + .edge { + fill: none; + } + .eventWrapper { + filter: brightness(120%); + } +`,"getStyles"),jde=fHe});var Qde={};hr(Qde,{diagram:()=>dHe});var dHe,Zde=R(()=>{"use strict";Lde();Gde();Xde();Kde();dHe={db:mP,renderer:qde,parser:_de,styles:jde}});var yP,t0e,r0e=R(()=>{"use strict";yP=function(){var t=o(function(S,T,E,_){for(E=E||{},_=S.length;_--;E[S[_]]=T);return E},"o"),e=[1,4],r=[1,13],n=[1,12],i=[1,15],a=[1,16],s=[1,20],l=[1,19],u=[6,7,8],h=[1,26],f=[1,24],d=[1,25],p=[6,7,11],m=[1,6,13,15,16,19,22],g=[1,33],y=[1,34],v=[1,6,7,11,13,15,16,19,22],x={trace:o(function(){},"trace"),yy:{},symbols_:{error:2,start:3,mindMap:4,spaceLines:5,SPACELINE:6,NL:7,MINDMAP:8,document:9,stop:10,EOF:11,statement:12,SPACELIST:13,node:14,ICON:15,CLASS:16,nodeWithId:17,nodeWithoutId:18,NODE_DSTART:19,NODE_DESCR:20,NODE_DEND:21,NODE_ID:22,$accept:0,$end:1},terminals_:{2:"error",6:"SPACELINE",7:"NL",8:"MINDMAP",11:"EOF",13:"SPACELIST",15:"ICON",16:"CLASS",19:"NODE_DSTART",20:"NODE_DESCR",21:"NODE_DEND",22:"NODE_ID"},productions_:[0,[3,1],[3,2],[5,1],[5,2],[5,2],[4,2],[4,3],[10,1],[10,1],[10,1],[10,2],[10,2],[9,3],[9,2],[12,2],[12,2],[12,2],[12,1],[12,1],[12,1],[12,1],[12,1],[14,1],[14,1],[18,3],[17,1],[17,4]],performAction:o(function(T,E,_,A,L,M,N){var k=M.length-1;switch(L){case 6:case 7:return A;case 8:A.getLogger().trace("Stop NL ");break;case 9:A.getLogger().trace("Stop EOF ");break;case 11:A.getLogger().trace("Stop NL2 ");break;case 12:A.getLogger().trace("Stop EOF2 ");break;case 15:A.getLogger().info("Node: ",M[k].id),A.addNode(M[k-1].length,M[k].id,M[k].descr,M[k].type);break;case 16:A.getLogger().trace("Icon: ",M[k]),A.decorateNode({icon:M[k]});break;case 17:case 21:A.decorateNode({class:M[k]});break;case 18:A.getLogger().trace("SPACELIST");break;case 19:A.getLogger().trace("Node: ",M[k].id),A.addNode(0,M[k].id,M[k].descr,M[k].type);break;case 20:A.decorateNode({icon:M[k]});break;case 25:A.getLogger().trace("node found ..",M[k-2]),this.$={id:M[k-1],descr:M[k-1],type:A.getType(M[k-2],M[k])};break;case 26:this.$={id:M[k],descr:M[k],type:A.nodeType.DEFAULT};break;case 27:A.getLogger().trace("node found ..",M[k-3]),this.$={id:M[k-3],descr:M[k-1],type:A.getType(M[k-2],M[k])};break}},"anonymous"),table:[{3:1,4:2,5:3,6:[1,5],8:e},{1:[3]},{1:[2,1]},{4:6,6:[1,7],7:[1,8],8:e},{6:r,7:[1,10],9:9,12:11,13:n,14:14,15:i,16:a,17:17,18:18,19:s,22:l},t(u,[2,3]),{1:[2,2]},t(u,[2,4]),t(u,[2,5]),{1:[2,6],6:r,12:21,13:n,14:14,15:i,16:a,17:17,18:18,19:s,22:l},{6:r,9:22,12:11,13:n,14:14,15:i,16:a,17:17,18:18,19:s,22:l},{6:h,7:f,10:23,11:d},t(p,[2,22],{17:17,18:18,14:27,15:[1,28],16:[1,29],19:s,22:l}),t(p,[2,18]),t(p,[2,19]),t(p,[2,20]),t(p,[2,21]),t(p,[2,23]),t(p,[2,24]),t(p,[2,26],{19:[1,30]}),{20:[1,31]},{6:h,7:f,10:32,11:d},{1:[2,7],6:r,12:21,13:n,14:14,15:i,16:a,17:17,18:18,19:s,22:l},t(m,[2,14],{7:g,11:y}),t(v,[2,8]),t(v,[2,9]),t(v,[2,10]),t(p,[2,15]),t(p,[2,16]),t(p,[2,17]),{20:[1,35]},{21:[1,36]},t(m,[2,13],{7:g,11:y}),t(v,[2,11]),t(v,[2,12]),{21:[1,37]},t(p,[2,25]),t(p,[2,27])],defaultActions:{2:[2,1],6:[2,2]},parseError:o(function(T,E){if(E.recoverable)this.trace(T);else{var _=new Error(T);throw _.hash=E,_}},"parseError"),parse:o(function(T){var E=this,_=[0],A=[],L=[null],M=[],N=this.table,k="",I=0,C=0,O=0,D=2,P=1,F=M.slice.call(arguments,1),B=Object.create(this.lexer),$={yy:{}};for(var z in this.yy)Object.prototype.hasOwnProperty.call(this.yy,z)&&($.yy[z]=this.yy[z]);B.setInput(T,$.yy),$.yy.lexer=B,$.yy.parser=this,typeof B.yylloc>"u"&&(B.yylloc={});var Y=B.yylloc;M.push(Y);var Q=B.options&&B.options.ranges;typeof $.yy.parseError=="function"?this.parseError=$.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function X(ke){_.length=_.length-2*ke,L.length=L.length-ke,M.length=M.length-ke}o(X,"popStack");function ie(){var ke;return ke=A.pop()||B.lex()||P,typeof ke!="number"&&(ke instanceof Array&&(A=ke,ke=A.pop()),ke=E.symbols_[ke]||ke),ke}o(ie,"lex");for(var j,J,Z,H,q,K,se={},ce,ue,te,De;;){if(Z=_[_.length-1],this.defaultActions[Z]?H=this.defaultActions[Z]:((j===null||typeof j>"u")&&(j=ie()),H=N[Z]&&N[Z][j]),typeof H>"u"||!H.length||!H[0]){var oe="";De=[];for(ce in N[Z])this.terminals_[ce]&&ce>D&&De.push("'"+this.terminals_[ce]+"'");B.showPosition?oe="Parse error on line "+(I+1)+`: +`+B.showPosition()+` +Expecting `+De.join(", ")+", got '"+(this.terminals_[j]||j)+"'":oe="Parse error on line "+(I+1)+": Unexpected "+(j==P?"end of input":"'"+(this.terminals_[j]||j)+"'"),this.parseError(oe,{text:B.match,token:this.terminals_[j]||j,line:B.yylineno,loc:Y,expected:De})}if(H[0]instanceof Array&&H.length>1)throw new Error("Parse Error: multiple actions possible at state: "+Z+", token: "+j);switch(H[0]){case 1:_.push(j),L.push(B.yytext),M.push(B.yylloc),_.push(H[1]),j=null,J?(j=J,J=null):(C=B.yyleng,k=B.yytext,I=B.yylineno,Y=B.yylloc,O>0&&O--);break;case 2:if(ue=this.productions_[H[1]][1],se.$=L[L.length-ue],se._$={first_line:M[M.length-(ue||1)].first_line,last_line:M[M.length-1].last_line,first_column:M[M.length-(ue||1)].first_column,last_column:M[M.length-1].last_column},Q&&(se._$.range=[M[M.length-(ue||1)].range[0],M[M.length-1].range[1]]),K=this.performAction.apply(se,[k,C,I,$.yy,H[1],L,M].concat(F)),typeof K<"u")return K;ue&&(_=_.slice(0,-1*ue*2),L=L.slice(0,-1*ue),M=M.slice(0,-1*ue)),_.push(this.productions_[H[1]][0]),L.push(se.$),M.push(se._$),te=N[_[_.length-2]][_[_.length-1]],_.push(te);break;case 3:return!0}}return!0},"parse")},b=function(){var S={EOF:1,parseError:o(function(E,_){if(this.yy.parser)this.yy.parser.parseError(E,_);else throw new Error(E)},"parseError"),setInput:o(function(T,E){return this.yy=E||this.yy||{},this._input=T,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:o(function(){var T=this._input[0];this.yytext+=T,this.yyleng++,this.offset++,this.match+=T,this.matched+=T;var E=T.match(/(?:\r\n?|\n).*/g);return E?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),T},"input"),unput:o(function(T){var E=T.length,_=T.split(/(?:\r\n?|\n)/g);this._input=T+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-E),this.offset-=E;var A=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),_.length-1&&(this.yylineno-=_.length-1);var L=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:_?(_.length===A.length?this.yylloc.first_column:0)+A[A.length-_.length].length-_[0].length:this.yylloc.first_column-E},this.options.ranges&&(this.yylloc.range=[L[0],L[0]+this.yyleng-E]),this.yyleng=this.yytext.length,this},"unput"),more:o(function(){return this._more=!0,this},"more"),reject:o(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:o(function(T){this.unput(this.match.slice(T))},"less"),pastInput:o(function(){var T=this.matched.substr(0,this.matched.length-this.match.length);return(T.length>20?"...":"")+T.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:o(function(){var T=this.match;return T.length<20&&(T+=this._input.substr(0,20-T.length)),(T.substr(0,20)+(T.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:o(function(){var T=this.pastInput(),E=new Array(T.length+1).join("-");return T+this.upcomingInput()+` +`+E+"^"},"showPosition"),test_match:o(function(T,E){var _,A,L;if(this.options.backtrack_lexer&&(L={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(L.yylloc.range=this.yylloc.range.slice(0))),A=T[0].match(/(?:\r\n?|\n).*/g),A&&(this.yylineno+=A.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:A?A[A.length-1].length-A[A.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+T[0].length},this.yytext+=T[0],this.match+=T[0],this.matches=T,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(T[0].length),this.matched+=T[0],_=this.performAction.call(this,this.yy,this,E,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),_)return _;if(this._backtrack){for(var M in L)this[M]=L[M];return!1}return!1},"test_match"),next:o(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var T,E,_,A;this._more||(this.yytext="",this.match="");for(var L=this._currentRules(),M=0;ME[0].length)){if(E=_,A=M,this.options.backtrack_lexer){if(T=this.test_match(_,L[M]),T!==!1)return T;if(this._backtrack){E=!1;continue}else return!1}else if(!this.options.flex)break}return E?(T=this.test_match(E,L[A]),T!==!1?T:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:o(function(){var E=this.next();return E||this.lex()},"lex"),begin:o(function(E){this.conditionStack.push(E)},"begin"),popState:o(function(){var E=this.conditionStack.length-1;return E>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:o(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:o(function(E){return E=this.conditionStack.length-1-Math.abs(E||0),E>=0?this.conditionStack[E]:"INITIAL"},"topState"),pushState:o(function(E){this.begin(E)},"pushState"),stateStackSize:o(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:o(function(E,_,A,L){var M=L;switch(A){case 0:return E.getLogger().trace("Found comment",_.yytext),6;break;case 1:return 8;case 2:this.begin("CLASS");break;case 3:return this.popState(),16;break;case 4:this.popState();break;case 5:E.getLogger().trace("Begin icon"),this.begin("ICON");break;case 6:return E.getLogger().trace("SPACELINE"),6;break;case 7:return 7;case 8:return 15;case 9:E.getLogger().trace("end icon"),this.popState();break;case 10:return E.getLogger().trace("Exploding node"),this.begin("NODE"),19;break;case 11:return E.getLogger().trace("Cloud"),this.begin("NODE"),19;break;case 12:return E.getLogger().trace("Explosion Bang"),this.begin("NODE"),19;break;case 13:return E.getLogger().trace("Cloud Bang"),this.begin("NODE"),19;break;case 14:return this.begin("NODE"),19;break;case 15:return this.begin("NODE"),19;break;case 16:return this.begin("NODE"),19;break;case 17:return this.begin("NODE"),19;break;case 18:return 13;case 19:return 22;case 20:return 11;case 21:this.begin("NSTR2");break;case 22:return"NODE_DESCR";case 23:this.popState();break;case 24:E.getLogger().trace("Starting NSTR"),this.begin("NSTR");break;case 25:return E.getLogger().trace("description:",_.yytext),"NODE_DESCR";break;case 26:this.popState();break;case 27:return this.popState(),E.getLogger().trace("node end ))"),"NODE_DEND";break;case 28:return this.popState(),E.getLogger().trace("node end )"),"NODE_DEND";break;case 29:return this.popState(),E.getLogger().trace("node end ...",_.yytext),"NODE_DEND";break;case 30:return this.popState(),E.getLogger().trace("node end (("),"NODE_DEND";break;case 31:return this.popState(),E.getLogger().trace("node end (-"),"NODE_DEND";break;case 32:return this.popState(),E.getLogger().trace("node end (-"),"NODE_DEND";break;case 33:return this.popState(),E.getLogger().trace("node end (("),"NODE_DEND";break;case 34:return this.popState(),E.getLogger().trace("node end (("),"NODE_DEND";break;case 35:return E.getLogger().trace("Long description:",_.yytext),20;break;case 36:return E.getLogger().trace("Long description:",_.yytext),20;break}},"anonymous"),rules:[/^(?:\s*%%.*)/i,/^(?:mindmap\b)/i,/^(?::::)/i,/^(?:.+)/i,/^(?:\n)/i,/^(?:::icon\()/i,/^(?:[\s]+[\n])/i,/^(?:[\n]+)/i,/^(?:[^\)]+)/i,/^(?:\))/i,/^(?:-\))/i,/^(?:\(-)/i,/^(?:\)\))/i,/^(?:\))/i,/^(?:\(\()/i,/^(?:\{\{)/i,/^(?:\()/i,/^(?:\[)/i,/^(?:[\s]+)/i,/^(?:[^\(\[\n\)\{\}]+)/i,/^(?:$)/i,/^(?:["][`])/i,/^(?:[^`"]+)/i,/^(?:[`]["])/i,/^(?:["])/i,/^(?:[^"]+)/i,/^(?:["])/i,/^(?:[\)]\))/i,/^(?:[\)])/i,/^(?:[\]])/i,/^(?:\}\})/i,/^(?:\(-)/i,/^(?:-\))/i,/^(?:\(\()/i,/^(?:\()/i,/^(?:[^\)\]\(\}]+)/i,/^(?:.+(?!\(\())/i],conditions:{CLASS:{rules:[3,4],inclusive:!1},ICON:{rules:[8,9],inclusive:!1},NSTR2:{rules:[22,23],inclusive:!1},NSTR:{rules:[25,26],inclusive:!1},NODE:{rules:[21,24,27,28,29,30,31,32,33,34,35,36],inclusive:!1},INITIAL:{rules:[0,1,2,5,6,7,10,11,12,13,14,15,16,17,18,19,20],inclusive:!0}}};return S}();x.lexer=b;function w(){this.yy={}}return o(w,"Parser"),w.prototype=x,x.Parser=w,new w}();yP.parser=yP;t0e=yP});var Gl,n0e,vP,yHe,vHe,xHe,bHe,$i,wHe,THe,kHe,EHe,CHe,SHe,AHe,i0e,a0e=R(()=>{"use strict";_t();rr();ut();sl();Gl=[],n0e=0,vP={},yHe=o(()=>{Gl=[],n0e=0,vP={}},"clear"),vHe=o(function(t){for(let e=Gl.length-1;e>=0;e--)if(Gl[e].levelGl.length>0?Gl[0]:null,"getMindmap"),bHe=o((t,e,r,n)=>{V.info("addNode",t,e,r,n);let i=de(),a=i.mindmap?.padding??mr.mindmap.padding;switch(n){case $i.ROUNDED_RECT:case $i.RECT:case $i.HEXAGON:a*=2}let s={id:n0e++,nodeId:qr(e,i),level:t,descr:qr(r,i),type:n,children:[],width:i.mindmap?.maxNodeWidth??mr.mindmap.maxNodeWidth,padding:a},l=vHe(t);if(l)l.children.push(s),Gl.push(s);else if(Gl.length===0)Gl.push(s);else throw new Error('There can be only one root. No parent could be found for ("'+s.descr+'")')},"addNode"),$i={DEFAULT:0,NO_BORDER:0,ROUNDED_RECT:1,RECT:2,CIRCLE:3,CLOUD:4,BANG:5,HEXAGON:6},wHe=o((t,e)=>{switch(V.debug("In get type",t,e),t){case"[":return $i.RECT;case"(":return e===")"?$i.ROUNDED_RECT:$i.CLOUD;case"((":return $i.CIRCLE;case")":return $i.CLOUD;case"))":return $i.BANG;case"{{":return $i.HEXAGON;default:return $i.DEFAULT}},"getType"),THe=o((t,e)=>{vP[t]=e},"setElementForId"),kHe=o(t=>{if(!t)return;let e=de(),r=Gl[Gl.length-1];t.icon&&(r.icon=qr(t.icon,e)),t.class&&(r.class=qr(t.class,e))},"decorateNode"),EHe=o(t=>{switch(t){case $i.DEFAULT:return"no-border";case $i.RECT:return"rect";case $i.ROUNDED_RECT:return"rounded-rect";case $i.CIRCLE:return"circle";case $i.CLOUD:return"cloud";case $i.BANG:return"bang";case $i.HEXAGON:return"hexgon";default:return"no-border"}},"type2Str"),CHe=o(()=>V,"getLogger"),SHe=o(t=>vP[t],"getElementById"),AHe={clear:yHe,addNode:bHe,getMindmap:xHe,nodeType:$i,getType:wHe,setElementForId:THe,decorateNode:kHe,type2Str:EHe,getLogger:CHe,getElementById:SHe},i0e=AHe});function Hi(t){"@babel/helpers - typeof";return Hi=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(e){return typeof e}:function(e){return e&&typeof Symbol=="function"&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},Hi(t)}function XP(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function s0e(t,e){for(var r=0;rt.length)&&(e=t.length);for(var r=0,n=new Array(e);r=t.length?{done:!0}:{done:!1,value:t[n++]}},"n"),e:o(function(u){throw u},"e"),f:i}}throw new TypeError(`Invalid attempt to iterate non-iterable instance. +In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}var a=!0,s=!1,l;return{s:o(function(){r=r.call(t)},"s"),n:o(function(){var u=r.next();return a=u.done,u},"n"),e:o(function(u){s=!0,l=u},"e"),f:o(function(){try{!a&&r.return!=null&&r.return()}finally{if(s)throw l}},"f")}}function eYe(t){var e=typeof t;return t!=null&&(e=="object"||e=="function")}function tYe(t,e){return e={exports:{}},t(e,e.exports),e.exports}function lYe(t){for(var e=t.length;e--&&oYe.test(t.charAt(e)););return e}function hYe(t){return t&&t.slice(0,cYe(t)+1).replace(uYe,"")}function gYe(t){var e=pYe.call(t,_x),r=t[_x];try{t[_x]=void 0;var n=!0}catch{}var i=mYe.call(t);return n&&(e?t[_x]=r:delete t[_x]),i}function bYe(t){return xYe.call(t)}function EYe(t){return t==null?t===void 0?kYe:TYe:u0e&&u0e in Object(t)?yYe(t):wYe(t)}function CYe(t){return t!=null&&typeof t=="object"}function _Ye(t){return typeof t=="symbol"||SYe(t)&&Rpe(t)==AYe}function MYe(t){if(typeof t=="number")return t;if(Jx(t))return h0e;if(V0(t)){var e=typeof t.valueOf=="function"?t.valueOf():t;t=V0(e)?e+"":e}if(typeof t!="string")return t===0?t:+t;t=fYe(t);var r=DYe.test(t);return r||RYe.test(t)?NYe(t.slice(2),r?2:8):LYe.test(t)?h0e:+t}function BYe(t,e,r){var n,i,a,s,l,u,h=0,f=!1,d=!1,p=!0;if(typeof t!="function")throw new TypeError(IYe);e=f0e(e)||0,V0(r)&&(f=!!r.leading,d="maxWait"in r,a=d?OYe(f0e(r.maxWait)||0,e):a,p="trailing"in r?!!r.trailing:p);function m(E){var _=n,A=i;return n=i=void 0,h=E,s=t.apply(A,_),s}o(m,"invokeFunc");function g(E){return h=E,l=setTimeout(x,e),f?m(E):s}o(g,"leadingEdge");function y(E){var _=E-u,A=E-h,L=e-_;return d?PYe(L,a-A):L}o(y,"remainingWait");function v(E){var _=E-u,A=E-h;return u===void 0||_>=e||_<0||d&&A>=a}o(v,"shouldInvoke");function x(){var E=xP();if(v(E))return b(E);l=setTimeout(x,y(E))}o(x,"timerExpired");function b(E){return l=void 0,p&&n?m(E):(n=i=void 0,s)}o(b,"trailingEdge");function w(){l!==void 0&&clearTimeout(l),h=0,n=u=i=l=void 0}o(w,"cancel");function S(){return l===void 0?s:b(xP())}o(S,"flush");function T(){var E=xP(),_=v(E);if(n=arguments,i=this,u=E,_){if(l===void 0)return g(u);if(d)return clearTimeout(l),l=setTimeout(x,e),m(u)}return l===void 0&&(l=setTimeout(x,e)),s}return o(T,"debounced"),T.cancel=w,T.flush=S,T}function z6(t,e,r,n,i,a){var s;return jn(t)?s=t:s=o1[t]||o1.euclidean,e===0&&jn(t)?s(i,a):s(e,r,n,i,a)}function Lqe(t,e){if(G6(t))return!1;var r=typeof t;return r=="number"||r=="symbol"||r=="boolean"||t==null||Jx(t)?!0:_qe.test(t)||!Aqe.test(t)||e!=null&&t in Object(e)}function Oqe(t){if(!V0(t))return!1;var e=Rpe(t);return e==Nqe||e==Mqe||e==Rqe||e==Iqe}function Fqe(t){return!!N0e&&N0e in t}function Vqe(t){if(t!=null){try{return $qe.call(t)}catch{}try{return t+""}catch{}}return""}function Qqe(t){if(!V0(t)||zqe(t))return!1;var e=Pqe(t)?Kqe:Yqe;return e.test(Uqe(t))}function Jqe(t,e){return t?.[e]}function tXe(t,e){var r=eXe(t,e);return Zqe(r)?r:void 0}function nXe(){this.__data__=Wx?Wx(null):{},this.size=0}function aXe(t){var e=this.has(t)&&delete this.__data__[t];return this.size-=e?1:0,e}function uXe(t){var e=this.__data__;if(Wx){var r=e[t];return r===oXe?void 0:r}return cXe.call(e,t)?e[t]:void 0}function pXe(t){var e=this.__data__;return Wx?e[t]!==void 0:dXe.call(e,t)}function yXe(t,e){var r=this.__data__;return this.size+=this.has(t)?0:1,r[t]=Wx&&e===void 0?gXe:e,this}function h1(t){var e=-1,r=t==null?0:t.length;for(this.clear();++e-1}function RXe(t,e){var r=this.__data__,n=$6(r,t);return n<0?(++this.size,r.push([t,e])):r[n][1]=e,this}function f1(t){var e=-1,r=t==null?0:t.length;for(this.clear();++e-1&&t%1==0&&t0;){var f=i.shift();e(f),a.add(f.id()),l&&n(i,a,f)}return t}function ume(t,e,r){if(r.isParent())for(var n=r._private.children,i=0;i0&&arguments[0]!==void 0?arguments[0]:mKe,e=arguments.length>1?arguments[1]:void 0,r=0;r0?k=C:N=C;while(Math.abs(I)>s&&++O=a?b(M,O):D===0?O:S(M,N,N+h)}o(T,"getTForX");var E=!1;function _(){E=!0,(t!==e||r!==n)&&w()}o(_,"precompute");var A=o(function(N){return E||_(),t===e&&r===n?N:N===0?0:N===1?1:v(T(N),e,n)},"f");A.getControlPoints=function(){return[{x:t,y:e},{x:r,y:n}]};var L="generateBezier("+[t,e,r,n]+")";return A.toString=function(){return L},A}function Q0e(t,e,r,n,i){if(n===1||e===r)return r;var a=i(e,r,n);return t==null||((t.roundValue||t.color)&&(a=Math.round(a)),t.min!==void 0&&(a=Math.max(a,t.min)),t.max!==void 0&&(a=Math.min(a,t.max))),a}function Z0e(t,e){return t.pfValue!=null||t.value!=null?t.pfValue!=null&&(e==null||e.type.units!=="%")?t.pfValue:t.value:t}function jg(t,e,r,n,i){var a=i!=null?i.type:null;r<0?r=0:r>1&&(r=1);var s=Z0e(t,i),l=Z0e(e,i);if(ft(s)&&ft(l))return Q0e(a,s,l,r,n);if(vn(s)&&vn(l)){for(var u=[],h=0;h0?(m==="spring"&&g.push(s.duration),s.easingImpl=v6[m].apply(null,g)):s.easingImpl=v6[m]}var y=s.easingImpl,v;if(s.duration===0?v=1:v=(r-u)/s.duration,s.applying&&(v=s.progress),v<0?v=0:v>1&&(v=1),s.delay==null){var x=s.startPosition,b=s.position;if(b&&i&&!t.locked()){var w={};Nx(x.x,b.x)&&(w.x=jg(x.x,b.x,v,y)),Nx(x.y,b.y)&&(w.y=jg(x.y,b.y,v,y)),t.position(w)}var S=s.startPan,T=s.pan,E=a.pan,_=T!=null&&n;_&&(Nx(S.x,T.x)&&(E.x=jg(S.x,T.x,v,y)),Nx(S.y,T.y)&&(E.y=jg(S.y,T.y,v,y)),t.emit("pan"));var A=s.startZoom,L=s.zoom,M=L!=null&&n;M&&(Nx(A,L)&&(a.zoom=Hx(a.minZoom,jg(A,L,v,y),a.maxZoom)),t.emit("zoom")),(_||M)&&t.emit("viewport");var N=s.style;if(N&&N.length>0&&i){for(var k=0;k=0;_--){var A=E[_];A()}E.splice(0,E.length)},"callbacks"),b=m.length-1;b>=0;b--){var w=m[b],S=w._private;if(S.stopped){m.splice(b,1),S.hooked=!1,S.playing=!1,S.started=!1,x(S.frames);continue}!S.playing&&!S.applying||(S.playing&&S.applying&&(S.applying=!1),S.started||LKe(f,w,t),_Ke(f,w,t,d),S.applying&&(S.applying=!1),x(S.frames),S.step!=null&&S.step(t),w.completed()&&(m.splice(b,1),S.hooked=!1,S.playing=!1,S.started=!1,x(S.completes)),y=!0)}return!d&&m.length===0&&g.length===0&&n.push(f),y}o(i,"stepOne");for(var a=!1,s=0;s0?e.notify("draw",r):e.notify("draw")),r.unmerge(n),e.emit("step")}function Ame(t){this.options=Wt({},BKe,FKe,t)}function _me(t){this.options=Wt({},zKe,t)}function Lme(t){this.options=Wt({},GKe,t)}function j6(t){this.options=Wt({},$Ke,t),this.options.layout=this;var e=this.options.eles.nodes(),r=this.options.eles.edges(),n=r.filter(function(i){var a=i.source().data("id"),s=i.target().data("id"),l=e.some(function(h){return h.data("id")===a}),u=e.some(function(h){return h.data("id")===s});return!l||!u});this.options.eles=this.options.eles.not(n)}function Rme(t){this.options=Wt({},iQe,t)}function dB(t){this.options=Wt({},aQe,t)}function Nme(t){this.options=Wt({},sQe,t)}function Mme(t){this.options=Wt({},oQe,t)}function Ime(t){this.options=t,this.notifications=0}function Bme(t,e){e.radius===0?t.lineTo(e.cx,e.cy):t.arc(e.cx,e.cy,e.radius,e.startAngle,e.endAngle,e.counterClockwise)}function mB(t,e,r,n){var i=arguments.length>4&&arguments[4]!==void 0?arguments[4]:!0;return n===0||e.radius===0?{cx:e.x,cy:e.y,radius:0,startX:e.x,startY:e.y,stopX:e.x,stopY:e.y,startAngle:void 0,endAngle:void 0,counterClockwise:void 0}:(uQe(t,e,r,n,i),{cx:GP,cy:$P,radius:z0,startX:Ome,startY:Pme,stopX:VP,stopY:UP,startAngle:Gc.ang+Math.PI/2*G0,endAngle:Jo.ang-Math.PI/2*G0,counterClockwise:w6})}function Fme(t){var e=[];if(t!=null){for(var r=0;r5&&arguments[5]!==void 0?arguments[5]:5,s=arguments.length>6?arguments[6]:void 0;t.beginPath(),t.moveTo(e+a,r),t.lineTo(e+n-a,r),t.quadraticCurveTo(e+n,r,e+n,r+a),t.lineTo(e+n,r+i-a),t.quadraticCurveTo(e+n,r+i,e+n-a,r+i),t.lineTo(e+a,r+i),t.quadraticCurveTo(e,r+i,e,r+i-a),t.lineTo(e,r+a),t.quadraticCurveTo(e,r,e+a,r),t.closePath(),s?t.stroke():t.fill()}function ZQe(t,e){for(var r=atob(t),n=new ArrayBuffer(r.length),i=new Uint8Array(n),a=0;a{"use strict";o(Hi,"_typeof");o(XP,"_classCallCheck");o(s0e,"_defineProperties");o(jP,"_createClass");o(bpe,"_defineProperty$1");o($l,"_slicedToArray");o(_He,"_arrayWithHoles");o(LHe,"_iterableToArrayLimit");o(wpe,"_unsupportedIterableToArray");o(o0e,"_arrayLikeToArray");o(DHe,"_nonIterableRest");o(Tpe,"_createForOfIteratorHelper");Vi=typeof window>"u"?null:window,l0e=Vi?Vi.navigator:null;Vi&&Vi.document;RHe=Hi(""),kpe=Hi({}),NHe=Hi(function(){}),MHe=typeof HTMLElement>"u"?"undefined":Hi(HTMLElement),Qx=o(function(e){return e&&e.instanceString&&jn(e.instanceString)?e.instanceString():null},"instanceStr"),zt=o(function(e){return e!=null&&Hi(e)==RHe},"string"),jn=o(function(e){return e!=null&&Hi(e)===NHe},"fn"),vn=o(function(e){return!xo(e)&&(Array.isArray?Array.isArray(e):e!=null&&e instanceof Array)},"array"),Mr=o(function(e){return e!=null&&Hi(e)===kpe&&!vn(e)&&e.constructor===Object},"plainObject"),IHe=o(function(e){return e!=null&&Hi(e)===kpe},"object"),ft=o(function(e){return e!=null&&Hi(e)===Hi(1)&&!isNaN(e)},"number"),OHe=o(function(e){return ft(e)&&Math.floor(e)===e},"integer"),k6=o(function(e){if(MHe!=="undefined")return e!=null&&e instanceof HTMLElement},"htmlElement"),xo=o(function(e){return Zx(e)||Epe(e)},"elementOrCollection"),Zx=o(function(e){return Qx(e)==="collection"&&e._private.single},"element"),Epe=o(function(e){return Qx(e)==="collection"&&!e._private.single},"collection"),KP=o(function(e){return Qx(e)==="core"},"core"),Cpe=o(function(e){return Qx(e)==="stylesheet"},"stylesheet"),PHe=o(function(e){return Qx(e)==="event"},"event"),Sf=o(function(e){return e==null?!0:!!(e===""||e.match(/^\s+$/))},"emptyString"),BHe=o(function(e){return typeof HTMLElement>"u"?!1:e instanceof HTMLElement},"domElement"),FHe=o(function(e){return Mr(e)&&ft(e.x1)&&ft(e.x2)&&ft(e.y1)&&ft(e.y2)},"boundingBox"),zHe=o(function(e){return IHe(e)&&jn(e.then)},"promise"),GHe=o(function(){return l0e&&l0e.userAgent.match(/msie|trident|edge/i)},"ms"),Gx=o(function(e,r){r||(r=o(function(){if(arguments.length===1)return arguments[0];if(arguments.length===0)return"undefined";for(var a=[],s=0;sr?1:0},"ascending"),qHe=o(function(e,r){return-1*Ape(e,r)},"descending"),Wt=Object.assign!=null?Object.assign.bind(Object):function(t){for(var e=arguments,r=1;r1&&(v-=1),v<1/6?g+(y-g)*6*v:v<1/2?y:v<2/3?g+(y-g)*(2/3-v)*6:g}o(f,"hue2rgb");var d=new RegExp("^"+UHe+"$").exec(e);if(d){if(n=parseInt(d[1]),n<0?n=(360- -1*n%360)%360:n>360&&(n=n%360),n/=360,i=parseFloat(d[2]),i<0||i>100||(i=i/100,a=parseFloat(d[3]),a<0||a>100)||(a=a/100,s=d[4],s!==void 0&&(s=parseFloat(s),s<0||s>1)))return;if(i===0)l=u=h=Math.round(a*255);else{var p=a<.5?a*(1+i):a+i-a*i,m=2*a-p;l=Math.round(255*f(m,p,n+1/3)),u=Math.round(255*f(m,p,n)),h=Math.round(255*f(m,p,n-1/3))}r=[l,u,h,s]}return r},"hsl2tuple"),KHe=o(function(e){var r,n=new RegExp("^"+$He+"$").exec(e);if(n){r=[];for(var i=[],a=1;a<=3;a++){var s=n[a];if(s[s.length-1]==="%"&&(i[a]=!0),s=parseFloat(s),i[a]&&(s=s/100*255),s<0||s>255)return;r.push(Math.floor(s))}var l=i[1]||i[2]||i[3],u=i[1]&&i[2]&&i[3];if(l&&!u)return;var h=n[4];if(h!==void 0){if(h=parseFloat(h),h<0||h>1)return;r.push(h)}}return r},"rgb2tuple"),QHe=o(function(e){return JHe[e.toLowerCase()]},"colorname2tuple"),ZHe=o(function(e){return(vn(e)?e:null)||QHe(e)||XHe(e)||KHe(e)||jHe(e)},"color2tuple"),JHe={transparent:[0,0,0,0],aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],grey:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},_pe=o(function(e){for(var r=e.map,n=e.keys,i=n.length,a=0;a1&&arguments[1]!==void 0?arguments[1]:Zg,n=r,i;i=e.next(),!i.done;)n=n*Mpe+i.value|0;return n},"hashIterableInts"),$x=o(function(e){var r=arguments.length>1&&arguments[1]!==void 0?arguments[1]:Zg;return r*Mpe+e|0},"hashInt"),Vx=o(function(e){var r=arguments.length>1&&arguments[1]!==void 0?arguments[1]:Ix;return(r<<5)+r+e|0},"hashIntAlt"),zYe=o(function(e,r){return e*2097152+r},"combineHashes"),bf=o(function(e){return e[0]*2097152+e[1]},"combineHashesArray"),r6=o(function(e,r){return[$x(e[0],r[0]),Vx(e[1],r[1])]},"hashArrays"),GYe=o(function(e,r){var n={value:0,done:!1},i=0,a=e.length,s={next:o(function(){return i=0&&!(e[i]===r&&(e.splice(i,1),n));i--);},"removeFromArray"),eB=o(function(e){e.splice(0,e.length)},"clearArray"),qYe=o(function(e,r){for(var n=0;n"u"?"undefined":Hi(Set))!==jYe?Set:KYe,B6=o(function(e,r){var n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:!0;if(e===void 0||r===void 0||!KP(e)){oi("An element must have a core reference and parameters set");return}var i=r.group;if(i==null&&(r.data&&r.data.source!=null&&r.data.target!=null?i="edges":i="nodes"),i!=="nodes"&&i!=="edges"){oi("An element must be of type `nodes` or `edges`; you specified `"+i+"`");return}this.length=1,this[0]=this;var a=this._private={cy:e,single:!0,data:r.data||{},position:r.position||{x:0,y:0},autoWidth:void 0,autoHeight:void 0,autoPadding:void 0,compoundBoundsClean:!1,listeners:[],group:i,style:{},rstyle:{},styleCxts:[],styleKeys:{},removed:!0,selected:!!r.selected,selectable:r.selectable===void 0?!0:!!r.selectable,locked:!!r.locked,grabbed:!1,grabbable:r.grabbable===void 0?!0:!!r.grabbable,pannable:r.pannable===void 0?i==="edges":!!r.pannable,active:!1,classes:new c1,animation:{current:[],queue:[]},rscratch:{},scratch:r.scratch||{},edges:[],children:[],parent:r.parent&&r.parent.isNode()?r.parent:null,traversalCache:{},backgrounding:!1,bbCache:null,bbCacheShift:{x:0,y:0},bodyBounds:null,overlayBounds:null,labelBounds:{all:null,source:null,target:null,main:null},arrowBounds:{source:null,target:null,"mid-source":null,"mid-target":null}};if(a.position.x==null&&(a.position.x=0),a.position.y==null&&(a.position.y=0),r.renderedPosition){var s=r.renderedPosition,l=e.pan(),u=e.zoom();a.position={x:(s.x-l.x)/u,y:(s.y-l.y)/u}}var h=[];vn(r.classes)?h=r.classes:zt(r.classes)&&(h=r.classes.split(/\s+/));for(var f=0,d=h.length;fb?1:0},"defaultCmp"),f=o(function(x,b,w,S,T){var E;if(w==null&&(w=0),T==null&&(T=n),w<0)throw new Error("lo must be non-negative");for(S==null&&(S=x.length);wM;0<=M?L++:L--)A.push(L);return A}.apply(this).reverse(),_=[],S=0,T=E.length;SN;0<=N?++A:--A)k.push(s(x,w));return k},"nsmallest"),y=o(function(x,b,w,S){var T,E,_;for(S==null&&(S=n),T=x[w];w>b;){if(_=w-1>>1,E=x[_],S(T,E)<0){x[w]=E,w=_;continue}break}return x[w]=T},"_siftdown"),v=o(function(x,b,w){var S,T,E,_,A;for(w==null&&(w=n),T=x.length,A=b,E=x[b],S=2*b+1;S0;){var E=b.pop(),_=v(E),A=E.id();if(p[A]=_,_!==1/0)for(var L=E.neighborhood().intersect(g),M=0;M0)for(F.unshift(P);d[$];){var z=d[$];F.unshift(z.edge),F.unshift(z.node),B=z.node,$=B.id()}return l.spawn(F)},"pathTo")}},"dijkstra")},eWe={kruskal:o(function(e){e=e||function(w){return 1};for(var r=this.byGroup(),n=r.nodes,i=r.edges,a=n.length,s=new Array(a),l=n,u=o(function(S){for(var T=0;T0;){if(T(),_++,S===f){for(var A=[],L=a,M=f,N=x[M];A.unshift(L),N!=null&&A.unshift(N),L=v[M],L!=null;)M=L.id(),N=x[M];return{found:!0,distance:d[S],path:this.spawn(A),steps:_}}m[S]=!0;for(var k=w._private.edges,I=0;IN&&(g[M]=N,b[M]=L,w[M]=T),!a){var k=L*f+A;!a&&g[k]>N&&(g[k]=N,b[k]=A,w[k]=T)}}}for(var I=0;I1&&arguments[1]!==void 0?arguments[1]:s,Se=w(ke),Ue=[],Pe=Se;;){if(Pe==null)return r.spawn();var _e=b(Pe),me=_e.edge,W=_e.pred;if(Ue.unshift(Pe[0]),Pe.same(Ie)&&Ue.length>0)break;me!=null&&Ue.unshift(me),Pe=W}return u.spawn(Ue)},"pathTo"),E=0;E=0;f--){var d=h[f],p=d[1],m=d[2];(r[p]===l&&r[m]===u||r[p]===u&&r[m]===l)&&h.splice(f,1)}for(var g=0;gi;){var a=Math.floor(Math.random()*r.length);r=lWe(a,e,r),n--}return r},"contractUntil"),cWe={kargerStein:o(function(){var e=this,r=this.byGroup(),n=r.nodes,i=r.edges;i.unmergeBy(function(F){return F.isLoop()});var a=n.length,s=i.length,l=Math.ceil(Math.pow(Math.log(a)/Math.LN2,2)),u=Math.floor(a/oWe);if(a<2){oi("At least 2 nodes are required for Karger-Stein algorithm");return}for(var h=[],f=0;f1&&arguments[1]!==void 0?arguments[1]:0,n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:e.length,i=1/0,a=r;a1&&arguments[1]!==void 0?arguments[1]:0,n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:e.length,i=-1/0,a=r;a1&&arguments[1]!==void 0?arguments[1]:0,n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:e.length,i=0,a=0,s=r;s1&&arguments[1]!==void 0?arguments[1]:0,n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:e.length,i=arguments.length>3&&arguments[3]!==void 0?arguments[3]:!0,a=arguments.length>4&&arguments[4]!==void 0?arguments[4]:!0,s=arguments.length>5&&arguments[5]!==void 0?arguments[5]:!0;i?e=e.slice(r,n):(n0&&e.splice(0,r));for(var l=0,u=e.length-1;u>=0;u--){var h=e[u];s?isFinite(h)||(e[u]=-1/0,l++):e.splice(u,1)}a&&e.sort(function(p,m){return p-m});var f=e.length,d=Math.floor(f/2);return f%2!==0?e[d+1+l]:(e[d-1+l]+e[d+l])/2},"median"),mWe=o(function(e){return Math.PI*e/180},"deg2rad"),n6=o(function(e,r){return Math.atan2(r,e)-Math.PI/2},"getAngleFromDisp"),tB=Math.log2||function(t){return Math.log(t)/Math.log(2)},$pe=o(function(e){return e>0?1:e<0?-1:0},"signum"),H0=o(function(e,r){return Math.sqrt(B0(e,r))},"dist"),B0=o(function(e,r){var n=r.x-e.x,i=r.y-e.y;return n*n+i*i},"sqdist"),gWe=o(function(e){for(var r=e.length,n=0,i=0;i=e.x1&&e.y2>=e.y1)return{x1:e.x1,y1:e.y1,x2:e.x2,y2:e.y2,w:e.x2-e.x1,h:e.y2-e.y1};if(e.w!=null&&e.h!=null&&e.w>=0&&e.h>=0)return{x1:e.x1,y1:e.y1,x2:e.x1+e.w,y2:e.y1+e.h,w:e.w,h:e.h}}},"makeBoundingBox"),vWe=o(function(e){return{x1:e.x1,x2:e.x2,w:e.w,y1:e.y1,y2:e.y2,h:e.h}},"copyBoundingBox"),xWe=o(function(e){e.x1=1/0,e.y1=1/0,e.x2=-1/0,e.y2=-1/0,e.w=0,e.h=0},"clearBoundingBox"),bWe=o(function(e,r,n){return{x1:e.x1+r,x2:e.x2+r,y1:e.y1+n,y2:e.y2+n,w:e.w,h:e.h}},"shiftBoundingBox"),Vpe=o(function(e,r){e.x1=Math.min(e.x1,r.x1),e.x2=Math.max(e.x2,r.x2),e.w=e.x2-e.x1,e.y1=Math.min(e.y1,r.y1),e.y2=Math.max(e.y2,r.y2),e.h=e.y2-e.y1},"updateBoundingBox"),wWe=o(function(e,r,n){e.x1=Math.min(e.x1,r),e.x2=Math.max(e.x2,r),e.w=e.x2-e.x1,e.y1=Math.min(e.y1,n),e.y2=Math.max(e.y2,n),e.h=e.y2-e.y1},"expandBoundingBoxByPoint"),p6=o(function(e){var r=arguments.length>1&&arguments[1]!==void 0?arguments[1]:0;return e.x1-=r,e.x2+=r,e.y1-=r,e.y2+=r,e.w=e.x2-e.x1,e.h=e.y2-e.y1,e},"expandBoundingBox"),m6=o(function(e){var r=arguments.length>1&&arguments[1]!==void 0?arguments[1]:[0],n,i,a,s;if(r.length===1)n=i=a=s=r[0];else if(r.length===2)n=a=r[0],s=i=r[1];else if(r.length===4){var l=$l(r,4);n=l[0],i=l[1],a=l[2],s=l[3]}return e.x1-=s,e.x2+=i,e.y1-=n,e.y2+=a,e.w=e.x2-e.x1,e.h=e.y2-e.y1,e},"expandBoundingBoxSides"),g0e=o(function(e,r){e.x1=r.x1,e.y1=r.y1,e.x2=r.x2,e.y2=r.y2,e.w=e.x2-e.x1,e.h=e.y2-e.y1},"assignBoundingBox"),rB=o(function(e,r){return!(e.x1>r.x2||r.x1>e.x2||e.x2r.y2||r.y1>e.y2)},"boundingBoxesIntersect"),s1=o(function(e,r,n){return e.x1<=r&&r<=e.x2&&e.y1<=n&&n<=e.y2},"inBoundingBox"),TWe=o(function(e,r){return s1(e,r.x,r.y)},"pointInBoundingBox"),Upe=o(function(e,r){return s1(e,r.x1,r.y1)&&s1(e,r.x2,r.y2)},"boundingBoxInBoundingBox"),Hpe=o(function(e,r,n,i,a,s,l){var u=arguments.length>7&&arguments[7]!==void 0?arguments[7]:"auto",h=u==="auto"?Y0(a,s):u,f=a/2,d=s/2;h=Math.min(h,f,d);var p=h!==f,m=h!==d,g;if(p){var y=n-f+h-l,v=i-d-l,x=n+f-h+l,b=v;if(g=kf(e,r,n,i,y,v,x,b,!1),g.length>0)return g}if(m){var w=n+f+l,S=i-d+h-l,T=w,E=i+d-h+l;if(g=kf(e,r,n,i,w,S,T,E,!1),g.length>0)return g}if(p){var _=n-f+h-l,A=i+d+l,L=n+f-h+l,M=A;if(g=kf(e,r,n,i,_,A,L,M,!1),g.length>0)return g}if(m){var N=n-f-l,k=i-d+h-l,I=N,C=i+d-h+l;if(g=kf(e,r,n,i,N,k,I,C,!1),g.length>0)return g}var O;{var D=n-f+h,P=i-d+h;if(O=Ox(e,r,n,i,D,P,h+l),O.length>0&&O[0]<=D&&O[1]<=P)return[O[0],O[1]]}{var F=n+f-h,B=i-d+h;if(O=Ox(e,r,n,i,F,B,h+l),O.length>0&&O[0]>=F&&O[1]<=B)return[O[0],O[1]]}{var $=n+f-h,z=i+d-h;if(O=Ox(e,r,n,i,$,z,h+l),O.length>0&&O[0]>=$&&O[1]>=z)return[O[0],O[1]]}{var Y=n-f+h,Q=i+d-h;if(O=Ox(e,r,n,i,Y,Q,h+l),O.length>0&&O[0]<=Y&&O[1]>=Q)return[O[0],O[1]]}return[]},"roundRectangleIntersectLine"),kWe=o(function(e,r,n,i,a,s,l){var u=l,h=Math.min(n,a),f=Math.max(n,a),d=Math.min(i,s),p=Math.max(i,s);return h-u<=e&&e<=f+u&&d-u<=r&&r<=p+u},"inLineVicinity"),EWe=o(function(e,r,n,i,a,s,l,u,h){var f={x1:Math.min(n,l,a)-h,x2:Math.max(n,l,a)+h,y1:Math.min(i,u,s)-h,y2:Math.max(i,u,s)+h};return!(ef.x2||rf.y2)},"inBezierVicinity"),CWe=o(function(e,r,n,i){n-=i;var a=r*r-4*e*n;if(a<0)return[];var s=Math.sqrt(a),l=2*e,u=(-r+s)/l,h=(-r-s)/l;return[u,h]},"solveQuadratic"),SWe=o(function(e,r,n,i,a){var s=1e-5;e===0&&(e=s),r/=e,n/=e,i/=e;var l,u,h,f,d,p,m,g;if(u=(3*n-r*r)/9,h=-(27*i)+r*(9*n-2*(r*r)),h/=54,l=u*u*u+h*h,a[1]=0,m=r/3,l>0){d=h+Math.sqrt(l),d=d<0?-Math.pow(-d,1/3):Math.pow(d,1/3),p=h-Math.sqrt(l),p=p<0?-Math.pow(-p,1/3):Math.pow(p,1/3),a[0]=-m+d+p,m+=(d+p)/2,a[4]=a[2]=-m,m=Math.sqrt(3)*(-p+d)/2,a[3]=m,a[5]=-m;return}if(a[5]=a[3]=0,l===0){g=h<0?-Math.pow(-h,1/3):Math.pow(h,1/3),a[0]=-m+2*g,a[4]=a[2]=-(g+m);return}u=-u,f=u*u*u,f=Math.acos(h/Math.sqrt(f)),g=2*Math.sqrt(u),a[0]=-m+g*Math.cos(f/3),a[2]=-m+g*Math.cos((f+2*Math.PI)/3),a[4]=-m+g*Math.cos((f+4*Math.PI)/3)},"solveCubic"),AWe=o(function(e,r,n,i,a,s,l,u){var h=1*n*n-4*n*a+2*n*l+4*a*a-4*a*l+l*l+i*i-4*i*s+2*i*u+4*s*s-4*s*u+u*u,f=1*9*n*a-3*n*n-3*n*l-6*a*a+3*a*l+9*i*s-3*i*i-3*i*u-6*s*s+3*s*u,d=1*3*n*n-6*n*a+n*l-n*e+2*a*a+2*a*e-l*e+3*i*i-6*i*s+i*u-i*r+2*s*s+2*s*r-u*r,p=1*n*a-n*n+n*e-a*e+i*s-i*i+i*r-s*r,m=[];SWe(h,f,d,p,m);for(var g=1e-7,y=[],v=0;v<6;v+=2)Math.abs(m[v+1])=0&&m[v]<=1&&y.push(m[v]);y.push(1),y.push(0);for(var x=-1,b,w,S,T=0;T=0?Sh?(e-a)*(e-a)+(r-s)*(r-s):f-p},"sqdistToFiniteLine"),zs=o(function(e,r,n){for(var i,a,s,l,u,h=0,f=0;f=e&&e>=s||i<=e&&e<=s)u=(e-i)/(s-i)*(l-a)+a,u>r&&h++;else continue;return h%2!==0},"pointInsidePolygonPoints"),Qu=o(function(e,r,n,i,a,s,l,u,h){var f=new Array(n.length),d;u[0]!=null?(d=Math.atan(u[1]/u[0]),u[0]<0?d=d+Math.PI/2:d=-d-Math.PI/2):d=u;for(var p=Math.cos(-d),m=Math.sin(-d),g=0;g0){var v=A6(f,-h);y=S6(v)}else y=f;return zs(e,r,y)},"pointInsidePolygon"),LWe=o(function(e,r,n,i,a,s,l,u){for(var h=new Array(n.length*2),f=0;f=0&&v<=1&&b.push(v),x>=0&&x<=1&&b.push(x),b.length===0)return[];var w=b[0]*u[0]+e,S=b[0]*u[1]+r;if(b.length>1){if(b[0]==b[1])return[w,S];var T=b[1]*u[0]+e,E=b[1]*u[1]+r;return[w,S,T,E]}else return[w,S]},"intersectLineCircle"),TP=o(function(e,r,n){return r<=e&&e<=n||n<=e&&e<=r?e:e<=r&&r<=n||n<=r&&r<=e?r:n},"midOfThree"),kf=o(function(e,r,n,i,a,s,l,u,h){var f=e-a,d=n-e,p=l-a,m=r-s,g=i-r,y=u-s,v=p*m-y*f,x=d*m-g*f,b=y*d-p*g;if(b!==0){var w=v/b,S=x/b,T=.001,E=0-T,_=1+T;return E<=w&&w<=_&&E<=S&&S<=_?[e+w*d,r+w*g]:h?[e+w*d,r+w*g]:[]}else return v===0||x===0?TP(e,n,l)===l?[l,u]:TP(e,n,a)===a?[a,s]:TP(a,l,n)===n?[n,i]:[]:[]},"finiteLinesIntersect"),Yx=o(function(e,r,n,i,a,s,l,u){var h=[],f,d=new Array(n.length),p=!0;s==null&&(p=!1);var m;if(p){for(var g=0;g0){var y=A6(d,-u);m=S6(y)}else m=d}else m=n;for(var v,x,b,w,S=0;S2){for(var g=[f[0],f[1]],y=Math.pow(g[0]-e,2)+Math.pow(g[1]-r,2),v=1;vf&&(f=S)},"set"),get:o(function(w){return h[w]},"get")},p=0;p0?D=O.edgesTo(C)[0]:D=C.edgesTo(O)[0];var P=i(D);C=C.id(),A[C]>A[k]+P&&(A[C]=A[k]+P,L.nodes.indexOf(C)<0?L.push(C):L.updateItem(C),_[C]=0,E[C]=[]),A[C]==A[k]+P&&(_[C]=_[C]+_[k],E[C].push(k))}else for(var F=0;F0;){for(var Y=T.pop(),Q=0;Q0&&l.push(n[u]);l.length!==0&&a.push(i.collection(l))}return a},"assign"),YWe=o(function(e,r){for(var n=0;n5&&arguments[5]!==void 0?arguments[5]:XWe,l=i,u,h,f=0;f=2?Lx(e,r,n,0,w0e,jWe):Lx(e,r,n,0,b0e)},"euclidean"),squaredEuclidean:o(function(e,r,n){return Lx(e,r,n,0,w0e)},"squaredEuclidean"),manhattan:o(function(e,r,n){return Lx(e,r,n,0,b0e)},"manhattan"),max:o(function(e,r,n){return Lx(e,r,n,-1/0,KWe)},"max")};o1["squared-euclidean"]=o1.squaredEuclidean;o1.squaredeuclidean=o1.squaredEuclidean;o(z6,"clusteringDistance");QWe=Sa({k:2,m:2,sensitivityThreshold:1e-4,distance:"euclidean",maxIterations:10,attributes:[],testMode:!1,testCentroids:null}),iB=o(function(e){return QWe(e)},"setOptions"),_6=o(function(e,r,n,i,a){var s=a!=="kMedoids",l=s?function(d){return n[d]}:function(d){return i[d](n)},u=o(function(p){return i[p](r)},"getQ"),h=n,f=r;return z6(e,i.length,l,u,h,f)},"getDist"),kP=o(function(e,r,n){for(var i=n.length,a=new Array(i),s=new Array(i),l=new Array(r),u=null,h=0;hn)return!1}return!0},"haveMatricesConverged"),eqe=o(function(e,r,n){for(var i=0;il&&(l=r[h][f],u=f);a[u].push(e[h])}for(var d=0;d=a.threshold||a.mode==="dendrogram"&&e.length===1)return!1;var g=r[s],y=r[i[s]],v;a.mode==="dendrogram"?v={left:g,right:y,key:g.key}:v={value:g.value.concat(y.value),key:g.key},e[g.index]=v,e.splice(y.index,1),r[g.key]=v;for(var x=0;xn[y.key][b.key]&&(u=n[y.key][b.key])):a.linkage==="max"?(u=n[g.key][b.key],n[g.key][b.key]0&&i.push(a);return i},"findExemplars"),A0e=o(function(e,r,n){for(var i=[],a=0;al&&(s=h,l=r[a*e+h])}s>0&&i.push(s)}for(var f=0;fh&&(u=f,h=d)}n[a]=s[u]}return i=A0e(e,r,n),i},"assign"),_0e=o(function(e){for(var r=this.cy(),n=this.nodes(),i=pqe(e),a={},s=0;s=N?(k=N,N=C,I=O):C>k&&(k=C);for(var D=0;D0?1:0;_[L%i.minIterations*l+Y]=Q,z+=Q}if(z>0&&(L>=i.minIterations-1||L==i.maxIterations-1)){for(var X=0,ie=0;ie1||E>1)&&(l=!0),d[w]=[],b.outgoers().forEach(function(A){A.isEdge()&&d[w].push(A.id())})}else p[w]=[void 0,b.target().id()]}):s.forEach(function(b){var w=b.id();if(b.isNode()){var S=b.degree(!0);S%2&&(u?h?l=!0:h=w:u=w),d[w]=[],b.connectedEdges().forEach(function(T){return d[w].push(T.id())})}else p[w]=[b.source().id(),b.target().id()]});var m={found:!1,trail:void 0};if(l)return m;if(h&&u)if(a){if(f&&h!=f)return m;f=h}else{if(f&&h!=f&&u!=f)return m;f||(f=h)}else f||(f=s[0].id());var g=o(function(w){for(var S=w,T=[w],E,_,A;d[S].length;)E=d[S].shift(),_=p[E][0],A=p[E][1],S!=A?(d[A]=d[A].filter(function(L){return L!=E}),S=A):!a&&S!=_&&(d[_]=d[_].filter(function(L){return L!=E}),S=_),T.unshift(E),T.unshift(S);return T},"walk"),y=[],v=[];for(v=g(f);v.length!=1;)d[v[0]].length==0?(y.unshift(s.getElementById(v.shift())),y.unshift(s.getElementById(v.shift()))):v=g(v.shift()).concat(v);y.unshift(s.getElementById(v.shift()));for(var x in d)if(d[x].length)return m;return m.found=!0,m.trail=this.spawn(y,!0),m},"hierholzer")},s6=o(function(){var e=this,r={},n=0,i=0,a=[],s=[],l={},u=o(function(p,m){for(var g=s.length-1,y=[],v=e.spawn();s[g].x!=p||s[g].y!=m;)y.push(s.pop().edge),g--;y.push(s.pop().edge),y.forEach(function(x){var b=x.connectedNodes().intersection(e);v.merge(x),b.forEach(function(w){var S=w.id(),T=w.connectedEdges().intersection(e);v.merge(w),r[S].cutVertex?v.merge(T.filter(function(E){return E.isLoop()})):v.merge(T)})}),a.push(v)},"buildComponent"),h=o(function d(p,m,g){p===g&&(i+=1),r[m]={id:n,low:n++,cutVertex:!1};var y=e.getElementById(m).connectedEdges().intersection(e);if(y.size()===0)a.push(e.spawn(e.getElementById(m)));else{var v,x,b,w;y.forEach(function(S){v=S.source().id(),x=S.target().id(),b=v===m?x:v,b!==g&&(w=S.id(),l[w]||(l[w]=!0,s.push({x:m,y:b,edge:S})),b in r?r[m].low=Math.min(r[m].low,r[b].id):(d(p,b,m),r[m].low=Math.min(r[m].low,r[b].low),r[m].id<=r[b].low&&(r[m].cutVertex=!0,u(m,b))))})}},"biconnectedSearch");e.forEach(function(d){if(d.isNode()){var p=d.id();p in r||(i=0,h(p,p),r[p].cutVertex=i>1)}});var f=Object.keys(r).filter(function(d){return r[d].cutVertex}).map(function(d){return e.getElementById(d)});return{cut:e.spawn(f),components:a}},"hopcroftTarjanBiconnected"),Tqe={hopcroftTarjanBiconnected:s6,htbc:s6,htb:s6,hopcroftTarjanBiconnectedComponents:s6},o6=o(function(){var e=this,r={},n=0,i=[],a=[],s=e.spawn(e),l=o(function u(h){a.push(h),r[h]={index:n,low:n++,explored:!1};var f=e.getElementById(h).connectedEdges().intersection(e);if(f.forEach(function(y){var v=y.target().id();v!==h&&(v in r||u(v),r[v].explored||(r[h].low=Math.min(r[h].low,r[v].low)))}),r[h].index===r[h].low){for(var d=e.spawn();;){var p=a.pop();if(d.merge(e.getElementById(p)),r[p].low=r[h].index,r[p].explored=!0,p===h)break}var m=d.edgesWith(d),g=d.merge(m);i.push(g),s=s.difference(g)}},"stronglyConnectedSearch");return e.forEach(function(u){if(u.isNode()){var h=u.id();h in r||l(h)}}),{cut:s,components:i}},"tarjanStronglyConnected"),kqe={tarjanStronglyConnected:o6,tsc:o6,tscc:o6,tarjanStronglyConnectedComponents:o6},Qpe={};[Ux,JYe,eWe,rWe,iWe,sWe,cWe,IWe,r1,n1,IP,qWe,sqe,fqe,xqe,wqe,Tqe,kqe].forEach(function(t){Wt(Qpe,t)});Zpe=0,Jpe=1,eme=2,Zu=o(function t(e){if(!(this instanceof t))return new t(e);this.id="Thenable/1.0.7",this.state=Zpe,this.fulfillValue=void 0,this.rejectReason=void 0,this.onFulfilled=[],this.onRejected=[],this.proxy={then:this.then.bind(this)},typeof e=="function"&&e.call(this,this.fulfill.bind(this),this.reject.bind(this))},"api");Zu.prototype={fulfill:o(function(e){return L0e(this,Jpe,"fulfillValue",e)},"fulfill"),reject:o(function(e){return L0e(this,eme,"rejectReason",e)},"reject"),then:o(function(e,r){var n=this,i=new Zu;return n.onFulfilled.push(R0e(e,i,"fulfill")),n.onRejected.push(R0e(r,i,"reject")),tme(n),i.proxy},"then")};L0e=o(function(e,r,n,i){return e.state===Zpe&&(e.state=r,e[n]=i,tme(e)),e},"deliver"),tme=o(function(e){e.state===Jpe?D0e(e,"onFulfilled",e.fulfillValue):e.state===eme&&D0e(e,"onRejected",e.rejectReason)},"execute"),D0e=o(function(e,r,n){if(e[r].length!==0){var i=e[r];e[r]=[];var a=o(function(){for(var l=0;l0},"animatedImpl")},"animated"),clearQueue:o(function(){return o(function(){var r=this,n=r.length!==void 0,i=n?r:[r],a=this._private.cy||this;if(!a.styleEnabled())return this;for(var s=0;s0&&this.spawn(i).updateStyle().emit("class"),r},"classes"),addClass:o(function(e){return this.toggleClass(e,!0)},"addClass"),hasClass:o(function(e){var r=this[0];return r!=null&&r._private.classes.has(e)},"hasClass"),toggleClass:o(function(e,r){vn(e)||(e=e.match(/\S+/g)||[]);for(var n=this,i=r===void 0,a=[],s=0,l=n.length;s0&&this.spawn(a).updateStyle().emit("class"),n},"toggleClass"),removeClass:o(function(e){return this.toggleClass(e,!1)},"removeClass"),flashClass:o(function(e,r){var n=this;if(r==null)r=250;else if(r===0)return n;return n.addClass(e),setTimeout(function(){n.removeClass(e)},r),n},"flashClass")};g6.className=g6.classNames=g6.classes;Nr={metaChar:"[\\!\\\"\\#\\$\\%\\&\\'\\(\\)\\*\\+\\,\\.\\/\\:\\;\\<\\=\\>\\?\\@\\[\\]\\^\\`\\{\\|\\}\\~]",comparatorOp:"=|\\!=|>|>=|<|<=|\\$=|\\^=|\\*=",boolOp:"\\?|\\!|\\^",string:`"(?:\\\\"|[^"])*"|'(?:\\\\'|[^'])*'`,number:Ui,meta:"degree|indegree|outdegree",separator:"\\s*,\\s*",descendant:"\\s+",child:"\\s+>\\s+",subject:"\\$",group:"node|edge|\\*",directedEdge:"\\s+->\\s+",undirectedEdge:"\\s+<->\\s+"};Nr.variable="(?:[\\w-.]|(?:\\\\"+Nr.metaChar+"))+";Nr.className="(?:[\\w-]|(?:\\\\"+Nr.metaChar+"))+";Nr.value=Nr.string+"|"+Nr.number;Nr.id=Nr.variable;(function(){var t,e,r;for(t=Nr.comparatorOp.split("|"),r=0;r=0)&&e!=="="&&(Nr.comparatorOp+="|\\!"+e)})();un=o(function(){return{checks:[]}},"newQuery"),Ct={GROUP:0,COLLECTION:1,FILTER:2,DATA_COMPARE:3,DATA_EXIST:4,DATA_BOOL:5,META_COMPARE:6,STATE:7,ID:8,CLASS:9,UNDIRECTED_EDGE:10,DIRECTED_EDGE:11,NODE_SOURCE:12,NODE_TARGET:13,NODE_NEIGHBOR:14,CHILD:15,DESCENDANT:16,PARENT:17,ANCESTOR:18,COMPOUND_SPLIT:19,TRUE:20},PP=[{selector:":selected",matches:o(function(e){return e.selected()},"matches")},{selector:":unselected",matches:o(function(e){return!e.selected()},"matches")},{selector:":selectable",matches:o(function(e){return e.selectable()},"matches")},{selector:":unselectable",matches:o(function(e){return!e.selectable()},"matches")},{selector:":locked",matches:o(function(e){return e.locked()},"matches")},{selector:":unlocked",matches:o(function(e){return!e.locked()},"matches")},{selector:":visible",matches:o(function(e){return e.visible()},"matches")},{selector:":hidden",matches:o(function(e){return!e.visible()},"matches")},{selector:":transparent",matches:o(function(e){return e.transparent()},"matches")},{selector:":grabbed",matches:o(function(e){return e.grabbed()},"matches")},{selector:":free",matches:o(function(e){return!e.grabbed()},"matches")},{selector:":removed",matches:o(function(e){return e.removed()},"matches")},{selector:":inside",matches:o(function(e){return!e.removed()},"matches")},{selector:":grabbable",matches:o(function(e){return e.grabbable()},"matches")},{selector:":ungrabbable",matches:o(function(e){return!e.grabbable()},"matches")},{selector:":animated",matches:o(function(e){return e.animated()},"matches")},{selector:":unanimated",matches:o(function(e){return!e.animated()},"matches")},{selector:":parent",matches:o(function(e){return e.isParent()},"matches")},{selector:":childless",matches:o(function(e){return e.isChildless()},"matches")},{selector:":child",matches:o(function(e){return e.isChild()},"matches")},{selector:":orphan",matches:o(function(e){return e.isOrphan()},"matches")},{selector:":nonorphan",matches:o(function(e){return e.isChild()},"matches")},{selector:":compound",matches:o(function(e){return e.isNode()?e.isParent():e.source().isParent()||e.target().isParent()},"matches")},{selector:":loop",matches:o(function(e){return e.isLoop()},"matches")},{selector:":simple",matches:o(function(e){return e.isSimple()},"matches")},{selector:":active",matches:o(function(e){return e.active()},"matches")},{selector:":inactive",matches:o(function(e){return!e.active()},"matches")},{selector:":backgrounding",matches:o(function(e){return e.backgrounding()},"matches")},{selector:":nonbackgrounding",matches:o(function(e){return!e.backgrounding()},"matches")}].sort(function(t,e){return qHe(t.selector,e.selector)}),Pje=function(){for(var t={},e,r=0;r0&&f.edgeCount>0)return tn("The selector `"+e+"` is invalid because it uses both a compound selector and an edge selector"),!1;if(f.edgeCount>1)return tn("The selector `"+e+"` is invalid because it uses multiple edge selectors"),!1;f.edgeCount===1&&tn("The selector `"+e+"` is deprecated. Edge selectors do not take effect on changes to source and target nodes after an edge is added, for performance reasons. Use a class or data selector on edges instead, updating the class or data of an edge when your app detects a change in source or target nodes.")}return!0},"parse"),Vje=o(function(){if(this.toStringCache!=null)return this.toStringCache;for(var e=o(function(f){return f??""},"clean"),r=o(function(f){return zt(f)?'"'+f+'"':e(f)},"cleanVal"),n=o(function(f){return" "+f+" "},"space"),i=o(function(f,d){var p=f.type,m=f.value;switch(p){case Ct.GROUP:{var g=e(m);return g.substring(0,g.length-1)}case Ct.DATA_COMPARE:{var y=f.field,v=f.operator;return"["+y+n(e(v))+r(m)+"]"}case Ct.DATA_BOOL:{var x=f.operator,b=f.field;return"["+e(x)+b+"]"}case Ct.DATA_EXIST:{var w=f.field;return"["+w+"]"}case Ct.META_COMPARE:{var S=f.operator,T=f.field;return"[["+T+n(e(S))+r(m)+"]]"}case Ct.STATE:return m;case Ct.ID:return"#"+m;case Ct.CLASS:return"."+m;case Ct.PARENT:case Ct.CHILD:return a(f.parent,d)+n(">")+a(f.child,d);case Ct.ANCESTOR:case Ct.DESCENDANT:return a(f.ancestor,d)+" "+a(f.descendant,d);case Ct.COMPOUND_SPLIT:{var E=a(f.left,d),_=a(f.subject,d),A=a(f.right,d);return E+(E.length>0?" ":"")+_+A}case Ct.TRUE:return""}},"checkToString"),a=o(function(f,d){return f.checks.reduce(function(p,m,g){return p+(d===f&&g===0?"$":"")+i(m,d)},"")},"queryToString"),s="",l=0;l1&&l=0&&(r=r.replace("!",""),d=!0),r.indexOf("@")>=0&&(r=r.replace("@",""),f=!0),(a||l||f)&&(u=!a&&!s?"":""+e,h=""+n),f&&(e=u=u.toLowerCase(),n=h=h.toLowerCase()),r){case"*=":i=u.indexOf(h)>=0;break;case"$=":i=u.indexOf(h,u.length-h.length)>=0;break;case"^=":i=u.indexOf(h)===0;break;case"=":i=e===n;break;case">":p=!0,i=e>n;break;case">=":p=!0,i=e>=n;break;case"<":p=!0,i=e1&&arguments[1]!==void 0?arguments[1]:!0;return cB(this,t,e,ume)};o(hme,"addParent");l1.forEachUp=function(t){var e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0;return cB(this,t,e,hme)};o(Kje,"addParentAndChildren");l1.forEachUpAndDown=function(t){var e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0;return cB(this,t,e,Kje)};l1.ancestors=l1.parents;qx=fme={data:en.data({field:"data",bindingEvent:"data",allowBinding:!0,allowSetting:!0,settingEvent:"data",settingTriggersEvent:!0,triggerFnName:"trigger",allowGetting:!0,immutableKeys:{id:!0,source:!0,target:!0,parent:!0},updateStyle:!0}),removeData:en.removeData({field:"data",event:"data",triggerFnName:"trigger",triggerEvent:!0,immutableKeys:{id:!0,source:!0,target:!0,parent:!0},updateStyle:!0}),scratch:en.data({field:"scratch",bindingEvent:"scratch",allowBinding:!0,allowSetting:!0,settingEvent:"scratch",settingTriggersEvent:!0,triggerFnName:"trigger",allowGetting:!0,updateStyle:!0}),removeScratch:en.removeData({field:"scratch",event:"scratch",triggerFnName:"trigger",triggerEvent:!0,updateStyle:!0}),rscratch:en.data({field:"rscratch",allowBinding:!1,allowSetting:!0,settingTriggersEvent:!1,allowGetting:!0}),removeRscratch:en.removeData({field:"rscratch",triggerEvent:!1}),id:o(function(){var e=this[0];if(e)return e._private.data.id},"id")};qx.attr=qx.data;qx.removeAttr=qx.removeData;Qje=fme,U6={};o(CP,"defineDegreeFunction");Wt(U6,{degree:CP(function(t,e){return e.source().same(e.target())?2:1}),indegree:CP(function(t,e){return e.target().same(t)?1:0}),outdegree:CP(function(t,e){return e.source().same(t)?1:0})});o(Xg,"defineDegreeBoundsFunction");Wt(U6,{minDegree:Xg("degree",function(t,e){return te}),minIndegree:Xg("indegree",function(t,e){return te}),minOutdegree:Xg("outdegree",function(t,e){return te})});Wt(U6,{totalDegree:o(function(e){for(var r=0,n=this.nodes(),i=0;i0,p=d;d&&(f=f[0]);var m=p?f.position():{x:0,y:0};r!==void 0?h.position(e,r+m[e]):a!==void 0&&h.position({x:a.x+m.x,y:a.y+m.y})}else{var g=n.position(),y=l?n.parent():null,v=y&&y.length>0,x=v;v&&(y=y[0]);var b=x?y.position():{x:0,y:0};return a={x:g.x-b.x,y:g.y-b.y},e===void 0?a:a[e]}else if(!s)return;return this},"relativePosition")};Hl.modelPosition=Hl.point=Hl.position;Hl.modelPositions=Hl.points=Hl.positions;Hl.renderedPoint=Hl.renderedPosition;Hl.relativePoint=Hl.relativePosition;Zje=dme;i1=Rf={};Rf.renderedBoundingBox=function(t){var e=this.boundingBox(t),r=this.cy(),n=r.zoom(),i=r.pan(),a=e.x1*n+i.x,s=e.x2*n+i.x,l=e.y1*n+i.y,u=e.y2*n+i.y;return{x1:a,x2:s,y1:l,y2:u,w:s-a,h:u-l}};Rf.dirtyCompoundBoundsCache=function(){var t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:!1,e=this.cy();return!e.styleEnabled()||!e.hasCompoundNodes()?this:(this.forEachUp(function(r){if(r.isParent()){var n=r._private;n.compoundBoundsClean=!1,n.bbCache=null,t||r.emitAndNotify("bounds")}}),this)};Rf.updateCompoundBounds=function(){var t=arguments.length>0&&arguments[0]!==void 0?arguments[0]:!1,e=this.cy();if(!e.styleEnabled()||!e.hasCompoundNodes())return this;if(!t&&e.batching())return this;function r(s){if(!s.isParent())return;var l=s._private,u=s.children(),h=s.pstyle("compound-sizing-wrt-labels").value==="include",f={width:{val:s.pstyle("min-width").pfValue,left:s.pstyle("min-width-bias-left"),right:s.pstyle("min-width-bias-right")},height:{val:s.pstyle("min-height").pfValue,top:s.pstyle("min-height-bias-top"),bottom:s.pstyle("min-height-bias-bottom")}},d=u.boundingBox({includeLabels:h,includeOverlays:!1,useCache:!1}),p=l.position;(d.w===0||d.h===0)&&(d={w:s.pstyle("width").pfValue,h:s.pstyle("height").pfValue},d.x1=p.x-d.w/2,d.x2=p.x+d.w/2,d.y1=p.y-d.h/2,d.y2=p.y+d.h/2);function m(L,M,N){var k=0,I=0,C=M+N;return L>0&&C>0&&(k=M/C*L,I=N/C*L),{biasDiff:k,biasComplementDiff:I}}o(m,"computeBiasValues");function g(L,M,N,k){if(N.units==="%")switch(k){case"width":return L>0?N.pfValue*L:0;case"height":return M>0?N.pfValue*M:0;case"average":return L>0&&M>0?N.pfValue*(L+M)/2:0;case"min":return L>0&&M>0?L>M?N.pfValue*M:N.pfValue*L:0;case"max":return L>0&&M>0?L>M?N.pfValue*L:N.pfValue*M:0;default:return 0}else return N.units==="px"?N.pfValue:0}o(g,"computePaddingValues");var y=f.width.left.value;f.width.left.units==="px"&&f.width.val>0&&(y=y*100/f.width.val);var v=f.width.right.value;f.width.right.units==="px"&&f.width.val>0&&(v=v*100/f.width.val);var x=f.height.top.value;f.height.top.units==="px"&&f.height.val>0&&(x=x*100/f.height.val);var b=f.height.bottom.value;f.height.bottom.units==="px"&&f.height.val>0&&(b=b*100/f.height.val);var w=m(f.width.val-d.w,y,v),S=w.biasDiff,T=w.biasComplementDiff,E=m(f.height.val-d.h,x,b),_=E.biasDiff,A=E.biasComplementDiff;l.autoPadding=g(d.w,d.h,s.pstyle("padding"),s.pstyle("padding-relative-to").value),l.autoWidth=Math.max(d.w,f.width.val),p.x=(-S+d.x1+d.x2+T)/2,l.autoHeight=Math.max(d.h,f.height.val),p.y=(-_+d.y1+d.y2+A)/2}o(r,"update");for(var n=0;ne.x2?i:e.x2,e.y1=ne.y2?a:e.y2,e.w=e.x2-e.x1,e.h=e.y2-e.y1)},"updateBounds"),F0=o(function(e,r){return r==null?e:Vl(e,r.x1,r.y1,r.x2,r.y2)},"updateBoundsFromBox"),Dx=o(function(e,r,n){return Ul(e,r,n)},"prefixedProperty"),l6=o(function(e,r,n){if(!r.cy().headless()){var i=r._private,a=i.rstyle,s=a.arrowWidth/2,l=r.pstyle(n+"-arrow-shape").value,u,h;if(l!=="none"){n==="source"?(u=a.srcX,h=a.srcY):n==="target"?(u=a.tgtX,h=a.tgtY):(u=a.midX,h=a.midY);var f=i.arrowBounds=i.arrowBounds||{},d=f[n]=f[n]||{};d.x1=u-s,d.y1=h-s,d.x2=u+s,d.y2=h+s,d.w=d.x2-d.x1,d.h=d.y2-d.y1,p6(d,1),Vl(e,d.x1,d.y1,d.x2,d.y2)}}},"updateBoundsFromArrow"),SP=o(function(e,r,n){if(!r.cy().headless()){var i;n?i=n+"-":i="";var a=r._private,s=a.rstyle,l=r.pstyle(i+"label").strValue;if(l){var u=r.pstyle("text-halign"),h=r.pstyle("text-valign"),f=Dx(s,"labelWidth",n),d=Dx(s,"labelHeight",n),p=Dx(s,"labelX",n),m=Dx(s,"labelY",n),g=r.pstyle(i+"text-margin-x").pfValue,y=r.pstyle(i+"text-margin-y").pfValue,v=r.isEdge(),x=r.pstyle(i+"text-rotation"),b=r.pstyle("text-outline-width").pfValue,w=r.pstyle("text-border-width").pfValue,S=w/2,T=r.pstyle("text-background-padding").pfValue,E=2,_=d,A=f,L=A/2,M=_/2,N,k,I,C;if(v)N=p-L,k=p+L,I=m-M,C=m+M;else{switch(u.value){case"left":N=p-A,k=p;break;case"center":N=p-L,k=p+L;break;case"right":N=p,k=p+A;break}switch(h.value){case"top":I=m-_,C=m;break;case"center":I=m-M,C=m+M;break;case"bottom":I=m,C=m+_;break}}N+=g-Math.max(b,S)-T-E,k+=g+Math.max(b,S)+T+E,I+=y-Math.max(b,S)-T-E,C+=y+Math.max(b,S)+T+E;var O=n||"main",D=a.labelBounds,P=D[O]=D[O]||{};P.x1=N,P.y1=I,P.x2=k,P.y2=C,P.w=k-N,P.h=C-I;var F=v&&x.strValue==="autorotate",B=x.pfValue!=null&&x.pfValue!==0;if(F||B){var $=F?Dx(a.rstyle,"labelAngle",n):x.pfValue,z=Math.cos($),Y=Math.sin($),Q=(N+k)/2,X=(I+C)/2;if(!v){switch(u.value){case"left":Q=k;break;case"right":Q=N;break}switch(h.value){case"top":X=C;break;case"bottom":X=I;break}}var ie=o(function(ce,ue){return ce=ce-Q,ue=ue-X,{x:ce*z-ue*Y+Q,y:ce*Y+ue*z+X}},"rotate"),j=ie(N,I),J=ie(N,C),Z=ie(k,I),H=ie(k,C);N=Math.min(j.x,J.x,Z.x,H.x),k=Math.max(j.x,J.x,Z.x,H.x),I=Math.min(j.y,J.y,Z.y,H.y),C=Math.max(j.y,J.y,Z.y,H.y)}var q=O+"Rot",K=D[q]=D[q]||{};K.x1=N,K.y1=I,K.x2=k,K.y2=C,K.w=k-N,K.h=C-I,Vl(e,N,I,k,C),Vl(a.labelBounds.all,N,I,k,C)}return e}},"updateBoundsFromLabel"),Jje=o(function(e,r){if(!r.cy().headless()){var n=r.pstyle("outline-opacity").value,i=r.pstyle("outline-width").value;if(n>0&&i>0){var a=r.pstyle("outline-offset").value,s=r.pstyle("shape").value,l=i+a,u=(e.w+l*2)/e.w,h=(e.h+l*2)/e.h,f=0,d=0;["diamond","pentagon","round-triangle"].includes(s)?(u=(e.w+l*2.4)/e.w,d=-l/3.6):["concave-hexagon","rhomboid","right-rhomboid"].includes(s)?u=(e.w+l*2.4)/e.w:s==="star"?(u=(e.w+l*2.8)/e.w,h=(e.h+l*2.6)/e.h,d=-l/3.8):s==="triangle"?(u=(e.w+l*2.8)/e.w,h=(e.h+l*2.4)/e.h,d=-l/1.4):s==="vee"&&(u=(e.w+l*4.4)/e.w,h=(e.h+l*3.8)/e.h,d=-l*.5);var p=e.h*h-e.h,m=e.w*u-e.w;if(m6(e,[Math.ceil(p/2),Math.ceil(m/2)]),f!=0||d!==0){var g=bWe(e,f,d);Vpe(e,g)}}}},"updateBoundsFromOutline"),eKe=o(function(e,r){var n=e._private.cy,i=n.styleEnabled(),a=n.headless(),s=Gs(),l=e._private,u=e.isNode(),h=e.isEdge(),f,d,p,m,g,y,v=l.rstyle,x=u&&i?e.pstyle("bounds-expansion").pfValue:[0],b=o(function(De){return De.pstyle("display").value!=="none"},"isDisplayed"),w=!i||b(e)&&(!h||b(e.source())&&b(e.target()));if(w){var S=0,T=0;i&&r.includeOverlays&&(S=e.pstyle("overlay-opacity").value,S!==0&&(T=e.pstyle("overlay-padding").value));var E=0,_=0;i&&r.includeUnderlays&&(E=e.pstyle("underlay-opacity").value,E!==0&&(_=e.pstyle("underlay-padding").value));var A=Math.max(T,_),L=0,M=0;if(i&&(L=e.pstyle("width").pfValue,M=L/2),u&&r.includeNodes){var N=e.position();g=N.x,y=N.y;var k=e.outerWidth(),I=k/2,C=e.outerHeight(),O=C/2;f=g-I,d=g+I,p=y-O,m=y+O,Vl(s,f,p,d,m),i&&r.includeOutlines&&Jje(s,e)}else if(h&&r.includeEdges)if(i&&!a){var D=e.pstyle("curve-style").strValue;if(f=Math.min(v.srcX,v.midX,v.tgtX),d=Math.max(v.srcX,v.midX,v.tgtX),p=Math.min(v.srcY,v.midY,v.tgtY),m=Math.max(v.srcY,v.midY,v.tgtY),f-=M,d+=M,p-=M,m+=M,Vl(s,f,p,d,m),D==="haystack"){var P=v.haystackPts;if(P&&P.length===2){if(f=P[0].x,p=P[0].y,d=P[1].x,m=P[1].y,f>d){var F=f;f=d,d=F}if(p>m){var B=p;p=m,m=B}Vl(s,f-M,p-M,d+M,m+M)}}else if(D==="bezier"||D==="unbundled-bezier"||D.endsWith("segments")||D.endsWith("taxi")){var $;switch(D){case"bezier":case"unbundled-bezier":$=v.bezierPts;break;case"segments":case"taxi":case"round-segments":case"round-taxi":$=v.linePts;break}if($!=null)for(var z=0;z<$.length;z++){var Y=$[z];f=Y.x-M,d=Y.x+M,p=Y.y-M,m=Y.y+M,Vl(s,f,p,d,m)}}}else{var Q=e.source(),X=Q.position(),ie=e.target(),j=ie.position();if(f=X.x,d=j.x,p=X.y,m=j.y,f>d){var J=f;f=d,d=J}if(p>m){var Z=p;p=m,m=Z}f-=M,d+=M,p-=M,m+=M,Vl(s,f,p,d,m)}if(i&&r.includeEdges&&h&&(l6(s,e,"mid-source"),l6(s,e,"mid-target"),l6(s,e,"source"),l6(s,e,"target")),i){var H=e.pstyle("ghost").value==="yes";if(H){var q=e.pstyle("ghost-offset-x").pfValue,K=e.pstyle("ghost-offset-y").pfValue;Vl(s,s.x1+q,s.y1+K,s.x2+q,s.y2+K)}}var se=l.bodyBounds=l.bodyBounds||{};g0e(se,s),m6(se,x),p6(se,1),i&&(f=s.x1,d=s.x2,p=s.y1,m=s.y2,Vl(s,f-A,p-A,d+A,m+A));var ce=l.overlayBounds=l.overlayBounds||{};g0e(ce,s),m6(ce,x),p6(ce,1);var ue=l.labelBounds=l.labelBounds||{};ue.all!=null?xWe(ue.all):ue.all=Gs(),i&&r.includeLabels&&(r.includeMainLabels&&SP(s,e,null),h&&(r.includeSourceLabels&&SP(s,e,"source"),r.includeTargetLabels&&SP(s,e,"target")))}return s.x1=el(s.x1),s.y1=el(s.y1),s.x2=el(s.x2),s.y2=el(s.y2),s.w=el(s.x2-s.x1),s.h=el(s.y2-s.y1),s.w>0&&s.h>0&&w&&(m6(s,x),p6(s,1)),s},"boundingBoxImpl"),mme=o(function(e){var r=0,n=o(function(s){return(s?1:0)<=0;l--)s(l);return this};Df.removeAllListeners=function(){return this.removeListener("*")};Df.emit=Df.trigger=function(t,e,r){var n=this.listeners,i=n.length;return this.emitting++,vn(e)||(e=[e]),gKe(this,function(a,s){r!=null&&(n=[{event:s.event,type:s.type,namespace:s.namespace,callback:r}],i=n.length);for(var l=o(function(f){var d=n[f];if(d.type===s.type&&(!d.namespace||d.namespace===s.namespace||d.namespace===pKe)&&a.eventMatches(a.context,d,s)){var p=[s];e!=null&&qYe(p,e),a.beforeEmit(a.context,d,s),d.conf&&d.conf.one&&(a.listeners=a.listeners.filter(function(y){return y!==d}));var m=a.callbackContext(a.context,d,s),g=d.callback.apply(m,p);a.afterEmit(a.context,d,s),g===!1&&(s.stopPropagation(),s.preventDefault())}},"_loop2"),u=0;u1&&!s){var l=this.length-1,u=this[l],h=u._private.data.id;this[l]=void 0,this[e]=u,a.set(h,{ele:u,index:e})}return this.length--,this},"unmergeAt"),unmergeOne:o(function(e){e=e[0];var r=this._private,n=e._private.data.id,i=r.map,a=i.get(n);if(!a)return this;var s=a.index;return this.unmergeAt(s),this},"unmergeOne"),unmerge:o(function(e){var r=this._private.cy;if(!e)return this;if(e&&zt(e)){var n=e;e=r.mutableElements().filter(n)}for(var i=0;i=0;r--){var n=this[r];e(n)&&this.unmergeAt(r)}return this},"unmergeBy"),map:o(function(e,r){for(var n=[],i=this,a=0;an&&(n=u,i=l)}return{value:n,ele:i}},"max"),min:o(function(e,r){for(var n=1/0,i,a=this,s=0;s=0&&a"u"?"undefined":Hi(Symbol))!=e&&Hi(Symbol.iterator)!=e;r&&(L6[Symbol.iterator]=function(){var n=this,i={value:void 0,done:!1},a=0,s=this.length;return bpe({next:o(function(){return a1&&arguments[1]!==void 0?arguments[1]:!0,n=this[0],i=n.cy();if(i.styleEnabled()&&n){this.cleanStyle();var a=n._private.style[e];return a??(r?i.style().getDefaultProperty(e):null)}},"parsedStyle"),numericStyle:o(function(e){var r=this[0];if(r.cy().styleEnabled()&&r){var n=r.pstyle(e);return n.pfValue!==void 0?n.pfValue:n.value}},"numericStyle"),numericStyleUnits:o(function(e){var r=this[0];if(r.cy().styleEnabled()&&r)return r.pstyle(e).units},"numericStyleUnits"),renderedStyle:o(function(e){var r=this.cy();if(!r.styleEnabled())return this;var n=this[0];if(n)return r.style().getRenderedStyle(n,e)},"renderedStyle"),style:o(function(e,r){var n=this.cy();if(!n.styleEnabled())return this;var i=!1,a=n.style();if(Mr(e)){var s=e;a.applyBypass(this,s,i),this.emitAndNotify("style")}else if(zt(e))if(r===void 0){var l=this[0];return l?a.getStylePropertyValue(l,e):void 0}else a.applyBypass(this,e,r,i),this.emitAndNotify("style");else if(e===void 0){var u=this[0];return u?a.getRawStyle(u):void 0}return this},"style"),removeStyle:o(function(e){var r=this.cy();if(!r.styleEnabled())return this;var n=!1,i=r.style(),a=this;if(e===void 0)for(var s=0;s0&&e.push(f[0]),e.push(l[0])}return this.spawn(e,!0).filter(t)},"neighborhood"),closedNeighborhood:o(function(e){return this.neighborhood().add(this).filter(e)},"closedNeighborhood"),openNeighborhood:o(function(e){return this.neighborhood(e)},"openNeighborhood")});Fa.neighbourhood=Fa.neighborhood;Fa.closedNeighbourhood=Fa.closedNeighborhood;Fa.openNeighbourhood=Fa.openNeighborhood;Wt(Fa,{source:tl(o(function(e){var r=this[0],n;return r&&(n=r._private.source||r.cy().collection()),n&&e?n.filter(e):n},"sourceImpl"),"source"),target:tl(o(function(e){var r=this[0],n;return r&&(n=r._private.target||r.cy().collection()),n&&e?n.filter(e):n},"targetImpl"),"target"),sources:X0e({attr:"source"}),targets:X0e({attr:"target"})});o(X0e,"defineSourceFunction");Wt(Fa,{edgesWith:tl(j0e(),"edgesWith"),edgesTo:tl(j0e({thisIsSrc:!0}),"edgesTo")});o(j0e,"defineEdgesWithFunction");Wt(Fa,{connectedEdges:tl(function(t){for(var e=[],r=this,n=0;n0);return s},"components"),component:o(function(){var e=this[0];return e.cy().mutableElements().components(e)[0]},"component")});Fa.componentsOf=Fa.components;Ca=o(function(e,r){var n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:!1,i=arguments.length>3&&arguments[3]!==void 0?arguments[3]:!1;if(e===void 0){oi("A collection must have a reference to the core");return}var a=new Vc,s=!1;if(!r)r=[];else if(r.length>0&&Mr(r[0])&&!Zx(r[0])){s=!0;for(var l=[],u=new c1,h=0,f=r.length;h0&&arguments[0]!==void 0?arguments[0]:!0,e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0,r=this,n=r.cy(),i=n._private,a=[],s=[],l,u=0,h=r.length;u0){for(var B=l.length===r.length?r:new Ca(n,l),$=0;$0&&arguments[0]!==void 0?arguments[0]:!0,e=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0,r=this,n=[],i={},a=r._private.cy;function s(C){for(var O=C._private.edges,D=0;D0&&(t?N.emitAndNotify("remove"):e&&N.emit("remove"));for(var k=0;kf&&Math.abs(g.v)>f;);return p?function(y){return u[y*(u.length-1)|0]}:h},"springRK4Factory")}(),Ln=o(function(e,r,n,i){var a=SKe(e,r,n,i);return function(s,l,u){return s+(l-s)*a(u)}},"cubicBezier"),v6={linear:o(function(e,r,n){return e+(r-e)*n},"linear"),ease:Ln(.25,.1,.25,1),"ease-in":Ln(.42,0,1,1),"ease-out":Ln(0,0,.58,1),"ease-in-out":Ln(.42,0,.58,1),"ease-in-sine":Ln(.47,0,.745,.715),"ease-out-sine":Ln(.39,.575,.565,1),"ease-in-out-sine":Ln(.445,.05,.55,.95),"ease-in-quad":Ln(.55,.085,.68,.53),"ease-out-quad":Ln(.25,.46,.45,.94),"ease-in-out-quad":Ln(.455,.03,.515,.955),"ease-in-cubic":Ln(.55,.055,.675,.19),"ease-out-cubic":Ln(.215,.61,.355,1),"ease-in-out-cubic":Ln(.645,.045,.355,1),"ease-in-quart":Ln(.895,.03,.685,.22),"ease-out-quart":Ln(.165,.84,.44,1),"ease-in-out-quart":Ln(.77,0,.175,1),"ease-in-quint":Ln(.755,.05,.855,.06),"ease-out-quint":Ln(.23,1,.32,1),"ease-in-out-quint":Ln(.86,0,.07,1),"ease-in-expo":Ln(.95,.05,.795,.035),"ease-out-expo":Ln(.19,1,.22,1),"ease-in-out-expo":Ln(1,0,0,1),"ease-in-circ":Ln(.6,.04,.98,.335),"ease-out-circ":Ln(.075,.82,.165,1),"ease-in-out-circ":Ln(.785,.135,.15,.86),spring:o(function(e,r,n){if(n===0)return v6.linear;var i=AKe(e,r,n);return function(a,s,l){return a+(s-a)*i(l)}},"spring"),"cubic-bezier":Ln};o(Q0e,"getEasedValue");o(Z0e,"getValue");o(jg,"ease");o(_Ke,"step$1");o(Nx,"valid");o(LKe,"startAnimation");o(J0e,"stepAll");DKe={animate:en.animate(),animation:en.animation(),animated:en.animated(),clearQueue:en.clearQueue(),delay:en.delay(),delayAnimation:en.delayAnimation(),stop:en.stop(),addToAnimationPool:o(function(e){var r=this;r.styleEnabled()&&r._private.aniEles.merge(e)},"addToAnimationPool"),stopAnimationLoop:o(function(){this._private.animationsRunning=!1},"stopAnimationLoop"),startAnimationLoop:o(function(){var e=this;if(e._private.animationsRunning=!0,!e.styleEnabled())return;function r(){e._private.animationsRunning&&E6(o(function(a){J0e(a,e),r()},"animationStep"))}o(r,"headlessStep");var n=e.renderer();n&&n.beforeRender?n.beforeRender(o(function(a,s){J0e(s,e)},"rendererAnimationStep"),n.beforeRenderPriorities.animations):r()},"startAnimationLoop")},RKe={qualifierCompare:o(function(e,r){return e==null||r==null?e==null&&r==null:e.sameText(r)},"qualifierCompare"),eventMatches:o(function(e,r,n){var i=r.qualifier;return i!=null?e!==n.target&&Zx(n.target)&&i.matches(n.target):!0},"eventMatches"),addEventFields:o(function(e,r){r.cy=e,r.target=e},"addEventFields"),callbackContext:o(function(e,r,n){return r.qualifier!=null?n.target:e},"callbackContext")},h6=o(function(e){return zt(e)?new _f(e):e},"argSelector"),Sme={createEmitter:o(function(){var e=this._private;return e.emitter||(e.emitter=new H6(RKe,this)),this},"createEmitter"),emitter:o(function(){return this._private.emitter},"emitter"),on:o(function(e,r,n){return this.emitter().on(e,h6(r),n),this},"on"),removeListener:o(function(e,r,n){return this.emitter().removeListener(e,h6(r),n),this},"removeListener"),removeAllListeners:o(function(){return this.emitter().removeAllListeners(),this},"removeAllListeners"),one:o(function(e,r,n){return this.emitter().one(e,h6(r),n),this},"one"),once:o(function(e,r,n){return this.emitter().one(e,h6(r),n),this},"once"),emit:o(function(e,r){return this.emitter().emit(e,r),this},"emit"),emitAndNotify:o(function(e,r){return this.emit(e),this.notify(e,r),this},"emitAndNotify")};en.eventAliasesOn(Sme);FP={png:o(function(e){var r=this._private.renderer;return e=e||{},r.png(e)},"png"),jpg:o(function(e){var r=this._private.renderer;return e=e||{},e.bg=e.bg||"#fff",r.jpg(e)},"jpg")};FP.jpeg=FP.jpg;x6={layout:o(function(e){var r=this;if(e==null){oi("Layout options must be specified to make a layout");return}if(e.name==null){oi("A `name` must be specified to make a layout");return}var n=e.name,i=r.extension("layout",n);if(i==null){oi("No such layout `"+n+"` found. Did you forget to import it and `cytoscape.use()` it?");return}var a;zt(e.eles)?a=r.$(e.eles):a=e.eles!=null?e.eles:r.$();var s=new i(Wt({},e,{cy:r,eles:a}));return s},"layout")};x6.createLayout=x6.makeLayout=x6.layout;NKe={notify:o(function(e,r){var n=this._private;if(this.batching()){n.batchNotifications=n.batchNotifications||{};var i=n.batchNotifications[e]=n.batchNotifications[e]||this.collection();r!=null&&i.merge(r);return}if(n.notificationsEnabled){var a=this.renderer();this.destroyed()||!a||a.notify(e,r)}},"notify"),notifications:o(function(e){var r=this._private;return e===void 0?r.notificationsEnabled:(r.notificationsEnabled=!!e,this)},"notifications"),noNotifications:o(function(e){this.notifications(!1),e(),this.notifications(!0)},"noNotifications"),batching:o(function(){return this._private.batchCount>0},"batching"),startBatch:o(function(){var e=this._private;return e.batchCount==null&&(e.batchCount=0),e.batchCount===0&&(e.batchStyleEles=this.collection(),e.batchNotifications={}),e.batchCount++,this},"startBatch"),endBatch:o(function(){var e=this._private;if(e.batchCount===0)return this;if(e.batchCount--,e.batchCount===0){e.batchStyleEles.updateStyle();var r=this.renderer();Object.keys(e.batchNotifications).forEach(function(n){var i=e.batchNotifications[n];i.empty()?r.notify(n):r.notify(n,i)})}return this},"endBatch"),batch:o(function(e){return this.startBatch(),e(),this.endBatch(),this},"batch"),batchData:o(function(e){var r=this;return this.batch(function(){for(var n=Object.keys(e),i=0;i0;)r.removeChild(r.childNodes[0]);e._private.renderer=null,e.mutableElements().forEach(function(n){var i=n._private;i.rscratch={},i.rstyle={},i.animation.current=[],i.animation.queue=[]})},"destroyRenderer"),onRender:o(function(e){return this.on("render",e)},"onRender"),offRender:o(function(e){return this.off("render",e)},"offRender")};zP.invalidateDimensions=zP.resize;b6={collection:o(function(e,r){return zt(e)?this.$(e):xo(e)?e.collection():vn(e)?(r||(r={}),new Ca(this,e,r.unique,r.removed)):new Ca(this)},"collection"),nodes:o(function(e){var r=this.$(function(n){return n.isNode()});return e?r.filter(e):r},"nodes"),edges:o(function(e){var r=this.$(function(n){return n.isEdge()});return e?r.filter(e):r},"edges"),$:o(function(e){var r=this._private.elements;return e?r.filter(e):r.spawnSelf()},"$"),mutableElements:o(function(){return this._private.elements},"mutableElements")};b6.elements=b6.filter=b6.$;Ga={},Fx="t",IKe="f";Ga.apply=function(t){for(var e=this,r=e._private,n=r.cy,i=n.collection(),a=0;a0;if(p||d&&m){var g=void 0;p&&m||p?g=h.properties:m&&(g=h.mappedProperties);for(var y=0;y1&&(S=1),l.color){var E=n.valueMin[0],_=n.valueMax[0],A=n.valueMin[1],L=n.valueMax[1],M=n.valueMin[2],N=n.valueMax[2],k=n.valueMin[3]==null?1:n.valueMin[3],I=n.valueMax[3]==null?1:n.valueMax[3],C=[Math.round(E+(_-E)*S),Math.round(A+(L-A)*S),Math.round(M+(N-M)*S),Math.round(k+(I-k)*S)];a={bypass:n.bypass,name:n.name,value:C,strValue:"rgb("+C[0]+", "+C[1]+", "+C[2]+")"}}else if(l.number){var O=n.valueMin+(n.valueMax-n.valueMin)*S;a=this.parse(n.name,O,n.bypass,p)}else return!1;if(!a)return y(),!1;a.mapping=n,n=a;break}case s.data:{for(var D=n.field.split("."),P=d.data,F=0;F0&&a>0){for(var l={},u=!1,h=0;h0?t.delayAnimation(s).play().promise().then(w):w()}).then(function(){return t.animation({style:l,duration:a,easing:t.pstyle("transition-timing-function").value,queue:!1}).play().promise()}).then(function(){r.removeBypasses(t,i),t.emitAndNotify("style"),n.transitioning=!1})}else n.transitioning&&(this.removeBypasses(t,i),t.emitAndNotify("style"),n.transitioning=!1)};Ga.checkTrigger=function(t,e,r,n,i,a){var s=this.properties[e],l=i(s);l!=null&&l(r,n)&&a(s)};Ga.checkZOrderTrigger=function(t,e,r,n){var i=this;this.checkTrigger(t,e,r,n,function(a){return a.triggersZOrder},function(){i._private.cy.notify("zorder",t)})};Ga.checkBoundsTrigger=function(t,e,r,n){this.checkTrigger(t,e,r,n,function(i){return i.triggersBounds},function(i){t.dirtyCompoundBoundsCache(),t.dirtyBoundingBoxCache(),i.triggersBoundsOfParallelBeziers&&e==="curve-style"&&(r==="bezier"||n==="bezier")&&t.parallelEdges().forEach(function(a){a.isBundledBezier()&&a.dirtyBoundingBoxCache()}),i.triggersBoundsOfConnectedEdges&&e==="display"&&(r==="none"||n==="none")&&t.connectedEdges().forEach(function(a){a.dirtyBoundingBoxCache()})})};Ga.checkTriggers=function(t,e,r,n){t.dirtyStyleCache(),this.checkZOrderTrigger(t,e,r,n),this.checkBoundsTrigger(t,e,r,n)};rb={};rb.applyBypass=function(t,e,r,n){var i=this,a=[],s=!0;if(e==="*"||e==="**"){if(r!==void 0)for(var l=0;li.length?n=n.substr(i.length):n=""}o(l,"removeSelAndBlockFromRemaining");function u(){a.length>s.length?a=a.substr(s.length):a=""}for(o(u,"removePropAndValFromRem");;){var h=n.match(/^\s*$/);if(h)break;var f=n.match(/^\s*((?:.|\s)+?)\s*\{((?:.|\s)+?)\}/);if(!f){tn("Halting stylesheet parsing: String stylesheet contains more to parse but no selector and block found in: "+n);break}i=f[0];var d=f[1];if(d!=="core"){var p=new _f(d);if(p.invalid){tn("Skipping parsing of block: Invalid selector found in string stylesheet: "+d),l();continue}}var m=f[2],g=!1;a=m;for(var y=[];;){var v=a.match(/^\s*$/);if(v)break;var x=a.match(/^\s*(.+?)\s*:\s*(.+?)(?:\s*;|\s*$)/);if(!x){tn("Skipping parsing of block: Invalid formatting of style property and value definitions found in:"+m),g=!0;break}s=x[0];var b=x[1],w=x[2],S=e.properties[b];if(!S){tn("Skipping property: Invalid property name in: "+s),u();continue}var T=r.parse(b,w);if(!T){tn("Skipping property: Invalid property definition in: "+s),u();continue}y.push({name:b,val:w}),u()}if(g){l();break}r.selector(d);for(var E=0;E=7&&e[0]==="d"&&(f=new RegExp(l.data.regex).exec(e))){if(r)return!1;var p=l.data;return{name:t,value:f,strValue:""+e,mapped:p,field:f[1],bypass:r}}else if(e.length>=10&&e[0]==="m"&&(d=new RegExp(l.mapData.regex).exec(e))){if(r||h.multiple)return!1;var m=l.mapData;if(!(h.color||h.number))return!1;var g=this.parse(t,d[4]);if(!g||g.mapped)return!1;var y=this.parse(t,d[5]);if(!y||y.mapped)return!1;if(g.pfValue===y.pfValue||g.strValue===y.strValue)return tn("`"+t+": "+e+"` is not a valid mapper because the output range is zero; converting to `"+t+": "+g.strValue+"`"),this.parse(t,g.strValue);if(h.color){var v=g.value,x=y.value,b=v[0]===x[0]&&v[1]===x[1]&&v[2]===x[2]&&(v[3]===x[3]||(v[3]==null||v[3]===1)&&(x[3]==null||x[3]===1));if(b)return!1}return{name:t,value:d,strValue:""+e,mapped:m,field:d[1],fieldMin:parseFloat(d[2]),fieldMax:parseFloat(d[3]),valueMin:g.value,valueMax:y.value,bypass:r}}}if(h.multiple&&n!=="multiple"){var w;if(u?w=e.split(/\s+/):vn(e)?w=e:w=[e],h.evenMultiple&&w.length%2!==0)return null;for(var S=[],T=[],E=[],_="",A=!1,L=0;L0?" ":"")+M.strValue}return h.validate&&!h.validate(S,T)?null:h.singleEnum&&A?S.length===1&&zt(S[0])?{name:t,value:S[0],strValue:S[0],bypass:r}:null:{name:t,value:S,pfValue:E,strValue:_,bypass:r,units:T}}var N=o(function(){for(var H=0;Hh.max||h.strictMax&&e===h.max))return null;var D={name:t,value:e,strValue:""+e+(k||""),units:k,bypass:r};return h.unitless||k!=="px"&&k!=="em"?D.pfValue=e:D.pfValue=k==="px"||!k?e:this.getEmSizeInPixels()*e,(k==="ms"||k==="s")&&(D.pfValue=k==="ms"?e:1e3*e),(k==="deg"||k==="rad")&&(D.pfValue=k==="rad"?e:mWe(e)),k==="%"&&(D.pfValue=e/100),D}else if(h.propList){var P=[],F=""+e;if(F!=="none"){for(var B=F.split(/\s*,\s*|\s+/),$=0;$0&&l>0&&!isNaN(n.w)&&!isNaN(n.h)&&n.w>0&&n.h>0){u=Math.min((s-2*r)/n.w,(l-2*r)/n.h),u=u>this._private.maxZoom?this._private.maxZoom:u,u=u=n.minZoom&&(n.maxZoom=r),this},"zoomRange"),minZoom:o(function(e){return e===void 0?this._private.minZoom:this.zoomRange({min:e})},"minZoom"),maxZoom:o(function(e){return e===void 0?this._private.maxZoom:this.zoomRange({max:e})},"maxZoom"),getZoomedViewport:o(function(e){var r=this._private,n=r.pan,i=r.zoom,a,s,l=!1;if(r.zoomingEnabled||(l=!0),ft(e)?s=e:Mr(e)&&(s=e.level,e.position!=null?a=F6(e.position,i,n):e.renderedPosition!=null&&(a=e.renderedPosition),a!=null&&!r.panningEnabled&&(l=!0)),s=s>r.maxZoom?r.maxZoom:s,s=sr.maxZoom||!r.zoomingEnabled?s=!0:(r.zoom=u,a.push("zoom"))}if(i&&(!s||!e.cancelOnFailedZoom)&&r.panningEnabled){var h=e.pan;ft(h.x)&&(r.pan.x=h.x,l=!1),ft(h.y)&&(r.pan.y=h.y,l=!1),l||a.push("pan")}return a.length>0&&(a.push("viewport"),this.emit(a.join(" ")),this.notify("viewport")),this},"viewport"),center:o(function(e){var r=this.getCenterPan(e);return r&&(this._private.pan=r,this.emit("pan viewport"),this.notify("viewport")),this},"center"),getCenterPan:o(function(e,r){if(this._private.panningEnabled){if(zt(e)){var n=e;e=this.mutableElements().filter(n)}else xo(e)||(e=this.mutableElements());if(e.length!==0){var i=e.boundingBox(),a=this.width(),s=this.height();r=r===void 0?this._private.zoom:r;var l={x:(a-r*(i.x1+i.x2))/2,y:(s-r*(i.y1+i.y2))/2};return l}}},"getCenterPan"),reset:o(function(){return!this._private.panningEnabled||!this._private.zoomingEnabled?this:(this.viewport({pan:{x:0,y:0},zoom:1}),this)},"reset"),invalidateSize:o(function(){this._private.sizeCache=null},"invalidateSize"),size:o(function(){var e=this._private,r=e.container,n=this;return e.sizeCache=e.sizeCache||(r?function(){var i=n.window().getComputedStyle(r),a=o(function(l){return parseFloat(i.getPropertyValue(l))},"val");return{width:r.clientWidth-a("padding-left")-a("padding-right"),height:r.clientHeight-a("padding-top")-a("padding-bottom")}}():{width:1,height:1})},"size"),width:o(function(){return this.size().width},"width"),height:o(function(){return this.size().height},"height"),extent:o(function(){var e=this._private.pan,r=this._private.zoom,n=this.renderedExtent(),i={x1:(n.x1-e.x)/r,x2:(n.x2-e.x)/r,y1:(n.y1-e.y)/r,y2:(n.y2-e.y)/r};return i.w=i.x2-i.x1,i.h=i.y2-i.y1,i},"extent"),renderedExtent:o(function(){var e=this.width(),r=this.height();return{x1:0,y1:0,x2:e,y2:r,w:e,h:r}},"renderedExtent"),multiClickDebounceTime:o(function(e){if(e)this._private.multiClickDebounceTime=e;else return this._private.multiClickDebounceTime;return this},"multiClickDebounceTime")};q0.centre=q0.center;q0.autolockNodes=q0.autolock;q0.autoungrabifyNodes=q0.autoungrabify;jx={data:en.data({field:"data",bindingEvent:"data",allowBinding:!0,allowSetting:!0,settingEvent:"data",settingTriggersEvent:!0,triggerFnName:"trigger",allowGetting:!0,updateStyle:!0}),removeData:en.removeData({field:"data",event:"data",triggerFnName:"trigger",triggerEvent:!0,updateStyle:!0}),scratch:en.data({field:"scratch",bindingEvent:"scratch",allowBinding:!0,allowSetting:!0,settingEvent:"scratch",settingTriggersEvent:!0,triggerFnName:"trigger",allowGetting:!0,updateStyle:!0}),removeScratch:en.removeData({field:"scratch",event:"scratch",triggerFnName:"trigger",triggerEvent:!0,updateStyle:!0})};jx.attr=jx.data;jx.removeAttr=jx.removeData;Kx=o(function(e){var r=this;e=Wt({},e);var n=e.container;n&&!k6(n)&&k6(n[0])&&(n=n[0]);var i=n?n._cyreg:null;i=i||{},i&&i.cy&&(i.cy.destroy(),i={});var a=i.readies=i.readies||[];n&&(n._cyreg=i),i.cy=r;var s=Vi!==void 0&&n!==void 0&&!e.headless,l=e;l.layout=Wt({name:s?"grid":"null"},l.layout),l.renderer=Wt({name:s?"canvas":"null"},l.renderer);var u=o(function(g,y,v){return y!==void 0?y:v!==void 0?v:g},"defVal"),h=this._private={container:n,ready:!1,options:l,elements:new Ca(this),listeners:[],aniEles:new Ca(this),data:l.data||{},scratch:{},layout:null,renderer:null,destroyed:!1,notificationsEnabled:!0,minZoom:1e-50,maxZoom:1e50,zoomingEnabled:u(!0,l.zoomingEnabled),userZoomingEnabled:u(!0,l.userZoomingEnabled),panningEnabled:u(!0,l.panningEnabled),userPanningEnabled:u(!0,l.userPanningEnabled),boxSelectionEnabled:u(!0,l.boxSelectionEnabled),autolock:u(!1,l.autolock,l.autolockNodes),autoungrabify:u(!1,l.autoungrabify,l.autoungrabifyNodes),autounselectify:u(!1,l.autounselectify),styleEnabled:l.styleEnabled===void 0?s:l.styleEnabled,zoom:ft(l.zoom)?l.zoom:1,pan:{x:Mr(l.pan)&&ft(l.pan.x)?l.pan.x:0,y:Mr(l.pan)&&ft(l.pan.y)?l.pan.y:0},animation:{current:[],queue:[]},hasCompoundNodes:!1,multiClickDebounceTime:u(250,l.multiClickDebounceTime)};this.createEmitter(),this.selectionType(l.selectionType),this.zoomRange({min:l.minZoom,max:l.maxZoom});var f=o(function(g,y){var v=g.some(zHe);if(v)return u1.all(g).then(y);y(g)},"loadExtData");h.styleEnabled&&r.setStyle([]);var d=Wt({},l,l.renderer);r.initRenderer(d);var p=o(function(g,y,v){r.notifications(!1);var x=r.mutableElements();x.length>0&&x.remove(),g!=null&&(Mr(g)||vn(g))&&r.add(g),r.one("layoutready",function(w){r.notifications(!0),r.emit(w),r.one("load",y),r.emitAndNotify("load")}).one("layoutstop",function(){r.one("done",v),r.emit("done")});var b=Wt({},r._private.options.layout);b.eles=r.elements(),r.layout(b).run()},"setElesAndLayout");f([l.style,l.elements],function(m){var g=m[0],y=m[1];h.styleEnabled&&r.style().append(g),p(y,function(){r.startAnimationLoop(),h.ready=!0,jn(l.ready)&&r.on("ready",l.ready);for(var v=0;v0,u=Gs(e.boundingBox?e.boundingBox:{x1:0,y1:0,w:r.width(),h:r.height()}),h;if(xo(e.roots))h=e.roots;else if(vn(e.roots)){for(var f=[],d=0;d0;){var O=C(),D=M(O,k);if(D)O.outgoers().filter(function(ue){return ue.isNode()&&n.has(ue)}).forEach(I);else if(D===null){tn("Detected double maximal shift for node `"+O.id()+"`. Bailing maximal adjustment due to cycle. Use `options.maximal: true` only on DAGs.");break}}}L();var P=0;if(e.avoidOverlap)for(var F=0;F0&&x[0].length<=3?Pe/2:0),W=2*Math.PI/x[oe].length*ke;return oe===0&&x[0].length===1&&(me=1),{x:K.x+me*Math.cos(W),y:K.y+me*Math.sin(W)}}else{var _e={x:K.x+(ke+1-(Ie+1)/2)*Se,y:(oe+1)*Ue};return _e}},"getPosition");return n.nodes().layoutPositions(this,e,ce),this};zKe={fit:!0,padding:30,boundingBox:void 0,avoidOverlap:!0,nodeDimensionsIncludeLabels:!1,spacingFactor:void 0,radius:void 0,startAngle:3/2*Math.PI,sweep:void 0,clockwise:!0,sort:void 0,animate:!1,animationDuration:500,animationEasing:void 0,animateFilter:o(function(e,r){return!0},"animateFilter"),ready:void 0,stop:void 0,transform:o(function(e,r){return r},"transform")};o(_me,"CircleLayout");_me.prototype.run=function(){var t=this.options,e=t,r=t.cy,n=e.eles,i=e.counterclockwise!==void 0?!e.counterclockwise:e.clockwise,a=n.nodes().not(":parent");e.sort&&(a=a.sort(e.sort));for(var s=Gs(e.boundingBox?e.boundingBox:{x1:0,y1:0,w:r.width(),h:r.height()}),l={x:s.x1+s.w/2,y:s.y1+s.h/2},u=e.sweep===void 0?2*Math.PI-2*Math.PI/a.length:e.sweep,h=u/Math.max(1,a.length-1),f,d=0,p=0;p1&&e.avoidOverlap){d*=1.75;var x=Math.cos(h)-Math.cos(0),b=Math.sin(h)-Math.sin(0),w=Math.sqrt(d*d/(x*x+b*b));f=Math.max(w,f)}var S=o(function(E,_){var A=e.startAngle+_*h*(i?1:-1),L=f*Math.cos(A),M=f*Math.sin(A),N={x:l.x+L,y:l.y+M};return N},"getPos");return n.nodes().layoutPositions(this,e,S),this};GKe={fit:!0,padding:30,startAngle:3/2*Math.PI,sweep:void 0,clockwise:!0,equidistant:!1,minNodeSpacing:10,boundingBox:void 0,avoidOverlap:!0,nodeDimensionsIncludeLabels:!1,height:void 0,width:void 0,spacingFactor:void 0,concentric:o(function(e){return e.degree()},"concentric"),levelWidth:o(function(e){return e.maxDegree()/4},"levelWidth"),animate:!1,animationDuration:500,animationEasing:void 0,animateFilter:o(function(e,r){return!0},"animateFilter"),ready:void 0,stop:void 0,transform:o(function(e,r){return r},"transform")};o(Lme,"ConcentricLayout");Lme.prototype.run=function(){for(var t=this.options,e=t,r=e.counterclockwise!==void 0?!e.counterclockwise:e.clockwise,n=t.cy,i=e.eles,a=i.nodes().not(":parent"),s=Gs(e.boundingBox?e.boundingBox:{x1:0,y1:0,w:n.width(),h:n.height()}),l={x:s.x1+s.w/2,y:s.y1+s.h/2},u=[],h=0,f=0;f0){var T=Math.abs(b[0].value-S.value);T>=v&&(b=[],x.push(b))}b.push(S)}var E=h+e.minNodeSpacing;if(!e.avoidOverlap){var _=x.length>0&&x[0].length>1,A=Math.min(s.w,s.h)/2-E,L=A/(x.length+_?1:0);E=Math.min(E,L)}for(var M=0,N=0;N1&&e.avoidOverlap){var O=Math.cos(C)-Math.cos(0),D=Math.sin(C)-Math.sin(0),P=Math.sqrt(E*E/(O*O+D*D));M=Math.max(P,M)}k.r=M,M+=E}if(e.equidistant){for(var F=0,B=0,$=0;$=t.numIter||(XKe(n,t),n.temperature=n.temperature*t.coolingFactor,n.temperature=t.animationThreshold&&a(),E6(d)}},"frame");f()}else{for(;h;)h=s(u),u++;rpe(n,t),l()}return this};j6.prototype.stop=function(){return this.stopped=!0,this.thread&&this.thread.stop(),this.emit("layoutstop"),this};j6.prototype.destroy=function(){return this.thread&&this.thread.stop(),this};VKe=o(function(e,r,n){for(var i=n.eles.edges(),a=n.eles.nodes(),s=Gs(n.boundingBox?n.boundingBox:{x1:0,y1:0,w:e.width(),h:e.height()}),l={isCompound:e.hasCompoundNodes(),layoutNodes:[],idToIndex:{},nodeSize:a.size(),graphSet:[],indexToGraph:[],layoutEdges:[],edgeSize:i.size(),temperature:n.initialTemp,clientWidth:s.w,clientHeight:s.h,boundingBox:s},u=n.eles.components(),h={},f=0;f0){l.graphSet.push(A);for(var f=0;fi.count?0:i.graph},"findLCA"),HKe=o(function t(e,r,n,i){var a=i.graphSet[n];if(-10)var d=i.nodeOverlap*f,p=Math.sqrt(l*l+u*u),m=d*l/p,g=d*u/p;else var y=R6(e,l,u),v=R6(r,-1*l,-1*u),x=v.x-y.x,b=v.y-y.y,w=x*x+b*b,p=Math.sqrt(w),d=(e.nodeRepulsion+r.nodeRepulsion)/w,m=d*x/p,g=d*b/p;e.isLocked||(e.offsetX-=m,e.offsetY-=g),r.isLocked||(r.offsetX+=m,r.offsetY+=g)}},"nodeRepulsion"),QKe=o(function(e,r,n,i){if(n>0)var a=e.maxX-r.minX;else var a=r.maxX-e.minX;if(i>0)var s=e.maxY-r.minY;else var s=r.maxY-e.minY;return a>=0&&s>=0?Math.sqrt(a*a+s*s):0},"nodesOverlap"),R6=o(function(e,r,n){var i=e.positionX,a=e.positionY,s=e.height||1,l=e.width||1,u=n/r,h=s/l,f={};return r===0&&0n?(f.x=i,f.y=a+s/2,f):0r&&-1*h<=u&&u<=h?(f.x=i-l/2,f.y=a-l*n/2/r,f):0=h)?(f.x=i+s*r/2/n,f.y=a+s/2,f):(0>n&&(u<=-1*h||u>=h)&&(f.x=i-s*r/2/n,f.y=a-s/2),f)},"findClippingPoint"),ZKe=o(function(e,r){for(var n=0;nn){var v=r.gravity*m/y,x=r.gravity*g/y;p.offsetX+=v,p.offsetY+=x}}}}},"calculateGravityForces"),eQe=o(function(e,r){var n=[],i=0,a=-1;for(n.push.apply(n,e.graphSet[0]),a+=e.graphSet[0].length;i<=a;){var s=n[i++],l=e.idToIndex[s],u=e.layoutNodes[l],h=u.children;if(0n)var a={x:n*e/i,y:n*r/i};else var a={x:e,y:r};return a},"limitForce"),nQe=o(function t(e,r){var n=e.parentId;if(n!=null){var i=r.layoutNodes[r.idToIndex[n]],a=!1;if((i.maxX==null||e.maxX+i.padRight>i.maxX)&&(i.maxX=e.maxX+i.padRight,a=!0),(i.minX==null||e.minX-i.padLefti.maxY)&&(i.maxY=e.maxY+i.padBottom,a=!0),(i.minY==null||e.minY-i.padTopx&&(g+=v+r.componentSpacing,m=0,y=0,v=0)}}},"separateComponents"),iQe={fit:!0,padding:30,boundingBox:void 0,avoidOverlap:!0,avoidOverlapPadding:10,nodeDimensionsIncludeLabels:!1,spacingFactor:void 0,condense:!1,rows:void 0,cols:void 0,position:o(function(e){},"position"),sort:void 0,animate:!1,animationDuration:500,animationEasing:void 0,animateFilter:o(function(e,r){return!0},"animateFilter"),ready:void 0,stop:void 0,transform:o(function(e,r){return r},"transform")};o(Rme,"GridLayout");Rme.prototype.run=function(){var t=this.options,e=t,r=t.cy,n=e.eles,i=n.nodes().not(":parent");e.sort&&(i=i.sort(e.sort));var a=Gs(e.boundingBox?e.boundingBox:{x1:0,y1:0,w:r.width(),h:r.height()});if(a.h===0||a.w===0)n.nodes().layoutPositions(this,e,function(Q){return{x:a.x1,y:a.y1}});else{var s=i.size(),l=Math.sqrt(s*a.h/a.w),u=Math.round(l),h=Math.round(a.w/a.h*l),f=o(function(X){if(X==null)return Math.min(u,h);var ie=Math.min(u,h);ie==u?u=X:h=X},"small"),d=o(function(X){if(X==null)return Math.max(u,h);var ie=Math.max(u,h);ie==u?u=X:h=X},"large"),p=e.rows,m=e.cols!=null?e.cols:e.columns;if(p!=null&&m!=null)u=p,h=m;else if(p!=null&&m==null)u=p,h=Math.ceil(s/u);else if(p==null&&m!=null)h=m,u=Math.ceil(s/h);else if(h*u>s){var g=f(),y=d();(g-1)*y>=s?f(g-1):(y-1)*g>=s&&d(y-1)}else for(;h*u=s?d(x+1):f(v+1)}var b=a.w/h,w=a.h/u;if(e.condense&&(b=0,w=0),e.avoidOverlap)for(var S=0;S=h&&(O=0,C++)},"moveToNextCell"),P={},F=0;F(O=_We(t,e,D[P],D[P+1],D[P+2],D[P+3])))return v(_,O),!0}else if(L.edgeType==="bezier"||L.edgeType==="multibezier"||L.edgeType==="self"||L.edgeType==="compound"){for(var D=L.allpts,P=0;P+5(O=AWe(t,e,D[P],D[P+1],D[P+2],D[P+3],D[P+4],D[P+5])))return v(_,O),!0}for(var F=F||A.source,B=B||A.target,$=i.getArrowWidth(M,N),z=[{name:"source",x:L.arrowStartX,y:L.arrowStartY,angle:L.srcArrowAngle},{name:"target",x:L.arrowEndX,y:L.arrowEndY,angle:L.tgtArrowAngle},{name:"mid-source",x:L.midX,y:L.midY,angle:L.midsrcArrowAngle},{name:"mid-target",x:L.midX,y:L.midY,angle:L.midtgtArrowAngle}],P=0;P0&&(x(F),x(B))}o(b,"checkEdge");function w(_,A,L){return Ul(_,A,L)}o(w,"preprop");function S(_,A){var L=_._private,M=p,N;A?N=A+"-":N="",_.boundingBox();var k=L.labelBounds[A||"main"],I=_.pstyle(N+"label").value,C=_.pstyle("text-events").strValue==="yes";if(!(!C||!I)){var O=w(L.rscratch,"labelX",A),D=w(L.rscratch,"labelY",A),P=w(L.rscratch,"labelAngle",A),F=_.pstyle(N+"text-margin-x").pfValue,B=_.pstyle(N+"text-margin-y").pfValue,$=k.x1-M-F,z=k.x2+M-F,Y=k.y1-M-B,Q=k.y2+M-B;if(P){var X=Math.cos(P),ie=Math.sin(P),j=o(function(ce,ue){return ce=ce-O,ue=ue-D,{x:ce*X-ue*ie+O,y:ce*ie+ue*X+D}},"rotate"),J=j($,Y),Z=j($,Q),H=j(z,Y),q=j(z,Q),K=[J.x+F,J.y+B,H.x+F,H.y+B,q.x+F,q.y+B,Z.x+F,Z.y+B];if(zs(t,e,K))return v(_),!0}else if(s1(k,t,e))return v(_),!0}}o(S,"checkLabel");for(var T=s.length-1;T>=0;T--){var E=s[T];E.isNode()?x(E)||S(E):b(E)||S(E)||S(E,"source")||S(E,"target")}return l};j0.getAllInBox=function(t,e,r,n){var i=this.getCachedZSortedEles().interactive,a=[],s=Math.min(t,r),l=Math.max(t,r),u=Math.min(e,n),h=Math.max(e,n);t=s,r=l,e=u,n=h;for(var f=Gs({x1:t,y1:e,x2:r,y2:n}),d=0;d0?-(Math.PI-e.ang):Math.PI+e.ang},"invertVec"),uQe=o(function(e,r,n,i,a){if(e!==ope?lpe(r,e,Gc):cQe(Jo,Gc),lpe(r,n,Jo),ape=Gc.nx*Jo.ny-Gc.ny*Jo.nx,spe=Gc.nx*Jo.nx-Gc.ny*-Jo.ny,ju=Math.asin(Math.max(-1,Math.min(1,ape))),Math.abs(ju)<1e-6){GP=r.x,$P=r.y,z0=Qg=0;return}G0=1,w6=!1,spe<0?ju<0?ju=Math.PI+ju:(ju=Math.PI-ju,G0=-1,w6=!0):ju>0&&(G0=-1,w6=!0),r.radius!==void 0?Qg=r.radius:Qg=i,O0=ju/2,f6=Math.min(Gc.len/2,Jo.len/2),a?(zc=Math.abs(Math.cos(O0)*Qg/Math.sin(O0)),zc>f6?(zc=f6,z0=Math.abs(zc*Math.sin(O0)/Math.cos(O0))):z0=Qg):(zc=Math.min(f6,Qg),z0=Math.abs(zc*Math.sin(O0)/Math.cos(O0))),VP=r.x+Jo.nx*zc,UP=r.y+Jo.ny*zc,GP=VP-Jo.ny*z0*G0,$P=UP+Jo.nx*z0*G0,Ome=r.x+Gc.nx*zc,Pme=r.y+Gc.ny*zc,ope=r},"calcCornerArc");o(Bme,"drawPreparedRoundCorner");o(mB,"getRoundCorner");$a={};$a.findMidptPtsEtc=function(t,e){var r=e.posPts,n=e.intersectionPts,i=e.vectorNormInverse,a,s=t.pstyle("source-endpoint"),l=t.pstyle("target-endpoint"),u=s.units!=null&&l.units!=null,h=o(function(T,E,_,A){var L=A-E,M=_-T,N=Math.sqrt(M*M+L*L);return{x:-L/N,y:M/N}},"recalcVectorNormInverse"),f=t.pstyle("edge-distances").value;switch(f){case"node-position":a=r;break;case"intersection":a=n;break;case"endpoints":{if(u){var d=this.manualEndptToPx(t.source()[0],s),p=$l(d,2),m=p[0],g=p[1],y=this.manualEndptToPx(t.target()[0],l),v=$l(y,2),x=v[0],b=v[1],w={x1:m,y1:g,x2:x,y2:b};i=h(m,g,x,b),a=w}else tn("Edge ".concat(t.id()," has edge-distances:endpoints specified without manual endpoints specified via source-endpoint and target-endpoint. Falling back on edge-distances:intersection (default).")),a=n;break}}return{midptPts:a,vectorNormInverse:i}};$a.findHaystackPoints=function(t){for(var e=0;e0?Math.max(Te-Ce,0):Math.min(Te+Ce,0)},"subDWH"),I=k(M,A),C=k(N,L),O=!1;b===h?x=Math.abs(I)>Math.abs(C)?i:n:b===u||b===l?(x=n,O=!0):(b===a||b===s)&&(x=i,O=!0);var D=x===n,P=D?C:I,F=D?N:M,B=$pe(F),$=!1;!(O&&(S||E))&&(b===l&&F<0||b===u&&F>0||b===a&&F>0||b===s&&F<0)&&(B*=-1,P=B*Math.abs(P),$=!0);var z;if(S){var Y=T<0?1+T:T;z=Y*P}else{var Q=T<0?P:0;z=Q+T*B}var X=o(function(Te){return Math.abs(Te)<_||Math.abs(Te)>=Math.abs(P)},"getIsTooClose"),ie=X(z),j=X(Math.abs(P)-Math.abs(z)),J=ie||j;if(J&&!$)if(D){var Z=Math.abs(F)<=p/2,H=Math.abs(M)<=m/2;if(Z){var q=(f.x1+f.x2)/2,K=f.y1,se=f.y2;r.segpts=[q,K,q,se]}else if(H){var ce=(f.y1+f.y2)/2,ue=f.x1,te=f.x2;r.segpts=[ue,ce,te,ce]}else r.segpts=[f.x1,f.y2]}else{var De=Math.abs(F)<=d/2,oe=Math.abs(N)<=g/2;if(De){var ke=(f.y1+f.y2)/2,Ie=f.x1,Se=f.x2;r.segpts=[Ie,ke,Se,ke]}else if(oe){var Ue=(f.x1+f.x2)/2,Pe=f.y1,_e=f.y2;r.segpts=[Ue,Pe,Ue,_e]}else r.segpts=[f.x2,f.y1]}else if(D){var me=f.y1+z+(v?p/2*B:0),W=f.x1,fe=f.x2;r.segpts=[W,me,fe,me]}else{var ge=f.x1+z+(v?d/2*B:0),re=f.y1,he=f.y2;r.segpts=[ge,re,ge,he]}if(r.isRound){var ne=t.pstyle("taxi-radius").value,ae=t.pstyle("radius-type").value[0]==="arc-radius";r.radii=new Array(r.segpts.length/2).fill(ne),r.isArcRadius=new Array(r.segpts.length/2).fill(ae)}};$a.tryToCorrectInvalidPoints=function(t,e){var r=t._private.rscratch;if(r.edgeType==="bezier"){var n=e.srcPos,i=e.tgtPos,a=e.srcW,s=e.srcH,l=e.tgtW,u=e.tgtH,h=e.srcShape,f=e.tgtShape,d=e.srcCornerRadius,p=e.tgtCornerRadius,m=e.srcRs,g=e.tgtRs,y=!ft(r.startX)||!ft(r.startY),v=!ft(r.arrowStartX)||!ft(r.arrowStartY),x=!ft(r.endX)||!ft(r.endY),b=!ft(r.arrowEndX)||!ft(r.arrowEndY),w=3,S=this.getArrowWidth(t.pstyle("width").pfValue,t.pstyle("arrow-scale").value)*this.arrowShapeWidth,T=w*S,E=H0({x:r.ctrlpts[0],y:r.ctrlpts[1]},{x:r.startX,y:r.startY}),_=EC.poolIndex()){var O=I;I=C,C=O}var D=L.srcPos=I.position(),P=L.tgtPos=C.position(),F=L.srcW=I.outerWidth(),B=L.srcH=I.outerHeight(),$=L.tgtW=C.outerWidth(),z=L.tgtH=C.outerHeight(),Y=L.srcShape=r.nodeShapes[e.getNodeShape(I)],Q=L.tgtShape=r.nodeShapes[e.getNodeShape(C)],X=L.srcCornerRadius=I.pstyle("corner-radius").value==="auto"?"auto":I.pstyle("corner-radius").pfValue,ie=L.tgtCornerRadius=C.pstyle("corner-radius").value==="auto"?"auto":C.pstyle("corner-radius").pfValue,j=L.tgtRs=C._private.rscratch,J=L.srcRs=I._private.rscratch;L.dirCounts={north:0,west:0,south:0,east:0,northwest:0,southwest:0,northeast:0,southeast:0};for(var Z=0;Z0){var se=a,ce=B0(se,Jg(r)),ue=B0(se,Jg(K)),te=ce;if(ue2){var De=B0(se,{x:K[2],y:K[3]});De0){var he=s,ne=B0(he,Jg(r)),ae=B0(he,Jg(re)),we=ne;if(ae2){var Te=B0(he,{x:re[2],y:re[3]});Te=g||_){v={cp:S,segment:E};break}}if(v)break}var A=v.cp,L=v.segment,M=(g-x)/L.length,N=L.t1-L.t0,k=m?L.t0+N*M:L.t1-N*M;k=Hx(0,k,1),e=t1(A.p0,A.p1,A.p2,k),p=fQe(A.p0,A.p1,A.p2,k);break}case"straight":case"segments":case"haystack":{for(var I=0,C,O,D,P,F=n.allpts.length,B=0;B+3=g));B+=2);var $=g-O,z=$/C;z=Hx(0,z,1),e=yWe(D,P,z),p=Gme(D,P);break}}s("labelX",d,e.x),s("labelY",d,e.y),s("labelAutoAngle",d,p)}},"calculateEndProjection");h("source"),h("target"),this.applyLabelDimensions(t)}};Hc.applyLabelDimensions=function(t){this.applyPrefixedLabelDimensions(t),t.isEdge()&&(this.applyPrefixedLabelDimensions(t,"source"),this.applyPrefixedLabelDimensions(t,"target"))};Hc.applyPrefixedLabelDimensions=function(t,e){var r=t._private,n=this.getLabelText(t,e),i=this.calculateLabelDimensions(t,n),a=t.pstyle("line-height").pfValue,s=t.pstyle("text-wrap").strValue,l=Ul(r.rscratch,"labelWrapCachedLines",e)||[],u=s!=="wrap"?1:Math.max(l.length,1),h=i.height/u,f=h*a,d=i.width,p=i.height+(u-1)*(a-1)*h;Tf(r.rstyle,"labelWidth",e,d),Tf(r.rscratch,"labelWidth",e,d),Tf(r.rstyle,"labelHeight",e,p),Tf(r.rscratch,"labelHeight",e,p),Tf(r.rscratch,"labelLineHeight",e,f)};Hc.getLabelText=function(t,e){var r=t._private,n=e?e+"-":"",i=t.pstyle(n+"label").strValue,a=t.pstyle("text-transform").value,s=o(function(Q,X){return X?(Tf(r.rscratch,Q,e,X),X):Ul(r.rscratch,Q,e)},"rscratch");if(!i)return"";a=="none"||(a=="uppercase"?i=i.toUpperCase():a=="lowercase"&&(i=i.toLowerCase()));var l=t.pstyle("text-wrap").value;if(l==="wrap"){var u=s("labelKey");if(u!=null&&s("labelWrapKey")===u)return s("labelWrapCachedText");for(var h="\u200B",f=i.split(` +`),d=t.pstyle("text-max-width").pfValue,p=t.pstyle("text-overflow-wrap").value,m=p==="anywhere",g=[],y=/[\s\u200b]+|$/g,v=0;vd){var T=x.matchAll(y),E="",_=0,A=Tpe(T),L;try{for(A.s();!(L=A.n()).done;){var M=L.value,N=M[0],k=x.substring(_,M.index);_=M.index+N.length;var I=E.length===0?k:E+k+N,C=this.calculateLabelDimensions(t,I),O=C.width;O<=d?E+=k+N:(E&&g.push(E),E=k+N)}}catch(Y){A.e(Y)}finally{A.f()}E.match(/^[\s\u200b]+$/)||g.push(E)}else g.push(x)}s("labelWrapCachedLines",g),i=s("labelWrapCachedText",g.join(` +`)),s("labelWrapKey",u)}else if(l==="ellipsis"){var D=t.pstyle("text-max-width").pfValue,P="",F="\u2026",B=!1;if(this.calculateLabelDimensions(t,i).widthD)break;P+=i[$],$===i.length-1&&(B=!0)}return B||(P+=F),P}return i};Hc.getLabelJustification=function(t){var e=t.pstyle("text-justification").strValue,r=t.pstyle("text-halign").strValue;if(e==="auto")if(t.isNode())switch(r){case"left":return"right";case"right":return"left";default:return"center"}else return"center";else return e};Hc.calculateLabelDimensions=function(t,e){var r=this,n=r.cy.window(),i=n.document,a=U0(e,t._private.labelDimsKey),s=r.labelDimCache||(r.labelDimCache=[]),l=s[a];if(l!=null)return l;var u=0,h=t.pstyle("font-style").strValue,f=t.pstyle("font-size").pfValue,d=t.pstyle("font-family").strValue,p=t.pstyle("font-weight").strValue,m=this.labelCalcCanvas,g=this.labelCalcCanvasContext;if(!m){m=this.labelCalcCanvas=i.createElement("canvas"),g=this.labelCalcCanvasContext=m.getContext("2d");var y=m.style;y.position="absolute",y.left="-9999px",y.top="-9999px",y.zIndex="-1",y.visibility="hidden",y.pointerEvents="none"}g.font="".concat(h," ").concat(p," ").concat(f,"px ").concat(d);for(var v=0,x=0,b=e.split(` +`),w=0;w1&&arguments[1]!==void 0?arguments[1]:!0;if(e.merge(s),l)for(var u=0;u=t.desktopTapThreshold2}var Je=i(W);ze&&(t.hoverData.tapholdCancelled=!0);var Ve=o(function(){var St=t.hoverData.dragDelta=t.hoverData.dragDelta||[];St.length===0?(St.push(ye[0]),St.push(ye[1])):(St[0]+=ye[0],St[1]+=ye[1])},"updateDragDelta");ge=!0,n(Ae,["mousemove","vmousemove","tapdrag"],W,{x:ae[0],y:ae[1]});var je=o(function(){t.data.bgActivePosistion=void 0,t.hoverData.selecting||re.emit({originalEvent:W,type:"boxstart",position:{x:ae[0],y:ae[1]}}),Ce[4]=1,t.hoverData.selecting=!0,t.redrawHint("select",!0),t.redraw()},"goIntoBoxMode");if(t.hoverData.which===3){if(ze){var kt={originalEvent:W,type:"cxtdrag",position:{x:ae[0],y:ae[1]}};Me?Me.emit(kt):re.emit(kt),t.hoverData.cxtDragged=!0,(!t.hoverData.cxtOver||Ae!==t.hoverData.cxtOver)&&(t.hoverData.cxtOver&&t.hoverData.cxtOver.emit({originalEvent:W,type:"cxtdragout",position:{x:ae[0],y:ae[1]}}),t.hoverData.cxtOver=Ae,Ae&&Ae.emit({originalEvent:W,type:"cxtdragover",position:{x:ae[0],y:ae[1]}}))}}else if(t.hoverData.dragging){if(ge=!0,re.panningEnabled()&&re.userPanningEnabled()){var at;if(t.hoverData.justStartedPan){var xt=t.hoverData.mdownPos;at={x:(ae[0]-xt[0])*he,y:(ae[1]-xt[1])*he},t.hoverData.justStartedPan=!1}else at={x:ye[0]*he,y:ye[1]*he};re.panBy(at),re.emit("dragpan"),t.hoverData.dragged=!0}ae=t.projectIntoViewport(W.clientX,W.clientY)}else if(Ce[4]==1&&(Me==null||Me.pannable())){if(ze){if(!t.hoverData.dragging&&re.boxSelectionEnabled()&&(Je||!re.panningEnabled()||!re.userPanningEnabled()))je();else if(!t.hoverData.selecting&&re.panningEnabled()&&re.userPanningEnabled()){var it=a(Me,t.hoverData.downs);it&&(t.hoverData.dragging=!0,t.hoverData.justStartedPan=!0,Ce[4]=0,t.data.bgActivePosistion=Jg(we),t.redrawHint("select",!0),t.redraw())}Me&&Me.pannable()&&Me.active()&&Me.unactivate()}}else{if(Me&&Me.pannable()&&Me.active()&&Me.unactivate(),(!Me||!Me.grabbed())&&Ae!=Ge&&(Ge&&n(Ge,["mouseout","tapdragout"],W,{x:ae[0],y:ae[1]}),Ae&&n(Ae,["mouseover","tapdragover"],W,{x:ae[0],y:ae[1]}),t.hoverData.last=Ae),Me)if(ze){if(re.boxSelectionEnabled()&&Je)Me&&Me.grabbed()&&(v(He),Me.emit("freeon"),He.emit("free"),t.dragData.didDrag&&(Me.emit("dragfreeon"),He.emit("dragfree"))),je();else if(Me&&Me.grabbed()&&t.nodeIsDraggable(Me)){var dt=!t.dragData.didDrag;dt&&t.redrawHint("eles",!0),t.dragData.didDrag=!0,t.hoverData.draggingEles||g(He,{inDragLayer:!0});var lt={x:0,y:0};if(ft(ye[0])&&ft(ye[1])&&(lt.x+=ye[0],lt.y+=ye[1],dt)){var It=t.hoverData.dragDelta;It&&ft(It[0])&&ft(It[1])&&(lt.x+=It[0],lt.y+=It[1])}t.hoverData.draggingEles=!0,He.silentShift(lt).emit("position drag"),t.redrawHint("drag",!0),t.redraw()}}else Ve();ge=!0}if(Ce[2]=ae[0],Ce[3]=ae[1],ge)return W.stopPropagation&&W.stopPropagation(),W.preventDefault&&W.preventDefault(),!1}},"mousemoveHandler"),!1);var M,N,k;t.registerBinding(e,"mouseup",o(function(W){if(!(t.hoverData.which===1&&W.which!==1&&t.hoverData.capture)){var fe=t.hoverData.capture;if(fe){t.hoverData.capture=!1;var ge=t.cy,re=t.projectIntoViewport(W.clientX,W.clientY),he=t.selection,ne=t.findNearestElement(re[0],re[1],!0,!1),ae=t.dragData.possibleDragElements,we=t.hoverData.down,Te=i(W);if(t.data.bgActivePosistion&&(t.redrawHint("select",!0),t.redraw()),t.hoverData.tapholdCancelled=!0,t.data.bgActivePosistion=void 0,we&&we.unactivate(),t.hoverData.which===3){var Ce={originalEvent:W,type:"cxttapend",position:{x:re[0],y:re[1]}};if(we?we.emit(Ce):ge.emit(Ce),!t.hoverData.cxtDragged){var Ae={originalEvent:W,type:"cxttap",position:{x:re[0],y:re[1]}};we?we.emit(Ae):ge.emit(Ae)}t.hoverData.cxtDragged=!1,t.hoverData.which=null}else if(t.hoverData.which===1){if(n(ne,["mouseup","tapend","vmouseup"],W,{x:re[0],y:re[1]}),!t.dragData.didDrag&&!t.hoverData.dragged&&!t.hoverData.selecting&&!t.hoverData.isOverThresholdDrag&&(n(we,["click","tap","vclick"],W,{x:re[0],y:re[1]}),N=!1,W.timeStamp-k<=ge.multiClickDebounceTime()?(M&&clearTimeout(M),N=!0,k=null,n(we,["dblclick","dbltap","vdblclick"],W,{x:re[0],y:re[1]})):(M=setTimeout(function(){N||n(we,["oneclick","onetap","voneclick"],W,{x:re[0],y:re[1]})},ge.multiClickDebounceTime()),k=W.timeStamp)),we==null&&!t.dragData.didDrag&&!t.hoverData.selecting&&!t.hoverData.dragged&&!i(W)&&(ge.$(r).unselect(["tapunselect"]),ae.length>0&&t.redrawHint("eles",!0),t.dragData.possibleDragElements=ae=ge.collection()),ne==we&&!t.dragData.didDrag&&!t.hoverData.selecting&&ne!=null&&ne._private.selectable&&(t.hoverData.dragging||(ge.selectionType()==="additive"||Te?ne.selected()?ne.unselect(["tapunselect"]):ne.select(["tapselect"]):Te||(ge.$(r).unmerge(ne).unselect(["tapunselect"]),ne.select(["tapselect"]))),t.redrawHint("eles",!0)),t.hoverData.selecting){var Ge=ge.collection(t.getAllInBox(he[0],he[1],he[2],he[3]));t.redrawHint("select",!0),Ge.length>0&&t.redrawHint("eles",!0),ge.emit({type:"boxend",originalEvent:W,position:{x:re[0],y:re[1]}});var Me=o(function(ze){return ze.selectable()&&!ze.selected()},"eleWouldBeSelected");ge.selectionType()==="additive"||Te||ge.$(r).unmerge(Ge).unselect(),Ge.emit("box").stdFilter(Me).select().emit("boxselect"),t.redraw()}if(t.hoverData.dragging&&(t.hoverData.dragging=!1,t.redrawHint("select",!0),t.redrawHint("eles",!0),t.redraw()),!he[4]){t.redrawHint("drag",!0),t.redrawHint("eles",!0);var ye=we&&we.grabbed();v(ae),ye&&(we.emit("freeon"),ae.emit("free"),t.dragData.didDrag&&(we.emit("dragfreeon"),ae.emit("dragfree")))}}he[4]=0,t.hoverData.down=null,t.hoverData.cxtStarted=!1,t.hoverData.draggingEles=!1,t.hoverData.selecting=!1,t.hoverData.isOverThresholdDrag=!1,t.dragData.didDrag=!1,t.hoverData.dragged=!1,t.hoverData.dragDelta=[],t.hoverData.mdownPos=null,t.hoverData.mdownGPos=null}}},"mouseupHandler"),!1);var I=o(function(W){if(!t.scrollingPage){var fe=t.cy,ge=fe.zoom(),re=fe.pan(),he=t.projectIntoViewport(W.clientX,W.clientY),ne=[he[0]*ge+re.x,he[1]*ge+re.y];if(t.hoverData.draggingEles||t.hoverData.dragging||t.hoverData.cxtStarted||A()){W.preventDefault();return}if(fe.panningEnabled()&&fe.userPanningEnabled()&&fe.zoomingEnabled()&&fe.userZoomingEnabled()){W.preventDefault(),t.data.wheelZooming=!0,clearTimeout(t.data.wheelTimeout),t.data.wheelTimeout=setTimeout(function(){t.data.wheelZooming=!1,t.redrawHint("eles",!0),t.redraw()},150);var ae;W.deltaY!=null?ae=W.deltaY/-250:W.wheelDeltaY!=null?ae=W.wheelDeltaY/1e3:ae=W.wheelDelta/1e3,ae=ae*t.wheelSensitivity;var we=W.deltaMode===1;we&&(ae*=33);var Te=fe.zoom()*Math.pow(10,ae);W.type==="gesturechange"&&(Te=t.gestureStartZoom*W.scale),fe.zoom({level:Te,renderedPosition:{x:ne[0],y:ne[1]}}),fe.emit(W.type==="gesturechange"?"pinchzoom":"scrollzoom")}}},"wheelHandler");t.registerBinding(t.container,"wheel",I,!0),t.registerBinding(e,"scroll",o(function(W){t.scrollingPage=!0,clearTimeout(t.scrollingPageTimeout),t.scrollingPageTimeout=setTimeout(function(){t.scrollingPage=!1},250)},"scrollHandler"),!0),t.registerBinding(t.container,"gesturestart",o(function(W){t.gestureStartZoom=t.cy.zoom(),t.hasTouchStarted||W.preventDefault()},"gestureStartHandler"),!0),t.registerBinding(t.container,"gesturechange",function(me){t.hasTouchStarted||I(me)},!0),t.registerBinding(t.container,"mouseout",o(function(W){var fe=t.projectIntoViewport(W.clientX,W.clientY);t.cy.emit({originalEvent:W,type:"mouseout",position:{x:fe[0],y:fe[1]}})},"mouseOutHandler"),!1),t.registerBinding(t.container,"mouseover",o(function(W){var fe=t.projectIntoViewport(W.clientX,W.clientY);t.cy.emit({originalEvent:W,type:"mouseover",position:{x:fe[0],y:fe[1]}})},"mouseOverHandler"),!1);var C,O,D,P,F,B,$,z,Y,Q,X,ie,j,J=o(function(W,fe,ge,re){return Math.sqrt((ge-W)*(ge-W)+(re-fe)*(re-fe))},"distance"),Z=o(function(W,fe,ge,re){return(ge-W)*(ge-W)+(re-fe)*(re-fe)},"distanceSq"),H;t.registerBinding(t.container,"touchstart",H=o(function(W){if(t.hasTouchStarted=!0,!!L(W)){b(),t.touchData.capture=!0,t.data.bgActivePosistion=void 0;var fe=t.cy,ge=t.touchData.now,re=t.touchData.earlier;if(W.touches[0]){var he=t.projectIntoViewport(W.touches[0].clientX,W.touches[0].clientY);ge[0]=he[0],ge[1]=he[1]}if(W.touches[1]){var he=t.projectIntoViewport(W.touches[1].clientX,W.touches[1].clientY);ge[2]=he[0],ge[3]=he[1]}if(W.touches[2]){var he=t.projectIntoViewport(W.touches[2].clientX,W.touches[2].clientY);ge[4]=he[0],ge[5]=he[1]}if(W.touches[1]){t.touchData.singleTouchMoved=!0,v(t.dragData.touchDragEles);var ne=t.findContainerClientCoords();Y=ne[0],Q=ne[1],X=ne[2],ie=ne[3],C=W.touches[0].clientX-Y,O=W.touches[0].clientY-Q,D=W.touches[1].clientX-Y,P=W.touches[1].clientY-Q,j=0<=C&&C<=X&&0<=D&&D<=X&&0<=O&&O<=ie&&0<=P&&P<=ie;var ae=fe.pan(),we=fe.zoom();F=J(C,O,D,P),B=Z(C,O,D,P),$=[(C+D)/2,(O+P)/2],z=[($[0]-ae.x)/we,($[1]-ae.y)/we];var Te=200,Ce=Te*Te;if(B=1){for(var gt=t.touchData.startPosition=[null,null,null,null,null,null],yt=0;yt=t.touchTapThreshold2}if(fe&&t.touchData.cxt){W.preventDefault();var gt=W.touches[0].clientX-Y,yt=W.touches[0].clientY-Q,tt=W.touches[1].clientX-Y,Ye=W.touches[1].clientY-Q,Je=Z(gt,yt,tt,Ye),Ve=Je/B,je=150,kt=je*je,at=1.5,xt=at*at;if(Ve>=xt||Je>=kt){t.touchData.cxt=!1,t.data.bgActivePosistion=void 0,t.redrawHint("select",!0);var it={originalEvent:W,type:"cxttapend",position:{x:he[0],y:he[1]}};t.touchData.start?(t.touchData.start.unactivate().emit(it),t.touchData.start=null):re.emit(it)}}if(fe&&t.touchData.cxt){var it={originalEvent:W,type:"cxtdrag",position:{x:he[0],y:he[1]}};t.data.bgActivePosistion=void 0,t.redrawHint("select",!0),t.touchData.start?t.touchData.start.emit(it):re.emit(it),t.touchData.start&&(t.touchData.start._private.grabbed=!1),t.touchData.cxtDragged=!0;var dt=t.findNearestElement(he[0],he[1],!0,!0);(!t.touchData.cxtOver||dt!==t.touchData.cxtOver)&&(t.touchData.cxtOver&&t.touchData.cxtOver.emit({originalEvent:W,type:"cxtdragout",position:{x:he[0],y:he[1]}}),t.touchData.cxtOver=dt,dt&&dt.emit({originalEvent:W,type:"cxtdragover",position:{x:he[0],y:he[1]}}))}else if(fe&&W.touches[2]&&re.boxSelectionEnabled())W.preventDefault(),t.data.bgActivePosistion=void 0,this.lastThreeTouch=+new Date,t.touchData.selecting||re.emit({originalEvent:W,type:"boxstart",position:{x:he[0],y:he[1]}}),t.touchData.selecting=!0,t.touchData.didSelect=!0,ge[4]=1,!ge||ge.length===0||ge[0]===void 0?(ge[0]=(he[0]+he[2]+he[4])/3,ge[1]=(he[1]+he[3]+he[5])/3,ge[2]=(he[0]+he[2]+he[4])/3+1,ge[3]=(he[1]+he[3]+he[5])/3+1):(ge[2]=(he[0]+he[2]+he[4])/3,ge[3]=(he[1]+he[3]+he[5])/3),t.redrawHint("select",!0),t.redraw();else if(fe&&W.touches[1]&&!t.touchData.didSelect&&re.zoomingEnabled()&&re.panningEnabled()&&re.userZoomingEnabled()&&re.userPanningEnabled()){W.preventDefault(),t.data.bgActivePosistion=void 0,t.redrawHint("select",!0);var lt=t.dragData.touchDragEles;if(lt){t.redrawHint("drag",!0);for(var It=0;It0&&!t.hoverData.draggingEles&&!t.swipePanning&&t.data.bgActivePosistion!=null&&(t.data.bgActivePosistion=void 0,t.redrawHint("select",!0),t.redraw())}},"touchmoveHandler"),!1);var K;t.registerBinding(e,"touchcancel",K=o(function(W){var fe=t.touchData.start;t.touchData.capture=!1,fe&&fe.unactivate()},"touchcancelHandler"));var se,ce,ue,te;if(t.registerBinding(e,"touchend",se=o(function(W){var fe=t.touchData.start,ge=t.touchData.capture;if(ge)W.touches.length===0&&(t.touchData.capture=!1),W.preventDefault();else return;var re=t.selection;t.swipePanning=!1,t.hoverData.draggingEles=!1;var he=t.cy,ne=he.zoom(),ae=t.touchData.now,we=t.touchData.earlier;if(W.touches[0]){var Te=t.projectIntoViewport(W.touches[0].clientX,W.touches[0].clientY);ae[0]=Te[0],ae[1]=Te[1]}if(W.touches[1]){var Te=t.projectIntoViewport(W.touches[1].clientX,W.touches[1].clientY);ae[2]=Te[0],ae[3]=Te[1]}if(W.touches[2]){var Te=t.projectIntoViewport(W.touches[2].clientX,W.touches[2].clientY);ae[4]=Te[0],ae[5]=Te[1]}fe&&fe.unactivate();var Ce;if(t.touchData.cxt){if(Ce={originalEvent:W,type:"cxttapend",position:{x:ae[0],y:ae[1]}},fe?fe.emit(Ce):he.emit(Ce),!t.touchData.cxtDragged){var Ae={originalEvent:W,type:"cxttap",position:{x:ae[0],y:ae[1]}};fe?fe.emit(Ae):he.emit(Ae)}t.touchData.start&&(t.touchData.start._private.grabbed=!1),t.touchData.cxt=!1,t.touchData.start=null,t.redraw();return}if(!W.touches[2]&&he.boxSelectionEnabled()&&t.touchData.selecting){t.touchData.selecting=!1;var Ge=he.collection(t.getAllInBox(re[0],re[1],re[2],re[3]));re[0]=void 0,re[1]=void 0,re[2]=void 0,re[3]=void 0,re[4]=0,t.redrawHint("select",!0),he.emit({type:"boxend",originalEvent:W,position:{x:ae[0],y:ae[1]}});var Me=o(function(kt){return kt.selectable()&&!kt.selected()},"eleWouldBeSelected");Ge.emit("box").stdFilter(Me).select().emit("boxselect"),Ge.nonempty()&&t.redrawHint("eles",!0),t.redraw()}if(fe?.unactivate(),W.touches[2])t.data.bgActivePosistion=void 0,t.redrawHint("select",!0);else if(!W.touches[1]){if(!W.touches[0]){if(!W.touches[0]){t.data.bgActivePosistion=void 0,t.redrawHint("select",!0);var ye=t.dragData.touchDragEles;if(fe!=null){var He=fe._private.grabbed;v(ye),t.redrawHint("drag",!0),t.redrawHint("eles",!0),He&&(fe.emit("freeon"),ye.emit("free"),t.dragData.didDrag&&(fe.emit("dragfreeon"),ye.emit("dragfree"))),n(fe,["touchend","tapend","vmouseup","tapdragout"],W,{x:ae[0],y:ae[1]}),fe.unactivate(),t.touchData.start=null}else{var ze=t.findNearestElement(ae[0],ae[1],!0,!0);n(ze,["touchend","tapend","vmouseup","tapdragout"],W,{x:ae[0],y:ae[1]})}var Ze=t.touchData.startPosition[0]-ae[0],gt=Ze*Ze,yt=t.touchData.startPosition[1]-ae[1],tt=yt*yt,Ye=gt+tt,Je=Ye*ne*ne;t.touchData.singleTouchMoved||(fe||he.$(":selected").unselect(["tapunselect"]),n(fe,["tap","vclick"],W,{x:ae[0],y:ae[1]}),ce=!1,W.timeStamp-te<=he.multiClickDebounceTime()?(ue&&clearTimeout(ue),ce=!0,te=null,n(fe,["dbltap","vdblclick"],W,{x:ae[0],y:ae[1]})):(ue=setTimeout(function(){ce||n(fe,["onetap","voneclick"],W,{x:ae[0],y:ae[1]})},he.multiClickDebounceTime()),te=W.timeStamp)),fe!=null&&!t.dragData.didDrag&&fe._private.selectable&&Je"u"){var De=[],oe=o(function(W){return{clientX:W.clientX,clientY:W.clientY,force:1,identifier:W.pointerId,pageX:W.pageX,pageY:W.pageY,radiusX:W.width/2,radiusY:W.height/2,screenX:W.screenX,screenY:W.screenY,target:W.target}},"makeTouch"),ke=o(function(W){return{event:W,touch:oe(W)}},"makePointer"),Ie=o(function(W){De.push(ke(W))},"addPointer"),Se=o(function(W){for(var fe=0;fe0)return Y[0]}return null},"getCurveT"),g=Object.keys(p),y=0;y0?m:Hpe(a,s,e,r,n,i,l,u)},"intersectLine"),checkPoint:o(function(e,r,n,i,a,s,l,u){u=u==="auto"?Y0(i,a):u;var h=2*u;if(Qu(e,r,this.points,s,l,i,a-h,[0,-1],n)||Qu(e,r,this.points,s,l,i-h,a,[0,-1],n))return!0;var f=i/2+2*n,d=a/2+2*n,p=[s-f,l-d,s-f,l,s+f,l,s+f,l-d];return!!(zs(e,r,p)||$0(e,r,h,h,s+i/2-u,l+a/2-u,n)||$0(e,r,h,h,s-i/2+u,l+a/2-u,n))},"checkPoint")}};Ju.registerNodeShapes=function(){var t=this.nodeShapes={},e=this;this.generateEllipse(),this.generatePolygon("triangle",ls(3,0)),this.generateRoundPolygon("round-triangle",ls(3,0)),this.generatePolygon("rectangle",ls(4,0)),t.square=t.rectangle,this.generateRoundRectangle(),this.generateCutRectangle(),this.generateBarrel(),this.generateBottomRoundrectangle();{var r=[0,1,1,0,0,-1,-1,0];this.generatePolygon("diamond",r),this.generateRoundPolygon("round-diamond",r)}this.generatePolygon("pentagon",ls(5,0)),this.generateRoundPolygon("round-pentagon",ls(5,0)),this.generatePolygon("hexagon",ls(6,0)),this.generateRoundPolygon("round-hexagon",ls(6,0)),this.generatePolygon("heptagon",ls(7,0)),this.generateRoundPolygon("round-heptagon",ls(7,0)),this.generatePolygon("octagon",ls(8,0)),this.generateRoundPolygon("round-octagon",ls(8,0));var n=new Array(20);{var i=NP(5,0),a=NP(5,Math.PI/5),s=.5*(3-Math.sqrt(5));s*=1.57;for(var l=0;l=e.deqFastCost*S)break}else if(h){if(b>=e.deqCost*m||b>=e.deqAvgCost*p)break}else if(w>=e.deqNoDrawCost*LP)break;var T=e.deq(n,v,y);if(T.length>0)for(var E=0;E0&&(e.onDeqd(n,g),!h&&e.shouldRedraw(n,g,v,y)&&a())},"dequeue"),l=e.priority||JP;i.beforeRender(s,l(n))}},"setupDequeueingImpl")},"setupDequeueing")},pQe=function(){function t(e){var r=arguments.length>1&&arguments[1]!==void 0?arguments[1]:C6;XP(this,t),this.idsByKey=new Vc,this.keyForId=new Vc,this.cachesByLvl=new Vc,this.lvls=[],this.getKey=e,this.doesEleInvalidateKey=r}return o(t,"ElementTextureCacheLookup"),jP(t,[{key:"getIdsFor",value:o(function(r){r==null&&oi("Can not get id list for null key");var n=this.idsByKey,i=this.idsByKey.get(r);return i||(i=new c1,n.set(r,i)),i},"getIdsFor")},{key:"addIdForKey",value:o(function(r,n){r!=null&&this.getIdsFor(r).add(n)},"addIdForKey")},{key:"deleteIdForKey",value:o(function(r,n){r!=null&&this.getIdsFor(r).delete(n)},"deleteIdForKey")},{key:"getNumberOfIdsForKey",value:o(function(r){return r==null?0:this.getIdsFor(r).size},"getNumberOfIdsForKey")},{key:"updateKeyMappingFor",value:o(function(r){var n=r.id(),i=this.keyForId.get(n),a=this.getKey(r);this.deleteIdForKey(i,n),this.addIdForKey(a,n),this.keyForId.set(n,a)},"updateKeyMappingFor")},{key:"deleteKeyMappingFor",value:o(function(r){var n=r.id(),i=this.keyForId.get(n);this.deleteIdForKey(i,n),this.keyForId.delete(n)},"deleteKeyMappingFor")},{key:"keyHasChangedFor",value:o(function(r){var n=r.id(),i=this.keyForId.get(n),a=this.getKey(r);return i!==a},"keyHasChangedFor")},{key:"isInvalid",value:o(function(r){return this.keyHasChangedFor(r)||this.doesEleInvalidateKey(r)},"isInvalid")},{key:"getCachesAt",value:o(function(r){var n=this.cachesByLvl,i=this.lvls,a=n.get(r);return a||(a=new Vc,n.set(r,a),i.push(r)),a},"getCachesAt")},{key:"getCache",value:o(function(r,n){return this.getCachesAt(n).get(r)},"getCache")},{key:"get",value:o(function(r,n){var i=this.getKey(r),a=this.getCache(i,n);return a!=null&&this.updateKeyMappingFor(r),a},"get")},{key:"getForCachedKey",value:o(function(r,n){var i=this.keyForId.get(r.id()),a=this.getCache(i,n);return a},"getForCachedKey")},{key:"hasCache",value:o(function(r,n){return this.getCachesAt(n).has(r)},"hasCache")},{key:"has",value:o(function(r,n){var i=this.getKey(r);return this.hasCache(i,n)},"has")},{key:"setCache",value:o(function(r,n,i){i.key=r,this.getCachesAt(n).set(r,i)},"setCache")},{key:"set",value:o(function(r,n,i){var a=this.getKey(r);this.setCache(a,n,i),this.updateKeyMappingFor(r)},"set")},{key:"deleteCache",value:o(function(r,n){this.getCachesAt(n).delete(r)},"deleteCache")},{key:"delete",value:o(function(r,n){var i=this.getKey(r);this.deleteCache(i,n)},"_delete")},{key:"invalidateKey",value:o(function(r){var n=this;this.lvls.forEach(function(i){return n.deleteCache(r,i)})},"invalidateKey")},{key:"invalidate",value:o(function(r){var n=r.id(),i=this.keyForId.get(n);this.deleteKeyMappingFor(r);var a=this.doesEleInvalidateKey(r);return a&&this.invalidateKey(i),a||this.getNumberOfIdsForKey(i)===0},"invalidate")}]),t}(),fpe=25,d6=50,T6=-4,HP=3,mQe=7.99,gQe=8,yQe=1024,vQe=1024,xQe=1024,bQe=.2,wQe=.8,TQe=10,kQe=.15,EQe=.1,CQe=.9,SQe=.9,AQe=100,_Qe=1,e1={dequeue:"dequeue",downscale:"downscale",highQuality:"highQuality"},LQe=Sa({getKey:null,doesEleInvalidateKey:C6,drawElement:null,getBoundingBox:null,getRotationPoint:null,getRotationOffset:null,isVisible:Ppe,allowEdgeTxrCaching:!0,allowParentTxrCaching:!0}),Bx=o(function(e,r){var n=this;n.renderer=e,n.onDequeues=[];var i=LQe(r);Wt(n,i),n.lookup=new pQe(i.getKey,i.doesEleInvalidateKey),n.setupDequeueing()},"ElementTextureCache"),Yi=Bx.prototype;Yi.reasons=e1;Yi.getTextureQueue=function(t){var e=this;return e.eleImgCaches=e.eleImgCaches||{},e.eleImgCaches[t]=e.eleImgCaches[t]||[]};Yi.getRetiredTextureQueue=function(t){var e=this,r=e.eleImgCaches.retired=e.eleImgCaches.retired||{},n=r[t]=r[t]||[];return n};Yi.getElementQueue=function(){var t=this,e=t.eleCacheQueue=t.eleCacheQueue||new eb(function(r,n){return n.reqs-r.reqs});return e};Yi.getElementKeyToQueue=function(){var t=this,e=t.eleKeyToCacheQueue=t.eleKeyToCacheQueue||{};return e};Yi.getElement=function(t,e,r,n,i){var a=this,s=this.renderer,l=s.cy.zoom(),u=this.lookup;if(!e||e.w===0||e.h===0||isNaN(e.w)||isNaN(e.h)||!t.visible()||t.removed()||!a.allowEdgeTxrCaching&&t.isEdge()||!a.allowParentTxrCaching&&t.isParent())return null;if(n==null&&(n=Math.ceil(tB(l*r))),n=mQe||n>HP)return null;var h=Math.pow(2,n),f=e.h*h,d=e.w*h,p=s.eleTextBiggerThanMin(t,h);if(!this.isVisible(t,p))return null;var m=u.get(t,n);if(m&&m.invalidated&&(m.invalidated=!1,m.texture.invalidatedWidth-=m.width),m)return m;var g;if(f<=fpe?g=fpe:f<=d6?g=d6:g=Math.ceil(f/d6)*d6,f>xQe||d>vQe)return null;var y=a.getTextureQueue(g),v=y[y.length-2],x=o(function(){return a.recycleTexture(g,d)||a.addTexture(g,d)},"addNewTxr");v||(v=y[y.length-1]),v||(v=x()),v.width-v.usedWidthn;N--)L=a.getElement(t,e,r,N,e1.downscale);M()}else return a.queueElement(t,E.level-1),E;else{var k;if(!w&&!S&&!T)for(var I=n-1;I>=T6;I--){var C=u.get(t,I);if(C){k=C;break}}if(b(k))return a.queueElement(t,n),k;v.context.translate(v.usedWidth,0),v.context.scale(h,h),this.drawElement(v.context,t,e,p,!1),v.context.scale(1/h,1/h),v.context.translate(-v.usedWidth,0)}return m={x:v.usedWidth,texture:v,level:n,scale:h,width:d,height:f,scaledLabelShown:p},v.usedWidth+=Math.ceil(d+gQe),v.eleCaches.push(m),u.set(t,n,m),a.checkTextureFullness(v),m};Yi.invalidateElements=function(t){for(var e=0;e=bQe*t.width&&this.retireTexture(t)};Yi.checkTextureFullness=function(t){var e=this,r=e.getTextureQueue(t.height);t.usedWidth/t.width>wQe&&t.fullnessChecks>=TQe?Af(r,t):t.fullnessChecks++};Yi.retireTexture=function(t){var e=this,r=t.height,n=e.getTextureQueue(r),i=this.lookup;Af(n,t),t.retired=!0;for(var a=t.eleCaches,s=0;s=e)return s.retired=!1,s.usedWidth=0,s.invalidatedWidth=0,s.fullnessChecks=0,eB(s.eleCaches),s.context.setTransform(1,0,0,1,0,0),s.context.clearRect(0,0,s.width,s.height),Af(i,s),n.push(s),s}};Yi.queueElement=function(t,e){var r=this,n=r.getElementQueue(),i=r.getElementKeyToQueue(),a=this.getKey(t),s=i[a];if(s)s.level=Math.max(s.level,e),s.eles.merge(t),s.reqs++,n.updateItem(s);else{var l={eles:t.spawn().merge(t),level:e,reqs:1,key:a};n.push(l),i[a]=l}};Yi.dequeue=function(t){for(var e=this,r=e.getElementQueue(),n=e.getElementKeyToQueue(),i=[],a=e.lookup,s=0;s<_Qe&&r.size()>0;s++){var l=r.pop(),u=l.key,h=l.eles[0],f=a.hasCache(h,l.level);if(n[u]=null,f)continue;i.push(l);var d=e.getBoundingBox(h);e.getElement(h,d,t,l.level,e1.dequeue)}return i};Yi.removeFromQueue=function(t){var e=this,r=e.getElementQueue(),n=e.getElementKeyToQueue(),i=this.getKey(t),a=n[i];a!=null&&(a.eles.length===1?(a.reqs=ZP,r.updateItem(a),r.pop(),n[i]=null):a.eles.unmerge(t))};Yi.onDequeue=function(t){this.onDequeues.push(t)};Yi.offDequeue=function(t){Af(this.onDequeues,t)};Yi.setupDequeueing=Yme.setupDequeueing({deqRedrawThreshold:AQe,deqCost:kQe,deqAvgCost:EQe,deqNoDrawCost:CQe,deqFastCost:SQe,deq:o(function(e,r,n){return e.dequeue(r,n)},"deq"),onDeqd:o(function(e,r){for(var n=0;n=RQe||r>M6)return null}n.validateLayersElesOrdering(r,t);var u=n.layersByLevel,h=Math.pow(2,r),f=u[r]=u[r]||[],d,p=n.levelIsComplete(r,t),m,g=o(function(){var M=o(function(O){if(n.validateLayersElesOrdering(O,t),n.levelIsComplete(O,t))return m=u[O],!0},"canUseAsTmpLvl"),N=o(function(O){if(!m)for(var D=r+O;zx<=D&&D<=M6&&!M(D);D+=O);},"checkLvls");N(1),N(-1);for(var k=f.length-1;k>=0;k--){var I=f[k];I.invalid&&Af(f,I)}},"checkTempLevels");if(!p)g();else return f;var y=o(function(){if(!d){d=Gs();for(var M=0;MzQe)return null;var I=n.makeLayer(d,r);if(N!=null){var C=f.indexOf(N)+1;f.splice(C,0,I)}else(M.insert===void 0||M.insert)&&f.unshift(I);return I},"makeLayer");if(n.skipping&&!l)return null;for(var x=null,b=t.length/DQe,w=!l,S=0;S=b||!Upe(x.bb,T.boundingBox()))&&(x=v({insert:!0,after:x}),!x))return null;m||w?n.queueLayer(x,T):n.drawEleInLayer(x,T,r,e),x.eles.push(T),_[r]=x}return m||(w?null:f)};Aa.getEleLevelForLayerLevel=function(t,e){return t};Aa.drawEleInLayer=function(t,e,r,n){var i=this,a=this.renderer,s=t.context,l=e.boundingBox();l.w===0||l.h===0||!e.visible()||(r=i.getEleLevelForLayerLevel(r,n),a.setImgSmoothing(s,!1),a.drawCachedElement(s,e,null,null,r,GQe),a.setImgSmoothing(s,!0))};Aa.levelIsComplete=function(t,e){var r=this,n=r.layersByLevel[t];if(!n||n.length===0)return!1;for(var i=0,a=0;a0||s.invalid)return!1;i+=s.eles.length}return i===e.length};Aa.validateLayersElesOrdering=function(t,e){var r=this.layersByLevel[t];if(r)for(var n=0;n0){e=!0;break}}return e};Aa.invalidateElements=function(t){var e=this;t.length!==0&&(e.lastInvalidationTime=Ku(),!(t.length===0||!e.haveLayers())&&e.updateElementsInLayers(t,o(function(n,i,a){e.invalidateLayer(n)},"invalAssocLayers")))};Aa.invalidateLayer=function(t){if(this.lastInvalidationTime=Ku(),!t.invalid){var e=t.level,r=t.eles,n=this.layersByLevel[e];Af(n,t),t.elesQueue=[],t.invalid=!0,t.replacement&&(t.replacement.invalid=!0);for(var i=0;i3&&arguments[3]!==void 0?arguments[3]:!0,i=arguments.length>4&&arguments[4]!==void 0?arguments[4]:!0,a=arguments.length>5&&arguments[5]!==void 0?arguments[5]:!0,s=this,l=e._private.rscratch;if(!(a&&!e.visible())&&!(l.badLine||l.allpts==null||isNaN(l.allpts[0]))){var u;r&&(u=r,t.translate(-u.x1,-u.y1));var h=a?e.pstyle("opacity").value:1,f=a?e.pstyle("line-opacity").value:1,d=e.pstyle("curve-style").value,p=e.pstyle("line-style").value,m=e.pstyle("width").pfValue,g=e.pstyle("line-cap").value,y=e.pstyle("line-outline-width").value,v=e.pstyle("line-outline-color").value,x=h*f,b=h*f,w=o(function(){var O=arguments.length>0&&arguments[0]!==void 0?arguments[0]:x;d==="straight-triangle"?(s.eleStrokeStyle(t,e,O),s.drawEdgeTrianglePath(e,t,l.allpts)):(t.lineWidth=m,t.lineCap=g,s.eleStrokeStyle(t,e,O),s.drawEdgePath(e,t,l.allpts,p),t.lineCap="butt")},"drawLine"),S=o(function(){var O=arguments.length>0&&arguments[0]!==void 0?arguments[0]:x;if(t.lineWidth=m+y,t.lineCap=g,y>0)s.colorStrokeStyle(t,v[0],v[1],v[2],O);else{t.lineCap="butt";return}d==="straight-triangle"?s.drawEdgeTrianglePath(e,t,l.allpts):(s.drawEdgePath(e,t,l.allpts,p),t.lineCap="butt")},"drawLineOutline"),T=o(function(){i&&s.drawEdgeOverlay(t,e)},"drawOverlay"),E=o(function(){i&&s.drawEdgeUnderlay(t,e)},"drawUnderlay"),_=o(function(){var O=arguments.length>0&&arguments[0]!==void 0?arguments[0]:b;s.drawArrowheads(t,e,O)},"drawArrows"),A=o(function(){s.drawElementText(t,e,null,n)},"drawText");t.lineJoin="round";var L=e.pstyle("ghost").value==="yes";if(L){var M=e.pstyle("ghost-offset-x").pfValue,N=e.pstyle("ghost-offset-y").pfValue,k=e.pstyle("ghost-opacity").value,I=x*k;t.translate(M,N),w(I),_(I),t.translate(-M,-N)}else S();E(),w(),_(),T(),A(),r&&t.translate(u.x1,u.y1)}};Xme=o(function(e){if(!["overlay","underlay"].includes(e))throw new Error("Invalid state");return function(r,n){if(n.visible()){var i=n.pstyle("".concat(e,"-opacity")).value;if(i!==0){var a=this,s=a.usePaths(),l=n._private.rscratch,u=n.pstyle("".concat(e,"-padding")).pfValue,h=2*u,f=n.pstyle("".concat(e,"-color")).value;r.lineWidth=h,l.edgeType==="self"&&!s?r.lineCap="butt":r.lineCap="round",a.colorStrokeStyle(r,f[0],f[1],f[2],i),a.drawEdgePath(n,r,l.allpts,"solid")}}}},"drawEdgeOverlayUnderlay");eh.drawEdgeOverlay=Xme("overlay");eh.drawEdgeUnderlay=Xme("underlay");eh.drawEdgePath=function(t,e,r,n){var i=t._private.rscratch,a=e,s,l=!1,u=this.usePaths(),h=t.pstyle("line-dash-pattern").pfValue,f=t.pstyle("line-dash-offset").pfValue;if(u){var d=r.join("$"),p=i.pathCacheKey&&i.pathCacheKey===d;p?(s=e=i.pathCache,l=!0):(s=e=new Path2D,i.pathCacheKey=d,i.pathCache=s)}if(a.setLineDash)switch(n){case"dotted":a.setLineDash([1,1]);break;case"dashed":a.setLineDash(h),a.lineDashOffset=f;break;case"solid":a.setLineDash([]);break}if(!l&&!i.badLine)switch(e.beginPath&&e.beginPath(),e.moveTo(r[0],r[1]),i.edgeType){case"bezier":case"self":case"compound":case"multibezier":for(var m=2;m+35&&arguments[5]!==void 0?arguments[5]:!0,s=this;if(n==null){if(a&&!s.eleTextBiggerThanMin(e))return}else if(n===!1)return;if(e.isNode()){var l=e.pstyle("label");if(!l||!l.value)return;var u=s.getLabelJustification(e);t.textAlign=u,t.textBaseline="bottom"}else{var h=e.element()._private.rscratch.badLine,f=e.pstyle("label"),d=e.pstyle("source-label"),p=e.pstyle("target-label");if(h||(!f||!f.value)&&(!d||!d.value)&&(!p||!p.value))return;t.textAlign="center",t.textBaseline="bottom"}var m=!r,g;r&&(g=r,t.translate(-g.x1,-g.y1)),i==null?(s.drawText(t,e,null,m,a),e.isEdge()&&(s.drawText(t,e,"source",m,a),s.drawText(t,e,"target",m,a))):s.drawText(t,e,i,m,a),r&&t.translate(g.x1,g.y1)};K0.getFontCache=function(t){var e;this.fontCaches=this.fontCaches||[];for(var r=0;r2&&arguments[2]!==void 0?arguments[2]:!0,n=e.pstyle("font-style").strValue,i=e.pstyle("font-size").pfValue+"px",a=e.pstyle("font-family").strValue,s=e.pstyle("font-weight").strValue,l=r?e.effectiveOpacity()*e.pstyle("text-opacity").value:1,u=e.pstyle("text-outline-opacity").value*l,h=e.pstyle("color").value,f=e.pstyle("text-outline-color").value;t.font=n+" "+s+" "+i+" "+a,t.lineJoin="round",this.colorFillStyle(t,h[0],h[1],h[2],l),this.colorStrokeStyle(t,f[0],f[1],f[2],u)};o(RP,"roundRect");K0.getTextAngle=function(t,e){var r,n=t._private,i=n.rscratch,a=e?e+"-":"",s=t.pstyle(a+"text-rotation"),l=Ul(i,"labelAngle",e);return s.strValue==="autorotate"?r=t.isEdge()?l:0:s.strValue==="none"?r=0:r=s.pfValue,r};K0.drawText=function(t,e,r){var n=arguments.length>3&&arguments[3]!==void 0?arguments[3]:!0,i=arguments.length>4&&arguments[4]!==void 0?arguments[4]:!0,a=e._private,s=a.rscratch,l=i?e.effectiveOpacity():1;if(!(i&&(l===0||e.pstyle("text-opacity").value===0))){r==="main"&&(r=null);var u=Ul(s,"labelX",r),h=Ul(s,"labelY",r),f,d,p=this.getLabelText(e,r);if(p!=null&&p!==""&&!isNaN(u)&&!isNaN(h)){this.setupTextStyle(t,e,i);var m=r?r+"-":"",g=Ul(s,"labelWidth",r),y=Ul(s,"labelHeight",r),v=e.pstyle(m+"text-margin-x").pfValue,x=e.pstyle(m+"text-margin-y").pfValue,b=e.isEdge(),w=e.pstyle("text-halign").value,S=e.pstyle("text-valign").value;b&&(w="center",S="center"),u+=v,h+=x;var T;switch(n?T=this.getTextAngle(e,r):T=0,T!==0&&(f=u,d=h,t.translate(f,d),t.rotate(T),u=0,h=0),S){case"top":break;case"center":h+=y/2;break;case"bottom":h+=y;break}var E=e.pstyle("text-background-opacity").value,_=e.pstyle("text-border-opacity").value,A=e.pstyle("text-border-width").pfValue,L=e.pstyle("text-background-padding").pfValue,M=e.pstyle("text-background-shape").strValue,N=M.indexOf("round")===0,k=2;if(E>0||A>0&&_>0){var I=u-L;switch(w){case"left":I-=g;break;case"center":I-=g/2;break}var C=h-y-L,O=g+2*L,D=y+2*L;if(E>0){var P=t.fillStyle,F=e.pstyle("text-background-color").value;t.fillStyle="rgba("+F[0]+","+F[1]+","+F[2]+","+E*l+")",N?RP(t,I,C,O,D,k):t.fillRect(I,C,O,D),t.fillStyle=P}if(A>0&&_>0){var B=t.strokeStyle,$=t.lineWidth,z=e.pstyle("text-border-color").value,Y=e.pstyle("text-border-style").value;if(t.strokeStyle="rgba("+z[0]+","+z[1]+","+z[2]+","+_*l+")",t.lineWidth=A,t.setLineDash)switch(Y){case"dotted":t.setLineDash([1,1]);break;case"dashed":t.setLineDash([4,2]);break;case"double":t.lineWidth=A/4,t.setLineDash([]);break;case"solid":t.setLineDash([]);break}if(N?RP(t,I,C,O,D,k,"stroke"):t.strokeRect(I,C,O,D),Y==="double"){var Q=A/2;N?RP(t,I+Q,C+Q,O-Q*2,D-Q*2,k,"stroke"):t.strokeRect(I+Q,C+Q,O-Q*2,D-Q*2)}t.setLineDash&&t.setLineDash([]),t.lineWidth=$,t.strokeStyle=B}}var X=2*e.pstyle("text-outline-width").pfValue;if(X>0&&(t.lineWidth=X),e.pstyle("text-wrap").value==="wrap"){var ie=Ul(s,"labelWrapCachedLines",r),j=Ul(s,"labelLineHeight",r),J=g/2,Z=this.getLabelJustification(e);switch(Z==="auto"||(w==="left"?Z==="left"?u+=-g:Z==="center"&&(u+=-J):w==="center"?Z==="left"?u+=-J:Z==="right"&&(u+=J):w==="right"&&(Z==="center"?u+=J:Z==="right"&&(u+=g))),S){case"top":h-=(ie.length-1)*j;break;case"center":case"bottom":h-=(ie.length-1)*j;break}for(var H=0;H0&&t.strokeText(ie[H],u,h),t.fillText(ie[H],u,h),h+=j}else X>0&&t.strokeText(p,u,h),t.fillText(p,u,h);T!==0&&(t.rotate(-T),t.translate(-f,-d))}}};v1={};v1.drawNode=function(t,e,r){var n=arguments.length>3&&arguments[3]!==void 0?arguments[3]:!0,i=arguments.length>4&&arguments[4]!==void 0?arguments[4]:!0,a=arguments.length>5&&arguments[5]!==void 0?arguments[5]:!0,s=this,l,u,h=e._private,f=h.rscratch,d=e.position();if(!(!ft(d.x)||!ft(d.y))&&!(a&&!e.visible())){var p=a?e.effectiveOpacity():1,m=s.usePaths(),g,y=!1,v=e.padding();l=e.width()+2*v,u=e.height()+2*v;var x;r&&(x=r,t.translate(-x.x1,-x.y1));for(var b=e.pstyle("background-image"),w=b.value,S=new Array(w.length),T=new Array(w.length),E=0,_=0;_0&&arguments[0]!==void 0?arguments[0]:I;s.eleFillStyle(t,e,ne)},"setupShapeColor"),H=o(function(){var ne=arguments.length>0&&arguments[0]!==void 0?arguments[0]:z;s.colorStrokeStyle(t,C[0],C[1],C[2],ne)},"setupBorderColor"),q=o(function(){var ne=arguments.length>0&&arguments[0]!==void 0?arguments[0]:ie;s.colorStrokeStyle(t,Q[0],Q[1],Q[2],ne)},"setupOutlineColor"),K=o(function(ne,ae,we,Te){var Ce=s.nodePathCache=s.nodePathCache||[],Ae=Ope(we==="polygon"?we+","+Te.join(","):we,""+ae,""+ne,""+J),Ge=Ce[Ae],Me,ye=!1;return Ge!=null?(Me=Ge,ye=!0,f.pathCache=Me):(Me=new Path2D,Ce[Ae]=f.pathCache=Me),{path:Me,cacheHit:ye}},"getPath"),se=e.pstyle("shape").strValue,ce=e.pstyle("shape-polygon-points").pfValue;if(m){t.translate(d.x,d.y);var ue=K(l,u,se,ce);g=ue.path,y=ue.cacheHit}var te=o(function(){if(!y){var ne=d;m&&(ne={x:0,y:0}),s.nodeShapes[s.getNodeShape(e)].draw(g||t,ne.x,ne.y,l,u,J,f)}m?t.fill(g):t.fill()},"drawShape"),De=o(function(){for(var ne=arguments.length>0&&arguments[0]!==void 0?arguments[0]:p,ae=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!0,we=h.backgrounding,Te=0,Ce=0;Ce0&&arguments[0]!==void 0?arguments[0]:!1,ae=arguments.length>1&&arguments[1]!==void 0?arguments[1]:p;s.hasPie(e)&&(s.drawPie(t,e,ae),ne&&(m||s.nodeShapes[s.getNodeShape(e)].draw(t,d.x,d.y,l,u,J,f)))},"drawPie"),ke=o(function(){var ne=arguments.length>0&&arguments[0]!==void 0?arguments[0]:p,ae=(N>0?N:-N)*ne,we=N>0?0:255;N!==0&&(s.colorFillStyle(t,we,we,we,ae),m?t.fill(g):t.fill())},"darken"),Ie=o(function(){if(k>0){if(t.lineWidth=k,t.lineCap=P,t.lineJoin=D,t.setLineDash)switch(O){case"dotted":t.setLineDash([1,1]);break;case"dashed":t.setLineDash(B),t.lineDashOffset=$;break;case"solid":case"double":t.setLineDash([]);break}if(F!=="center"){if(t.save(),t.lineWidth*=2,F==="inside")m?t.clip(g):t.clip();else{var ne=new Path2D;ne.rect(-l/2-k,-u/2-k,l+2*k,u+2*k),ne.addPath(g),t.clip(ne,"evenodd")}m?t.stroke(g):t.stroke(),t.restore()}else m?t.stroke(g):t.stroke();if(O==="double"){t.lineWidth=k/3;var ae=t.globalCompositeOperation;t.globalCompositeOperation="destination-out",m?t.stroke(g):t.stroke(),t.globalCompositeOperation=ae}t.setLineDash&&t.setLineDash([])}},"drawBorder"),Se=o(function(){if(Y>0){if(t.lineWidth=Y,t.lineCap="butt",t.setLineDash)switch(X){case"dotted":t.setLineDash([1,1]);break;case"dashed":t.setLineDash([4,2]);break;case"solid":case"double":t.setLineDash([]);break}var ne=d;m&&(ne={x:0,y:0});var ae=s.getNodeShape(e),we=k;F==="inside"&&(we=0),F==="outside"&&(we*=2);var Te=(l+we+(Y+j))/l,Ce=(u+we+(Y+j))/u,Ae=l*Te,Ge=u*Ce,Me=s.nodeShapes[ae].points,ye;if(m){var He=K(Ae,Ge,ae,Me);ye=He.path}if(ae==="ellipse")s.drawEllipsePath(ye||t,ne.x,ne.y,Ae,Ge);else if(["round-diamond","round-heptagon","round-hexagon","round-octagon","round-pentagon","round-polygon","round-triangle","round-tag"].includes(ae)){var ze=0,Ze=0,gt=0;ae==="round-diamond"?ze=(we+j+Y)*1.4:ae==="round-heptagon"?(ze=(we+j+Y)*1.075,gt=-(we/2+j+Y)/35):ae==="round-hexagon"?ze=(we+j+Y)*1.12:ae==="round-pentagon"?(ze=(we+j+Y)*1.13,gt=-(we/2+j+Y)/15):ae==="round-tag"?(ze=(we+j+Y)*1.12,Ze=(we/2+Y+j)*.07):ae==="round-triangle"&&(ze=(we+j+Y)*(Math.PI/2),gt=-(we+j/2+Y)/Math.PI),ze!==0&&(Te=(l+ze)/l,Ae=l*Te,["round-hexagon","round-tag"].includes(ae)||(Ce=(u+ze)/u,Ge=u*Ce)),J=J==="auto"?Wpe(Ae,Ge):J;for(var yt=Ae/2,tt=Ge/2,Ye=J+(we+Y+j)/2,Je=new Array(Me.length/2),Ve=new Array(Me.length/2),je=0;je0){if(i=i||n.position(),a==null||s==null){var m=n.padding();a=n.width()+2*m,s=n.height()+2*m}l.colorFillStyle(r,f[0],f[1],f[2],h),l.nodeShapes[d].draw(r,i.x,i.y,a+u*2,s+u*2,p),r.fill()}}}},"drawNodeOverlayUnderlay");v1.drawNodeOverlay=jme("overlay");v1.drawNodeUnderlay=jme("underlay");v1.hasPie=function(t){return t=t[0],t._private.hasPie};v1.drawPie=function(t,e,r,n){e=e[0],n=n||e.position();var i=e.cy().style(),a=e.pstyle("pie-size"),s=n.x,l=n.y,u=e.width(),h=e.height(),f=Math.min(u,h)/2,d=0,p=this.usePaths();p&&(s=0,l=0),a.units==="%"?f=f*a.pfValue:a.pfValue!==void 0&&(f=a.pfValue/2);for(var m=1;m<=i.pieBackgroundN;m++){var g=e.pstyle("pie-"+m+"-background-size").value,y=e.pstyle("pie-"+m+"-background-color").value,v=e.pstyle("pie-"+m+"-background-opacity").value*r,x=g/100;x+d>1&&(x=1-d);var b=1.5*Math.PI+2*Math.PI*d,w=2*Math.PI*x,S=b+w;g===0||d>=1||d+x>1||(t.beginPath(),t.moveTo(s,l),t.arc(s,l,f,b,S),t.closePath(),this.colorFillStyle(t,y[0],y[1],y[2],v),t.fill(),d+=x)}};bo={},QQe=100;bo.getPixelRatio=function(){var t=this.data.contexts[0];if(this.forcedPixelRatio!=null)return this.forcedPixelRatio;var e=this.cy.window(),r=t.backingStorePixelRatio||t.webkitBackingStorePixelRatio||t.mozBackingStorePixelRatio||t.msBackingStorePixelRatio||t.oBackingStorePixelRatio||t.backingStorePixelRatio||1;return(e.devicePixelRatio||1)/r};bo.paintCache=function(t){for(var e=this.paintCaches=this.paintCaches||[],r=!0,n,i=0;is.minMbLowQualFrames&&(s.motionBlurPxRatio=s.mbPxRBlurry)),s.clearingMotionBlur&&(s.motionBlurPxRatio=1),s.textureDrawLastFrame&&!d&&(f[s.NODE]=!0,f[s.SELECT_BOX]=!0);var b=u.style(),w=u.zoom(),S=i!==void 0?i:w,T=u.pan(),E={x:T.x,y:T.y},_={zoom:w,pan:{x:T.x,y:T.y}},A=s.prevViewport,L=A===void 0||_.zoom!==A.zoom||_.pan.x!==A.pan.x||_.pan.y!==A.pan.y;!L&&!(y&&!g)&&(s.motionBlurPxRatio=1),a&&(E=a),S*=l,E.x*=l,E.y*=l;var M=s.getCachedZSortedEles();function N(ue,te,De,oe,ke){var Ie=ue.globalCompositeOperation;ue.globalCompositeOperation="destination-out",s.colorFillStyle(ue,255,255,255,s.motionBlurTransparency),ue.fillRect(te,De,oe,ke),ue.globalCompositeOperation=Ie}o(N,"mbclear");function k(ue,te){var De,oe,ke,Ie;!s.clearingMotionBlur&&(ue===h.bufferContexts[s.MOTIONBLUR_BUFFER_NODE]||ue===h.bufferContexts[s.MOTIONBLUR_BUFFER_DRAG])?(De={x:T.x*m,y:T.y*m},oe=w*m,ke=s.canvasWidth*m,Ie=s.canvasHeight*m):(De=E,oe=S,ke=s.canvasWidth,Ie=s.canvasHeight),ue.setTransform(1,0,0,1,0,0),te==="motionBlur"?N(ue,0,0,ke,Ie):!e&&(te===void 0||te)&&ue.clearRect(0,0,ke,Ie),r||(ue.translate(De.x,De.y),ue.scale(oe,oe)),a&&ue.translate(a.x,a.y),i&&ue.scale(i,i)}if(o(k,"setContextTransform"),d||(s.textureDrawLastFrame=!1),d){if(s.textureDrawLastFrame=!0,!s.textureCache){s.textureCache={},s.textureCache.bb=u.mutableElements().boundingBox(),s.textureCache.texture=s.data.bufferCanvases[s.TEXTURE_BUFFER];var I=s.data.bufferContexts[s.TEXTURE_BUFFER];I.setTransform(1,0,0,1,0,0),I.clearRect(0,0,s.canvasWidth*s.textureMult,s.canvasHeight*s.textureMult),s.render({forcedContext:I,drawOnlyNodeLayer:!0,forcedPxRatio:l*s.textureMult});var _=s.textureCache.viewport={zoom:u.zoom(),pan:u.pan(),width:s.canvasWidth,height:s.canvasHeight};_.mpan={x:(0-_.pan.x)/_.zoom,y:(0-_.pan.y)/_.zoom}}f[s.DRAG]=!1,f[s.NODE]=!1;var C=h.contexts[s.NODE],O=s.textureCache.texture,_=s.textureCache.viewport;C.setTransform(1,0,0,1,0,0),p?N(C,0,0,_.width,_.height):C.clearRect(0,0,_.width,_.height);var D=b.core("outside-texture-bg-color").value,P=b.core("outside-texture-bg-opacity").value;s.colorFillStyle(C,D[0],D[1],D[2],P),C.fillRect(0,0,_.width,_.height);var w=u.zoom();k(C,!1),C.clearRect(_.mpan.x,_.mpan.y,_.width/_.zoom/l,_.height/_.zoom/l),C.drawImage(O,_.mpan.x,_.mpan.y,_.width/_.zoom/l,_.height/_.zoom/l)}else s.textureOnViewport&&!e&&(s.textureCache=null);var F=u.extent(),B=s.pinching||s.hoverData.dragging||s.swipePanning||s.data.wheelZooming||s.hoverData.draggingEles||s.cy.animated(),$=s.hideEdgesOnViewport&&B,z=[];if(z[s.NODE]=!f[s.NODE]&&p&&!s.clearedForMotionBlur[s.NODE]||s.clearingMotionBlur,z[s.NODE]&&(s.clearedForMotionBlur[s.NODE]=!0),z[s.DRAG]=!f[s.DRAG]&&p&&!s.clearedForMotionBlur[s.DRAG]||s.clearingMotionBlur,z[s.DRAG]&&(s.clearedForMotionBlur[s.DRAG]=!0),f[s.NODE]||r||n||z[s.NODE]){var Y=p&&!z[s.NODE]&&m!==1,C=e||(Y?s.data.bufferContexts[s.MOTIONBLUR_BUFFER_NODE]:h.contexts[s.NODE]),Q=p&&!Y?"motionBlur":void 0;k(C,Q),$?s.drawCachedNodes(C,M.nondrag,l,F):s.drawLayeredElements(C,M.nondrag,l,F),s.debug&&s.drawDebugPoints(C,M.nondrag),!r&&!p&&(f[s.NODE]=!1)}if(!n&&(f[s.DRAG]||r||z[s.DRAG])){var Y=p&&!z[s.DRAG]&&m!==1,C=e||(Y?s.data.bufferContexts[s.MOTIONBLUR_BUFFER_DRAG]:h.contexts[s.DRAG]);k(C,p&&!Y?"motionBlur":void 0),$?s.drawCachedNodes(C,M.drag,l,F):s.drawCachedElements(C,M.drag,l,F),s.debug&&s.drawDebugPoints(C,M.drag),!r&&!p&&(f[s.DRAG]=!1)}if(s.showFps||!n&&f[s.SELECT_BOX]&&!r){var C=e||h.contexts[s.SELECT_BOX];if(k(C),s.selection[4]==1&&(s.hoverData.selecting||s.touchData.selecting)){var w=s.cy.zoom(),X=b.core("selection-box-border-width").value/w;C.lineWidth=X,C.fillStyle="rgba("+b.core("selection-box-color").value[0]+","+b.core("selection-box-color").value[1]+","+b.core("selection-box-color").value[2]+","+b.core("selection-box-opacity").value+")",C.fillRect(s.selection[0],s.selection[1],s.selection[2]-s.selection[0],s.selection[3]-s.selection[1]),X>0&&(C.strokeStyle="rgba("+b.core("selection-box-border-color").value[0]+","+b.core("selection-box-border-color").value[1]+","+b.core("selection-box-border-color").value[2]+","+b.core("selection-box-opacity").value+")",C.strokeRect(s.selection[0],s.selection[1],s.selection[2]-s.selection[0],s.selection[3]-s.selection[1]))}if(h.bgActivePosistion&&!s.hoverData.selecting){var w=s.cy.zoom(),ie=h.bgActivePosistion;C.fillStyle="rgba("+b.core("active-bg-color").value[0]+","+b.core("active-bg-color").value[1]+","+b.core("active-bg-color").value[2]+","+b.core("active-bg-opacity").value+")",C.beginPath(),C.arc(ie.x,ie.y,b.core("active-bg-size").pfValue/w,0,2*Math.PI),C.fill()}var j=s.lastRedrawTime;if(s.showFps&&j){j=Math.round(j);var J=Math.round(1e3/j);C.setTransform(1,0,0,1,0,0),C.fillStyle="rgba(255, 0, 0, 0.75)",C.strokeStyle="rgba(255, 0, 0, 0.75)",C.lineWidth=1,C.fillText("1 frame = "+j+" ms = "+J+" fps",0,20);var Z=60;C.strokeRect(0,30,250,20),C.fillRect(0,30,250*Math.min(J/Z,1),20)}r||(f[s.SELECT_BOX]=!1)}if(p&&m!==1){var H=h.contexts[s.NODE],q=s.data.bufferCanvases[s.MOTIONBLUR_BUFFER_NODE],K=h.contexts[s.DRAG],se=s.data.bufferCanvases[s.MOTIONBLUR_BUFFER_DRAG],ce=o(function(te,De,oe){te.setTransform(1,0,0,1,0,0),oe||!x?te.clearRect(0,0,s.canvasWidth,s.canvasHeight):N(te,0,0,s.canvasWidth,s.canvasHeight);var ke=m;te.drawImage(De,0,0,s.canvasWidth*ke,s.canvasHeight*ke,0,0,s.canvasWidth,s.canvasHeight)},"drawMotionBlur");(f[s.NODE]||z[s.NODE])&&(ce(H,q,z[s.NODE]),f[s.NODE]=!1),(f[s.DRAG]||z[s.DRAG])&&(ce(K,se,z[s.DRAG]),f[s.DRAG]=!1)}s.prevViewport=_,s.clearingMotionBlur&&(s.clearingMotionBlur=!1,s.motionBlurCleared=!0,s.motionBlur=!0),p&&(s.motionBlurTimeout=setTimeout(function(){s.motionBlurTimeout=null,s.clearedForMotionBlur[s.NODE]=!1,s.clearedForMotionBlur[s.DRAG]=!1,s.motionBlur=!1,s.clearingMotionBlur=!d,s.mbFrames=0,f[s.NODE]=!0,f[s.DRAG]=!0,s.redraw()},QQe)),e||u.emit("render")};Nf={};Nf.drawPolygonPath=function(t,e,r,n,i,a){var s=n/2,l=i/2;t.beginPath&&t.beginPath(),t.moveTo(e+s*a[0],r+l*a[1]);for(var u=1;u0&&s>0){m.clearRect(0,0,a,s),m.globalCompositeOperation="source-over";var g=this.getCachedZSortedEles();if(t.full)m.translate(-n.x1*h,-n.y1*h),m.scale(h,h),this.drawElements(m,g),m.scale(1/h,1/h),m.translate(n.x1*h,n.y1*h);else{var y=e.pan(),v={x:y.x*h,y:y.y*h};h*=e.zoom(),m.translate(v.x,v.y),m.scale(h,h),this.drawElements(m,g),m.scale(1/h,1/h),m.translate(-v.x,-v.y)}t.bg&&(m.globalCompositeOperation="destination-over",m.fillStyle=t.bg,m.rect(0,0,a,s),m.fill())}return p};o(ZQe,"b64ToBlob");o(xpe,"b64UriToB64");o(Qme,"output");ab.png=function(t){return Qme(t,this.bufferCanvasImage(t),"image/png")};ab.jpg=function(t){return Qme(t,this.bufferCanvasImage(t),"image/jpeg")};Zme={};Zme.nodeShapeImpl=function(t,e,r,n,i,a,s,l){switch(t){case"ellipse":return this.drawEllipsePath(e,r,n,i,a);case"polygon":return this.drawPolygonPath(e,r,n,i,a,s);case"round-polygon":return this.drawRoundPolygonPath(e,r,n,i,a,s,l);case"roundrectangle":case"round-rectangle":return this.drawRoundRectanglePath(e,r,n,i,a,l);case"cutrectangle":case"cut-rectangle":return this.drawCutRectanglePath(e,r,n,i,a,s,l);case"bottomroundrectangle":case"bottom-round-rectangle":return this.drawBottomRoundRectanglePath(e,r,n,i,a,l);case"barrel":return this.drawBarrelPath(e,r,n,i,a)}};JQe=Jme,Yr=Jme.prototype;Yr.CANVAS_LAYERS=3;Yr.SELECT_BOX=0;Yr.DRAG=1;Yr.NODE=2;Yr.BUFFER_COUNT=3;Yr.TEXTURE_BUFFER=0;Yr.MOTIONBLUR_BUFFER_NODE=1;Yr.MOTIONBLUR_BUFFER_DRAG=2;o(Jme,"CanvasRenderer");Yr.redrawHint=function(t,e){var r=this;switch(t){case"eles":r.data.canvasNeedsRedraw[Yr.NODE]=e;break;case"drag":r.data.canvasNeedsRedraw[Yr.DRAG]=e;break;case"select":r.data.canvasNeedsRedraw[Yr.SELECT_BOX]=e;break}};eZe=typeof Path2D<"u";Yr.path2dEnabled=function(t){if(t===void 0)return this.pathsEnabled;this.pathsEnabled=!!t};Yr.usePaths=function(){return eZe&&this.pathsEnabled};Yr.setImgSmoothing=function(t,e){t.imageSmoothingEnabled!=null?t.imageSmoothingEnabled=e:(t.webkitImageSmoothingEnabled=e,t.mozImageSmoothingEnabled=e,t.msImageSmoothingEnabled=e)};Yr.getImgSmoothing=function(t){return t.imageSmoothingEnabled!=null?t.imageSmoothingEnabled:t.webkitImageSmoothingEnabled||t.mozImageSmoothingEnabled||t.msImageSmoothingEnabled};Yr.makeOffscreenCanvas=function(t,e){var r;if((typeof OffscreenCanvas>"u"?"undefined":Hi(OffscreenCanvas))!=="undefined")r=new OffscreenCanvas(t,e);else{var n=this.cy.window(),i=n.document;r=i.createElement("canvas"),r.width=t,r.height=e}return r};[qme,Yc,eh,yB,K0,v1,bo,Nf,ab,Zme].forEach(function(t){Wt(Yr,t)});tZe=[{name:"null",impl:Ime},{name:"base",impl:Hme},{name:"canvas",impl:JQe}],rZe=[{type:"layout",extensions:lQe},{type:"renderer",extensions:tZe}],ege={},tge={};o(rge,"setExtension");o(nge,"getExtension");o(nZe,"setModule");o(iZe,"getModule");qP=o(function(){if(arguments.length===2)return nge.apply(null,arguments);if(arguments.length===3)return rge.apply(null,arguments);if(arguments.length===4)return iZe.apply(null,arguments);if(arguments.length===5)return nZe.apply(null,arguments);oi("Invalid extension access syntax")},"extension");Kx.prototype.extension=qP;rZe.forEach(function(t){t.extensions.forEach(function(e){rge(t.type,e.name,e.impl)})});ige=o(function t(){if(!(this instanceof t))return new t;this.length=0},"Stylesheet"),X0=ige.prototype;X0.instanceString=function(){return"stylesheet"};X0.selector=function(t){var e=this.length++;return this[e]={selector:t,properties:[]},this};X0.css=function(t,e){var r=this.length-1;if(zt(t))this[r].properties.push({name:t,value:e});else if(Mr(t))for(var n=t,i=Object.keys(n),a=0;a{"use strict";o(function(e,r){typeof sb=="object"&&typeof xB=="object"?xB.exports=r():typeof define=="function"&&define.amd?define([],r):typeof sb=="object"?sb.layoutBase=r():e.layoutBase=r()},"webpackUniversalModuleDefinition")(sb,function(){return function(t){var e={};function r(n){if(e[n])return e[n].exports;var i=e[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}return o(r,"__webpack_require__"),r.m=t,r.c=e,r.i=function(n){return n},r.d=function(n,i,a){r.o(n,i)||Object.defineProperty(n,i,{configurable:!1,enumerable:!0,get:a})},r.n=function(n){var i=n&&n.__esModule?o(function(){return n.default},"getDefault"):o(function(){return n},"getModuleExports");return r.d(i,"a",i),i},r.o=function(n,i){return Object.prototype.hasOwnProperty.call(n,i)},r.p="",r(r.s=26)}([function(t,e,r){"use strict";function n(){}o(n,"LayoutConstants"),n.QUALITY=1,n.DEFAULT_CREATE_BENDS_AS_NEEDED=!1,n.DEFAULT_INCREMENTAL=!1,n.DEFAULT_ANIMATION_ON_LAYOUT=!0,n.DEFAULT_ANIMATION_DURING_LAYOUT=!1,n.DEFAULT_ANIMATION_PERIOD=50,n.DEFAULT_UNIFORM_LEAF_NODE_SIZES=!1,n.DEFAULT_GRAPH_MARGIN=15,n.NODE_DIMENSIONS_INCLUDE_LABELS=!1,n.SIMPLE_NODE_SIZE=40,n.SIMPLE_NODE_HALF_SIZE=n.SIMPLE_NODE_SIZE/2,n.EMPTY_COMPOUND_NODE_SIZE=40,n.MIN_EDGE_LENGTH=1,n.WORLD_BOUNDARY=1e6,n.INITIAL_WORLD_BOUNDARY=n.WORLD_BOUNDARY/1e3,n.WORLD_CENTER_X=1200,n.WORLD_CENTER_Y=900,t.exports=n},function(t,e,r){"use strict";var n=r(2),i=r(8),a=r(9);function s(u,h,f){n.call(this,f),this.isOverlapingSourceAndTarget=!1,this.vGraphObject=f,this.bendpoints=[],this.source=u,this.target=h}o(s,"LEdge"),s.prototype=Object.create(n.prototype);for(var l in n)s[l]=n[l];s.prototype.getSource=function(){return this.source},s.prototype.getTarget=function(){return this.target},s.prototype.isInterGraph=function(){return this.isInterGraph},s.prototype.getLength=function(){return this.length},s.prototype.isOverlapingSourceAndTarget=function(){return this.isOverlapingSourceAndTarget},s.prototype.getBendpoints=function(){return this.bendpoints},s.prototype.getLca=function(){return this.lca},s.prototype.getSourceInLca=function(){return this.sourceInLca},s.prototype.getTargetInLca=function(){return this.targetInLca},s.prototype.getOtherEnd=function(u){if(this.source===u)return this.target;if(this.target===u)return this.source;throw"Node is not incident with this edge"},s.prototype.getOtherEndInGraph=function(u,h){for(var f=this.getOtherEnd(u),d=h.getGraphManager().getRoot();;){if(f.getOwner()==h)return f;if(f.getOwner()==d)break;f=f.getOwner().getParent()}return null},s.prototype.updateLength=function(){var u=new Array(4);this.isOverlapingSourceAndTarget=i.getIntersection(this.target.getRect(),this.source.getRect(),u),this.isOverlapingSourceAndTarget||(this.lengthX=u[0]-u[2],this.lengthY=u[1]-u[3],Math.abs(this.lengthX)<1&&(this.lengthX=a.sign(this.lengthX)),Math.abs(this.lengthY)<1&&(this.lengthY=a.sign(this.lengthY)),this.length=Math.sqrt(this.lengthX*this.lengthX+this.lengthY*this.lengthY))},s.prototype.updateLengthSimple=function(){this.lengthX=this.target.getCenterX()-this.source.getCenterX(),this.lengthY=this.target.getCenterY()-this.source.getCenterY(),Math.abs(this.lengthX)<1&&(this.lengthX=a.sign(this.lengthX)),Math.abs(this.lengthY)<1&&(this.lengthY=a.sign(this.lengthY)),this.length=Math.sqrt(this.lengthX*this.lengthX+this.lengthY*this.lengthY)},t.exports=s},function(t,e,r){"use strict";function n(i){this.vGraphObject=i}o(n,"LGraphObject"),t.exports=n},function(t,e,r){"use strict";var n=r(2),i=r(10),a=r(13),s=r(0),l=r(16),u=r(4);function h(d,p,m,g){m==null&&g==null&&(g=p),n.call(this,g),d.graphManager!=null&&(d=d.graphManager),this.estimatedSize=i.MIN_VALUE,this.inclusionTreeDepth=i.MAX_VALUE,this.vGraphObject=g,this.edges=[],this.graphManager=d,m!=null&&p!=null?this.rect=new a(p.x,p.y,m.width,m.height):this.rect=new a}o(h,"LNode"),h.prototype=Object.create(n.prototype);for(var f in n)h[f]=n[f];h.prototype.getEdges=function(){return this.edges},h.prototype.getChild=function(){return this.child},h.prototype.getOwner=function(){return this.owner},h.prototype.getWidth=function(){return this.rect.width},h.prototype.setWidth=function(d){this.rect.width=d},h.prototype.getHeight=function(){return this.rect.height},h.prototype.setHeight=function(d){this.rect.height=d},h.prototype.getCenterX=function(){return this.rect.x+this.rect.width/2},h.prototype.getCenterY=function(){return this.rect.y+this.rect.height/2},h.prototype.getCenter=function(){return new u(this.rect.x+this.rect.width/2,this.rect.y+this.rect.height/2)},h.prototype.getLocation=function(){return new u(this.rect.x,this.rect.y)},h.prototype.getRect=function(){return this.rect},h.prototype.getDiagonal=function(){return Math.sqrt(this.rect.width*this.rect.width+this.rect.height*this.rect.height)},h.prototype.getHalfTheDiagonal=function(){return Math.sqrt(this.rect.height*this.rect.height+this.rect.width*this.rect.width)/2},h.prototype.setRect=function(d,p){this.rect.x=d.x,this.rect.y=d.y,this.rect.width=p.width,this.rect.height=p.height},h.prototype.setCenter=function(d,p){this.rect.x=d-this.rect.width/2,this.rect.y=p-this.rect.height/2},h.prototype.setLocation=function(d,p){this.rect.x=d,this.rect.y=p},h.prototype.moveBy=function(d,p){this.rect.x+=d,this.rect.y+=p},h.prototype.getEdgeListToNode=function(d){var p=[],m,g=this;return g.edges.forEach(function(y){if(y.target==d){if(y.source!=g)throw"Incorrect edge source!";p.push(y)}}),p},h.prototype.getEdgesBetween=function(d){var p=[],m,g=this;return g.edges.forEach(function(y){if(!(y.source==g||y.target==g))throw"Incorrect edge source and/or target";(y.target==d||y.source==d)&&p.push(y)}),p},h.prototype.getNeighborsList=function(){var d=new Set,p=this;return p.edges.forEach(function(m){if(m.source==p)d.add(m.target);else{if(m.target!=p)throw"Incorrect incidency!";d.add(m.source)}}),d},h.prototype.withChildren=function(){var d=new Set,p,m;if(d.add(this),this.child!=null)for(var g=this.child.getNodes(),y=0;yp&&(this.rect.x-=(this.labelWidth-p)/2,this.setWidth(this.labelWidth)),this.labelHeight>m&&(this.labelPos=="center"?this.rect.y-=(this.labelHeight-m)/2:this.labelPos=="top"&&(this.rect.y-=this.labelHeight-m),this.setHeight(this.labelHeight))}}},h.prototype.getInclusionTreeDepth=function(){if(this.inclusionTreeDepth==i.MAX_VALUE)throw"assert failed";return this.inclusionTreeDepth},h.prototype.transform=function(d){var p=this.rect.x;p>s.WORLD_BOUNDARY?p=s.WORLD_BOUNDARY:p<-s.WORLD_BOUNDARY&&(p=-s.WORLD_BOUNDARY);var m=this.rect.y;m>s.WORLD_BOUNDARY?m=s.WORLD_BOUNDARY:m<-s.WORLD_BOUNDARY&&(m=-s.WORLD_BOUNDARY);var g=new u(p,m),y=d.inverseTransformPoint(g);this.setLocation(y.x,y.y)},h.prototype.getLeft=function(){return this.rect.x},h.prototype.getRight=function(){return this.rect.x+this.rect.width},h.prototype.getTop=function(){return this.rect.y},h.prototype.getBottom=function(){return this.rect.y+this.rect.height},h.prototype.getParent=function(){return this.owner==null?null:this.owner.getParent()},t.exports=h},function(t,e,r){"use strict";function n(i,a){i==null&&a==null?(this.x=0,this.y=0):(this.x=i,this.y=a)}o(n,"PointD"),n.prototype.getX=function(){return this.x},n.prototype.getY=function(){return this.y},n.prototype.setX=function(i){this.x=i},n.prototype.setY=function(i){this.y=i},n.prototype.getDifference=function(i){return new DimensionD(this.x-i.x,this.y-i.y)},n.prototype.getCopy=function(){return new n(this.x,this.y)},n.prototype.translate=function(i){return this.x+=i.width,this.y+=i.height,this},t.exports=n},function(t,e,r){"use strict";var n=r(2),i=r(10),a=r(0),s=r(6),l=r(3),u=r(1),h=r(13),f=r(12),d=r(11);function p(g,y,v){n.call(this,v),this.estimatedSize=i.MIN_VALUE,this.margin=a.DEFAULT_GRAPH_MARGIN,this.edges=[],this.nodes=[],this.isConnected=!1,this.parent=g,y!=null&&y instanceof s?this.graphManager=y:y!=null&&y instanceof Layout&&(this.graphManager=y.graphManager)}o(p,"LGraph"),p.prototype=Object.create(n.prototype);for(var m in n)p[m]=n[m];p.prototype.getNodes=function(){return this.nodes},p.prototype.getEdges=function(){return this.edges},p.prototype.getGraphManager=function(){return this.graphManager},p.prototype.getParent=function(){return this.parent},p.prototype.getLeft=function(){return this.left},p.prototype.getRight=function(){return this.right},p.prototype.getTop=function(){return this.top},p.prototype.getBottom=function(){return this.bottom},p.prototype.isConnected=function(){return this.isConnected},p.prototype.add=function(g,y,v){if(y==null&&v==null){var x=g;if(this.graphManager==null)throw"Graph has no graph mgr!";if(this.getNodes().indexOf(x)>-1)throw"Node already in graph!";return x.owner=this,this.getNodes().push(x),x}else{var b=g;if(!(this.getNodes().indexOf(y)>-1&&this.getNodes().indexOf(v)>-1))throw"Source or target not in graph!";if(!(y.owner==v.owner&&y.owner==this))throw"Both owners must be this graph!";return y.owner!=v.owner?null:(b.source=y,b.target=v,b.isInterGraph=!1,this.getEdges().push(b),y.edges.push(b),v!=y&&v.edges.push(b),b)}},p.prototype.remove=function(g){var y=g;if(g instanceof l){if(y==null)throw"Node is null!";if(!(y.owner!=null&&y.owner==this))throw"Owner graph is invalid!";if(this.graphManager==null)throw"Owner graph manager is invalid!";for(var v=y.edges.slice(),x,b=v.length,w=0;w-1&&E>-1))throw"Source and/or target doesn't know this edge!";x.source.edges.splice(T,1),x.target!=x.source&&x.target.edges.splice(E,1);var S=x.source.owner.getEdges().indexOf(x);if(S==-1)throw"Not in owner's edge list!";x.source.owner.getEdges().splice(S,1)}},p.prototype.updateLeftTop=function(){for(var g=i.MAX_VALUE,y=i.MAX_VALUE,v,x,b,w=this.getNodes(),S=w.length,T=0;Tv&&(g=v),y>x&&(y=x)}return g==i.MAX_VALUE?null:(w[0].getParent().paddingLeft!=null?b=w[0].getParent().paddingLeft:b=this.margin,this.left=y-b,this.top=g-b,new f(this.left,this.top))},p.prototype.updateBounds=function(g){for(var y=i.MAX_VALUE,v=-i.MAX_VALUE,x=i.MAX_VALUE,b=-i.MAX_VALUE,w,S,T,E,_,A=this.nodes,L=A.length,M=0;Mw&&(y=w),vT&&(x=T),bw&&(y=w),vT&&(x=T),b=this.nodes.length){var L=0;v.forEach(function(M){M.owner==g&&L++}),L==this.nodes.length&&(this.isConnected=!0)}},t.exports=p},function(t,e,r){"use strict";var n,i=r(1);function a(s){n=r(5),this.layout=s,this.graphs=[],this.edges=[]}o(a,"LGraphManager"),a.prototype.addRoot=function(){var s=this.layout.newGraph(),l=this.layout.newNode(null),u=this.add(s,l);return this.setRootGraph(u),this.rootGraph},a.prototype.add=function(s,l,u,h,f){if(u==null&&h==null&&f==null){if(s==null)throw"Graph is null!";if(l==null)throw"Parent node is null!";if(this.graphs.indexOf(s)>-1)throw"Graph already in this graph mgr!";if(this.graphs.push(s),s.parent!=null)throw"Already has a parent!";if(l.child!=null)throw"Already has a child!";return s.parent=l,l.child=s,s}else{f=u,h=l,u=s;var d=h.getOwner(),p=f.getOwner();if(!(d!=null&&d.getGraphManager()==this))throw"Source not in this graph mgr!";if(!(p!=null&&p.getGraphManager()==this))throw"Target not in this graph mgr!";if(d==p)return u.isInterGraph=!1,d.add(u,h,f);if(u.isInterGraph=!0,u.source=h,u.target=f,this.edges.indexOf(u)>-1)throw"Edge already in inter-graph edge list!";if(this.edges.push(u),!(u.source!=null&&u.target!=null))throw"Edge source and/or target is null!";if(!(u.source.edges.indexOf(u)==-1&&u.target.edges.indexOf(u)==-1))throw"Edge already in source and/or target incidency list!";return u.source.edges.push(u),u.target.edges.push(u),u}},a.prototype.remove=function(s){if(s instanceof n){var l=s;if(l.getGraphManager()!=this)throw"Graph not in this graph mgr";if(!(l==this.rootGraph||l.parent!=null&&l.parent.graphManager==this))throw"Invalid parent node!";var u=[];u=u.concat(l.getEdges());for(var h,f=u.length,d=0;d=s.getRight()?l[0]+=Math.min(s.getX()-a.getX(),a.getRight()-s.getRight()):s.getX()<=a.getX()&&s.getRight()>=a.getRight()&&(l[0]+=Math.min(a.getX()-s.getX(),s.getRight()-a.getRight())),a.getY()<=s.getY()&&a.getBottom()>=s.getBottom()?l[1]+=Math.min(s.getY()-a.getY(),a.getBottom()-s.getBottom()):s.getY()<=a.getY()&&s.getBottom()>=a.getBottom()&&(l[1]+=Math.min(a.getY()-s.getY(),s.getBottom()-a.getBottom()));var f=Math.abs((s.getCenterY()-a.getCenterY())/(s.getCenterX()-a.getCenterX()));s.getCenterY()===a.getCenterY()&&s.getCenterX()===a.getCenterX()&&(f=1);var d=f*l[0],p=l[1]/f;l[0]d)return l[0]=u,l[1]=m,l[2]=f,l[3]=A,!1;if(hf)return l[0]=p,l[1]=h,l[2]=E,l[3]=d,!1;if(uf?(l[0]=y,l[1]=v,k=!0):(l[0]=g,l[1]=m,k=!0):C===D&&(u>f?(l[0]=p,l[1]=m,k=!0):(l[0]=x,l[1]=v,k=!0)),-O===D?f>u?(l[2]=_,l[3]=A,I=!0):(l[2]=E,l[3]=T,I=!0):O===D&&(f>u?(l[2]=S,l[3]=T,I=!0):(l[2]=L,l[3]=A,I=!0)),k&&I)return!1;if(u>f?h>d?(P=this.getCardinalDirection(C,D,4),F=this.getCardinalDirection(O,D,2)):(P=this.getCardinalDirection(-C,D,3),F=this.getCardinalDirection(-O,D,1)):h>d?(P=this.getCardinalDirection(-C,D,1),F=this.getCardinalDirection(-O,D,3)):(P=this.getCardinalDirection(C,D,2),F=this.getCardinalDirection(O,D,4)),!k)switch(P){case 1:$=m,B=u+-w/D,l[0]=B,l[1]=$;break;case 2:B=x,$=h+b*D,l[0]=B,l[1]=$;break;case 3:$=v,B=u+w/D,l[0]=B,l[1]=$;break;case 4:B=y,$=h+-b*D,l[0]=B,l[1]=$;break}if(!I)switch(F){case 1:Y=T,z=f+-N/D,l[2]=z,l[3]=Y;break;case 2:z=L,Y=d+M*D,l[2]=z,l[3]=Y;break;case 3:Y=A,z=f+N/D,l[2]=z,l[3]=Y;break;case 4:z=_,Y=d+-M*D,l[2]=z,l[3]=Y;break}}return!1},i.getCardinalDirection=function(a,s,l){return a>s?l:1+l%4},i.getIntersection=function(a,s,l,u){if(u==null)return this.getIntersection2(a,s,l);var h=a.x,f=a.y,d=s.x,p=s.y,m=l.x,g=l.y,y=u.x,v=u.y,x=void 0,b=void 0,w=void 0,S=void 0,T=void 0,E=void 0,_=void 0,A=void 0,L=void 0;return w=p-f,T=h-d,_=d*f-h*p,S=v-g,E=m-y,A=y*g-m*v,L=w*E-S*T,L===0?null:(x=(T*A-E*_)/L,b=(S*_-w*A)/L,new n(x,b))},i.angleOfVector=function(a,s,l,u){var h=void 0;return a!==l?(h=Math.atan((u-s)/(l-a)),l0?1:i<0?-1:0},n.floor=function(i){return i<0?Math.ceil(i):Math.floor(i)},n.ceil=function(i){return i<0?Math.floor(i):Math.ceil(i)},t.exports=n},function(t,e,r){"use strict";function n(){}o(n,"Integer"),n.MAX_VALUE=2147483647,n.MIN_VALUE=-2147483648,t.exports=n},function(t,e,r){"use strict";var n=function(){function h(f,d){for(var p=0;p"u"?"undefined":n(a);return a==null||s!="object"&&s!="function"},t.exports=i},function(t,e,r){"use strict";function n(m){if(Array.isArray(m)){for(var g=0,y=Array(m.length);g0&&g;){for(w.push(T[0]);w.length>0&&g;){var E=w[0];w.splice(0,1),b.add(E);for(var _=E.getEdges(),x=0;x<_.length;x++){var A=_[x].getOtherEnd(E);if(S.get(E)!=A)if(!b.has(A))w.push(A),S.set(A,E);else{g=!1;break}}}if(!g)m=[];else{var L=[].concat(n(b));m.push(L);for(var x=0;x-1&&T.splice(N,1)}b=new Set,S=new Map}}return m},p.prototype.createDummyNodesForBendpoints=function(m){for(var g=[],y=m.source,v=this.graphManager.calcLowestCommonAncestor(m.source,m.target),x=0;x0){for(var v=this.edgeToDummyNodes.get(y),x=0;x=0&&g.splice(A,1);var L=S.getNeighborsList();L.forEach(function(k){if(y.indexOf(k)<0){var I=v.get(k),C=I-1;C==1&&E.push(k),v.set(k,C)}})}y=y.concat(E),(g.length==1||g.length==2)&&(x=!0,b=g[0])}return b},p.prototype.setGraphManager=function(m){this.graphManager=m},t.exports=p},function(t,e,r){"use strict";function n(){}o(n,"RandomSeed"),n.seed=1,n.x=0,n.nextDouble=function(){return n.x=Math.sin(n.seed++)*1e4,n.x-Math.floor(n.x)},t.exports=n},function(t,e,r){"use strict";var n=r(4);function i(a,s){this.lworldOrgX=0,this.lworldOrgY=0,this.ldeviceOrgX=0,this.ldeviceOrgY=0,this.lworldExtX=1,this.lworldExtY=1,this.ldeviceExtX=1,this.ldeviceExtY=1}o(i,"Transform"),i.prototype.getWorldOrgX=function(){return this.lworldOrgX},i.prototype.setWorldOrgX=function(a){this.lworldOrgX=a},i.prototype.getWorldOrgY=function(){return this.lworldOrgY},i.prototype.setWorldOrgY=function(a){this.lworldOrgY=a},i.prototype.getWorldExtX=function(){return this.lworldExtX},i.prototype.setWorldExtX=function(a){this.lworldExtX=a},i.prototype.getWorldExtY=function(){return this.lworldExtY},i.prototype.setWorldExtY=function(a){this.lworldExtY=a},i.prototype.getDeviceOrgX=function(){return this.ldeviceOrgX},i.prototype.setDeviceOrgX=function(a){this.ldeviceOrgX=a},i.prototype.getDeviceOrgY=function(){return this.ldeviceOrgY},i.prototype.setDeviceOrgY=function(a){this.ldeviceOrgY=a},i.prototype.getDeviceExtX=function(){return this.ldeviceExtX},i.prototype.setDeviceExtX=function(a){this.ldeviceExtX=a},i.prototype.getDeviceExtY=function(){return this.ldeviceExtY},i.prototype.setDeviceExtY=function(a){this.ldeviceExtY=a},i.prototype.transformX=function(a){var s=0,l=this.lworldExtX;return l!=0&&(s=this.ldeviceOrgX+(a-this.lworldOrgX)*this.ldeviceExtX/l),s},i.prototype.transformY=function(a){var s=0,l=this.lworldExtY;return l!=0&&(s=this.ldeviceOrgY+(a-this.lworldOrgY)*this.ldeviceExtY/l),s},i.prototype.inverseTransformX=function(a){var s=0,l=this.ldeviceExtX;return l!=0&&(s=this.lworldOrgX+(a-this.ldeviceOrgX)*this.lworldExtX/l),s},i.prototype.inverseTransformY=function(a){var s=0,l=this.ldeviceExtY;return l!=0&&(s=this.lworldOrgY+(a-this.ldeviceOrgY)*this.lworldExtY/l),s},i.prototype.inverseTransformPoint=function(a){var s=new n(this.inverseTransformX(a.x),this.inverseTransformY(a.y));return s},t.exports=i},function(t,e,r){"use strict";function n(d){if(Array.isArray(d)){for(var p=0,m=Array(d.length);pa.ADAPTATION_LOWER_NODE_LIMIT&&(this.coolingFactor=Math.max(this.coolingFactor*a.COOLING_ADAPTATION_FACTOR,this.coolingFactor-(d-a.ADAPTATION_LOWER_NODE_LIMIT)/(a.ADAPTATION_UPPER_NODE_LIMIT-a.ADAPTATION_LOWER_NODE_LIMIT)*this.coolingFactor*(1-a.COOLING_ADAPTATION_FACTOR))),this.maxNodeDisplacement=a.MAX_NODE_DISPLACEMENT_INCREMENTAL):(d>a.ADAPTATION_LOWER_NODE_LIMIT?this.coolingFactor=Math.max(a.COOLING_ADAPTATION_FACTOR,1-(d-a.ADAPTATION_LOWER_NODE_LIMIT)/(a.ADAPTATION_UPPER_NODE_LIMIT-a.ADAPTATION_LOWER_NODE_LIMIT)*(1-a.COOLING_ADAPTATION_FACTOR)):this.coolingFactor=1,this.initialCoolingFactor=this.coolingFactor,this.maxNodeDisplacement=a.MAX_NODE_DISPLACEMENT),this.maxIterations=Math.max(this.getAllNodes().length*5,this.maxIterations),this.totalDisplacementThreshold=this.displacementThresholdPerNode*this.getAllNodes().length,this.repulsionRange=this.calcRepulsionRange()},h.prototype.calcSpringForces=function(){for(var d=this.getAllEdges(),p,m=0;m0&&arguments[0]!==void 0?arguments[0]:!0,p=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1,m,g,y,v,x=this.getAllNodes(),b;if(this.useFRGridVariant)for(this.totalIterations%a.GRID_CALCULATION_CHECK_PERIOD==1&&d&&this.updateGrid(),b=new Set,m=0;mw||b>w)&&(d.gravitationForceX=-this.gravityConstant*y,d.gravitationForceY=-this.gravityConstant*v)):(w=p.getEstimatedSize()*this.compoundGravityRangeFactor,(x>w||b>w)&&(d.gravitationForceX=-this.gravityConstant*y*this.compoundGravityConstant,d.gravitationForceY=-this.gravityConstant*v*this.compoundGravityConstant))},h.prototype.isConverged=function(){var d,p=!1;return this.totalIterations>this.maxIterations/3&&(p=Math.abs(this.totalDisplacement-this.oldTotalDisplacement)<2),d=this.totalDisplacement=x.length||w>=x[0].length)){for(var S=0;Sh},"_defaultCompareFunction")}]),l}();t.exports=s},function(t,e,r){"use strict";var n=function(){function s(l,u){for(var h=0;h2&&arguments[2]!==void 0?arguments[2]:1,f=arguments.length>3&&arguments[3]!==void 0?arguments[3]:-1,d=arguments.length>4&&arguments[4]!==void 0?arguments[4]:-1;i(this,s),this.sequence1=l,this.sequence2=u,this.match_score=h,this.mismatch_penalty=f,this.gap_penalty=d,this.iMax=l.length+1,this.jMax=u.length+1,this.grid=new Array(this.iMax);for(var p=0;p=0;l--){var u=this.listeners[l];u.event===a&&u.callback===s&&this.listeners.splice(l,1)}},i.emit=function(a,s){for(var l=0;l{"use strict";o(function(e,r){typeof ob=="object"&&typeof wB=="object"?wB.exports=r(bB()):typeof define=="function"&&define.amd?define(["layout-base"],r):typeof ob=="object"?ob.coseBase=r(bB()):e.coseBase=r(e.layoutBase)},"webpackUniversalModuleDefinition")(ob,function(t){return function(e){var r={};function n(i){if(r[i])return r[i].exports;var a=r[i]={i,l:!1,exports:{}};return e[i].call(a.exports,a,a.exports,n),a.l=!0,a.exports}return o(n,"__webpack_require__"),n.m=e,n.c=r,n.i=function(i){return i},n.d=function(i,a,s){n.o(i,a)||Object.defineProperty(i,a,{configurable:!1,enumerable:!0,get:s})},n.n=function(i){var a=i&&i.__esModule?o(function(){return i.default},"getDefault"):o(function(){return i},"getModuleExports");return n.d(a,"a",a),a},n.o=function(i,a){return Object.prototype.hasOwnProperty.call(i,a)},n.p="",n(n.s=7)}([function(e,r){e.exports=t},function(e,r,n){"use strict";var i=n(0).FDLayoutConstants;function a(){}o(a,"CoSEConstants");for(var s in i)a[s]=i[s];a.DEFAULT_USE_MULTI_LEVEL_SCALING=!1,a.DEFAULT_RADIAL_SEPARATION=i.DEFAULT_EDGE_LENGTH,a.DEFAULT_COMPONENT_SEPERATION=60,a.TILE=!0,a.TILING_PADDING_VERTICAL=10,a.TILING_PADDING_HORIZONTAL=10,a.TREE_REDUCTION_ON_INCREMENTAL=!1,e.exports=a},function(e,r,n){"use strict";var i=n(0).FDLayoutEdge;function a(l,u,h){i.call(this,l,u,h)}o(a,"CoSEEdge"),a.prototype=Object.create(i.prototype);for(var s in i)a[s]=i[s];e.exports=a},function(e,r,n){"use strict";var i=n(0).LGraph;function a(l,u,h){i.call(this,l,u,h)}o(a,"CoSEGraph"),a.prototype=Object.create(i.prototype);for(var s in i)a[s]=i[s];e.exports=a},function(e,r,n){"use strict";var i=n(0).LGraphManager;function a(l){i.call(this,l)}o(a,"CoSEGraphManager"),a.prototype=Object.create(i.prototype);for(var s in i)a[s]=i[s];e.exports=a},function(e,r,n){"use strict";var i=n(0).FDLayoutNode,a=n(0).IMath;function s(u,h,f,d){i.call(this,u,h,f,d)}o(s,"CoSENode"),s.prototype=Object.create(i.prototype);for(var l in i)s[l]=i[l];s.prototype.move=function(){var u=this.graphManager.getLayout();this.displacementX=u.coolingFactor*(this.springForceX+this.repulsionForceX+this.gravitationForceX)/this.noOfChildren,this.displacementY=u.coolingFactor*(this.springForceY+this.repulsionForceY+this.gravitationForceY)/this.noOfChildren,Math.abs(this.displacementX)>u.coolingFactor*u.maxNodeDisplacement&&(this.displacementX=u.coolingFactor*u.maxNodeDisplacement*a.sign(this.displacementX)),Math.abs(this.displacementY)>u.coolingFactor*u.maxNodeDisplacement&&(this.displacementY=u.coolingFactor*u.maxNodeDisplacement*a.sign(this.displacementY)),this.child==null?this.moveBy(this.displacementX,this.displacementY):this.child.getNodes().length==0?this.moveBy(this.displacementX,this.displacementY):this.propogateDisplacementToChildren(this.displacementX,this.displacementY),u.totalDisplacement+=Math.abs(this.displacementX)+Math.abs(this.displacementY),this.springForceX=0,this.springForceY=0,this.repulsionForceX=0,this.repulsionForceY=0,this.gravitationForceX=0,this.gravitationForceY=0,this.displacementX=0,this.displacementY=0},s.prototype.propogateDisplacementToChildren=function(u,h){for(var f=this.getChild().getNodes(),d,p=0;p0)this.positionNodesRadially(T);else{this.reduceTrees(),this.graphManager.resetAllNodesToApplyGravitation();var E=new Set(this.getAllNodes()),_=this.nodesWithGravity.filter(function(A){return E.has(A)});this.graphManager.setAllNodesToApplyGravitation(_),this.positionNodesRandomly()}}return this.initSpringEmbedder(),this.runSpringEmbedder(),!0},w.prototype.tick=function(){if(this.totalIterations++,this.totalIterations===this.maxIterations&&!this.isTreeGrowing&&!this.isGrowthFinished)if(this.prunedNodesAll.length>0)this.isTreeGrowing=!0;else return!0;if(this.totalIterations%f.CONVERGENCE_CHECK_PERIOD==0&&!this.isTreeGrowing&&!this.isGrowthFinished){if(this.isConverged())if(this.prunedNodesAll.length>0)this.isTreeGrowing=!0;else return!0;this.coolingCycle++,this.layoutQuality==0?this.coolingAdjuster=this.coolingCycle:this.layoutQuality==1&&(this.coolingAdjuster=this.coolingCycle/3),this.coolingFactor=Math.max(this.initialCoolingFactor-Math.pow(this.coolingCycle,Math.log(100*(this.initialCoolingFactor-this.finalTemperature))/Math.log(this.maxCoolingCycle))/100*this.coolingAdjuster,this.finalTemperature),this.animationPeriod=Math.ceil(this.initialAnimationPeriod*Math.sqrt(this.coolingFactor))}if(this.isTreeGrowing){if(this.growTreeIterations%10==0)if(this.prunedNodesAll.length>0){this.graphManager.updateBounds(),this.updateGrid(),this.growTree(this.prunedNodesAll),this.graphManager.resetAllNodesToApplyGravitation();var T=new Set(this.getAllNodes()),E=this.nodesWithGravity.filter(function(L){return T.has(L)});this.graphManager.setAllNodesToApplyGravitation(E),this.graphManager.updateBounds(),this.updateGrid(),this.coolingFactor=f.DEFAULT_COOLING_FACTOR_INCREMENTAL}else this.isTreeGrowing=!1,this.isGrowthFinished=!0;this.growTreeIterations++}if(this.isGrowthFinished){if(this.isConverged())return!0;this.afterGrowthIterations%10==0&&(this.graphManager.updateBounds(),this.updateGrid()),this.coolingFactor=f.DEFAULT_COOLING_FACTOR_INCREMENTAL*((100-this.afterGrowthIterations)/100),this.afterGrowthIterations++}var _=!this.isTreeGrowing&&!this.isGrowthFinished,A=this.growTreeIterations%10==1&&this.isTreeGrowing||this.afterGrowthIterations%10==1&&this.isGrowthFinished;return this.totalDisplacement=0,this.graphManager.updateBounds(),this.calcSpringForces(),this.calcRepulsionForces(_,A),this.calcGravitationalForces(),this.moveNodes(),this.animate(),!1},w.prototype.getPositionsData=function(){for(var T=this.graphManager.getAllNodes(),E={},_=0;_1){var k;for(k=0;kA&&(A=Math.floor(N.y)),M=Math.floor(N.x+h.DEFAULT_COMPONENT_SEPERATION)}this.transform(new m(d.WORLD_CENTER_X-N.x/2,d.WORLD_CENTER_Y-N.y/2))},w.radialLayout=function(T,E,_){var A=Math.max(this.maxDiagonalInTree(T),h.DEFAULT_RADIAL_SEPARATION);w.branchRadialLayout(E,null,0,359,0,A);var L=x.calculateBounds(T),M=new b;M.setDeviceOrgX(L.getMinX()),M.setDeviceOrgY(L.getMinY()),M.setWorldOrgX(_.x),M.setWorldOrgY(_.y);for(var N=0;N1;){var Q=Y[0];Y.splice(0,1);var X=P.indexOf(Q);X>=0&&P.splice(X,1),$--,F--}E!=null?z=(P.indexOf(Y[0])+1)%$:z=0;for(var ie=Math.abs(A-_)/F,j=z;B!=F;j=++j%$){var J=P[j].getOtherEnd(T);if(J!=E){var Z=(_+B*ie)%360,H=(Z+ie)%360;w.branchRadialLayout(J,T,Z,H,L+M,M),B++}}},w.maxDiagonalInTree=function(T){for(var E=y.MIN_VALUE,_=0;_E&&(E=L)}return E},w.prototype.calcRepulsionRange=function(){return 2*(this.level+1)*this.idealEdgeLength},w.prototype.groupZeroDegreeMembers=function(){var T=this,E={};this.memberGroups={},this.idToDummyNode={};for(var _=[],A=this.graphManager.getAllNodes(),L=0;L"u"&&(E[k]=[]),E[k]=E[k].concat(M)}Object.keys(E).forEach(function(I){if(E[I].length>1){var C="DummyCompound_"+I;T.memberGroups[C]=E[I];var O=E[I][0].getParent(),D=new l(T.graphManager);D.id=C,D.paddingLeft=O.paddingLeft||0,D.paddingRight=O.paddingRight||0,D.paddingBottom=O.paddingBottom||0,D.paddingTop=O.paddingTop||0,T.idToDummyNode[C]=D;var P=T.getGraphManager().add(T.newGraph(),D),F=O.getChild();F.add(D);for(var B=0;B=0;T--){var E=this.compoundOrder[T],_=E.id,A=E.paddingLeft,L=E.paddingTop;this.adjustLocations(this.tiledMemberPack[_],E.rect.x,E.rect.y,A,L)}},w.prototype.repopulateZeroDegreeMembers=function(){var T=this,E=this.tiledZeroDegreePack;Object.keys(E).forEach(function(_){var A=T.idToDummyNode[_],L=A.paddingLeft,M=A.paddingTop;T.adjustLocations(E[_],A.rect.x,A.rect.y,L,M)})},w.prototype.getToBeTiled=function(T){var E=T.id;if(this.toBeTiled[E]!=null)return this.toBeTiled[E];var _=T.getChild();if(_==null)return this.toBeTiled[E]=!1,!1;for(var A=_.getNodes(),L=0;L0)return this.toBeTiled[E]=!1,!1;if(M.getChild()==null){this.toBeTiled[M.id]=!1;continue}if(!this.getToBeTiled(M))return this.toBeTiled[E]=!1,!1}return this.toBeTiled[E]=!0,!0},w.prototype.getNodeDegree=function(T){for(var E=T.id,_=T.getEdges(),A=0,L=0;L<_.length;L++){var M=_[L];M.getSource().id!==M.getTarget().id&&(A=A+1)}return A},w.prototype.getNodeDegreeWithChildren=function(T){var E=this.getNodeDegree(T);if(T.getChild()==null)return E;for(var _=T.getChild().getNodes(),A=0;A<_.length;A++){var L=_[A];E+=this.getNodeDegreeWithChildren(L)}return E},w.prototype.performDFSOnCompounds=function(){this.compoundOrder=[],this.fillCompexOrderByDFS(this.graphManager.getRoot().getNodes())},w.prototype.fillCompexOrderByDFS=function(T){for(var E=0;EI&&(I=O.rect.height)}_+=I+T.verticalPadding}},w.prototype.tileCompoundMembers=function(T,E){var _=this;this.tiledMemberPack=[],Object.keys(T).forEach(function(A){var L=E[A];_.tiledMemberPack[A]=_.tileNodes(T[A],L.paddingLeft+L.paddingRight),L.rect.width=_.tiledMemberPack[A].width,L.rect.height=_.tiledMemberPack[A].height})},w.prototype.tileNodes=function(T,E){var _=h.TILING_PADDING_VERTICAL,A=h.TILING_PADDING_HORIZONTAL,L={rows:[],rowWidth:[],rowHeight:[],width:0,height:E,verticalPadding:_,horizontalPadding:A};T.sort(function(k,I){return k.rect.width*k.rect.height>I.rect.width*I.rect.height?-1:k.rect.width*k.rect.height0&&(N+=T.horizontalPadding),T.rowWidth[_]=N,T.width0&&(k+=T.verticalPadding);var I=0;k>T.rowHeight[_]&&(I=T.rowHeight[_],T.rowHeight[_]=k,I=T.rowHeight[_]-I),T.height+=I,T.rows[_].push(E)},w.prototype.getShortestRowIndex=function(T){for(var E=-1,_=Number.MAX_VALUE,A=0;A_&&(E=A,_=T.rowWidth[A]);return E},w.prototype.canAddHorizontal=function(T,E,_){var A=this.getShortestRowIndex(T);if(A<0)return!0;var L=T.rowWidth[A];if(L+T.horizontalPadding+E<=T.width)return!0;var M=0;T.rowHeight[A]<_&&A>0&&(M=_+T.verticalPadding-T.rowHeight[A]);var N;T.width-L>=E+T.horizontalPadding?N=(T.height+M)/(L+E+T.horizontalPadding):N=(T.height+M)/T.width,M=_+T.verticalPadding;var k;return T.widthM&&E!=_){A.splice(-1,1),T.rows[_].push(L),T.rowWidth[E]=T.rowWidth[E]-M,T.rowWidth[_]=T.rowWidth[_]+M,T.width=T.rowWidth[instance.getLongestRowIndex(T)];for(var N=Number.MIN_VALUE,k=0;kN&&(N=A[k].height);E>0&&(N+=T.verticalPadding);var I=T.rowHeight[E]+T.rowHeight[_];T.rowHeight[E]=N,T.rowHeight[_]0)for(var F=L;F<=M;F++)P[0]+=this.grid[F][N-1].length+this.grid[F][N].length-1;if(M0)for(var F=N;F<=k;F++)P[3]+=this.grid[L-1][F].length+this.grid[L][F].length-1;for(var B=y.MAX_VALUE,$,z,Y=0;Y{"use strict";o(function(e,r){typeof lb=="object"&&typeof kB=="object"?kB.exports=r(TB()):typeof define=="function"&&define.amd?define(["cose-base"],r):typeof lb=="object"?lb.cytoscapeCoseBilkent=r(TB()):e.cytoscapeCoseBilkent=r(e.coseBase)},"webpackUniversalModuleDefinition")(lb,function(t){return function(e){var r={};function n(i){if(r[i])return r[i].exports;var a=r[i]={i,l:!1,exports:{}};return e[i].call(a.exports,a,a.exports,n),a.l=!0,a.exports}return o(n,"__webpack_require__"),n.m=e,n.c=r,n.i=function(i){return i},n.d=function(i,a,s){n.o(i,a)||Object.defineProperty(i,a,{configurable:!1,enumerable:!0,get:s})},n.n=function(i){var a=i&&i.__esModule?o(function(){return i.default},"getDefault"):o(function(){return i},"getModuleExports");return n.d(a,"a",a),a},n.o=function(i,a){return Object.prototype.hasOwnProperty.call(i,a)},n.p="",n(n.s=1)}([function(e,r){e.exports=t},function(e,r,n){"use strict";var i=n(0).layoutBase.LayoutConstants,a=n(0).layoutBase.FDLayoutConstants,s=n(0).CoSEConstants,l=n(0).CoSELayout,u=n(0).CoSENode,h=n(0).layoutBase.PointD,f=n(0).layoutBase.DimensionD,d={ready:o(function(){},"ready"),stop:o(function(){},"stop"),quality:"default",nodeDimensionsIncludeLabels:!1,refresh:30,fit:!0,padding:10,randomize:!0,nodeRepulsion:4500,idealEdgeLength:50,edgeElasticity:.45,nestingFactor:.1,gravity:.25,numIter:2500,tile:!0,animate:"end",animationDuration:500,tilingPaddingVertical:10,tilingPaddingHorizontal:10,gravityRangeCompound:1.5,gravityCompound:1,gravityRange:3.8,initialEnergyOnIncremental:.5};function p(v,x){var b={};for(var w in v)b[w]=v[w];for(var w in x)b[w]=x[w];return b}o(p,"extend");function m(v){this.options=p(d,v),g(this.options)}o(m,"_CoSELayout");var g=o(function(x){x.nodeRepulsion!=null&&(s.DEFAULT_REPULSION_STRENGTH=a.DEFAULT_REPULSION_STRENGTH=x.nodeRepulsion),x.idealEdgeLength!=null&&(s.DEFAULT_EDGE_LENGTH=a.DEFAULT_EDGE_LENGTH=x.idealEdgeLength),x.edgeElasticity!=null&&(s.DEFAULT_SPRING_STRENGTH=a.DEFAULT_SPRING_STRENGTH=x.edgeElasticity),x.nestingFactor!=null&&(s.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR=a.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR=x.nestingFactor),x.gravity!=null&&(s.DEFAULT_GRAVITY_STRENGTH=a.DEFAULT_GRAVITY_STRENGTH=x.gravity),x.numIter!=null&&(s.MAX_ITERATIONS=a.MAX_ITERATIONS=x.numIter),x.gravityRange!=null&&(s.DEFAULT_GRAVITY_RANGE_FACTOR=a.DEFAULT_GRAVITY_RANGE_FACTOR=x.gravityRange),x.gravityCompound!=null&&(s.DEFAULT_COMPOUND_GRAVITY_STRENGTH=a.DEFAULT_COMPOUND_GRAVITY_STRENGTH=x.gravityCompound),x.gravityRangeCompound!=null&&(s.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR=a.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR=x.gravityRangeCompound),x.initialEnergyOnIncremental!=null&&(s.DEFAULT_COOLING_FACTOR_INCREMENTAL=a.DEFAULT_COOLING_FACTOR_INCREMENTAL=x.initialEnergyOnIncremental),x.quality=="draft"?i.QUALITY=0:x.quality=="proof"?i.QUALITY=2:i.QUALITY=1,s.NODE_DIMENSIONS_INCLUDE_LABELS=a.NODE_DIMENSIONS_INCLUDE_LABELS=i.NODE_DIMENSIONS_INCLUDE_LABELS=x.nodeDimensionsIncludeLabels,s.DEFAULT_INCREMENTAL=a.DEFAULT_INCREMENTAL=i.DEFAULT_INCREMENTAL=!x.randomize,s.ANIMATE=a.ANIMATE=i.ANIMATE=x.animate,s.TILE=x.tile,s.TILING_PADDING_VERTICAL=typeof x.tilingPaddingVertical=="function"?x.tilingPaddingVertical.call():x.tilingPaddingVertical,s.TILING_PADDING_HORIZONTAL=typeof x.tilingPaddingHorizontal=="function"?x.tilingPaddingHorizontal.call():x.tilingPaddingHorizontal},"getUserOptions");m.prototype.run=function(){var v,x,b=this.options,w=this.idToLNode={},S=this.layout=new l,T=this;T.stopped=!1,this.cy=this.options.cy,this.cy.trigger({type:"layoutstart",layout:this});var E=S.newGraphManager();this.gm=E;var _=this.options.eles.nodes(),A=this.options.eles.edges();this.root=E.addRoot(),this.processChildrenList(this.root,this.getTopMostNodes(_),S);for(var L=0;L0){var k;k=b.getGraphManager().add(b.newGraph(),_),this.processChildrenList(k,E,b)}}},m.prototype.stop=function(){return this.stopped=!0,this};var y=o(function(x){x("layout","cose-bilkent",m)},"register");typeof cytoscape<"u"&&y(cytoscape),e.exports=y}])})});function fZe(t,e,r,n,i){return t.insert("polygon",":first-child").attr("points",n.map(function(a){return a.x+","+a.y}).join(" ")).attr("transform","translate("+(i.width-e)/2+", "+r+")")}var sZe,oZe,lZe,cZe,uZe,hZe,dZe,pZe,sge,oge,lge=R(()=>{"use strict";Al();xr();sZe=12,oZe=o(function(t,e,r,n){e.append("path").attr("id","node-"+r.id).attr("class","node-bkg node-"+t.type2Str(r.type)).attr("d",`M0 ${r.height-5} v${-r.height+2*5} q0,-5 5,-5 h${r.width-2*5} q5,0 5,5 v${r.height-5} H0 Z`),e.append("line").attr("class","node-line-"+n).attr("x1",0).attr("y1",r.height).attr("x2",r.width).attr("y2",r.height)},"defaultBkg"),lZe=o(function(t,e,r){e.append("rect").attr("id","node-"+r.id).attr("class","node-bkg node-"+t.type2Str(r.type)).attr("height",r.height).attr("width",r.width)},"rectBkg"),cZe=o(function(t,e,r){let n=r.width,i=r.height,a=.15*n,s=.25*n,l=.35*n,u=.2*n;e.append("path").attr("id","node-"+r.id).attr("class","node-bkg node-"+t.type2Str(r.type)).attr("d",`M0 0 a${a},${a} 0 0,1 ${n*.25},${-1*n*.1} + a${l},${l} 1 0,1 ${n*.4},${-1*n*.1} + a${s},${s} 1 0,1 ${n*.35},${1*n*.2} + + a${a},${a} 1 0,1 ${n*.15},${1*i*.35} + a${u},${u} 1 0,1 ${-1*n*.15},${1*i*.65} + + a${s},${a} 1 0,1 ${-1*n*.25},${n*.15} + a${l},${l} 1 0,1 ${-1*n*.5},0 + a${a},${a} 1 0,1 ${-1*n*.25},${-1*n*.15} + + a${a},${a} 1 0,1 ${-1*n*.1},${-1*i*.35} + a${u},${u} 1 0,1 ${n*.1},${-1*i*.65} + + H0 V0 Z`)},"cloudBkg"),uZe=o(function(t,e,r){let n=r.width,i=r.height,a=.15*n;e.append("path").attr("id","node-"+r.id).attr("class","node-bkg node-"+t.type2Str(r.type)).attr("d",`M0 0 a${a},${a} 1 0,0 ${n*.25},${-1*i*.1} + a${a},${a} 1 0,0 ${n*.25},0 + a${a},${a} 1 0,0 ${n*.25},0 + a${a},${a} 1 0,0 ${n*.25},${1*i*.1} + + a${a},${a} 1 0,0 ${n*.15},${1*i*.33} + a${a*.8},${a*.8} 1 0,0 0,${1*i*.34} + a${a},${a} 1 0,0 ${-1*n*.15},${1*i*.33} + + a${a},${a} 1 0,0 ${-1*n*.25},${i*.15} + a${a},${a} 1 0,0 ${-1*n*.25},0 + a${a},${a} 1 0,0 ${-1*n*.25},0 + a${a},${a} 1 0,0 ${-1*n*.25},${-1*i*.15} + + a${a},${a} 1 0,0 ${-1*n*.1},${-1*i*.33} + a${a*.8},${a*.8} 1 0,0 0,${-1*i*.34} + a${a},${a} 1 0,0 ${n*.1},${-1*i*.33} + + H0 V0 Z`)},"bangBkg"),hZe=o(function(t,e,r){e.append("circle").attr("id","node-"+r.id).attr("class","node-bkg node-"+t.type2Str(r.type)).attr("r",r.width/2)},"circleBkg");o(fZe,"insertPolygonShape");dZe=o(function(t,e,r){let n=r.height,a=n/4,s=r.width-r.padding+2*a,l=[{x:a,y:0},{x:s-a,y:0},{x:s,y:-n/2},{x:s-a,y:-n},{x:a,y:-n},{x:0,y:-n/2}];fZe(e,s,n,l,r)},"hexagonBkg"),pZe=o(function(t,e,r){e.append("rect").attr("id","node-"+r.id).attr("class","node-bkg node-"+t.type2Str(r.type)).attr("height",r.height).attr("rx",r.padding).attr("ry",r.padding).attr("width",r.width)},"roundedRectBkg"),sge=o(async function(t,e,r,n,i){let a=i.htmlLabels,s=n%(sZe-1),l=e.append("g");r.section=s;let u="section-"+s;s<0&&(u+=" section-root"),l.attr("class",(r.class?r.class+" ":"")+"mindmap-node "+u);let h=l.append("g"),f=l.append("g"),d=r.descr.replace(/()/g,` +`);await ta(f,d,{useHtmlLabels:a,width:r.width,classes:"mindmap-node-label"},i),a||f.attr("dy","1em").attr("alignment-baseline","middle").attr("dominant-baseline","middle").attr("text-anchor","middle");let p=f.node().getBBox(),[m]=mc(i.fontSize);if(r.height=p.height+m*1.1*.5+r.padding,r.width=p.width+2*r.padding,r.icon)if(r.type===t.nodeType.CIRCLE)r.height+=50,r.width+=50,l.append("foreignObject").attr("height","50px").attr("width",r.width).attr("style","text-align: center;").append("div").attr("class","icon-container").append("i").attr("class","node-icon-"+s+" "+r.icon),f.attr("transform","translate("+r.width/2+", "+(r.height/2-1.5*r.padding)+")");else{r.width+=50;let g=r.height;r.height=Math.max(g,60);let y=Math.abs(r.height-g);l.append("foreignObject").attr("width","60px").attr("height",r.height).attr("style","text-align: center;margin-top:"+y/2+"px;").append("div").attr("class","icon-container").append("i").attr("class","node-icon-"+s+" "+r.icon),f.attr("transform","translate("+(25+r.width/2)+", "+(y/2+r.padding/2)+")")}else if(a){let g=(r.width-p.width)/2,y=(r.height-p.height)/2;f.attr("transform","translate("+g+", "+y+")")}else{let g=r.width/2,y=r.padding/2;f.attr("transform","translate("+g+", "+y+")")}switch(r.type){case t.nodeType.DEFAULT:oZe(t,h,r,s);break;case t.nodeType.ROUNDED_RECT:pZe(t,h,r,s);break;case t.nodeType.RECT:lZe(t,h,r,s);break;case t.nodeType.CIRCLE:h.attr("transform","translate("+r.width/2+", "+ +r.height/2+")"),hZe(t,h,r,s);break;case t.nodeType.CLOUD:cZe(t,h,r,s);break;case t.nodeType.BANG:uZe(t,h,r,s);break;case t.nodeType.HEXAGON:dZe(t,h,r,s);break}return t.setElementForId(r.id,l),r.height},"drawNode"),oge=o(function(t,e){let r=t.getElementById(e.id),n=e.x||0,i=e.y||0;r.attr("transform","translate("+n+","+i+")")},"positionNode")});async function uge(t,e,r,n,i){await sge(t,e,r,n,i),r.children&&await Promise.all(r.children.map((a,s)=>uge(t,e,a,n<0?s:n,i)))}function mZe(t,e){e.edges().map((r,n)=>{let i=r.data();if(r[0]._private.bodyBounds){let a=r[0]._private.rscratch;V.trace("Edge: ",n,i),t.insert("path").attr("d",`M ${a.startX},${a.startY} L ${a.midX},${a.midY} L${a.endX},${a.endY} `).attr("class","edge section-edge-"+i.section+" edge-depth-"+i.depth)}})}function hge(t,e,r,n){e.add({group:"nodes",data:{id:t.id.toString(),labelText:t.descr,height:t.height,width:t.width,level:n,nodeId:t.id,padding:t.padding,type:t.type},position:{x:t.x,y:t.y}}),t.children&&t.children.forEach(i=>{hge(i,e,r,n+1),e.add({group:"edges",data:{id:`${t.id}_${i.id}`,source:t.id,target:i.id,depth:n,section:i.section}})})}function gZe(t,e){return new Promise(r=>{let n=$e("body").append("div").attr("id","cy").attr("style","display:none"),i=rl({container:document.getElementById("cy"),style:[{selector:"edge",style:{"curve-style":"bezier"}}]});n.remove(),hge(t,i,e,0),i.nodes().forEach(function(a){a.layoutDimensions=()=>{let s=a.data();return{w:s.width,h:s.height}}}),i.layout({name:"cose-bilkent",quality:"proof",styleEnabled:!1,animate:!1}).run(),i.ready(a=>{V.info("Ready",a),r(i)})})}function yZe(t,e){e.nodes().map((r,n)=>{let i=r.data();i.x=r.position().x,i.y=r.position().y,oge(t,i);let a=t.getElementById(i.nodeId);V.info("Id:",n,"Position: (",r.position().x,", ",r.position().y,")",i),a.attr("transform",`translate(${r.position().x-i.width/2}, ${r.position().y-i.height/2})`),a.attr("attr",`apa-${n})`)})}var cge,vZe,fge,dge=R(()=>{"use strict";vB();cge=Xi(age(),1);Zt();_t();ut();pf();Yn();lge();sl();rl.use(cge.default);o(uge,"drawNodes");o(mZe,"drawEdges");o(hge,"addNodes");o(gZe,"layoutMindmap");o(yZe,"positionNodes");vZe=o(async(t,e,r,n)=>{V.debug(`Rendering mindmap diagram +`+t);let i=n.db,a=i.getMindmap();if(!a)return;let s=de();s.htmlLabels=!1;let l=Ps(e),u=l.append("g");u.attr("class","mindmap-edges");let h=l.append("g");h.attr("class","mindmap-nodes"),await uge(i,h,a,-1,s);let f=await gZe(a,s);mZe(u,f),yZe(i,f),Lo(void 0,l,s.mindmap?.padding??mr.mindmap.padding,s.mindmap?.useMaxWidth??mr.mindmap.useMaxWidth)},"draw"),fge={draw:vZe}});var xZe,bZe,pge,mge=R(()=>{"use strict";al();xZe=o(t=>{let e="";for(let r=0;r` + .edge { + stroke-width: 3; + } + ${xZe(t)} + .section-root rect, .section-root path, .section-root circle, .section-root polygon { + fill: ${t.git0}; + } + .section-root text { + fill: ${t.gitBranchLabel0}; + } + .icon-container { + height:100%; + display: flex; + justify-content: center; + align-items: center; + } + .edge { + fill: none; + } + .mindmap-node-label { + dy: 1em; + alignment-baseline: middle; + text-anchor: middle; + dominant-baseline: middle; + text-align: center; + } +`,"getStyles"),pge=bZe});var gge={};hr(gge,{diagram:()=>wZe});var wZe,yge=R(()=>{"use strict";r0e();a0e();dge();mge();wZe={db:i0e,renderer:fge,parser:t0e,styles:pge}});var EB,cb,bge=R(()=>{"use strict";EB=function(){var t=o(function(l,u,h,f){for(h=h||{},f=l.length;f--;h[l[f]]=u);return h},"o"),e=[1,9],r=[1,10],n=[1,5,10,12],i={trace:o(function(){},"trace"),yy:{},symbols_:{error:2,start:3,SANKEY:4,NEWLINE:5,csv:6,opt_eof:7,record:8,csv_tail:9,EOF:10,"field[source]":11,COMMA:12,"field[target]":13,"field[value]":14,field:15,escaped:16,non_escaped:17,DQUOTE:18,ESCAPED_TEXT:19,NON_ESCAPED_TEXT:20,$accept:0,$end:1},terminals_:{2:"error",4:"SANKEY",5:"NEWLINE",10:"EOF",11:"field[source]",12:"COMMA",13:"field[target]",14:"field[value]",18:"DQUOTE",19:"ESCAPED_TEXT",20:"NON_ESCAPED_TEXT"},productions_:[0,[3,4],[6,2],[9,2],[9,0],[7,1],[7,0],[8,5],[15,1],[15,1],[16,3],[17,1]],performAction:o(function(u,h,f,d,p,m,g){var y=m.length-1;switch(p){case 7:let v=d.findOrCreateNode(m[y-4].trim().replaceAll('""','"')),x=d.findOrCreateNode(m[y-2].trim().replaceAll('""','"')),b=parseFloat(m[y].trim());d.addLink(v,x,b);break;case 8:case 9:case 11:this.$=m[y];break;case 10:this.$=m[y-1];break}},"anonymous"),table:[{3:1,4:[1,2]},{1:[3]},{5:[1,3]},{6:4,8:5,15:6,16:7,17:8,18:e,20:r},{1:[2,6],7:11,10:[1,12]},t(r,[2,4],{9:13,5:[1,14]}),{12:[1,15]},t(n,[2,8]),t(n,[2,9]),{19:[1,16]},t(n,[2,11]),{1:[2,1]},{1:[2,5]},t(r,[2,2]),{6:17,8:5,15:6,16:7,17:8,18:e,20:r},{15:18,16:7,17:8,18:e,20:r},{18:[1,19]},t(r,[2,3]),{12:[1,20]},t(n,[2,10]),{15:21,16:7,17:8,18:e,20:r},t([1,5,10],[2,7])],defaultActions:{11:[2,1],12:[2,5]},parseError:o(function(u,h){if(h.recoverable)this.trace(u);else{var f=new Error(u);throw f.hash=h,f}},"parseError"),parse:o(function(u){var h=this,f=[0],d=[],p=[null],m=[],g=this.table,y="",v=0,x=0,b=0,w=2,S=1,T=m.slice.call(arguments,1),E=Object.create(this.lexer),_={yy:{}};for(var A in this.yy)Object.prototype.hasOwnProperty.call(this.yy,A)&&(_.yy[A]=this.yy[A]);E.setInput(u,_.yy),_.yy.lexer=E,_.yy.parser=this,typeof E.yylloc>"u"&&(E.yylloc={});var L=E.yylloc;m.push(L);var M=E.options&&E.options.ranges;typeof _.yy.parseError=="function"?this.parseError=_.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function N(ie){f.length=f.length-2*ie,p.length=p.length-ie,m.length=m.length-ie}o(N,"popStack");function k(){var ie;return ie=d.pop()||E.lex()||S,typeof ie!="number"&&(ie instanceof Array&&(d=ie,ie=d.pop()),ie=h.symbols_[ie]||ie),ie}o(k,"lex");for(var I,C,O,D,P,F,B={},$,z,Y,Q;;){if(O=f[f.length-1],this.defaultActions[O]?D=this.defaultActions[O]:((I===null||typeof I>"u")&&(I=k()),D=g[O]&&g[O][I]),typeof D>"u"||!D.length||!D[0]){var X="";Q=[];for($ in g[O])this.terminals_[$]&&$>w&&Q.push("'"+this.terminals_[$]+"'");E.showPosition?X="Parse error on line "+(v+1)+`: +`+E.showPosition()+` +Expecting `+Q.join(", ")+", got '"+(this.terminals_[I]||I)+"'":X="Parse error on line "+(v+1)+": Unexpected "+(I==S?"end of input":"'"+(this.terminals_[I]||I)+"'"),this.parseError(X,{text:E.match,token:this.terminals_[I]||I,line:E.yylineno,loc:L,expected:Q})}if(D[0]instanceof Array&&D.length>1)throw new Error("Parse Error: multiple actions possible at state: "+O+", token: "+I);switch(D[0]){case 1:f.push(I),p.push(E.yytext),m.push(E.yylloc),f.push(D[1]),I=null,C?(I=C,C=null):(x=E.yyleng,y=E.yytext,v=E.yylineno,L=E.yylloc,b>0&&b--);break;case 2:if(z=this.productions_[D[1]][1],B.$=p[p.length-z],B._$={first_line:m[m.length-(z||1)].first_line,last_line:m[m.length-1].last_line,first_column:m[m.length-(z||1)].first_column,last_column:m[m.length-1].last_column},M&&(B._$.range=[m[m.length-(z||1)].range[0],m[m.length-1].range[1]]),F=this.performAction.apply(B,[y,x,v,_.yy,D[1],p,m].concat(T)),typeof F<"u")return F;z&&(f=f.slice(0,-1*z*2),p=p.slice(0,-1*z),m=m.slice(0,-1*z)),f.push(this.productions_[D[1]][0]),p.push(B.$),m.push(B._$),Y=g[f[f.length-2]][f[f.length-1]],f.push(Y);break;case 3:return!0}}return!0},"parse")},a=function(){var l={EOF:1,parseError:o(function(h,f){if(this.yy.parser)this.yy.parser.parseError(h,f);else throw new Error(h)},"parseError"),setInput:o(function(u,h){return this.yy=h||this.yy||{},this._input=u,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:o(function(){var u=this._input[0];this.yytext+=u,this.yyleng++,this.offset++,this.match+=u,this.matched+=u;var h=u.match(/(?:\r\n?|\n).*/g);return h?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),u},"input"),unput:o(function(u){var h=u.length,f=u.split(/(?:\r\n?|\n)/g);this._input=u+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-h),this.offset-=h;var d=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),f.length-1&&(this.yylineno-=f.length-1);var p=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:f?(f.length===d.length?this.yylloc.first_column:0)+d[d.length-f.length].length-f[0].length:this.yylloc.first_column-h},this.options.ranges&&(this.yylloc.range=[p[0],p[0]+this.yyleng-h]),this.yyleng=this.yytext.length,this},"unput"),more:o(function(){return this._more=!0,this},"more"),reject:o(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:o(function(u){this.unput(this.match.slice(u))},"less"),pastInput:o(function(){var u=this.matched.substr(0,this.matched.length-this.match.length);return(u.length>20?"...":"")+u.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:o(function(){var u=this.match;return u.length<20&&(u+=this._input.substr(0,20-u.length)),(u.substr(0,20)+(u.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:o(function(){var u=this.pastInput(),h=new Array(u.length+1).join("-");return u+this.upcomingInput()+` +`+h+"^"},"showPosition"),test_match:o(function(u,h){var f,d,p;if(this.options.backtrack_lexer&&(p={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(p.yylloc.range=this.yylloc.range.slice(0))),d=u[0].match(/(?:\r\n?|\n).*/g),d&&(this.yylineno+=d.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:d?d[d.length-1].length-d[d.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+u[0].length},this.yytext+=u[0],this.match+=u[0],this.matches=u,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(u[0].length),this.matched+=u[0],f=this.performAction.call(this,this.yy,this,h,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),f)return f;if(this._backtrack){for(var m in p)this[m]=p[m];return!1}return!1},"test_match"),next:o(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var u,h,f,d;this._more||(this.yytext="",this.match="");for(var p=this._currentRules(),m=0;mh[0].length)){if(h=f,d=m,this.options.backtrack_lexer){if(u=this.test_match(f,p[m]),u!==!1)return u;if(this._backtrack){h=!1;continue}else return!1}else if(!this.options.flex)break}return h?(u=this.test_match(h,p[d]),u!==!1?u:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:o(function(){var h=this.next();return h||this.lex()},"lex"),begin:o(function(h){this.conditionStack.push(h)},"begin"),popState:o(function(){var h=this.conditionStack.length-1;return h>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:o(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:o(function(h){return h=this.conditionStack.length-1-Math.abs(h||0),h>=0?this.conditionStack[h]:"INITIAL"},"topState"),pushState:o(function(h){this.begin(h)},"pushState"),stateStackSize:o(function(){return this.conditionStack.length},"stateStackSize"),options:{"case-insensitive":!0},performAction:o(function(h,f,d,p){var m=p;switch(d){case 0:return this.pushState("csv"),4;break;case 1:return 10;case 2:return 5;case 3:return 12;case 4:return this.pushState("escaped_text"),18;break;case 5:return 20;case 6:return this.popState("escaped_text"),18;break;case 7:return 19}},"anonymous"),rules:[/^(?:sankey-beta\b)/i,/^(?:$)/i,/^(?:((\u000D\u000A)|(\u000A)))/i,/^(?:(\u002C))/i,/^(?:(\u0022))/i,/^(?:([\u0020-\u0021\u0023-\u002B\u002D-\u007E])*)/i,/^(?:(\u0022)(?!(\u0022)))/i,/^(?:(([\u0020-\u0021\u0023-\u002B\u002D-\u007E])|(\u002C)|(\u000D)|(\u000A)|(\u0022)(\u0022))*)/i],conditions:{csv:{rules:[1,2,3,4,5,6,7],inclusive:!1},escaped_text:{rules:[6,7],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,7],inclusive:!0}}};return l}();i.lexer=a;function s(){this.yy={}}return o(s,"Parser"),s.prototype=i,i.Parser=s,new s}();EB.parser=EB;cb=EB});var J6,eC,Z6,CZe,CB,SZe,SB,AZe,_Ze,LZe,DZe,wge,Tge=R(()=>{"use strict";_t();rr();bi();J6=[],eC=[],Z6=new Map,CZe=o(()=>{J6=[],eC=[],Z6=new Map,vr()},"clear"),CB=class{constructor(e,r,n=0){this.source=e;this.target=r;this.value=n}static{o(this,"SankeyLink")}},SZe=o((t,e,r)=>{J6.push(new CB(t,e,r))},"addLink"),SB=class{constructor(e){this.ID=e}static{o(this,"SankeyNode")}},AZe=o(t=>{t=We.sanitizeText(t,de());let e=Z6.get(t);return e===void 0&&(e=new SB(t),Z6.set(t,e),eC.push(e)),e},"findOrCreateNode"),_Ze=o(()=>eC,"getNodes"),LZe=o(()=>J6,"getLinks"),DZe=o(()=>({nodes:eC.map(t=>({id:t.ID})),links:J6.map(t=>({source:t.source.ID,target:t.target.ID,value:t.value}))}),"getGraph"),wge={nodesMap:Z6,getConfig:o(()=>de().sankey,"getConfig"),getNodes:_Ze,getLinks:LZe,getGraph:DZe,addLink:SZe,findOrCreateNode:AZe,getAccTitle:Ar,setAccTitle:kr,getAccDescription:Lr,setAccDescription:_r,getDiagramTitle:Xr,setDiagramTitle:nn,clear:CZe}});function ub(t,e){let r;if(e===void 0)for(let n of t)n!=null&&(r=n)&&(r=n);else{let n=-1;for(let i of t)(i=e(i,++n,t))!=null&&(r=i)&&(r=i)}return r}var kge=R(()=>{"use strict";o(ub,"max")});function x1(t,e){let r;if(e===void 0)for(let n of t)n!=null&&(r>n||r===void 0&&n>=n)&&(r=n);else{let n=-1;for(let i of t)(i=e(i,++n,t))!=null&&(r>i||r===void 0&&i>=i)&&(r=i)}return r}var Ege=R(()=>{"use strict";o(x1,"min")});function b1(t,e){let r=0;if(e===void 0)for(let n of t)(n=+n)&&(r+=n);else{let n=-1;for(let i of t)(i=+e(i,++n,t))&&(r+=i)}return r}var Cge=R(()=>{"use strict";o(b1,"sum")});var AB=R(()=>{"use strict";kge();Ege();Cge()});function RZe(t){return t.target.depth}function _B(t){return t.depth}function LB(t,e){return e-1-t.height}function hb(t,e){return t.sourceLinks.length?t.depth:e-1}function DB(t){return t.targetLinks.length?t.depth:t.sourceLinks.length?x1(t.sourceLinks,RZe)-1:0}var RB=R(()=>{"use strict";AB();o(RZe,"targetDepth");o(_B,"left");o(LB,"right");o(hb,"justify");o(DB,"center")});function w1(t){return function(){return t}}var Sge=R(()=>{"use strict";o(w1,"constant")});function Age(t,e){return tC(t.source,e.source)||t.index-e.index}function _ge(t,e){return tC(t.target,e.target)||t.index-e.index}function tC(t,e){return t.y0-e.y0}function NB(t){return t.value}function NZe(t){return t.index}function MZe(t){return t.nodes}function IZe(t){return t.links}function Lge(t,e){let r=t.get(e);if(!r)throw new Error("missing: "+e);return r}function Dge({nodes:t}){for(let e of t){let r=e.y0,n=r;for(let i of e.sourceLinks)i.y0=r+i.width/2,r+=i.width;for(let i of e.targetLinks)i.y1=n+i.width/2,n+=i.width}}function rC(){let t=0,e=0,r=1,n=1,i=24,a=8,s,l=NZe,u=hb,h,f,d=MZe,p=IZe,m=6;function g(){let O={nodes:d.apply(null,arguments),links:p.apply(null,arguments)};return y(O),v(O),x(O),b(O),T(O),Dge(O),O}o(g,"sankey"),g.update=function(O){return Dge(O),O},g.nodeId=function(O){return arguments.length?(l=typeof O=="function"?O:w1(O),g):l},g.nodeAlign=function(O){return arguments.length?(u=typeof O=="function"?O:w1(O),g):u},g.nodeSort=function(O){return arguments.length?(h=O,g):h},g.nodeWidth=function(O){return arguments.length?(i=+O,g):i},g.nodePadding=function(O){return arguments.length?(a=s=+O,g):a},g.nodes=function(O){return arguments.length?(d=typeof O=="function"?O:w1(O),g):d},g.links=function(O){return arguments.length?(p=typeof O=="function"?O:w1(O),g):p},g.linkSort=function(O){return arguments.length?(f=O,g):f},g.size=function(O){return arguments.length?(t=e=0,r=+O[0],n=+O[1],g):[r-t,n-e]},g.extent=function(O){return arguments.length?(t=+O[0][0],r=+O[1][0],e=+O[0][1],n=+O[1][1],g):[[t,e],[r,n]]},g.iterations=function(O){return arguments.length?(m=+O,g):m};function y({nodes:O,links:D}){for(let[F,B]of O.entries())B.index=F,B.sourceLinks=[],B.targetLinks=[];let P=new Map(O.map((F,B)=>[l(F,B,O),F]));for(let[F,B]of D.entries()){B.index=F;let{source:$,target:z}=B;typeof $!="object"&&($=B.source=Lge(P,$)),typeof z!="object"&&(z=B.target=Lge(P,z)),$.sourceLinks.push(B),z.targetLinks.push(B)}if(f!=null)for(let{sourceLinks:F,targetLinks:B}of O)F.sort(f),B.sort(f)}o(y,"computeNodeLinks");function v({nodes:O}){for(let D of O)D.value=D.fixedValue===void 0?Math.max(b1(D.sourceLinks,NB),b1(D.targetLinks,NB)):D.fixedValue}o(v,"computeNodeValues");function x({nodes:O}){let D=O.length,P=new Set(O),F=new Set,B=0;for(;P.size;){for(let $ of P){$.depth=B;for(let{target:z}of $.sourceLinks)F.add(z)}if(++B>D)throw new Error("circular link");P=F,F=new Set}}o(x,"computeNodeDepths");function b({nodes:O}){let D=O.length,P=new Set(O),F=new Set,B=0;for(;P.size;){for(let $ of P){$.height=B;for(let{source:z}of $.targetLinks)F.add(z)}if(++B>D)throw new Error("circular link");P=F,F=new Set}}o(b,"computeNodeHeights");function w({nodes:O}){let D=ub(O,B=>B.depth)+1,P=(r-t-i)/(D-1),F=new Array(D);for(let B of O){let $=Math.max(0,Math.min(D-1,Math.floor(u.call(null,B,D))));B.layer=$,B.x0=t+$*P,B.x1=B.x0+i,F[$]?F[$].push(B):F[$]=[B]}if(h)for(let B of F)B.sort(h);return F}o(w,"computeNodeLayers");function S(O){let D=x1(O,P=>(n-e-(P.length-1)*s)/b1(P,NB));for(let P of O){let F=e;for(let B of P){B.y0=F,B.y1=F+B.value*D,F=B.y1+s;for(let $ of B.sourceLinks)$.width=$.value*D}F=(n-F+s)/(P.length+1);for(let B=0;BP.length)-1)),S(D);for(let P=0;P0))continue;let X=(Y/Q-z.y0)*D;z.y0+=X,z.y1+=X,N(z)}h===void 0&&$.sort(tC),A($,P)}}o(E,"relaxLeftToRight");function _(O,D,P){for(let F=O.length,B=F-2;B>=0;--B){let $=O[B];for(let z of $){let Y=0,Q=0;for(let{target:ie,value:j}of z.sourceLinks){let J=j*(ie.layer-z.layer);Y+=C(z,ie)*J,Q+=J}if(!(Q>0))continue;let X=(Y/Q-z.y0)*D;z.y0+=X,z.y1+=X,N(z)}h===void 0&&$.sort(tC),A($,P)}}o(_,"relaxRightToLeft");function A(O,D){let P=O.length>>1,F=O[P];M(O,F.y0-s,P-1,D),L(O,F.y1+s,P+1,D),M(O,n,O.length-1,D),L(O,e,0,D)}o(A,"resolveCollisions");function L(O,D,P,F){for(;P1e-6&&(B.y0+=$,B.y1+=$),D=B.y1+s}}o(L,"resolveCollisionsTopToBottom");function M(O,D,P,F){for(;P>=0;--P){let B=O[P],$=(B.y1-D)*F;$>1e-6&&(B.y0-=$,B.y1-=$),D=B.y0-s}}o(M,"resolveCollisionsBottomToTop");function N({sourceLinks:O,targetLinks:D}){if(f===void 0){for(let{source:{sourceLinks:P}}of D)P.sort(_ge);for(let{target:{targetLinks:P}}of O)P.sort(Age)}}o(N,"reorderNodeLinks");function k(O){if(f===void 0)for(let{sourceLinks:D,targetLinks:P}of O)D.sort(_ge),P.sort(Age)}o(k,"reorderLinks");function I(O,D){let P=O.y0-(O.sourceLinks.length-1)*s/2;for(let{target:F,width:B}of O.sourceLinks){if(F===D)break;P+=B+s}for(let{source:F,width:B}of D.targetLinks){if(F===O)break;P-=B}return P}o(I,"targetTop");function C(O,D){let P=D.y0-(D.targetLinks.length-1)*s/2;for(let{source:F,width:B}of D.targetLinks){if(F===O)break;P+=B+s}for(let{target:F,width:B}of O.sourceLinks){if(F===D)break;P-=B}return P}return o(C,"sourceTop"),g}var Rge=R(()=>{"use strict";AB();RB();Sge();o(Age,"ascendingSourceBreadth");o(_ge,"ascendingTargetBreadth");o(tC,"ascendingBreadth");o(NB,"value");o(NZe,"defaultId");o(MZe,"defaultNodes");o(IZe,"defaultLinks");o(Lge,"find");o(Dge,"computeLinkBreadths");o(rC,"Sankey")});function OB(){this._x0=this._y0=this._x1=this._y1=null,this._=""}function Nge(){return new OB}var MB,IB,Q0,OZe,PB,Mge=R(()=>{"use strict";MB=Math.PI,IB=2*MB,Q0=1e-6,OZe=IB-Q0;o(OB,"Path");o(Nge,"path");OB.prototype=Nge.prototype={constructor:OB,moveTo:o(function(t,e){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+e)},"moveTo"),closePath:o(function(){this._x1!==null&&(this._x1=this._x0,this._y1=this._y0,this._+="Z")},"closePath"),lineTo:o(function(t,e){this._+="L"+(this._x1=+t)+","+(this._y1=+e)},"lineTo"),quadraticCurveTo:o(function(t,e,r,n){this._+="Q"+ +t+","+ +e+","+(this._x1=+r)+","+(this._y1=+n)},"quadraticCurveTo"),bezierCurveTo:o(function(t,e,r,n,i,a){this._+="C"+ +t+","+ +e+","+ +r+","+ +n+","+(this._x1=+i)+","+(this._y1=+a)},"bezierCurveTo"),arcTo:o(function(t,e,r,n,i){t=+t,e=+e,r=+r,n=+n,i=+i;var a=this._x1,s=this._y1,l=r-t,u=n-e,h=a-t,f=s-e,d=h*h+f*f;if(i<0)throw new Error("negative radius: "+i);if(this._x1===null)this._+="M"+(this._x1=t)+","+(this._y1=e);else if(d>Q0)if(!(Math.abs(f*l-u*h)>Q0)||!i)this._+="L"+(this._x1=t)+","+(this._y1=e);else{var p=r-a,m=n-s,g=l*l+u*u,y=p*p+m*m,v=Math.sqrt(g),x=Math.sqrt(d),b=i*Math.tan((MB-Math.acos((g+d-y)/(2*v*x)))/2),w=b/x,S=b/v;Math.abs(w-1)>Q0&&(this._+="L"+(t+w*h)+","+(e+w*f)),this._+="A"+i+","+i+",0,0,"+ +(f*p>h*m)+","+(this._x1=t+S*l)+","+(this._y1=e+S*u)}},"arcTo"),arc:o(function(t,e,r,n,i,a){t=+t,e=+e,r=+r,a=!!a;var s=r*Math.cos(n),l=r*Math.sin(n),u=t+s,h=e+l,f=1^a,d=a?n-i:i-n;if(r<0)throw new Error("negative radius: "+r);this._x1===null?this._+="M"+u+","+h:(Math.abs(this._x1-u)>Q0||Math.abs(this._y1-h)>Q0)&&(this._+="L"+u+","+h),r&&(d<0&&(d=d%IB+IB),d>OZe?this._+="A"+r+","+r+",0,1,"+f+","+(t-s)+","+(e-l)+"A"+r+","+r+",0,1,"+f+","+(this._x1=u)+","+(this._y1=h):d>Q0&&(this._+="A"+r+","+r+",0,"+ +(d>=MB)+","+f+","+(this._x1=t+r*Math.cos(i))+","+(this._y1=e+r*Math.sin(i))))},"arc"),rect:o(function(t,e,r,n){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+e)+"h"+ +r+"v"+ +n+"h"+-r+"Z"},"rect"),toString:o(function(){return this._},"toString")};PB=Nge});var Ige=R(()=>{"use strict";Mge()});function nC(t){return o(function(){return t},"constant")}var Oge=R(()=>{"use strict";o(nC,"default")});function Pge(t){return t[0]}function Bge(t){return t[1]}var Fge=R(()=>{"use strict";o(Pge,"x");o(Bge,"y")});var zge,Gge=R(()=>{"use strict";zge=Array.prototype.slice});function PZe(t){return t.source}function BZe(t){return t.target}function FZe(t){var e=PZe,r=BZe,n=Pge,i=Bge,a=null;function s(){var l,u=zge.call(arguments),h=e.apply(this,u),f=r.apply(this,u);if(a||(a=l=PB()),t(a,+n.apply(this,(u[0]=h,u)),+i.apply(this,u),+n.apply(this,(u[0]=f,u)),+i.apply(this,u)),l)return a=null,l+""||null}return o(s,"link"),s.source=function(l){return arguments.length?(e=l,s):e},s.target=function(l){return arguments.length?(r=l,s):r},s.x=function(l){return arguments.length?(n=typeof l=="function"?l:nC(+l),s):n},s.y=function(l){return arguments.length?(i=typeof l=="function"?l:nC(+l),s):i},s.context=function(l){return arguments.length?(a=l??null,s):a},s}function zZe(t,e,r,n,i){t.moveTo(e,r),t.bezierCurveTo(e=(e+n)/2,r,e,i,n,i)}function BB(){return FZe(zZe)}var $ge=R(()=>{"use strict";Ige();Gge();Oge();Fge();o(PZe,"linkSource");o(BZe,"linkTarget");o(FZe,"link");o(zZe,"curveHorizontal");o(BB,"linkHorizontal")});var Vge=R(()=>{"use strict";$ge()});function GZe(t){return[t.source.x1,t.y0]}function $Ze(t){return[t.target.x0,t.y1]}function iC(){return BB().source(GZe).target($Ze)}var Uge=R(()=>{"use strict";Vge();o(GZe,"horizontalSource");o($Ze,"horizontalTarget");o(iC,"default")});var Hge=R(()=>{"use strict";Rge();RB();Uge()});var fb,Yge=R(()=>{"use strict";fb=class t{static{o(this,"Uid")}static{this.count=0}static next(e){return new t(e+ ++t.count)}constructor(e){this.id=e,this.href=`#${e}`}toString(){return"url("+this.href+")"}}});var VZe,UZe,Wge,qge=R(()=>{"use strict";_t();Zt();Hge();Yn();Yge();VZe={left:_B,right:LB,center:DB,justify:hb},UZe=o(function(t,e,r,n){let{securityLevel:i,sankey:a}=de(),s=_4.sankey,l;i==="sandbox"&&(l=$e("#i"+e));let u=i==="sandbox"?$e(l.nodes()[0].contentDocument.body):$e("body"),h=i==="sandbox"?u.select(`[id="${e}"]`):$e(`[id="${e}"]`),f=a?.width??s.width,d=a?.height??s.width,p=a?.useMaxWidth??s.useMaxWidth,m=a?.nodeAlignment??s.nodeAlignment,g=a?.prefix??s.prefix,y=a?.suffix??s.suffix,v=a?.showValues??s.showValues,x=n.db.getGraph(),b=VZe[m];rC().nodeId(M=>M.id).nodeWidth(10).nodePadding(10+(v?15:0)).nodeAlign(b).extent([[0,0],[f,d]])(x);let T=pu(Z8);h.append("g").attr("class","nodes").selectAll(".node").data(x.nodes).join("g").attr("class","node").attr("id",M=>(M.uid=fb.next("node-")).id).attr("transform",function(M){return"translate("+M.x0+","+M.y0+")"}).attr("x",M=>M.x0).attr("y",M=>M.y0).append("rect").attr("height",M=>M.y1-M.y0).attr("width",M=>M.x1-M.x0).attr("fill",M=>T(M.id));let E=o(({id:M,value:N})=>v?`${M} +${g}${Math.round(N*100)/100}${y}`:M,"getText");h.append("g").attr("class","node-labels").attr("font-family","sans-serif").attr("font-size",14).selectAll("text").data(x.nodes).join("text").attr("x",M=>M.x0(M.y1+M.y0)/2).attr("dy",`${v?"0":"0.35"}em`).attr("text-anchor",M=>M.x0(N.uid=fb.next("linearGradient-")).id).attr("gradientUnits","userSpaceOnUse").attr("x1",N=>N.source.x1).attr("x2",N=>N.target.x0);M.append("stop").attr("offset","0%").attr("stop-color",N=>T(N.source.id)),M.append("stop").attr("offset","100%").attr("stop-color",N=>T(N.target.id))}let L;switch(A){case"gradient":L=o(M=>M.uid,"coloring");break;case"source":L=o(M=>T(M.source.id),"coloring");break;case"target":L=o(M=>T(M.target.id),"coloring");break;default:L=A}_.append("path").attr("d",iC()).attr("stroke",L).attr("stroke-width",M=>Math.max(1,M.width)),Lo(void 0,h,0,p)},"draw"),Wge={draw:UZe}});var Xge,jge=R(()=>{"use strict";Xge=o(t=>t.replaceAll(/^[^\S\n\r]+|[^\S\n\r]+$/g,"").replaceAll(/([\n\r])+/g,` +`).trim(),"prepareTextForParsing")});var Kge={};hr(Kge,{diagram:()=>YZe});var HZe,YZe,Qge=R(()=>{"use strict";bge();Tge();qge();jge();HZe=cb.parse.bind(cb);cb.parse=t=>HZe(Xge(t));YZe={parser:cb,db:wge,renderer:Wge}});var e1e,FB,jZe,KZe,QZe,ZZe,JZe,Mf,zB=R(()=>{"use strict";qs();sl();xr();bi();e1e={packet:[]},FB=structuredClone(e1e),jZe=mr.packet,KZe=o(()=>{let t=Ts({...jZe,...Or().packet});return t.showBits&&(t.paddingY+=10),t},"getConfig"),QZe=o(()=>FB.packet,"getPacket"),ZZe=o(t=>{t.length>0&&FB.packet.push(t)},"pushWord"),JZe=o(()=>{vr(),FB=structuredClone(e1e)},"clear"),Mf={pushWord:ZZe,getPacket:QZe,getConfig:KZe,clear:JZe,setAccTitle:kr,getAccTitle:Ar,setDiagramTitle:nn,getDiagramTitle:Xr,getAccDescription:Lr,setAccDescription:_r}});var eJe,tJe,rJe,t1e,r1e=R(()=>{"use strict";Lg();ut();sx();zB();eJe=1e4,tJe=o(t=>{cf(t,Mf);let e=-1,r=[],n=1,{bitsPerRow:i}=Mf.getConfig();for(let{start:a,end:s,label:l}of t.blocks){if(s&&s{if(t.end===void 0&&(t.end=t.start),t.start>t.end)throw new Error(`Block start ${t.start} is greater than block end ${t.end}.`);return t.end+1<=e*r?[t,void 0]:[{start:t.start,end:e*r-1,label:t.label},{start:e*r,end:t.end,label:t.label}]},"getNextFittingBlock"),t1e={parse:o(async t=>{let e=await Fl("packet",t);V.debug(e),tJe(e)},"parse")}});var nJe,iJe,n1e,i1e=R(()=>{"use strict";pf();Yn();nJe=o((t,e,r,n)=>{let i=n.db,a=i.getConfig(),{rowHeight:s,paddingY:l,bitWidth:u,bitsPerRow:h}=a,f=i.getPacket(),d=i.getDiagramTitle(),p=s+l,m=p*(f.length+1)-(d?0:s),g=u*h+2,y=Ps(e);y.attr("viewbox",`0 0 ${g} ${m}`),Sr(y,m,g,a.useMaxWidth);for(let[v,x]of f.entries())iJe(y,x,v,a);y.append("text").text(d).attr("x",g/2).attr("y",m-p/2).attr("dominant-baseline","middle").attr("text-anchor","middle").attr("class","packetTitle")},"draw"),iJe=o((t,e,r,{rowHeight:n,paddingX:i,paddingY:a,bitWidth:s,bitsPerRow:l,showBits:u})=>{let h=t.append("g"),f=r*(n+a)+a;for(let d of e){let p=d.start%l*s+1,m=(d.end-d.start+1)*s-i;if(h.append("rect").attr("x",p).attr("y",f).attr("width",m).attr("height",n).attr("class","packetBlock"),h.append("text").attr("x",p+m/2).attr("y",f+n/2).attr("class","packetLabel").attr("dominant-baseline","middle").attr("text-anchor","middle").text(d.label),!u)continue;let g=d.end===d.start,y=f-2;h.append("text").attr("x",p+(g?m/2:0)).attr("y",y).attr("class","packetByte start").attr("dominant-baseline","auto").attr("text-anchor",g?"middle":"start").text(d.start),g||h.append("text").attr("x",p+m).attr("y",y).attr("class","packetByte end").attr("dominant-baseline","auto").attr("text-anchor","end").text(d.end)}},"drawWord"),n1e={draw:nJe}});var aJe,a1e,s1e=R(()=>{"use strict";xr();aJe={byteFontSize:"10px",startByteColor:"black",endByteColor:"black",labelColor:"black",labelFontSize:"12px",titleColor:"black",titleFontSize:"14px",blockStrokeColor:"black",blockStrokeWidth:"1",blockFillColor:"#efefef"},a1e=o(({packet:t}={})=>{let e=Ts(aJe,t);return` + .packetByte { + font-size: ${e.byteFontSize}; + } + .packetByte.start { + fill: ${e.startByteColor}; + } + .packetByte.end { + fill: ${e.endByteColor}; + } + .packetLabel { + fill: ${e.labelColor}; + font-size: ${e.labelFontSize}; + } + .packetTitle { + fill: ${e.titleColor}; + font-size: ${e.titleFontSize}; + } + .packetBlock { + stroke: ${e.blockStrokeColor}; + stroke-width: ${e.blockStrokeWidth}; + fill: ${e.blockFillColor}; + } + `},"styles")});var o1e={};hr(o1e,{diagram:()=>sJe});var sJe,l1e=R(()=>{"use strict";zB();r1e();i1e();s1e();sJe={parser:t1e,db:Mf,renderer:n1e,styles:a1e}});var GB,h1e,f1e=R(()=>{"use strict";GB=function(){var t=o(function(w,S,T,E){for(T=T||{},E=w.length;E--;T[w[E]]=S);return T},"o"),e=[1,7],r=[1,13],n=[1,14],i=[1,15],a=[1,19],s=[1,16],l=[1,17],u=[1,18],h=[8,30],f=[8,21,28,29,30,31,32,40,44,47],d=[1,23],p=[1,24],m=[8,15,16,21,28,29,30,31,32,40,44,47],g=[8,15,16,21,27,28,29,30,31,32,40,44,47],y=[1,49],v={trace:o(function(){},"trace"),yy:{},symbols_:{error:2,spaceLines:3,SPACELINE:4,NL:5,separator:6,SPACE:7,EOF:8,start:9,BLOCK_DIAGRAM_KEY:10,document:11,stop:12,statement:13,link:14,LINK:15,START_LINK:16,LINK_LABEL:17,STR:18,nodeStatement:19,columnsStatement:20,SPACE_BLOCK:21,blockStatement:22,classDefStatement:23,cssClassStatement:24,styleStatement:25,node:26,SIZE:27,COLUMNS:28,"id-block":29,end:30,block:31,NODE_ID:32,nodeShapeNLabel:33,dirList:34,DIR:35,NODE_DSTART:36,NODE_DEND:37,BLOCK_ARROW_START:38,BLOCK_ARROW_END:39,classDef:40,CLASSDEF_ID:41,CLASSDEF_STYLEOPTS:42,DEFAULT:43,class:44,CLASSENTITY_IDS:45,STYLECLASS:46,style:47,STYLE_ENTITY_IDS:48,STYLE_DEFINITION_DATA:49,$accept:0,$end:1},terminals_:{2:"error",4:"SPACELINE",5:"NL",7:"SPACE",8:"EOF",10:"BLOCK_DIAGRAM_KEY",15:"LINK",16:"START_LINK",17:"LINK_LABEL",18:"STR",21:"SPACE_BLOCK",27:"SIZE",28:"COLUMNS",29:"id-block",30:"end",31:"block",32:"NODE_ID",35:"DIR",36:"NODE_DSTART",37:"NODE_DEND",38:"BLOCK_ARROW_START",39:"BLOCK_ARROW_END",40:"classDef",41:"CLASSDEF_ID",42:"CLASSDEF_STYLEOPTS",43:"DEFAULT",44:"class",45:"CLASSENTITY_IDS",46:"STYLECLASS",47:"style",48:"STYLE_ENTITY_IDS",49:"STYLE_DEFINITION_DATA"},productions_:[0,[3,1],[3,2],[3,2],[6,1],[6,1],[6,1],[9,3],[12,1],[12,1],[12,2],[12,2],[11,1],[11,2],[14,1],[14,4],[13,1],[13,1],[13,1],[13,1],[13,1],[13,1],[13,1],[19,3],[19,2],[19,1],[20,1],[22,4],[22,3],[26,1],[26,2],[34,1],[34,2],[33,3],[33,4],[23,3],[23,3],[24,3],[25,3]],performAction:o(function(S,T,E,_,A,L,M){var N=L.length-1;switch(A){case 4:_.getLogger().debug("Rule: separator (NL) ");break;case 5:_.getLogger().debug("Rule: separator (Space) ");break;case 6:_.getLogger().debug("Rule: separator (EOF) ");break;case 7:_.getLogger().debug("Rule: hierarchy: ",L[N-1]),_.setHierarchy(L[N-1]);break;case 8:_.getLogger().debug("Stop NL ");break;case 9:_.getLogger().debug("Stop EOF ");break;case 10:_.getLogger().debug("Stop NL2 ");break;case 11:_.getLogger().debug("Stop EOF2 ");break;case 12:_.getLogger().debug("Rule: statement: ",L[N]),typeof L[N].length=="number"?this.$=L[N]:this.$=[L[N]];break;case 13:_.getLogger().debug("Rule: statement #2: ",L[N-1]),this.$=[L[N-1]].concat(L[N]);break;case 14:_.getLogger().debug("Rule: link: ",L[N],S),this.$={edgeTypeStr:L[N],label:""};break;case 15:_.getLogger().debug("Rule: LABEL link: ",L[N-3],L[N-1],L[N]),this.$={edgeTypeStr:L[N],label:L[N-1]};break;case 18:let k=parseInt(L[N]),I=_.generateId();this.$={id:I,type:"space",label:"",width:k,children:[]};break;case 23:_.getLogger().debug("Rule: (nodeStatement link node) ",L[N-2],L[N-1],L[N]," typestr: ",L[N-1].edgeTypeStr);let C=_.edgeStrToEdgeData(L[N-1].edgeTypeStr);this.$=[{id:L[N-2].id,label:L[N-2].label,type:L[N-2].type,directions:L[N-2].directions},{id:L[N-2].id+"-"+L[N].id,start:L[N-2].id,end:L[N].id,label:L[N-1].label,type:"edge",directions:L[N].directions,arrowTypeEnd:C,arrowTypeStart:"arrow_open"},{id:L[N].id,label:L[N].label,type:_.typeStr2Type(L[N].typeStr),directions:L[N].directions}];break;case 24:_.getLogger().debug("Rule: nodeStatement (abc88 node size) ",L[N-1],L[N]),this.$={id:L[N-1].id,label:L[N-1].label,type:_.typeStr2Type(L[N-1].typeStr),directions:L[N-1].directions,widthInColumns:parseInt(L[N],10)};break;case 25:_.getLogger().debug("Rule: nodeStatement (node) ",L[N]),this.$={id:L[N].id,label:L[N].label,type:_.typeStr2Type(L[N].typeStr),directions:L[N].directions,widthInColumns:1};break;case 26:_.getLogger().debug("APA123",this?this:"na"),_.getLogger().debug("COLUMNS: ",L[N]),this.$={type:"column-setting",columns:L[N]==="auto"?-1:parseInt(L[N])};break;case 27:_.getLogger().debug("Rule: id-block statement : ",L[N-2],L[N-1]);let O=_.generateId();this.$={...L[N-2],type:"composite",children:L[N-1]};break;case 28:_.getLogger().debug("Rule: blockStatement : ",L[N-2],L[N-1],L[N]);let D=_.generateId();this.$={id:D,type:"composite",label:"",children:L[N-1]};break;case 29:_.getLogger().debug("Rule: node (NODE_ID separator): ",L[N]),this.$={id:L[N]};break;case 30:_.getLogger().debug("Rule: node (NODE_ID nodeShapeNLabel separator): ",L[N-1],L[N]),this.$={id:L[N-1],label:L[N].label,typeStr:L[N].typeStr,directions:L[N].directions};break;case 31:_.getLogger().debug("Rule: dirList: ",L[N]),this.$=[L[N]];break;case 32:_.getLogger().debug("Rule: dirList: ",L[N-1],L[N]),this.$=[L[N-1]].concat(L[N]);break;case 33:_.getLogger().debug("Rule: nodeShapeNLabel: ",L[N-2],L[N-1],L[N]),this.$={typeStr:L[N-2]+L[N],label:L[N-1]};break;case 34:_.getLogger().debug("Rule: BLOCK_ARROW nodeShapeNLabel: ",L[N-3],L[N-2]," #3:",L[N-1],L[N]),this.$={typeStr:L[N-3]+L[N],label:L[N-2],directions:L[N-1]};break;case 35:case 36:this.$={type:"classDef",id:L[N-1].trim(),css:L[N].trim()};break;case 37:this.$={type:"applyClass",id:L[N-1].trim(),styleClass:L[N].trim()};break;case 38:this.$={type:"applyStyles",id:L[N-1].trim(),stylesStr:L[N].trim()};break}},"anonymous"),table:[{9:1,10:[1,2]},{1:[3]},{11:3,13:4,19:5,20:6,21:e,22:8,23:9,24:10,25:11,26:12,28:r,29:n,31:i,32:a,40:s,44:l,47:u},{8:[1,20]},t(h,[2,12],{13:4,19:5,20:6,22:8,23:9,24:10,25:11,26:12,11:21,21:e,28:r,29:n,31:i,32:a,40:s,44:l,47:u}),t(f,[2,16],{14:22,15:d,16:p}),t(f,[2,17]),t(f,[2,18]),t(f,[2,19]),t(f,[2,20]),t(f,[2,21]),t(f,[2,22]),t(m,[2,25],{27:[1,25]}),t(f,[2,26]),{19:26,26:12,32:a},{11:27,13:4,19:5,20:6,21:e,22:8,23:9,24:10,25:11,26:12,28:r,29:n,31:i,32:a,40:s,44:l,47:u},{41:[1,28],43:[1,29]},{45:[1,30]},{48:[1,31]},t(g,[2,29],{33:32,36:[1,33],38:[1,34]}),{1:[2,7]},t(h,[2,13]),{26:35,32:a},{32:[2,14]},{17:[1,36]},t(m,[2,24]),{11:37,13:4,14:22,15:d,16:p,19:5,20:6,21:e,22:8,23:9,24:10,25:11,26:12,28:r,29:n,31:i,32:a,40:s,44:l,47:u},{30:[1,38]},{42:[1,39]},{42:[1,40]},{46:[1,41]},{49:[1,42]},t(g,[2,30]),{18:[1,43]},{18:[1,44]},t(m,[2,23]),{18:[1,45]},{30:[1,46]},t(f,[2,28]),t(f,[2,35]),t(f,[2,36]),t(f,[2,37]),t(f,[2,38]),{37:[1,47]},{34:48,35:y},{15:[1,50]},t(f,[2,27]),t(g,[2,33]),{39:[1,51]},{34:52,35:y,39:[2,31]},{32:[2,15]},t(g,[2,34]),{39:[2,32]}],defaultActions:{20:[2,7],23:[2,14],50:[2,15],52:[2,32]},parseError:o(function(S,T){if(T.recoverable)this.trace(S);else{var E=new Error(S);throw E.hash=T,E}},"parseError"),parse:o(function(S){var T=this,E=[0],_=[],A=[null],L=[],M=this.table,N="",k=0,I=0,C=0,O=2,D=1,P=L.slice.call(arguments,1),F=Object.create(this.lexer),B={yy:{}};for(var $ in this.yy)Object.prototype.hasOwnProperty.call(this.yy,$)&&(B.yy[$]=this.yy[$]);F.setInput(S,B.yy),B.yy.lexer=F,B.yy.parser=this,typeof F.yylloc>"u"&&(F.yylloc={});var z=F.yylloc;L.push(z);var Y=F.options&&F.options.ranges;typeof B.yy.parseError=="function"?this.parseError=B.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;function Q(oe){E.length=E.length-2*oe,A.length=A.length-oe,L.length=L.length-oe}o(Q,"popStack");function X(){var oe;return oe=_.pop()||F.lex()||D,typeof oe!="number"&&(oe instanceof Array&&(_=oe,oe=_.pop()),oe=T.symbols_[oe]||oe),oe}o(X,"lex");for(var ie,j,J,Z,H,q,K={},se,ce,ue,te;;){if(J=E[E.length-1],this.defaultActions[J]?Z=this.defaultActions[J]:((ie===null||typeof ie>"u")&&(ie=X()),Z=M[J]&&M[J][ie]),typeof Z>"u"||!Z.length||!Z[0]){var De="";te=[];for(se in M[J])this.terminals_[se]&&se>O&&te.push("'"+this.terminals_[se]+"'");F.showPosition?De="Parse error on line "+(k+1)+`: +`+F.showPosition()+` +Expecting `+te.join(", ")+", got '"+(this.terminals_[ie]||ie)+"'":De="Parse error on line "+(k+1)+": Unexpected "+(ie==D?"end of input":"'"+(this.terminals_[ie]||ie)+"'"),this.parseError(De,{text:F.match,token:this.terminals_[ie]||ie,line:F.yylineno,loc:z,expected:te})}if(Z[0]instanceof Array&&Z.length>1)throw new Error("Parse Error: multiple actions possible at state: "+J+", token: "+ie);switch(Z[0]){case 1:E.push(ie),A.push(F.yytext),L.push(F.yylloc),E.push(Z[1]),ie=null,j?(ie=j,j=null):(I=F.yyleng,N=F.yytext,k=F.yylineno,z=F.yylloc,C>0&&C--);break;case 2:if(ce=this.productions_[Z[1]][1],K.$=A[A.length-ce],K._$={first_line:L[L.length-(ce||1)].first_line,last_line:L[L.length-1].last_line,first_column:L[L.length-(ce||1)].first_column,last_column:L[L.length-1].last_column},Y&&(K._$.range=[L[L.length-(ce||1)].range[0],L[L.length-1].range[1]]),q=this.performAction.apply(K,[N,I,k,B.yy,Z[1],A,L].concat(P)),typeof q<"u")return q;ce&&(E=E.slice(0,-1*ce*2),A=A.slice(0,-1*ce),L=L.slice(0,-1*ce)),E.push(this.productions_[Z[1]][0]),A.push(K.$),L.push(K._$),ue=M[E[E.length-2]][E[E.length-1]],E.push(ue);break;case 3:return!0}}return!0},"parse")},x=function(){var w={EOF:1,parseError:o(function(T,E){if(this.yy.parser)this.yy.parser.parseError(T,E);else throw new Error(T)},"parseError"),setInput:o(function(S,T){return this.yy=T||this.yy||{},this._input=S,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},"setInput"),input:o(function(){var S=this._input[0];this.yytext+=S,this.yyleng++,this.offset++,this.match+=S,this.matched+=S;var T=S.match(/(?:\r\n?|\n).*/g);return T?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),S},"input"),unput:o(function(S){var T=S.length,E=S.split(/(?:\r\n?|\n)/g);this._input=S+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-T),this.offset-=T;var _=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),E.length-1&&(this.yylineno-=E.length-1);var A=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:E?(E.length===_.length?this.yylloc.first_column:0)+_[_.length-E.length].length-E[0].length:this.yylloc.first_column-T},this.options.ranges&&(this.yylloc.range=[A[0],A[0]+this.yyleng-T]),this.yyleng=this.yytext.length,this},"unput"),more:o(function(){return this._more=!0,this},"more"),reject:o(function(){if(this.options.backtrack_lexer)this._backtrack=!0;else return this.parseError("Lexical error on line "+(this.yylineno+1)+`. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true). +`+this.showPosition(),{text:"",token:null,line:this.yylineno});return this},"reject"),less:o(function(S){this.unput(this.match.slice(S))},"less"),pastInput:o(function(){var S=this.matched.substr(0,this.matched.length-this.match.length);return(S.length>20?"...":"")+S.substr(-20).replace(/\n/g,"")},"pastInput"),upcomingInput:o(function(){var S=this.match;return S.length<20&&(S+=this._input.substr(0,20-S.length)),(S.substr(0,20)+(S.length>20?"...":"")).replace(/\n/g,"")},"upcomingInput"),showPosition:o(function(){var S=this.pastInput(),T=new Array(S.length+1).join("-");return S+this.upcomingInput()+` +`+T+"^"},"showPosition"),test_match:o(function(S,T){var E,_,A;if(this.options.backtrack_lexer&&(A={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(A.yylloc.range=this.yylloc.range.slice(0))),_=S[0].match(/(?:\r\n?|\n).*/g),_&&(this.yylineno+=_.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:_?_[_.length-1].length-_[_.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+S[0].length},this.yytext+=S[0],this.match+=S[0],this.matches=S,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(S[0].length),this.matched+=S[0],E=this.performAction.call(this,this.yy,this,T,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),E)return E;if(this._backtrack){for(var L in A)this[L]=A[L];return!1}return!1},"test_match"),next:o(function(){if(this.done)return this.EOF;this._input||(this.done=!0);var S,T,E,_;this._more||(this.yytext="",this.match="");for(var A=this._currentRules(),L=0;LT[0].length)){if(T=E,_=L,this.options.backtrack_lexer){if(S=this.test_match(E,A[L]),S!==!1)return S;if(this._backtrack){T=!1;continue}else return!1}else if(!this.options.flex)break}return T?(S=this.test_match(T,A[_]),S!==!1?S:!1):this._input===""?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+`. Unrecognized text. +`+this.showPosition(),{text:"",token:null,line:this.yylineno})},"next"),lex:o(function(){var T=this.next();return T||this.lex()},"lex"),begin:o(function(T){this.conditionStack.push(T)},"begin"),popState:o(function(){var T=this.conditionStack.length-1;return T>0?this.conditionStack.pop():this.conditionStack[0]},"popState"),_currentRules:o(function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},"_currentRules"),topState:o(function(T){return T=this.conditionStack.length-1-Math.abs(T||0),T>=0?this.conditionStack[T]:"INITIAL"},"topState"),pushState:o(function(T){this.begin(T)},"pushState"),stateStackSize:o(function(){return this.conditionStack.length},"stateStackSize"),options:{},performAction:o(function(T,E,_,A){var L=A;switch(_){case 0:return 10;case 1:return T.getLogger().debug("Found space-block"),31;break;case 2:return T.getLogger().debug("Found nl-block"),31;break;case 3:return T.getLogger().debug("Found space-block"),29;break;case 4:T.getLogger().debug(".",E.yytext);break;case 5:T.getLogger().debug("_",E.yytext);break;case 6:return 5;case 7:return E.yytext=-1,28;break;case 8:return E.yytext=E.yytext.replace(/columns\s+/,""),T.getLogger().debug("COLUMNS (LEX)",E.yytext),28;break;case 9:this.pushState("md_string");break;case 10:return"MD_STR";case 11:this.popState();break;case 12:this.pushState("string");break;case 13:T.getLogger().debug("LEX: POPPING STR:",E.yytext),this.popState();break;case 14:return T.getLogger().debug("LEX: STR end:",E.yytext),"STR";break;case 15:return E.yytext=E.yytext.replace(/space\:/,""),T.getLogger().debug("SPACE NUM (LEX)",E.yytext),21;break;case 16:return E.yytext="1",T.getLogger().debug("COLUMNS (LEX)",E.yytext),21;break;case 17:return 43;case 18:return"LINKSTYLE";case 19:return"INTERPOLATE";case 20:return this.pushState("CLASSDEF"),40;break;case 21:return this.popState(),this.pushState("CLASSDEFID"),"DEFAULT_CLASSDEF_ID";break;case 22:return this.popState(),this.pushState("CLASSDEFID"),41;break;case 23:return this.popState(),42;break;case 24:return this.pushState("CLASS"),44;break;case 25:return this.popState(),this.pushState("CLASS_STYLE"),45;break;case 26:return this.popState(),46;break;case 27:return this.pushState("STYLE_STMNT"),47;break;case 28:return this.popState(),this.pushState("STYLE_DEFINITION"),48;break;case 29:return this.popState(),49;break;case 30:return this.pushState("acc_title"),"acc_title";break;case 31:return this.popState(),"acc_title_value";break;case 32:return this.pushState("acc_descr"),"acc_descr";break;case 33:return this.popState(),"acc_descr_value";break;case 34:this.pushState("acc_descr_multiline");break;case 35:this.popState();break;case 36:return"acc_descr_multiline_value";case 37:return 30;case 38:return this.popState(),T.getLogger().debug("Lex: (("),"NODE_DEND";break;case 39:return this.popState(),T.getLogger().debug("Lex: (("),"NODE_DEND";break;case 40:return this.popState(),T.getLogger().debug("Lex: ))"),"NODE_DEND";break;case 41:return this.popState(),T.getLogger().debug("Lex: (("),"NODE_DEND";break;case 42:return this.popState(),T.getLogger().debug("Lex: (("),"NODE_DEND";break;case 43:return this.popState(),T.getLogger().debug("Lex: (-"),"NODE_DEND";break;case 44:return this.popState(),T.getLogger().debug("Lex: -)"),"NODE_DEND";break;case 45:return this.popState(),T.getLogger().debug("Lex: (("),"NODE_DEND";break;case 46:return this.popState(),T.getLogger().debug("Lex: ]]"),"NODE_DEND";break;case 47:return this.popState(),T.getLogger().debug("Lex: ("),"NODE_DEND";break;case 48:return this.popState(),T.getLogger().debug("Lex: ])"),"NODE_DEND";break;case 49:return this.popState(),T.getLogger().debug("Lex: /]"),"NODE_DEND";break;case 50:return this.popState(),T.getLogger().debug("Lex: /]"),"NODE_DEND";break;case 51:return this.popState(),T.getLogger().debug("Lex: )]"),"NODE_DEND";break;case 52:return this.popState(),T.getLogger().debug("Lex: )"),"NODE_DEND";break;case 53:return this.popState(),T.getLogger().debug("Lex: ]>"),"NODE_DEND";break;case 54:return this.popState(),T.getLogger().debug("Lex: ]"),"NODE_DEND";break;case 55:return T.getLogger().debug("Lexa: -)"),this.pushState("NODE"),36;break;case 56:return T.getLogger().debug("Lexa: (-"),this.pushState("NODE"),36;break;case 57:return T.getLogger().debug("Lexa: ))"),this.pushState("NODE"),36;break;case 58:return T.getLogger().debug("Lexa: )"),this.pushState("NODE"),36;break;case 59:return T.getLogger().debug("Lex: ((("),this.pushState("NODE"),36;break;case 60:return T.getLogger().debug("Lexa: )"),this.pushState("NODE"),36;break;case 61:return T.getLogger().debug("Lexa: )"),this.pushState("NODE"),36;break;case 62:return T.getLogger().debug("Lexa: )"),this.pushState("NODE"),36;break;case 63:return T.getLogger().debug("Lexc: >"),this.pushState("NODE"),36;break;case 64:return T.getLogger().debug("Lexa: (["),this.pushState("NODE"),36;break;case 65:return T.getLogger().debug("Lexa: )"),this.pushState("NODE"),36;break;case 66:return this.pushState("NODE"),36;break;case 67:return this.pushState("NODE"),36;break;case 68:return this.pushState("NODE"),36;break;case 69:return this.pushState("NODE"),36;break;case 70:return this.pushState("NODE"),36;break;case 71:return this.pushState("NODE"),36;break;case 72:return this.pushState("NODE"),36;break;case 73:return T.getLogger().debug("Lexa: ["),this.pushState("NODE"),36;break;case 74:return this.pushState("BLOCK_ARROW"),T.getLogger().debug("LEX ARR START"),38;break;case 75:return T.getLogger().debug("Lex: NODE_ID",E.yytext),32;break;case 76:return T.getLogger().debug("Lex: EOF",E.yytext),8;break;case 77:this.pushState("md_string");break;case 78:this.pushState("md_string");break;case 79:return"NODE_DESCR";case 80:this.popState();break;case 81:T.getLogger().debug("Lex: Starting string"),this.pushState("string");break;case 82:T.getLogger().debug("LEX ARR: Starting string"),this.pushState("string");break;case 83:return T.getLogger().debug("LEX: NODE_DESCR:",E.yytext),"NODE_DESCR";break;case 84:T.getLogger().debug("LEX POPPING"),this.popState();break;case 85:T.getLogger().debug("Lex: =>BAE"),this.pushState("ARROW_DIR");break;case 86:return E.yytext=E.yytext.replace(/^,\s*/,""),T.getLogger().debug("Lex (right): dir:",E.yytext),"DIR";break;case 87:return E.yytext=E.yytext.replace(/^,\s*/,""),T.getLogger().debug("Lex (left):",E.yytext),"DIR";break;case 88:return E.yytext=E.yytext.replace(/^,\s*/,""),T.getLogger().debug("Lex (x):",E.yytext),"DIR";break;case 89:return E.yytext=E.yytext.replace(/^,\s*/,""),T.getLogger().debug("Lex (y):",E.yytext),"DIR";break;case 90:return E.yytext=E.yytext.replace(/^,\s*/,""),T.getLogger().debug("Lex (up):",E.yytext),"DIR";break;case 91:return E.yytext=E.yytext.replace(/^,\s*/,""),T.getLogger().debug("Lex (down):",E.yytext),"DIR";break;case 92:return E.yytext="]>",T.getLogger().debug("Lex (ARROW_DIR end):",E.yytext),this.popState(),this.popState(),"BLOCK_ARROW_END";break;case 93:return T.getLogger().debug("Lex: LINK","#"+E.yytext+"#"),15;break;case 94:return T.getLogger().debug("Lex: LINK",E.yytext),15;break;case 95:return T.getLogger().debug("Lex: LINK",E.yytext),15;break;case 96:return T.getLogger().debug("Lex: LINK",E.yytext),15;break;case 97:return T.getLogger().debug("Lex: START_LINK",E.yytext),this.pushState("LLABEL"),16;break;case 98:return T.getLogger().debug("Lex: START_LINK",E.yytext),this.pushState("LLABEL"),16;break;case 99:return T.getLogger().debug("Lex: START_LINK",E.yytext),this.pushState("LLABEL"),16;break;case 100:this.pushState("md_string");break;case 101:return T.getLogger().debug("Lex: Starting string"),this.pushState("string"),"LINK_LABEL";break;case 102:return this.popState(),T.getLogger().debug("Lex: LINK","#"+E.yytext+"#"),15;break;case 103:return this.popState(),T.getLogger().debug("Lex: LINK",E.yytext),15;break;case 104:return this.popState(),T.getLogger().debug("Lex: LINK",E.yytext),15;break;case 105:return T.getLogger().debug("Lex: COLON",E.yytext),E.yytext=E.yytext.slice(1),27;break}},"anonymous"),rules:[/^(?:block-beta\b)/,/^(?:block\s+)/,/^(?:block\n+)/,/^(?:block:)/,/^(?:[\s]+)/,/^(?:[\n]+)/,/^(?:((\u000D\u000A)|(\u000A)))/,/^(?:columns\s+auto\b)/,/^(?:columns\s+[\d]+)/,/^(?:["][`])/,/^(?:[^`"]+)/,/^(?:[`]["])/,/^(?:["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:space[:]\d+)/,/^(?:space\b)/,/^(?:default\b)/,/^(?:linkStyle\b)/,/^(?:interpolate\b)/,/^(?:classDef\s+)/,/^(?:DEFAULT\s+)/,/^(?:\w+\s+)/,/^(?:[^\n]*)/,/^(?:class\s+)/,/^(?:(\w+)+((,\s*\w+)*))/,/^(?:[^\n]*)/,/^(?:style\s+)/,/^(?:(\w+)+((,\s*\w+)*))/,/^(?:[^\n]*)/,/^(?:accTitle\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*\{\s*)/,/^(?:[\}])/,/^(?:[^\}]*)/,/^(?:end\b\s*)/,/^(?:\(\(\()/,/^(?:\)\)\))/,/^(?:[\)]\))/,/^(?:\}\})/,/^(?:\})/,/^(?:\(-)/,/^(?:-\))/,/^(?:\(\()/,/^(?:\]\])/,/^(?:\()/,/^(?:\]\))/,/^(?:\\\])/,/^(?:\/\])/,/^(?:\)\])/,/^(?:[\)])/,/^(?:\]>)/,/^(?:[\]])/,/^(?:-\))/,/^(?:\(-)/,/^(?:\)\))/,/^(?:\))/,/^(?:\(\(\()/,/^(?:\(\()/,/^(?:\{\{)/,/^(?:\{)/,/^(?:>)/,/^(?:\(\[)/,/^(?:\()/,/^(?:\[\[)/,/^(?:\[\|)/,/^(?:\[\()/,/^(?:\)\)\))/,/^(?:\[\\)/,/^(?:\[\/)/,/^(?:\[\\)/,/^(?:\[)/,/^(?:<\[)/,/^(?:[^\(\[\n\-\)\{\}\s\<\>:]+)/,/^(?:$)/,/^(?:["][`])/,/^(?:["][`])/,/^(?:[^`"]+)/,/^(?:[`]["])/,/^(?:["])/,/^(?:["])/,/^(?:[^"]+)/,/^(?:["])/,/^(?:\]>\s*\()/,/^(?:,?\s*right\s*)/,/^(?:,?\s*left\s*)/,/^(?:,?\s*x\s*)/,/^(?:,?\s*y\s*)/,/^(?:,?\s*up\s*)/,/^(?:,?\s*down\s*)/,/^(?:\)\s*)/,/^(?:\s*[xo<]?--+[-xo>]\s*)/,/^(?:\s*[xo<]?==+[=xo>]\s*)/,/^(?:\s*[xo<]?-?\.+-[xo>]?\s*)/,/^(?:\s*~~[\~]+\s*)/,/^(?:\s*[xo<]?--\s*)/,/^(?:\s*[xo<]?==\s*)/,/^(?:\s*[xo<]?-\.\s*)/,/^(?:["][`])/,/^(?:["])/,/^(?:\s*[xo<]?--+[-xo>]\s*)/,/^(?:\s*[xo<]?==+[=xo>]\s*)/,/^(?:\s*[xo<]?-?\.+-[xo>]?\s*)/,/^(?::\d+)/],conditions:{STYLE_DEFINITION:{rules:[29],inclusive:!1},STYLE_STMNT:{rules:[28],inclusive:!1},CLASSDEFID:{rules:[23],inclusive:!1},CLASSDEF:{rules:[21,22],inclusive:!1},CLASS_STYLE:{rules:[26],inclusive:!1},CLASS:{rules:[25],inclusive:!1},LLABEL:{rules:[100,101,102,103,104],inclusive:!1},ARROW_DIR:{rules:[86,87,88,89,90,91,92],inclusive:!1},BLOCK_ARROW:{rules:[77,82,85],inclusive:!1},NODE:{rules:[38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,78,81],inclusive:!1},md_string:{rules:[10,11,79,80],inclusive:!1},space:{rules:[],inclusive:!1},string:{rules:[13,14,83,84],inclusive:!1},acc_descr_multiline:{rules:[35,36],inclusive:!1},acc_descr:{rules:[33],inclusive:!1},acc_title:{rules:[31],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,7,8,9,12,15,16,17,18,19,20,24,27,30,32,34,37,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,93,94,95,96,97,98,99,105],inclusive:!0}}};return w}();v.lexer=x;function b(){this.yy={}}return o(b,"Parser"),b.prototype=v,v.Parser=b,new b}();GB.parser=GB;h1e=GB});function gJe(t){switch(V.debug("typeStr2Type",t),t){case"[]":return"square";case"()":return V.debug("we have a round"),"round";case"(())":return"circle";case">]":return"rect_left_inv_arrow";case"{}":return"diamond";case"{{}}":return"hexagon";case"([])":return"stadium";case"[[]]":return"subroutine";case"[()]":return"cylinder";case"((()))":return"doublecircle";case"[//]":return"lean_right";case"[\\\\]":return"lean_left";case"[/\\]":return"trapezoid";case"[\\/]":return"inv_trapezoid";case"<[]>":return"block_arrow";default:return"na"}}function yJe(t){switch(V.debug("typeStr2Type",t),t){case"==":return"thick";default:return"normal"}}function vJe(t){switch(t.trim()){case"--x":return"arrow_cross";case"--o":return"arrow_circle";default:return"arrow_point"}}var Yl,VB,$B,d1e,p1e,cJe,g1e,uJe,aC,hJe,fJe,dJe,pJe,y1e,UB,db,mJe,m1e,xJe,bJe,wJe,TJe,kJe,EJe,CJe,SJe,AJe,_Je,LJe,v1e,x1e=R(()=>{"use strict";gL();qs();_t();ut();rr();bi();Yl=new Map,VB=[],$B=new Map,d1e="color",p1e="fill",cJe="bgFill",g1e=",",uJe=de(),aC=new Map,hJe=o(t=>We.sanitizeText(t,uJe),"sanitizeText"),fJe=o(function(t,e=""){let r=aC.get(t);r||(r={id:t,styles:[],textStyles:[]},aC.set(t,r)),e?.split(g1e).forEach(n=>{let i=n.replace(/([^;]*);/,"$1").trim();if(RegExp(d1e).exec(n)){let s=i.replace(p1e,cJe).replace(d1e,p1e);r.textStyles.push(s)}r.styles.push(i)})},"addStyleClass"),dJe=o(function(t,e=""){let r=Yl.get(t);e!=null&&(r.styles=e.split(g1e))},"addStyle2Node"),pJe=o(function(t,e){t.split(",").forEach(function(r){let n=Yl.get(r);if(n===void 0){let i=r.trim();n={id:i,type:"na",children:[]},Yl.set(i,n)}n.classes||(n.classes=[]),n.classes.push(e)})},"setCssClass"),y1e=o((t,e)=>{let r=t.flat(),n=[];for(let i of r){if(i.label&&(i.label=hJe(i.label)),i.type==="classDef"){fJe(i.id,i.css);continue}if(i.type==="applyClass"){pJe(i.id,i?.styleClass??"");continue}if(i.type==="applyStyles"){i?.stylesStr&&dJe(i.id,i?.stylesStr);continue}if(i.type==="column-setting")e.columns=i.columns??-1;else if(i.type==="edge"){let a=($B.get(i.id)??0)+1;$B.set(i.id,a),i.id=a+"-"+i.id,VB.push(i)}else{i.label||(i.type==="composite"?i.label="":i.label=i.id);let a=Yl.get(i.id);if(a===void 0?Yl.set(i.id,i):(i.type!=="na"&&(a.type=i.type),i.label!==i.id&&(a.label=i.label)),i.children&&y1e(i.children,i),i.type==="space"){let s=i.width??1;for(let l=0;l{V.debug("Clear called"),vr(),db={id:"root",type:"composite",children:[],columns:-1},Yl=new Map([["root",db]]),UB=[],aC=new Map,VB=[],$B=new Map},"clear");o(gJe,"typeStr2Type");o(yJe,"edgeTypeStr2Type");o(vJe,"edgeStrToEdgeData");m1e=0,xJe=o(()=>(m1e++,"id-"+Math.random().toString(36).substr(2,12)+"-"+m1e),"generateId"),bJe=o(t=>{db.children=t,y1e(t,db),UB=db.children},"setHierarchy"),wJe=o(t=>{let e=Yl.get(t);return e?e.columns?e.columns:e.children?e.children.length:-1:-1},"getColumns"),TJe=o(()=>[...Yl.values()],"getBlocksFlat"),kJe=o(()=>UB||[],"getBlocks"),EJe=o(()=>VB,"getEdges"),CJe=o(t=>Yl.get(t),"getBlock"),SJe=o(t=>{Yl.set(t.id,t)},"setBlock"),AJe=o(()=>console,"getLogger"),_Je=o(function(){return aC},"getClasses"),LJe={getConfig:o(()=>Or().block,"getConfig"),typeStr2Type:gJe,edgeTypeStr2Type:yJe,edgeStrToEdgeData:vJe,getLogger:AJe,getBlocksFlat:TJe,getBlocks:kJe,getEdges:EJe,setHierarchy:bJe,getBlock:CJe,setBlock:SJe,getColumns:wJe,getClasses:_Je,clear:mJe,generateId:xJe},v1e=LJe});var sC,DJe,b1e,w1e=R(()=>{"use strict";al();sC=o((t,e)=>{let r=X1,n=r(t,"r"),i=r(t,"g"),a=r(t,"b");return Ws(n,i,a,e)},"fade"),DJe=o(t=>`.label { + font-family: ${t.fontFamily}; + color: ${t.nodeTextColor||t.textColor}; + } + .cluster-label text { + fill: ${t.titleColor}; + } + .cluster-label span,p { + color: ${t.titleColor}; + } + + + + .label text,span,p { + fill: ${t.nodeTextColor||t.textColor}; + color: ${t.nodeTextColor||t.textColor}; + } + + .node rect, + .node circle, + .node ellipse, + .node polygon, + .node path { + fill: ${t.mainBkg}; + stroke: ${t.nodeBorder}; + stroke-width: 1px; + } + .flowchart-label text { + text-anchor: middle; + } + // .flowchart-label .text-outer-tspan { + // text-anchor: middle; + // } + // .flowchart-label .text-inner-tspan { + // text-anchor: start; + // } + + .node .label { + text-align: center; + } + .node.clickable { + cursor: pointer; + } + + .arrowheadPath { + fill: ${t.arrowheadColor}; + } + + .edgePath .path { + stroke: ${t.lineColor}; + stroke-width: 2.0px; + } + + .flowchart-link { + stroke: ${t.lineColor}; + fill: none; + } + + .edgeLabel { + background-color: ${t.edgeLabelBackground}; + rect { + opacity: 0.5; + background-color: ${t.edgeLabelBackground}; + fill: ${t.edgeLabelBackground}; + } + text-align: center; + } + + /* For html labels only */ + .labelBkg { + background-color: ${sC(t.edgeLabelBackground,.5)}; + // background-color: + } + + .node .cluster { + // fill: ${sC(t.mainBkg,.5)}; + fill: ${sC(t.clusterBkg,.5)}; + stroke: ${sC(t.clusterBorder,.2)}; + box-shadow: rgba(50, 50, 93, 0.25) 0px 13px 27px -5px, rgba(0, 0, 0, 0.3) 0px 8px 16px -8px; + stroke-width: 1px; + } + + .cluster text { + fill: ${t.titleColor}; + } + + .cluster span,p { + color: ${t.titleColor}; + } + /* .cluster div { + color: ${t.titleColor}; + } */ + + div.mermaidTooltip { + position: absolute; + text-align: center; + max-width: 200px; + padding: 2px; + font-family: ${t.fontFamily}; + font-size: 12px; + background: ${t.tertiaryColor}; + border: 1px solid ${t.border2}; + border-radius: 2px; + pointer-events: none; + z-index: 100; + } + + .flowchartTitleText { + text-anchor: middle; + font-size: 18px; + fill: ${t.textColor}; + } +`,"getStyles"),b1e=DJe});function RJe(t,e){if(t===0||!Number.isInteger(t))throw new Error("Columns must be an integer !== 0.");if(e<0||!Number.isInteger(e))throw new Error("Position must be a non-negative integer."+e);if(t<0)return{px:e,py:0};if(t===1)return{px:0,py:e};let r=e%t,n=Math.floor(e/t);return{px:r,py:n}}function HB(t,e,r=0,n=0){V.debug("setBlockSizes abc95 (start)",t.id,t?.size?.x,"block width =",t?.size,"sieblingWidth",r),t?.size?.width||(t.size={width:r,height:n,x:0,y:0});let i=0,a=0;if(t.children?.length>0){for(let m of t.children)HB(m,e);let s=NJe(t);i=s.width,a=s.height,V.debug("setBlockSizes abc95 maxWidth of",t.id,":s children is ",i,a);for(let m of t.children)m.size&&(V.debug(`abc95 Setting size of children of ${t.id} id=${m.id} ${i} ${a} ${JSON.stringify(m.size)}`),m.size.width=i*(m.widthInColumns??1)+mi*((m.widthInColumns??1)-1),m.size.height=a,m.size.x=0,m.size.y=0,V.debug(`abc95 updating size of ${t.id} children child:${m.id} maxWidth:${i} maxHeight:${a}`));for(let m of t.children)HB(m,e,i,a);let l=t.columns??-1,u=0;for(let m of t.children)u+=m.widthInColumns??1;let h=t.children.length;l>0&&l0?Math.min(t.children.length,l):t.children.length;if(m>0){let g=(d-m*mi-mi)/m;V.debug("abc95 (growing to fit) width",t.id,d,t.size?.width,g);for(let y of t.children)y.size&&(y.size.width=g)}}t.size={width:d,height:p,x:0,y:0}}V.debug("setBlockSizes abc94 (done)",t.id,t?.size?.x,t?.size?.width,t?.size?.y,t?.size?.height)}function T1e(t,e){V.debug(`abc85 layout blocks (=>layoutBlocks) ${t.id} x: ${t?.size?.x} y: ${t?.size?.y} width: ${t?.size?.width}`);let r=t.columns??-1;if(V.debug("layoutBlocks columns abc95",t.id,"=>",r,t),t.children&&t.children.length>0){let n=t?.children[0]?.size?.width??0,i=t.children.length*n+(t.children.length-1)*mi;V.debug("widthOfChildren 88",i,"posX");let a=0;V.debug("abc91 block?.size?.x",t.id,t?.size?.x);let s=t?.size?.x?t?.size?.x+(-t?.size?.width/2||0):-mi,l=0;for(let u of t.children){let h=t;if(!u.size)continue;let{width:f,height:d}=u.size,{px:p,py:m}=RJe(r,a);if(m!=l&&(l=m,s=t?.size?.x?t?.size?.x+(-t?.size?.width/2||0):-mi,V.debug("New row in layout for block",t.id," and child ",u.id,l)),V.debug(`abc89 layout blocks (child) id: ${u.id} Pos: ${a} (px, py) ${p},${m} (${h?.size?.x},${h?.size?.y}) parent: ${h.id} width: ${f}${mi}`),h.size){let g=f/2;u.size.x=s+mi+g,V.debug(`abc91 layout blocks (calc) px, pyid:${u.id} startingPos=X${s} new startingPosX${u.size.x} ${g} padding=${mi} width=${f} halfWidth=${g} => x:${u.size.x} y:${u.size.y} ${u.widthInColumns} (width * (child?.w || 1)) / 2 ${f*(u?.widthInColumns??1)/2}`),s=u.size.x+g,u.size.y=h.size.y-h.size.height/2+m*(d+mi)+d/2+mi,V.debug(`abc88 layout blocks (calc) px, pyid:${u.id}startingPosX${s}${mi}${g}=>x:${u.size.x}y:${u.size.y}${u.widthInColumns}(width * (child?.w || 1)) / 2${f*(u?.widthInColumns??1)/2}`)}u.children&&T1e(u,e),a+=u?.widthInColumns??1,V.debug("abc88 columnsPos",u,a)}}V.debug(`layout blocks (<==layoutBlocks) ${t.id} x: ${t?.size?.x} y: ${t?.size?.y} width: ${t?.size?.width}`)}function k1e(t,{minX:e,minY:r,maxX:n,maxY:i}={minX:0,minY:0,maxX:0,maxY:0}){if(t.size&&t.id!=="root"){let{x:a,y:s,width:l,height:u}=t.size;a-l/2n&&(n=a+l/2),s+u/2>i&&(i=s+u/2)}if(t.children)for(let a of t.children)({minX:e,minY:r,maxX:n,maxY:i}=k1e(a,{minX:e,minY:r,maxX:n,maxY:i}));return{minX:e,minY:r,maxX:n,maxY:i}}function E1e(t){let e=t.getBlock("root");if(!e)return;HB(e,t,0,0),T1e(e,t),V.debug("getBlocks",JSON.stringify(e,null,2));let{minX:r,minY:n,maxX:i,maxY:a}=k1e(e),s=a-n,l=i-r;return{x:r,y:n,width:l,height:s}}var mi,NJe,C1e=R(()=>{"use strict";ut();_t();mi=de()?.block?.padding??8;o(RJe,"calculateBlockPosition");NJe=o(t=>{let e=0,r=0;for(let n of t.children){let{width:i,height:a,x:s,y:l}=n.size??{width:0,height:0,x:0,y:0};V.debug("getMaxChildSize abc95 child:",n.id,"width:",i,"height:",a,"x:",s,"y:",l,n.type),n.type!=="space"&&(i>e&&(e=i/(t.widthInColumns??1)),a>r&&(r=a))}return{width:e,height:r}},"getMaxChildSize");o(HB,"setBlockSizes");o(T1e,"layoutBlocks");o(k1e,"findBounds");o(E1e,"layout")});function S1e(t,e,r=!1){let n=t,i="default";(n?.classes?.length||0)>0&&(i=(n?.classes??[]).join(" ")),i=i+" flowchart-label";let a=0,s="",l;switch(n.type){case"round":a=5,s="rect";break;case"composite":a=0,s="composite",l=0;break;case"square":s="rect";break;case"diamond":s="question";break;case"hexagon":s="hexagon";break;case"block_arrow":s="block_arrow";break;case"odd":s="rect_left_inv_arrow";break;case"lean_right":s="lean_right";break;case"lean_left":s="lean_left";break;case"trapezoid":s="trapezoid";break;case"inv_trapezoid":s="inv_trapezoid";break;case"rect_left_inv_arrow":s="rect_left_inv_arrow";break;case"circle":s="circle";break;case"ellipse":s="ellipse";break;case"stadium":s="stadium";break;case"subroutine":s="subroutine";break;case"cylinder":s="cylinder";break;case"group":s="rect";break;case"doublecircle":s="doublecircle";break;default:s="rect"}let u=lm(n?.styles??[]),h=n.label,f=n.size??{width:0,height:0,x:0,y:0};return{labelStyle:u.labelStyle,shape:s,labelText:h,rx:a,ry:a,class:i,style:u.style,id:n.id,directions:n.directions,width:f.width,height:f.height,x:f.x,y:f.y,positioned:r,intersect:void 0,type:n.type,padding:l??Or()?.block?.padding??0}}async function MJe(t,e,r){let n=S1e(e,r,!1);if(n.type==="group")return;let i=await pm(t,n),a=i.node().getBBox(),s=r.getBlock(n.id);s.size={width:a.width,height:a.height,x:0,y:0,node:i},r.setBlock(s),i.remove()}async function IJe(t,e,r){let n=S1e(e,r,!0);r.getBlock(n.id).type!=="space"&&(await pm(t,n),e.intersect=n?.intersect,wv(n))}async function YB(t,e,r,n){for(let i of e)await n(t,i,r),i.children&&await YB(t,i.children,r,n)}async function A1e(t,e,r){await YB(t,e,r,MJe)}async function _1e(t,e,r){await YB(t,e,r,IJe)}async function L1e(t,e,r,n,i){let a=new lr({multigraph:!0,compound:!0});a.setGraph({rankdir:"TB",nodesep:10,ranksep:10,marginx:8,marginy:8});for(let s of r)s.size&&a.setNode(s.id,{width:s.size.width,height:s.size.height,intersect:s.intersect});for(let s of e)if(s.start&&s.end){let l=n.getBlock(s.start),u=n.getBlock(s.end);if(l?.size&&u?.size){let h=l.size,f=u.size,d=[{x:h.x,y:h.y},{x:h.x+(f.x-h.x)/2,y:h.y+(f.y-h.y)/2},{x:f.x,y:f.y}];PE(t,{v:s.start,w:s.end,name:s.id},{...s,arrowTypeEnd:s.arrowTypeEnd,arrowTypeStart:s.arrowTypeStart,points:d,classes:"edge-thickness-normal edge-pattern-solid flowchart-link LS-a1 LE-b1"},void 0,"block",a,i),s.label&&(await IE(t,{...s,label:s.label,labelStyle:"stroke: #333; stroke-width: 1.5px;fill:none;",arrowTypeEnd:s.arrowTypeEnd,arrowTypeStart:s.arrowTypeStart,points:d,classes:"edge-thickness-normal edge-pattern-solid flowchart-link LS-a1 LE-b1"}),OE({...s,x:d[1].x,y:d[1].y},{originalPath:d}))}}}var D1e=R(()=>{"use strict";ya();qs();DO();M5();xr();o(S1e,"getNodeFromBlock");o(MJe,"calculateBlockSize");o(IJe,"insertBlockPositioned");o(YB,"performOperations");o(A1e,"calculateBlockSizes");o(_1e,"insertBlocks");o(L1e,"insertEdges")});var OJe,PJe,R1e,N1e=R(()=>{"use strict";Zt();qs();LO();ut();Yn();C1e();D1e();OJe=o(function(t,e){return e.db.getClasses()},"getClasses"),PJe=o(async function(t,e,r,n){let{securityLevel:i,block:a}=Or(),s=n.db,l;i==="sandbox"&&(l=$e("#i"+e));let u=i==="sandbox"?$e(l.nodes()[0].contentDocument.body):$e("body"),h=i==="sandbox"?u.select(`[id="${e}"]`):$e(`[id="${e}"]`);LE(h,["point","circle","cross"],n.type,e);let d=s.getBlocks(),p=s.getBlocksFlat(),m=s.getEdges(),g=h.insert("g").attr("class","block");await A1e(g,d,s);let y=E1e(s);if(await _1e(g,d,s),await L1e(g,m,p,s,e),y){let v=y,x=Math.max(1,Math.round(.125*(v.width/v.height))),b=v.height+x+10,w=v.width+10,{useMaxWidth:S}=a;Sr(h,b,w,!!S),V.debug("Here Bounds",y,v),h.attr("viewBox",`${v.x-5} ${v.y-5} ${v.width+10} ${v.height+10}`)}},"draw"),R1e={draw:PJe,getClasses:OJe}});var M1e={};hr(M1e,{diagram:()=>BJe});var BJe,I1e=R(()=>{"use strict";f1e();x1e();w1e();N1e();BJe={parser:h1e,db:v1e,renderer:R1e,styles:b1e}});var WB,qB,pb,B1e,XB,cs,Wc,oC,F1e,$Je,mb,z1e,G1e,$1e,V1e,lC,If,cC=R(()=>{"use strict";WB={L:"left",R:"right",T:"top",B:"bottom"},qB={L:o(t=>`${t},${t/2} 0,${t} 0,0`,"L"),R:o(t=>`0,${t/2} ${t},0 ${t},${t}`,"R"),T:o(t=>`0,0 ${t},0 ${t/2},${t}`,"T"),B:o(t=>`${t/2},0 ${t},${t} 0,${t}`,"B")},pb={L:o((t,e)=>t-e+2,"L"),R:o((t,e)=>t-2,"R"),T:o((t,e)=>t-e+2,"T"),B:o((t,e)=>t-2,"B")},B1e=o(function(t){return cs(t)?t==="L"?"R":"L":t==="T"?"B":"T"},"getOppositeArchitectureDirection"),XB=o(function(t){let e=t;return e==="L"||e==="R"||e==="T"||e==="B"},"isArchitectureDirection"),cs=o(function(t){let e=t;return e==="L"||e==="R"},"isArchitectureDirectionX"),Wc=o(function(t){let e=t;return e==="T"||e==="B"},"isArchitectureDirectionY"),oC=o(function(t,e){let r=cs(t)&&Wc(e),n=Wc(t)&&cs(e);return r||n},"isArchitectureDirectionXY"),F1e=o(function(t){let e=t[0],r=t[1],n=cs(e)&&Wc(r),i=Wc(e)&&cs(r);return n||i},"isArchitecturePairXY"),$Je=o(function(t){return t!=="LL"&&t!=="RR"&&t!=="TT"&&t!=="BB"},"isValidArchitectureDirectionPair"),mb=o(function(t,e){let r=`${t}${e}`;return $Je(r)?r:void 0},"getArchitectureDirectionPair"),z1e=o(function([t,e],r){let n=r[0],i=r[1];return cs(n)?Wc(i)?[t+(n==="L"?-1:1),e+(i==="T"?1:-1)]:[t+(n==="L"?-1:1),e]:cs(i)?[t+(i==="L"?1:-1),e+(n==="T"?1:-1)]:[t,e+(n==="T"?1:-1)]},"shiftPositionByArchitectureDirectionPair"),G1e=o(function(t){return t==="LT"||t==="TL"?[1,1]:t==="BL"||t==="LB"?[1,-1]:t==="BR"||t==="RB"?[-1,-1]:[-1,1]},"getArchitectureDirectionXYFactors"),$1e=o(function(t){return t.type==="service"},"isArchitectureService"),V1e=o(function(t){return t.type==="junction"},"isArchitectureJunction"),lC=o(t=>t.data(),"edgeData"),If=o(t=>t.data(),"nodeData")});function Ci(t){let e=de().architecture;return e?.[t]?e[t]:U1e[t]}var U1e,nr,VJe,UJe,HJe,YJe,WJe,qJe,XJe,jJe,KJe,QJe,ZJe,JJe,eet,tet,Z0,gb=R(()=>{"use strict";sl();_t();Jk();bi();cC();U1e=mr.architecture,nr=new uf(()=>({nodes:{},groups:{},edges:[],registeredIds:{},config:U1e,dataStructures:void 0,elements:{}})),VJe=o(()=>{nr.reset(),vr()},"clear"),UJe=o(function({id:t,icon:e,in:r,title:n,iconText:i}){if(nr.records.registeredIds[t]!==void 0)throw new Error(`The service id [${t}] is already in use by another ${nr.records.registeredIds[t]}`);if(r!==void 0){if(t===r)throw new Error(`The service [${t}] cannot be placed within itself`);if(nr.records.registeredIds[r]===void 0)throw new Error(`The service [${t}]'s parent does not exist. Please make sure the parent is created before this service`);if(nr.records.registeredIds[r]==="node")throw new Error(`The service [${t}]'s parent is not a group`)}nr.records.registeredIds[t]="node",nr.records.nodes[t]={id:t,type:"service",icon:e,iconText:i,title:n,edges:[],in:r}},"addService"),HJe=o(()=>Object.values(nr.records.nodes).filter($1e),"getServices"),YJe=o(function({id:t,in:e}){nr.records.registeredIds[t]="node",nr.records.nodes[t]={id:t,type:"junction",edges:[],in:e}},"addJunction"),WJe=o(()=>Object.values(nr.records.nodes).filter(V1e),"getJunctions"),qJe=o(()=>Object.values(nr.records.nodes),"getNodes"),XJe=o(t=>nr.records.nodes[t],"getNode"),jJe=o(function({id:t,icon:e,in:r,title:n}){if(nr.records.registeredIds[t]!==void 0)throw new Error(`The group id [${t}] is already in use by another ${nr.records.registeredIds[t]}`);if(r!==void 0){if(t===r)throw new Error(`The group [${t}] cannot be placed within itself`);if(nr.records.registeredIds[r]===void 0)throw new Error(`The group [${t}]'s parent does not exist. Please make sure the parent is created before this group`);if(nr.records.registeredIds[r]==="node")throw new Error(`The group [${t}]'s parent is not a group`)}nr.records.registeredIds[t]="group",nr.records.groups[t]={id:t,icon:e,title:n,in:r}},"addGroup"),KJe=o(()=>Object.values(nr.records.groups),"getGroups"),QJe=o(function({lhsId:t,rhsId:e,lhsDir:r,rhsDir:n,lhsInto:i,rhsInto:a,lhsGroup:s,rhsGroup:l,title:u}){if(!XB(r))throw new Error(`Invalid direction given for left hand side of edge ${t}--${e}. Expected (L,R,T,B) got ${r}`);if(!XB(n))throw new Error(`Invalid direction given for right hand side of edge ${t}--${e}. Expected (L,R,T,B) got ${n}`);if(nr.records.nodes[t]===void 0&&nr.records.groups[t]===void 0)throw new Error(`The left-hand id [${t}] does not yet exist. Please create the service/group before declaring an edge to it.`);if(nr.records.nodes[e]===void 0&&nr.records.groups[t]===void 0)throw new Error(`The right-hand id [${e}] does not yet exist. Please create the service/group before declaring an edge to it.`);let h=nr.records.nodes[t].in,f=nr.records.nodes[e].in;if(s&&h&&f&&h==f)throw new Error(`The left-hand id [${t}] is modified to traverse the group boundary, but the edge does not pass through two groups.`);if(l&&h&&f&&h==f)throw new Error(`The right-hand id [${e}] is modified to traverse the group boundary, but the edge does not pass through two groups.`);let d={lhsId:t,lhsDir:r,lhsInto:i,lhsGroup:s,rhsId:e,rhsDir:n,rhsInto:a,rhsGroup:l,title:u};nr.records.edges.push(d),nr.records.nodes[t]&&nr.records.nodes[e]&&(nr.records.nodes[t].edges.push(nr.records.edges[nr.records.edges.length-1]),nr.records.nodes[e].edges.push(nr.records.edges[nr.records.edges.length-1]))},"addEdge"),ZJe=o(()=>nr.records.edges,"getEdges"),JJe=o(()=>{if(nr.records.dataStructures===void 0){let t=Object.entries(nr.records.nodes).reduce((s,[l,u])=>(s[l]=u.edges.reduce((h,f)=>{if(f.lhsId===l){let d=mb(f.lhsDir,f.rhsDir);d&&(h[d]=f.rhsId)}else{let d=mb(f.rhsDir,f.lhsDir);d&&(h[d]=f.lhsId)}return h},{}),s),{}),e=Object.keys(t)[0],r={[e]:1},n=Object.keys(t).reduce((s,l)=>l===e?s:{...s,[l]:1},{}),i=o(s=>{let l={[s]:[0,0]},u=[s];for(;u.length>0;){let h=u.shift();if(h){r[h]=1,delete n[h];let f=t[h],[d,p]=l[h];Object.entries(f).forEach(([m,g])=>{r[g]||(l[g]=z1e([d,p],m),u.push(g))})}}return l},"BFS"),a=[i(e)];for(;Object.keys(n).length>0;)a.push(i(Object.keys(n)[0]));nr.records.dataStructures={adjList:t,spatialMaps:a}}return nr.records.dataStructures},"getDataStructures"),eet=o((t,e)=>{nr.records.elements[t]=e},"setElementForId"),tet=o(t=>nr.records.elements[t],"getElementById"),Z0={clear:VJe,setDiagramTitle:nn,getDiagramTitle:Xr,setAccTitle:kr,getAccTitle:Ar,setAccDescription:_r,getAccDescription:Lr,addService:UJe,getServices:HJe,addJunction:YJe,getJunctions:WJe,getNodes:qJe,getNode:XJe,addGroup:jJe,getGroups:KJe,addEdge:QJe,getEdges:ZJe,setElementForId:eet,getElementById:tet,getDataStructures:JJe};o(Ci,"getConfigField")});var ret,H1e,Y1e=R(()=>{"use strict";Lg();ut();sx();gb();ret=o((t,e)=>{cf(t,e),t.groups.map(e.addGroup),t.services.map(r=>e.addService({...r,type:"service"})),t.junctions.map(r=>e.addJunction({...r,type:"junction"})),t.edges.map(e.addEdge)},"populateDb"),H1e={parse:o(async t=>{let e=await Fl("architecture",t);V.debug(e),ret(e,Z0)},"parse")}});var net,W1e,q1e=R(()=>{"use strict";net=o(t=>` + .edge { + stroke-width: ${t.archEdgeWidth}; + stroke: ${t.archEdgeColor}; + fill: none; + } + + .arrow { + fill: ${t.archEdgeArrowColor}; + } + + .node-bkg { + fill: none; + stroke: ${t.archGroupBorderColor}; + stroke-width: ${t.archGroupBorderWidth}; + stroke-dasharray: 8; + } + .node-icon-text { + display: flex; + align-items: center; + } + + .node-icon-text > div { + color: #fff; + margin: 1px; + height: fit-content; + text-align: center; + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + } +`,"getStyles"),W1e=net});var KB=gi((yb,jB)=>{"use strict";o(function(e,r){typeof yb=="object"&&typeof jB=="object"?jB.exports=r():typeof define=="function"&&define.amd?define([],r):typeof yb=="object"?yb.layoutBase=r():e.layoutBase=r()},"webpackUniversalModuleDefinition")(yb,function(){return function(t){var e={};function r(n){if(e[n])return e[n].exports;var i=e[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}return o(r,"__webpack_require__"),r.m=t,r.c=e,r.i=function(n){return n},r.d=function(n,i,a){r.o(n,i)||Object.defineProperty(n,i,{configurable:!1,enumerable:!0,get:a})},r.n=function(n){var i=n&&n.__esModule?o(function(){return n.default},"getDefault"):o(function(){return n},"getModuleExports");return r.d(i,"a",i),i},r.o=function(n,i){return Object.prototype.hasOwnProperty.call(n,i)},r.p="",r(r.s=28)}([function(t,e,r){"use strict";function n(){}o(n,"LayoutConstants"),n.QUALITY=1,n.DEFAULT_CREATE_BENDS_AS_NEEDED=!1,n.DEFAULT_INCREMENTAL=!1,n.DEFAULT_ANIMATION_ON_LAYOUT=!0,n.DEFAULT_ANIMATION_DURING_LAYOUT=!1,n.DEFAULT_ANIMATION_PERIOD=50,n.DEFAULT_UNIFORM_LEAF_NODE_SIZES=!1,n.DEFAULT_GRAPH_MARGIN=15,n.NODE_DIMENSIONS_INCLUDE_LABELS=!1,n.SIMPLE_NODE_SIZE=40,n.SIMPLE_NODE_HALF_SIZE=n.SIMPLE_NODE_SIZE/2,n.EMPTY_COMPOUND_NODE_SIZE=40,n.MIN_EDGE_LENGTH=1,n.WORLD_BOUNDARY=1e6,n.INITIAL_WORLD_BOUNDARY=n.WORLD_BOUNDARY/1e3,n.WORLD_CENTER_X=1200,n.WORLD_CENTER_Y=900,t.exports=n},function(t,e,r){"use strict";var n=r(2),i=r(8),a=r(9);function s(u,h,f){n.call(this,f),this.isOverlapingSourceAndTarget=!1,this.vGraphObject=f,this.bendpoints=[],this.source=u,this.target=h}o(s,"LEdge"),s.prototype=Object.create(n.prototype);for(var l in n)s[l]=n[l];s.prototype.getSource=function(){return this.source},s.prototype.getTarget=function(){return this.target},s.prototype.isInterGraph=function(){return this.isInterGraph},s.prototype.getLength=function(){return this.length},s.prototype.isOverlapingSourceAndTarget=function(){return this.isOverlapingSourceAndTarget},s.prototype.getBendpoints=function(){return this.bendpoints},s.prototype.getLca=function(){return this.lca},s.prototype.getSourceInLca=function(){return this.sourceInLca},s.prototype.getTargetInLca=function(){return this.targetInLca},s.prototype.getOtherEnd=function(u){if(this.source===u)return this.target;if(this.target===u)return this.source;throw"Node is not incident with this edge"},s.prototype.getOtherEndInGraph=function(u,h){for(var f=this.getOtherEnd(u),d=h.getGraphManager().getRoot();;){if(f.getOwner()==h)return f;if(f.getOwner()==d)break;f=f.getOwner().getParent()}return null},s.prototype.updateLength=function(){var u=new Array(4);this.isOverlapingSourceAndTarget=i.getIntersection(this.target.getRect(),this.source.getRect(),u),this.isOverlapingSourceAndTarget||(this.lengthX=u[0]-u[2],this.lengthY=u[1]-u[3],Math.abs(this.lengthX)<1&&(this.lengthX=a.sign(this.lengthX)),Math.abs(this.lengthY)<1&&(this.lengthY=a.sign(this.lengthY)),this.length=Math.sqrt(this.lengthX*this.lengthX+this.lengthY*this.lengthY))},s.prototype.updateLengthSimple=function(){this.lengthX=this.target.getCenterX()-this.source.getCenterX(),this.lengthY=this.target.getCenterY()-this.source.getCenterY(),Math.abs(this.lengthX)<1&&(this.lengthX=a.sign(this.lengthX)),Math.abs(this.lengthY)<1&&(this.lengthY=a.sign(this.lengthY)),this.length=Math.sqrt(this.lengthX*this.lengthX+this.lengthY*this.lengthY)},t.exports=s},function(t,e,r){"use strict";function n(i){this.vGraphObject=i}o(n,"LGraphObject"),t.exports=n},function(t,e,r){"use strict";var n=r(2),i=r(10),a=r(13),s=r(0),l=r(16),u=r(5);function h(d,p,m,g){m==null&&g==null&&(g=p),n.call(this,g),d.graphManager!=null&&(d=d.graphManager),this.estimatedSize=i.MIN_VALUE,this.inclusionTreeDepth=i.MAX_VALUE,this.vGraphObject=g,this.edges=[],this.graphManager=d,m!=null&&p!=null?this.rect=new a(p.x,p.y,m.width,m.height):this.rect=new a}o(h,"LNode"),h.prototype=Object.create(n.prototype);for(var f in n)h[f]=n[f];h.prototype.getEdges=function(){return this.edges},h.prototype.getChild=function(){return this.child},h.prototype.getOwner=function(){return this.owner},h.prototype.getWidth=function(){return this.rect.width},h.prototype.setWidth=function(d){this.rect.width=d},h.prototype.getHeight=function(){return this.rect.height},h.prototype.setHeight=function(d){this.rect.height=d},h.prototype.getCenterX=function(){return this.rect.x+this.rect.width/2},h.prototype.getCenterY=function(){return this.rect.y+this.rect.height/2},h.prototype.getCenter=function(){return new u(this.rect.x+this.rect.width/2,this.rect.y+this.rect.height/2)},h.prototype.getLocation=function(){return new u(this.rect.x,this.rect.y)},h.prototype.getRect=function(){return this.rect},h.prototype.getDiagonal=function(){return Math.sqrt(this.rect.width*this.rect.width+this.rect.height*this.rect.height)},h.prototype.getHalfTheDiagonal=function(){return Math.sqrt(this.rect.height*this.rect.height+this.rect.width*this.rect.width)/2},h.prototype.setRect=function(d,p){this.rect.x=d.x,this.rect.y=d.y,this.rect.width=p.width,this.rect.height=p.height},h.prototype.setCenter=function(d,p){this.rect.x=d-this.rect.width/2,this.rect.y=p-this.rect.height/2},h.prototype.setLocation=function(d,p){this.rect.x=d,this.rect.y=p},h.prototype.moveBy=function(d,p){this.rect.x+=d,this.rect.y+=p},h.prototype.getEdgeListToNode=function(d){var p=[],m,g=this;return g.edges.forEach(function(y){if(y.target==d){if(y.source!=g)throw"Incorrect edge source!";p.push(y)}}),p},h.prototype.getEdgesBetween=function(d){var p=[],m,g=this;return g.edges.forEach(function(y){if(!(y.source==g||y.target==g))throw"Incorrect edge source and/or target";(y.target==d||y.source==d)&&p.push(y)}),p},h.prototype.getNeighborsList=function(){var d=new Set,p=this;return p.edges.forEach(function(m){if(m.source==p)d.add(m.target);else{if(m.target!=p)throw"Incorrect incidency!";d.add(m.source)}}),d},h.prototype.withChildren=function(){var d=new Set,p,m;if(d.add(this),this.child!=null)for(var g=this.child.getNodes(),y=0;yp?(this.rect.x-=(this.labelWidth-p)/2,this.setWidth(this.labelWidth)):this.labelPosHorizontal=="right"&&this.setWidth(p+this.labelWidth)),this.labelHeight&&(this.labelPosVertical=="top"?(this.rect.y-=this.labelHeight,this.setHeight(m+this.labelHeight)):this.labelPosVertical=="center"&&this.labelHeight>m?(this.rect.y-=(this.labelHeight-m)/2,this.setHeight(this.labelHeight)):this.labelPosVertical=="bottom"&&this.setHeight(m+this.labelHeight))}}},h.prototype.getInclusionTreeDepth=function(){if(this.inclusionTreeDepth==i.MAX_VALUE)throw"assert failed";return this.inclusionTreeDepth},h.prototype.transform=function(d){var p=this.rect.x;p>s.WORLD_BOUNDARY?p=s.WORLD_BOUNDARY:p<-s.WORLD_BOUNDARY&&(p=-s.WORLD_BOUNDARY);var m=this.rect.y;m>s.WORLD_BOUNDARY?m=s.WORLD_BOUNDARY:m<-s.WORLD_BOUNDARY&&(m=-s.WORLD_BOUNDARY);var g=new u(p,m),y=d.inverseTransformPoint(g);this.setLocation(y.x,y.y)},h.prototype.getLeft=function(){return this.rect.x},h.prototype.getRight=function(){return this.rect.x+this.rect.width},h.prototype.getTop=function(){return this.rect.y},h.prototype.getBottom=function(){return this.rect.y+this.rect.height},h.prototype.getParent=function(){return this.owner==null?null:this.owner.getParent()},t.exports=h},function(t,e,r){"use strict";var n=r(0);function i(){}o(i,"FDLayoutConstants");for(var a in n)i[a]=n[a];i.MAX_ITERATIONS=2500,i.DEFAULT_EDGE_LENGTH=50,i.DEFAULT_SPRING_STRENGTH=.45,i.DEFAULT_REPULSION_STRENGTH=4500,i.DEFAULT_GRAVITY_STRENGTH=.4,i.DEFAULT_COMPOUND_GRAVITY_STRENGTH=1,i.DEFAULT_GRAVITY_RANGE_FACTOR=3.8,i.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR=1.5,i.DEFAULT_USE_SMART_IDEAL_EDGE_LENGTH_CALCULATION=!0,i.DEFAULT_USE_SMART_REPULSION_RANGE_CALCULATION=!0,i.DEFAULT_COOLING_FACTOR_INCREMENTAL=.3,i.COOLING_ADAPTATION_FACTOR=.33,i.ADAPTATION_LOWER_NODE_LIMIT=1e3,i.ADAPTATION_UPPER_NODE_LIMIT=5e3,i.MAX_NODE_DISPLACEMENT_INCREMENTAL=100,i.MAX_NODE_DISPLACEMENT=i.MAX_NODE_DISPLACEMENT_INCREMENTAL*3,i.MIN_REPULSION_DIST=i.DEFAULT_EDGE_LENGTH/10,i.CONVERGENCE_CHECK_PERIOD=100,i.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR=.1,i.MIN_EDGE_LENGTH=1,i.GRID_CALCULATION_CHECK_PERIOD=10,t.exports=i},function(t,e,r){"use strict";function n(i,a){i==null&&a==null?(this.x=0,this.y=0):(this.x=i,this.y=a)}o(n,"PointD"),n.prototype.getX=function(){return this.x},n.prototype.getY=function(){return this.y},n.prototype.setX=function(i){this.x=i},n.prototype.setY=function(i){this.y=i},n.prototype.getDifference=function(i){return new DimensionD(this.x-i.x,this.y-i.y)},n.prototype.getCopy=function(){return new n(this.x,this.y)},n.prototype.translate=function(i){return this.x+=i.width,this.y+=i.height,this},t.exports=n},function(t,e,r){"use strict";var n=r(2),i=r(10),a=r(0),s=r(7),l=r(3),u=r(1),h=r(13),f=r(12),d=r(11);function p(g,y,v){n.call(this,v),this.estimatedSize=i.MIN_VALUE,this.margin=a.DEFAULT_GRAPH_MARGIN,this.edges=[],this.nodes=[],this.isConnected=!1,this.parent=g,y!=null&&y instanceof s?this.graphManager=y:y!=null&&y instanceof Layout&&(this.graphManager=y.graphManager)}o(p,"LGraph"),p.prototype=Object.create(n.prototype);for(var m in n)p[m]=n[m];p.prototype.getNodes=function(){return this.nodes},p.prototype.getEdges=function(){return this.edges},p.prototype.getGraphManager=function(){return this.graphManager},p.prototype.getParent=function(){return this.parent},p.prototype.getLeft=function(){return this.left},p.prototype.getRight=function(){return this.right},p.prototype.getTop=function(){return this.top},p.prototype.getBottom=function(){return this.bottom},p.prototype.isConnected=function(){return this.isConnected},p.prototype.add=function(g,y,v){if(y==null&&v==null){var x=g;if(this.graphManager==null)throw"Graph has no graph mgr!";if(this.getNodes().indexOf(x)>-1)throw"Node already in graph!";return x.owner=this,this.getNodes().push(x),x}else{var b=g;if(!(this.getNodes().indexOf(y)>-1&&this.getNodes().indexOf(v)>-1))throw"Source or target not in graph!";if(!(y.owner==v.owner&&y.owner==this))throw"Both owners must be this graph!";return y.owner!=v.owner?null:(b.source=y,b.target=v,b.isInterGraph=!1,this.getEdges().push(b),y.edges.push(b),v!=y&&v.edges.push(b),b)}},p.prototype.remove=function(g){var y=g;if(g instanceof l){if(y==null)throw"Node is null!";if(!(y.owner!=null&&y.owner==this))throw"Owner graph is invalid!";if(this.graphManager==null)throw"Owner graph manager is invalid!";for(var v=y.edges.slice(),x,b=v.length,w=0;w-1&&E>-1))throw"Source and/or target doesn't know this edge!";x.source.edges.splice(T,1),x.target!=x.source&&x.target.edges.splice(E,1);var S=x.source.owner.getEdges().indexOf(x);if(S==-1)throw"Not in owner's edge list!";x.source.owner.getEdges().splice(S,1)}},p.prototype.updateLeftTop=function(){for(var g=i.MAX_VALUE,y=i.MAX_VALUE,v,x,b,w=this.getNodes(),S=w.length,T=0;Tv&&(g=v),y>x&&(y=x)}return g==i.MAX_VALUE?null:(w[0].getParent().paddingLeft!=null?b=w[0].getParent().paddingLeft:b=this.margin,this.left=y-b,this.top=g-b,new f(this.left,this.top))},p.prototype.updateBounds=function(g){for(var y=i.MAX_VALUE,v=-i.MAX_VALUE,x=i.MAX_VALUE,b=-i.MAX_VALUE,w,S,T,E,_,A=this.nodes,L=A.length,M=0;Mw&&(y=w),vT&&(x=T),bw&&(y=w),vT&&(x=T),b=this.nodes.length){var L=0;v.forEach(function(M){M.owner==g&&L++}),L==this.nodes.length&&(this.isConnected=!0)}},t.exports=p},function(t,e,r){"use strict";var n,i=r(1);function a(s){n=r(6),this.layout=s,this.graphs=[],this.edges=[]}o(a,"LGraphManager"),a.prototype.addRoot=function(){var s=this.layout.newGraph(),l=this.layout.newNode(null),u=this.add(s,l);return this.setRootGraph(u),this.rootGraph},a.prototype.add=function(s,l,u,h,f){if(u==null&&h==null&&f==null){if(s==null)throw"Graph is null!";if(l==null)throw"Parent node is null!";if(this.graphs.indexOf(s)>-1)throw"Graph already in this graph mgr!";if(this.graphs.push(s),s.parent!=null)throw"Already has a parent!";if(l.child!=null)throw"Already has a child!";return s.parent=l,l.child=s,s}else{f=u,h=l,u=s;var d=h.getOwner(),p=f.getOwner();if(!(d!=null&&d.getGraphManager()==this))throw"Source not in this graph mgr!";if(!(p!=null&&p.getGraphManager()==this))throw"Target not in this graph mgr!";if(d==p)return u.isInterGraph=!1,d.add(u,h,f);if(u.isInterGraph=!0,u.source=h,u.target=f,this.edges.indexOf(u)>-1)throw"Edge already in inter-graph edge list!";if(this.edges.push(u),!(u.source!=null&&u.target!=null))throw"Edge source and/or target is null!";if(!(u.source.edges.indexOf(u)==-1&&u.target.edges.indexOf(u)==-1))throw"Edge already in source and/or target incidency list!";return u.source.edges.push(u),u.target.edges.push(u),u}},a.prototype.remove=function(s){if(s instanceof n){var l=s;if(l.getGraphManager()!=this)throw"Graph not in this graph mgr";if(!(l==this.rootGraph||l.parent!=null&&l.parent.graphManager==this))throw"Invalid parent node!";var u=[];u=u.concat(l.getEdges());for(var h,f=u.length,d=0;d=s.getRight()?l[0]+=Math.min(s.getX()-a.getX(),a.getRight()-s.getRight()):s.getX()<=a.getX()&&s.getRight()>=a.getRight()&&(l[0]+=Math.min(a.getX()-s.getX(),s.getRight()-a.getRight())),a.getY()<=s.getY()&&a.getBottom()>=s.getBottom()?l[1]+=Math.min(s.getY()-a.getY(),a.getBottom()-s.getBottom()):s.getY()<=a.getY()&&s.getBottom()>=a.getBottom()&&(l[1]+=Math.min(a.getY()-s.getY(),s.getBottom()-a.getBottom()));var f=Math.abs((s.getCenterY()-a.getCenterY())/(s.getCenterX()-a.getCenterX()));s.getCenterY()===a.getCenterY()&&s.getCenterX()===a.getCenterX()&&(f=1);var d=f*l[0],p=l[1]/f;l[0]d)return l[0]=u,l[1]=m,l[2]=f,l[3]=A,!1;if(hf)return l[0]=p,l[1]=h,l[2]=E,l[3]=d,!1;if(uf?(l[0]=y,l[1]=v,k=!0):(l[0]=g,l[1]=m,k=!0):C===D&&(u>f?(l[0]=p,l[1]=m,k=!0):(l[0]=x,l[1]=v,k=!0)),-O===D?f>u?(l[2]=_,l[3]=A,I=!0):(l[2]=E,l[3]=T,I=!0):O===D&&(f>u?(l[2]=S,l[3]=T,I=!0):(l[2]=L,l[3]=A,I=!0)),k&&I)return!1;if(u>f?h>d?(P=this.getCardinalDirection(C,D,4),F=this.getCardinalDirection(O,D,2)):(P=this.getCardinalDirection(-C,D,3),F=this.getCardinalDirection(-O,D,1)):h>d?(P=this.getCardinalDirection(-C,D,1),F=this.getCardinalDirection(-O,D,3)):(P=this.getCardinalDirection(C,D,2),F=this.getCardinalDirection(O,D,4)),!k)switch(P){case 1:$=m,B=u+-w/D,l[0]=B,l[1]=$;break;case 2:B=x,$=h+b*D,l[0]=B,l[1]=$;break;case 3:$=v,B=u+w/D,l[0]=B,l[1]=$;break;case 4:B=y,$=h+-b*D,l[0]=B,l[1]=$;break}if(!I)switch(F){case 1:Y=T,z=f+-N/D,l[2]=z,l[3]=Y;break;case 2:z=L,Y=d+M*D,l[2]=z,l[3]=Y;break;case 3:Y=A,z=f+N/D,l[2]=z,l[3]=Y;break;case 4:z=_,Y=d+-M*D,l[2]=z,l[3]=Y;break}}return!1},i.getCardinalDirection=function(a,s,l){return a>s?l:1+l%4},i.getIntersection=function(a,s,l,u){if(u==null)return this.getIntersection2(a,s,l);var h=a.x,f=a.y,d=s.x,p=s.y,m=l.x,g=l.y,y=u.x,v=u.y,x=void 0,b=void 0,w=void 0,S=void 0,T=void 0,E=void 0,_=void 0,A=void 0,L=void 0;return w=p-f,T=h-d,_=d*f-h*p,S=v-g,E=m-y,A=y*g-m*v,L=w*E-S*T,L===0?null:(x=(T*A-E*_)/L,b=(S*_-w*A)/L,new n(x,b))},i.angleOfVector=function(a,s,l,u){var h=void 0;return a!==l?(h=Math.atan((u-s)/(l-a)),l=0){var v=(-m+Math.sqrt(m*m-4*p*g))/(2*p),x=(-m-Math.sqrt(m*m-4*p*g))/(2*p),b=null;return v>=0&&v<=1?[v]:x>=0&&x<=1?[x]:b}else return null},i.HALF_PI=.5*Math.PI,i.ONE_AND_HALF_PI=1.5*Math.PI,i.TWO_PI=2*Math.PI,i.THREE_PI=3*Math.PI,t.exports=i},function(t,e,r){"use strict";function n(){}o(n,"IMath"),n.sign=function(i){return i>0?1:i<0?-1:0},n.floor=function(i){return i<0?Math.ceil(i):Math.floor(i)},n.ceil=function(i){return i<0?Math.floor(i):Math.ceil(i)},t.exports=n},function(t,e,r){"use strict";function n(){}o(n,"Integer"),n.MAX_VALUE=2147483647,n.MIN_VALUE=-2147483648,t.exports=n},function(t,e,r){"use strict";var n=function(){function h(f,d){for(var p=0;p"u"?"undefined":n(a);return a==null||s!="object"&&s!="function"},t.exports=i},function(t,e,r){"use strict";function n(m){if(Array.isArray(m)){for(var g=0,y=Array(m.length);g0&&g;){for(w.push(T[0]);w.length>0&&g;){var E=w[0];w.splice(0,1),b.add(E);for(var _=E.getEdges(),x=0;x<_.length;x++){var A=_[x].getOtherEnd(E);if(S.get(E)!=A)if(!b.has(A))w.push(A),S.set(A,E);else{g=!1;break}}}if(!g)m=[];else{var L=[].concat(n(b));m.push(L);for(var x=0;x-1&&T.splice(N,1)}b=new Set,S=new Map}}return m},p.prototype.createDummyNodesForBendpoints=function(m){for(var g=[],y=m.source,v=this.graphManager.calcLowestCommonAncestor(m.source,m.target),x=0;x0){for(var v=this.edgeToDummyNodes.get(y),x=0;x=0&&g.splice(A,1);var L=S.getNeighborsList();L.forEach(function(k){if(y.indexOf(k)<0){var I=v.get(k),C=I-1;C==1&&E.push(k),v.set(k,C)}})}y=y.concat(E),(g.length==1||g.length==2)&&(x=!0,b=g[0])}return b},p.prototype.setGraphManager=function(m){this.graphManager=m},t.exports=p},function(t,e,r){"use strict";function n(){}o(n,"RandomSeed"),n.seed=1,n.x=0,n.nextDouble=function(){return n.x=Math.sin(n.seed++)*1e4,n.x-Math.floor(n.x)},t.exports=n},function(t,e,r){"use strict";var n=r(5);function i(a,s){this.lworldOrgX=0,this.lworldOrgY=0,this.ldeviceOrgX=0,this.ldeviceOrgY=0,this.lworldExtX=1,this.lworldExtY=1,this.ldeviceExtX=1,this.ldeviceExtY=1}o(i,"Transform"),i.prototype.getWorldOrgX=function(){return this.lworldOrgX},i.prototype.setWorldOrgX=function(a){this.lworldOrgX=a},i.prototype.getWorldOrgY=function(){return this.lworldOrgY},i.prototype.setWorldOrgY=function(a){this.lworldOrgY=a},i.prototype.getWorldExtX=function(){return this.lworldExtX},i.prototype.setWorldExtX=function(a){this.lworldExtX=a},i.prototype.getWorldExtY=function(){return this.lworldExtY},i.prototype.setWorldExtY=function(a){this.lworldExtY=a},i.prototype.getDeviceOrgX=function(){return this.ldeviceOrgX},i.prototype.setDeviceOrgX=function(a){this.ldeviceOrgX=a},i.prototype.getDeviceOrgY=function(){return this.ldeviceOrgY},i.prototype.setDeviceOrgY=function(a){this.ldeviceOrgY=a},i.prototype.getDeviceExtX=function(){return this.ldeviceExtX},i.prototype.setDeviceExtX=function(a){this.ldeviceExtX=a},i.prototype.getDeviceExtY=function(){return this.ldeviceExtY},i.prototype.setDeviceExtY=function(a){this.ldeviceExtY=a},i.prototype.transformX=function(a){var s=0,l=this.lworldExtX;return l!=0&&(s=this.ldeviceOrgX+(a-this.lworldOrgX)*this.ldeviceExtX/l),s},i.prototype.transformY=function(a){var s=0,l=this.lworldExtY;return l!=0&&(s=this.ldeviceOrgY+(a-this.lworldOrgY)*this.ldeviceExtY/l),s},i.prototype.inverseTransformX=function(a){var s=0,l=this.ldeviceExtX;return l!=0&&(s=this.lworldOrgX+(a-this.ldeviceOrgX)*this.lworldExtX/l),s},i.prototype.inverseTransformY=function(a){var s=0,l=this.ldeviceExtY;return l!=0&&(s=this.lworldOrgY+(a-this.ldeviceOrgY)*this.lworldExtY/l),s},i.prototype.inverseTransformPoint=function(a){var s=new n(this.inverseTransformX(a.x),this.inverseTransformY(a.y));return s},t.exports=i},function(t,e,r){"use strict";function n(d){if(Array.isArray(d)){for(var p=0,m=Array(d.length);pa.ADAPTATION_LOWER_NODE_LIMIT&&(this.coolingFactor=Math.max(this.coolingFactor*a.COOLING_ADAPTATION_FACTOR,this.coolingFactor-(d-a.ADAPTATION_LOWER_NODE_LIMIT)/(a.ADAPTATION_UPPER_NODE_LIMIT-a.ADAPTATION_LOWER_NODE_LIMIT)*this.coolingFactor*(1-a.COOLING_ADAPTATION_FACTOR))),this.maxNodeDisplacement=a.MAX_NODE_DISPLACEMENT_INCREMENTAL):(d>a.ADAPTATION_LOWER_NODE_LIMIT?this.coolingFactor=Math.max(a.COOLING_ADAPTATION_FACTOR,1-(d-a.ADAPTATION_LOWER_NODE_LIMIT)/(a.ADAPTATION_UPPER_NODE_LIMIT-a.ADAPTATION_LOWER_NODE_LIMIT)*(1-a.COOLING_ADAPTATION_FACTOR)):this.coolingFactor=1,this.initialCoolingFactor=this.coolingFactor,this.maxNodeDisplacement=a.MAX_NODE_DISPLACEMENT),this.maxIterations=Math.max(this.getAllNodes().length*5,this.maxIterations),this.displacementThresholdPerNode=3*a.DEFAULT_EDGE_LENGTH/100,this.totalDisplacementThreshold=this.displacementThresholdPerNode*this.getAllNodes().length,this.repulsionRange=this.calcRepulsionRange()},h.prototype.calcSpringForces=function(){for(var d=this.getAllEdges(),p,m=0;m0&&arguments[0]!==void 0?arguments[0]:!0,p=arguments.length>1&&arguments[1]!==void 0?arguments[1]:!1,m,g,y,v,x=this.getAllNodes(),b;if(this.useFRGridVariant)for(this.totalIterations%a.GRID_CALCULATION_CHECK_PERIOD==1&&d&&this.updateGrid(),b=new Set,m=0;mw||b>w)&&(d.gravitationForceX=-this.gravityConstant*y,d.gravitationForceY=-this.gravityConstant*v)):(w=p.getEstimatedSize()*this.compoundGravityRangeFactor,(x>w||b>w)&&(d.gravitationForceX=-this.gravityConstant*y*this.compoundGravityConstant,d.gravitationForceY=-this.gravityConstant*v*this.compoundGravityConstant))},h.prototype.isConverged=function(){var d,p=!1;return this.totalIterations>this.maxIterations/3&&(p=Math.abs(this.totalDisplacement-this.oldTotalDisplacement)<2),d=this.totalDisplacement=x.length||w>=x[0].length)){for(var S=0;Sh},"_defaultCompareFunction")}]),l}();t.exports=s},function(t,e,r){"use strict";function n(){}o(n,"SVD"),n.svd=function(i){this.U=null,this.V=null,this.s=null,this.m=0,this.n=0,this.m=i.length,this.n=i[0].length;var a=Math.min(this.m,this.n);this.s=function(it){for(var dt=[];it-- >0;)dt.push(0);return dt}(Math.min(this.m+1,this.n)),this.U=function(it){var dt=o(function lt(It){if(It.length==0)return 0;for(var mt=[],St=0;St0;)dt.push(0);return dt}(this.n),l=function(it){for(var dt=[];it-- >0;)dt.push(0);return dt}(this.m),u=!0,h=!0,f=Math.min(this.m-1,this.n),d=Math.max(0,Math.min(this.n-2,this.m)),p=0;p=0;D--)if(this.s[D]!==0){for(var P=D+1;P=0;X--){if(function(it,dt){return it&&dt}(X0;){var ue=void 0,te=void 0;for(ue=I-2;ue>=-1&&ue!==-1;ue--)if(Math.abs(s[ue])<=ce+se*(Math.abs(this.s[ue])+Math.abs(this.s[ue+1]))){s[ue]=0;break}if(ue===I-2)te=4;else{var De=void 0;for(De=I-1;De>=ue&&De!==ue;De--){var oe=(De!==I?Math.abs(s[De]):0)+(De!==ue+1?Math.abs(s[De-1]):0);if(Math.abs(this.s[De])<=ce+se*oe){this.s[De]=0;break}}De===ue?te=3:De===I-1?te=1:(te=2,ue=De)}switch(ue++,te){case 1:{var ke=s[I-2];s[I-2]=0;for(var Ie=I-2;Ie>=ue;Ie--){var Se=n.hypot(this.s[Ie],ke),Ue=this.s[Ie]/Se,Pe=ke/Se;if(this.s[Ie]=Se,Ie!==ue&&(ke=-Pe*s[Ie-1],s[Ie-1]=Ue*s[Ie-1]),h)for(var _e=0;_e=this.s[ue+1]);){var je=this.s[ue];if(this.s[ue]=this.s[ue+1],this.s[ue+1]=je,h&&ueMath.abs(a)?(s=a/i,s=Math.abs(i)*Math.sqrt(1+s*s)):a!=0?(s=i/a,s=Math.abs(a)*Math.sqrt(1+s*s)):s=0,s},t.exports=n},function(t,e,r){"use strict";var n=function(){function s(l,u){for(var h=0;h2&&arguments[2]!==void 0?arguments[2]:1,f=arguments.length>3&&arguments[3]!==void 0?arguments[3]:-1,d=arguments.length>4&&arguments[4]!==void 0?arguments[4]:-1;i(this,s),this.sequence1=l,this.sequence2=u,this.match_score=h,this.mismatch_penalty=f,this.gap_penalty=d,this.iMax=l.length+1,this.jMax=u.length+1,this.grid=new Array(this.iMax);for(var p=0;p=0;l--){var u=this.listeners[l];u.event===a&&u.callback===s&&this.listeners.splice(l,1)}},i.emit=function(a,s){for(var l=0;l{"use strict";o(function(e,r){typeof vb=="object"&&typeof QB=="object"?QB.exports=r(KB()):typeof define=="function"&&define.amd?define(["layout-base"],r):typeof vb=="object"?vb.coseBase=r(KB()):e.coseBase=r(e.layoutBase)},"webpackUniversalModuleDefinition")(vb,function(t){return(()=>{"use strict";var e={45:(a,s,l)=>{var u={};u.layoutBase=l(551),u.CoSEConstants=l(806),u.CoSEEdge=l(767),u.CoSEGraph=l(880),u.CoSEGraphManager=l(578),u.CoSELayout=l(765),u.CoSENode=l(991),u.ConstraintHandler=l(902),a.exports=u},806:(a,s,l)=>{var u=l(551).FDLayoutConstants;function h(){}o(h,"CoSEConstants");for(var f in u)h[f]=u[f];h.DEFAULT_USE_MULTI_LEVEL_SCALING=!1,h.DEFAULT_RADIAL_SEPARATION=u.DEFAULT_EDGE_LENGTH,h.DEFAULT_COMPONENT_SEPERATION=60,h.TILE=!0,h.TILING_PADDING_VERTICAL=10,h.TILING_PADDING_HORIZONTAL=10,h.TRANSFORM_ON_CONSTRAINT_HANDLING=!0,h.ENFORCE_CONSTRAINTS=!0,h.APPLY_LAYOUT=!0,h.RELAX_MOVEMENT_ON_CONSTRAINTS=!0,h.TREE_REDUCTION_ON_INCREMENTAL=!0,h.PURE_INCREMENTAL=h.DEFAULT_INCREMENTAL,a.exports=h},767:(a,s,l)=>{var u=l(551).FDLayoutEdge;function h(d,p,m){u.call(this,d,p,m)}o(h,"CoSEEdge"),h.prototype=Object.create(u.prototype);for(var f in u)h[f]=u[f];a.exports=h},880:(a,s,l)=>{var u=l(551).LGraph;function h(d,p,m){u.call(this,d,p,m)}o(h,"CoSEGraph"),h.prototype=Object.create(u.prototype);for(var f in u)h[f]=u[f];a.exports=h},578:(a,s,l)=>{var u=l(551).LGraphManager;function h(d){u.call(this,d)}o(h,"CoSEGraphManager"),h.prototype=Object.create(u.prototype);for(var f in u)h[f]=u[f];a.exports=h},765:(a,s,l)=>{var u=l(551).FDLayout,h=l(578),f=l(880),d=l(991),p=l(767),m=l(806),g=l(902),y=l(551).FDLayoutConstants,v=l(551).LayoutConstants,x=l(551).Point,b=l(551).PointD,w=l(551).DimensionD,S=l(551).Layout,T=l(551).Integer,E=l(551).IGeometry,_=l(551).LGraph,A=l(551).Transform,L=l(551).LinkedList;function M(){u.call(this),this.toBeTiled={},this.constraints={}}o(M,"CoSELayout"),M.prototype=Object.create(u.prototype);for(var N in u)M[N]=u[N];M.prototype.newGraphManager=function(){var k=new h(this);return this.graphManager=k,k},M.prototype.newGraph=function(k){return new f(null,this.graphManager,k)},M.prototype.newNode=function(k){return new d(this.graphManager,k)},M.prototype.newEdge=function(k){return new p(null,null,k)},M.prototype.initParameters=function(){u.prototype.initParameters.call(this,arguments),this.isSubLayout||(m.DEFAULT_EDGE_LENGTH<10?this.idealEdgeLength=10:this.idealEdgeLength=m.DEFAULT_EDGE_LENGTH,this.useSmartIdealEdgeLengthCalculation=m.DEFAULT_USE_SMART_IDEAL_EDGE_LENGTH_CALCULATION,this.gravityConstant=y.DEFAULT_GRAVITY_STRENGTH,this.compoundGravityConstant=y.DEFAULT_COMPOUND_GRAVITY_STRENGTH,this.gravityRangeFactor=y.DEFAULT_GRAVITY_RANGE_FACTOR,this.compoundGravityRangeFactor=y.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR,this.prunedNodesAll=[],this.growTreeIterations=0,this.afterGrowthIterations=0,this.isTreeGrowing=!1,this.isGrowthFinished=!1)},M.prototype.initSpringEmbedder=function(){u.prototype.initSpringEmbedder.call(this),this.coolingCycle=0,this.maxCoolingCycle=this.maxIterations/y.CONVERGENCE_CHECK_PERIOD,this.finalTemperature=.04,this.coolingAdjuster=1},M.prototype.layout=function(){var k=v.DEFAULT_CREATE_BENDS_AS_NEEDED;return k&&(this.createBendpoints(),this.graphManager.resetAllEdges()),this.level=0,this.classicLayout()},M.prototype.classicLayout=function(){if(this.nodesWithGravity=this.calculateNodesToApplyGravitationTo(),this.graphManager.setAllNodesToApplyGravitation(this.nodesWithGravity),this.calcNoOfChildrenForAllNodes(),this.graphManager.calcLowestCommonAncestors(),this.graphManager.calcInclusionTreeDepths(),this.graphManager.getRoot().calcEstimatedSize(),this.calcIdealEdgeLengths(),this.incremental){if(m.TREE_REDUCTION_ON_INCREMENTAL){this.reduceTrees(),this.graphManager.resetAllNodesToApplyGravitation();var I=new Set(this.getAllNodes()),C=this.nodesWithGravity.filter(function(P){return I.has(P)});this.graphManager.setAllNodesToApplyGravitation(C)}}else{var k=this.getFlatForest();if(k.length>0)this.positionNodesRadially(k);else{this.reduceTrees(),this.graphManager.resetAllNodesToApplyGravitation();var I=new Set(this.getAllNodes()),C=this.nodesWithGravity.filter(function(O){return I.has(O)});this.graphManager.setAllNodesToApplyGravitation(C),this.positionNodesRandomly()}}return Object.keys(this.constraints).length>0&&(g.handleConstraints(this),this.initConstraintVariables()),this.initSpringEmbedder(),m.APPLY_LAYOUT&&this.runSpringEmbedder(),!0},M.prototype.tick=function(){if(this.totalIterations++,this.totalIterations===this.maxIterations&&!this.isTreeGrowing&&!this.isGrowthFinished)if(this.prunedNodesAll.length>0)this.isTreeGrowing=!0;else return!0;if(this.totalIterations%y.CONVERGENCE_CHECK_PERIOD==0&&!this.isTreeGrowing&&!this.isGrowthFinished){if(this.isConverged())if(this.prunedNodesAll.length>0)this.isTreeGrowing=!0;else return!0;this.coolingCycle++,this.layoutQuality==0?this.coolingAdjuster=this.coolingCycle:this.layoutQuality==1&&(this.coolingAdjuster=this.coolingCycle/3),this.coolingFactor=Math.max(this.initialCoolingFactor-Math.pow(this.coolingCycle,Math.log(100*(this.initialCoolingFactor-this.finalTemperature))/Math.log(this.maxCoolingCycle))/100*this.coolingAdjuster,this.finalTemperature),this.animationPeriod=Math.ceil(this.initialAnimationPeriod*Math.sqrt(this.coolingFactor))}if(this.isTreeGrowing){if(this.growTreeIterations%10==0)if(this.prunedNodesAll.length>0){this.graphManager.updateBounds(),this.updateGrid(),this.growTree(this.prunedNodesAll),this.graphManager.resetAllNodesToApplyGravitation();var k=new Set(this.getAllNodes()),I=this.nodesWithGravity.filter(function(D){return k.has(D)});this.graphManager.setAllNodesToApplyGravitation(I),this.graphManager.updateBounds(),this.updateGrid(),m.PURE_INCREMENTAL?this.coolingFactor=y.DEFAULT_COOLING_FACTOR_INCREMENTAL/2:this.coolingFactor=y.DEFAULT_COOLING_FACTOR_INCREMENTAL}else this.isTreeGrowing=!1,this.isGrowthFinished=!0;this.growTreeIterations++}if(this.isGrowthFinished){if(this.isConverged())return!0;this.afterGrowthIterations%10==0&&(this.graphManager.updateBounds(),this.updateGrid()),m.PURE_INCREMENTAL?this.coolingFactor=y.DEFAULT_COOLING_FACTOR_INCREMENTAL/2*((100-this.afterGrowthIterations)/100):this.coolingFactor=y.DEFAULT_COOLING_FACTOR_INCREMENTAL*((100-this.afterGrowthIterations)/100),this.afterGrowthIterations++}var C=!this.isTreeGrowing&&!this.isGrowthFinished,O=this.growTreeIterations%10==1&&this.isTreeGrowing||this.afterGrowthIterations%10==1&&this.isGrowthFinished;return this.totalDisplacement=0,this.graphManager.updateBounds(),this.calcSpringForces(),this.calcRepulsionForces(C,O),this.calcGravitationalForces(),this.moveNodes(),this.animate(),!1},M.prototype.getPositionsData=function(){for(var k=this.graphManager.getAllNodes(),I={},C=0;C0&&this.updateDisplacements();for(var C=0;C0&&(O.fixedNodeWeight=P)}}if(this.constraints.relativePlacementConstraint){var F=new Map,B=new Map;if(this.dummyToNodeForVerticalAlignment=new Map,this.dummyToNodeForHorizontalAlignment=new Map,this.fixedNodesOnHorizontal=new Set,this.fixedNodesOnVertical=new Set,this.fixedNodeSet.forEach(function(J){k.fixedNodesOnHorizontal.add(J),k.fixedNodesOnVertical.add(J)}),this.constraints.alignmentConstraint){if(this.constraints.alignmentConstraint.vertical)for(var $=this.constraints.alignmentConstraint.vertical,C=0;C<$.length;C++)this.dummyToNodeForVerticalAlignment.set("dummy"+C,[]),$[C].forEach(function(Z){F.set(Z,"dummy"+C),k.dummyToNodeForVerticalAlignment.get("dummy"+C).push(Z),k.fixedNodeSet.has(Z)&&k.fixedNodesOnHorizontal.add("dummy"+C)});if(this.constraints.alignmentConstraint.horizontal)for(var z=this.constraints.alignmentConstraint.horizontal,C=0;C=2*J.length/3;q--)Z=Math.floor(Math.random()*(q+1)),H=J[q],J[q]=J[Z],J[Z]=H;return J},this.nodesInRelativeHorizontal=[],this.nodesInRelativeVertical=[],this.nodeToRelativeConstraintMapHorizontal=new Map,this.nodeToRelativeConstraintMapVertical=new Map,this.nodeToTempPositionMapHorizontal=new Map,this.nodeToTempPositionMapVertical=new Map,this.constraints.relativePlacementConstraint.forEach(function(J){if(J.left){var Z=F.has(J.left)?F.get(J.left):J.left,H=F.has(J.right)?F.get(J.right):J.right;k.nodesInRelativeHorizontal.includes(Z)||(k.nodesInRelativeHorizontal.push(Z),k.nodeToRelativeConstraintMapHorizontal.set(Z,[]),k.dummyToNodeForVerticalAlignment.has(Z)?k.nodeToTempPositionMapHorizontal.set(Z,k.idToNodeMap.get(k.dummyToNodeForVerticalAlignment.get(Z)[0]).getCenterX()):k.nodeToTempPositionMapHorizontal.set(Z,k.idToNodeMap.get(Z).getCenterX())),k.nodesInRelativeHorizontal.includes(H)||(k.nodesInRelativeHorizontal.push(H),k.nodeToRelativeConstraintMapHorizontal.set(H,[]),k.dummyToNodeForVerticalAlignment.has(H)?k.nodeToTempPositionMapHorizontal.set(H,k.idToNodeMap.get(k.dummyToNodeForVerticalAlignment.get(H)[0]).getCenterX()):k.nodeToTempPositionMapHorizontal.set(H,k.idToNodeMap.get(H).getCenterX())),k.nodeToRelativeConstraintMapHorizontal.get(Z).push({right:H,gap:J.gap}),k.nodeToRelativeConstraintMapHorizontal.get(H).push({left:Z,gap:J.gap})}else{var q=B.has(J.top)?B.get(J.top):J.top,K=B.has(J.bottom)?B.get(J.bottom):J.bottom;k.nodesInRelativeVertical.includes(q)||(k.nodesInRelativeVertical.push(q),k.nodeToRelativeConstraintMapVertical.set(q,[]),k.dummyToNodeForHorizontalAlignment.has(q)?k.nodeToTempPositionMapVertical.set(q,k.idToNodeMap.get(k.dummyToNodeForHorizontalAlignment.get(q)[0]).getCenterY()):k.nodeToTempPositionMapVertical.set(q,k.idToNodeMap.get(q).getCenterY())),k.nodesInRelativeVertical.includes(K)||(k.nodesInRelativeVertical.push(K),k.nodeToRelativeConstraintMapVertical.set(K,[]),k.dummyToNodeForHorizontalAlignment.has(K)?k.nodeToTempPositionMapVertical.set(K,k.idToNodeMap.get(k.dummyToNodeForHorizontalAlignment.get(K)[0]).getCenterY()):k.nodeToTempPositionMapVertical.set(K,k.idToNodeMap.get(K).getCenterY())),k.nodeToRelativeConstraintMapVertical.get(q).push({bottom:K,gap:J.gap}),k.nodeToRelativeConstraintMapVertical.get(K).push({top:q,gap:J.gap})}});else{var Y=new Map,Q=new Map;this.constraints.relativePlacementConstraint.forEach(function(J){if(J.left){var Z=F.has(J.left)?F.get(J.left):J.left,H=F.has(J.right)?F.get(J.right):J.right;Y.has(Z)?Y.get(Z).push(H):Y.set(Z,[H]),Y.has(H)?Y.get(H).push(Z):Y.set(H,[Z])}else{var q=B.has(J.top)?B.get(J.top):J.top,K=B.has(J.bottom)?B.get(J.bottom):J.bottom;Q.has(q)?Q.get(q).push(K):Q.set(q,[K]),Q.has(K)?Q.get(K).push(q):Q.set(K,[q])}});var X=o(function(Z,H){var q=[],K=[],se=new L,ce=new Set,ue=0;return Z.forEach(function(te,De){if(!ce.has(De)){q[ue]=[],K[ue]=!1;var oe=De;for(se.push(oe),ce.add(oe),q[ue].push(oe);se.length!=0;){oe=se.shift(),H.has(oe)&&(K[ue]=!0);var ke=Z.get(oe);ke.forEach(function(Ie){ce.has(Ie)||(se.push(Ie),ce.add(Ie),q[ue].push(Ie))})}ue++}}),{components:q,isFixed:K}},"constructComponents"),ie=X(Y,k.fixedNodesOnHorizontal);this.componentsOnHorizontal=ie.components,this.fixedComponentsOnHorizontal=ie.isFixed;var j=X(Q,k.fixedNodesOnVertical);this.componentsOnVertical=j.components,this.fixedComponentsOnVertical=j.isFixed}}},M.prototype.updateDisplacements=function(){var k=this;if(this.constraints.fixedNodeConstraint&&this.constraints.fixedNodeConstraint.forEach(function(j){var J=k.idToNodeMap.get(j.nodeId);J.displacementX=0,J.displacementY=0}),this.constraints.alignmentConstraint){if(this.constraints.alignmentConstraint.vertical)for(var I=this.constraints.alignmentConstraint.vertical,C=0;C1){var B;for(B=0;BO&&(O=Math.floor(F.y)),P=Math.floor(F.x+m.DEFAULT_COMPONENT_SEPERATION)}this.transform(new b(v.WORLD_CENTER_X-F.x/2,v.WORLD_CENTER_Y-F.y/2))},M.radialLayout=function(k,I,C){var O=Math.max(this.maxDiagonalInTree(k),m.DEFAULT_RADIAL_SEPARATION);M.branchRadialLayout(I,null,0,359,0,O);var D=_.calculateBounds(k),P=new A;P.setDeviceOrgX(D.getMinX()),P.setDeviceOrgY(D.getMinY()),P.setWorldOrgX(C.x),P.setWorldOrgY(C.y);for(var F=0;F1;){var q=H[0];H.splice(0,1);var K=X.indexOf(q);K>=0&&X.splice(K,1),J--,ie--}I!=null?Z=(X.indexOf(H[0])+1)%J:Z=0;for(var se=Math.abs(O-C)/ie,ce=Z;j!=ie;ce=++ce%J){var ue=X[ce].getOtherEnd(k);if(ue!=I){var te=(C+j*se)%360,De=(te+se)%360;M.branchRadialLayout(ue,k,te,De,D+P,P),j++}}},M.maxDiagonalInTree=function(k){for(var I=T.MIN_VALUE,C=0;CI&&(I=D)}return I},M.prototype.calcRepulsionRange=function(){return 2*(this.level+1)*this.idealEdgeLength},M.prototype.groupZeroDegreeMembers=function(){var k=this,I={};this.memberGroups={},this.idToDummyNode={};for(var C=[],O=this.graphManager.getAllNodes(),D=0;D"u"&&(I[B]=[]),I[B]=I[B].concat(P)}Object.keys(I).forEach(function($){if(I[$].length>1){var z="DummyCompound_"+$;k.memberGroups[z]=I[$];var Y=I[$][0].getParent(),Q=new d(k.graphManager);Q.id=z,Q.paddingLeft=Y.paddingLeft||0,Q.paddingRight=Y.paddingRight||0,Q.paddingBottom=Y.paddingBottom||0,Q.paddingTop=Y.paddingTop||0,k.idToDummyNode[z]=Q;var X=k.getGraphManager().add(k.newGraph(),Q),ie=Y.getChild();ie.add(Q);for(var j=0;jD?(O.rect.x-=(O.labelWidth-D)/2,O.setWidth(O.labelWidth),O.labelMarginLeft=(O.labelWidth-D)/2):O.labelPosHorizontal=="right"&&O.setWidth(D+O.labelWidth)),O.labelHeight&&(O.labelPosVertical=="top"?(O.rect.y-=O.labelHeight,O.setHeight(P+O.labelHeight),O.labelMarginTop=O.labelHeight):O.labelPosVertical=="center"&&O.labelHeight>P?(O.rect.y-=(O.labelHeight-P)/2,O.setHeight(O.labelHeight),O.labelMarginTop=(O.labelHeight-P)/2):O.labelPosVertical=="bottom"&&O.setHeight(P+O.labelHeight))}})},M.prototype.repopulateCompounds=function(){for(var k=this.compoundOrder.length-1;k>=0;k--){var I=this.compoundOrder[k],C=I.id,O=I.paddingLeft,D=I.paddingTop,P=I.labelMarginLeft,F=I.labelMarginTop;this.adjustLocations(this.tiledMemberPack[C],I.rect.x,I.rect.y,O,D,P,F)}},M.prototype.repopulateZeroDegreeMembers=function(){var k=this,I=this.tiledZeroDegreePack;Object.keys(I).forEach(function(C){var O=k.idToDummyNode[C],D=O.paddingLeft,P=O.paddingTop,F=O.labelMarginLeft,B=O.labelMarginTop;k.adjustLocations(I[C],O.rect.x,O.rect.y,D,P,F,B)})},M.prototype.getToBeTiled=function(k){var I=k.id;if(this.toBeTiled[I]!=null)return this.toBeTiled[I];var C=k.getChild();if(C==null)return this.toBeTiled[I]=!1,!1;for(var O=C.getNodes(),D=0;D0)return this.toBeTiled[I]=!1,!1;if(P.getChild()==null){this.toBeTiled[P.id]=!1;continue}if(!this.getToBeTiled(P))return this.toBeTiled[I]=!1,!1}return this.toBeTiled[I]=!0,!0},M.prototype.getNodeDegree=function(k){for(var I=k.id,C=k.getEdges(),O=0,D=0;DY&&(Y=X.rect.height)}C+=Y+k.verticalPadding}},M.prototype.tileCompoundMembers=function(k,I){var C=this;this.tiledMemberPack=[],Object.keys(k).forEach(function(O){var D=I[O];if(C.tiledMemberPack[O]=C.tileNodes(k[O],D.paddingLeft+D.paddingRight),D.rect.width=C.tiledMemberPack[O].width,D.rect.height=C.tiledMemberPack[O].height,D.setCenter(C.tiledMemberPack[O].centerX,C.tiledMemberPack[O].centerY),D.labelMarginLeft=0,D.labelMarginTop=0,m.NODE_DIMENSIONS_INCLUDE_LABELS){var P=D.rect.width,F=D.rect.height;D.labelWidth&&(D.labelPosHorizontal=="left"?(D.rect.x-=D.labelWidth,D.setWidth(P+D.labelWidth),D.labelMarginLeft=D.labelWidth):D.labelPosHorizontal=="center"&&D.labelWidth>P?(D.rect.x-=(D.labelWidth-P)/2,D.setWidth(D.labelWidth),D.labelMarginLeft=(D.labelWidth-P)/2):D.labelPosHorizontal=="right"&&D.setWidth(P+D.labelWidth)),D.labelHeight&&(D.labelPosVertical=="top"?(D.rect.y-=D.labelHeight,D.setHeight(F+D.labelHeight),D.labelMarginTop=D.labelHeight):D.labelPosVertical=="center"&&D.labelHeight>F?(D.rect.y-=(D.labelHeight-F)/2,D.setHeight(D.labelHeight),D.labelMarginTop=(D.labelHeight-F)/2):D.labelPosVertical=="bottom"&&D.setHeight(F+D.labelHeight))}})},M.prototype.tileNodes=function(k,I){var C=this.tileNodesByFavoringDim(k,I,!0),O=this.tileNodesByFavoringDim(k,I,!1),D=this.getOrgRatio(C),P=this.getOrgRatio(O),F;return PB&&(B=j.getWidth())});var $=P/D,z=F/D,Y=Math.pow(C-O,2)+4*($+O)*(z+C)*D,Q=(O-C+Math.sqrt(Y))/(2*($+O)),X;I?(X=Math.ceil(Q),X==Q&&X++):X=Math.floor(Q);var ie=X*($+O)-O;return B>ie&&(ie=B),ie+=O*2,ie},M.prototype.tileNodesByFavoringDim=function(k,I,C){var O=m.TILING_PADDING_VERTICAL,D=m.TILING_PADDING_HORIZONTAL,P=m.TILING_COMPARE_BY,F={rows:[],rowWidth:[],rowHeight:[],width:0,height:I,verticalPadding:O,horizontalPadding:D,centerX:0,centerY:0};P&&(F.idealRowWidth=this.calcIdealRowWidth(k,C));var B=o(function(J){return J.rect.width*J.rect.height},"getNodeArea"),$=o(function(J,Z){return B(Z)-B(J)},"areaCompareFcn");k.sort(function(j,J){var Z=$;return F.idealRowWidth?(Z=P,Z(j.id,J.id)):Z(j,J)});for(var z=0,Y=0,Q=0;Q0&&(F+=k.horizontalPadding),k.rowWidth[C]=F,k.width0&&(B+=k.verticalPadding);var $=0;B>k.rowHeight[C]&&($=k.rowHeight[C],k.rowHeight[C]=B,$=k.rowHeight[C]-$),k.height+=$,k.rows[C].push(I)},M.prototype.getShortestRowIndex=function(k){for(var I=-1,C=Number.MAX_VALUE,O=0;OC&&(I=O,C=k.rowWidth[O]);return I},M.prototype.canAddHorizontal=function(k,I,C){if(k.idealRowWidth){var O=k.rows.length-1,D=k.rowWidth[O];return D+I+k.horizontalPadding<=k.idealRowWidth}var P=this.getShortestRowIndex(k);if(P<0)return!0;var F=k.rowWidth[P];if(F+k.horizontalPadding+I<=k.width)return!0;var B=0;k.rowHeight[P]0&&(B=C+k.verticalPadding-k.rowHeight[P]);var $;k.width-F>=I+k.horizontalPadding?$=(k.height+B)/(F+I+k.horizontalPadding):$=(k.height+B)/k.width,B=C+k.verticalPadding;var z;return k.widthP&&I!=C){O.splice(-1,1),k.rows[C].push(D),k.rowWidth[I]=k.rowWidth[I]-P,k.rowWidth[C]=k.rowWidth[C]+P,k.width=k.rowWidth[instance.getLongestRowIndex(k)];for(var F=Number.MIN_VALUE,B=0;BF&&(F=O[B].height);I>0&&(F+=k.verticalPadding);var $=k.rowHeight[I]+k.rowHeight[C];k.rowHeight[I]=F,k.rowHeight[C]0)for(var ie=D;ie<=P;ie++)X[0]+=this.grid[ie][F-1].length+this.grid[ie][F].length-1;if(P0)for(var ie=F;ie<=B;ie++)X[3]+=this.grid[D-1][ie].length+this.grid[D][ie].length-1;for(var j=T.MAX_VALUE,J,Z,H=0;H{var u=l(551).FDLayoutNode,h=l(551).IMath;function f(p,m,g,y){u.call(this,p,m,g,y)}o(f,"CoSENode"),f.prototype=Object.create(u.prototype);for(var d in u)f[d]=u[d];f.prototype.calculateDisplacement=function(){var p=this.graphManager.getLayout();this.getChild()!=null&&this.fixedNodeWeight?(this.displacementX+=p.coolingFactor*(this.springForceX+this.repulsionForceX+this.gravitationForceX)/this.fixedNodeWeight,this.displacementY+=p.coolingFactor*(this.springForceY+this.repulsionForceY+this.gravitationForceY)/this.fixedNodeWeight):(this.displacementX+=p.coolingFactor*(this.springForceX+this.repulsionForceX+this.gravitationForceX)/this.noOfChildren,this.displacementY+=p.coolingFactor*(this.springForceY+this.repulsionForceY+this.gravitationForceY)/this.noOfChildren),Math.abs(this.displacementX)>p.coolingFactor*p.maxNodeDisplacement&&(this.displacementX=p.coolingFactor*p.maxNodeDisplacement*h.sign(this.displacementX)),Math.abs(this.displacementY)>p.coolingFactor*p.maxNodeDisplacement&&(this.displacementY=p.coolingFactor*p.maxNodeDisplacement*h.sign(this.displacementY)),this.child&&this.child.getNodes().length>0&&this.propogateDisplacementToChildren(this.displacementX,this.displacementY)},f.prototype.propogateDisplacementToChildren=function(p,m){for(var g=this.getChild().getNodes(),y,v=0;v{function u(g){if(Array.isArray(g)){for(var y=0,v=Array(g.length);y0){var Je=0;Ye.forEach(function(je){we=="horizontal"?(ye.set(je,x.has(je)?b[x.get(je)]:Ce.get(je)),Je+=ye.get(je)):(ye.set(je,x.has(je)?w[x.get(je)]:Ce.get(je)),Je+=ye.get(je))}),Je=Je/Ye.length,tt.forEach(function(je){Te.has(je)||ye.set(je,Je)})}else{var Ve=0;tt.forEach(function(je){we=="horizontal"?Ve+=x.has(je)?b[x.get(je)]:Ce.get(je):Ve+=x.has(je)?w[x.get(je)]:Ce.get(je)}),Ve=Ve/tt.length,tt.forEach(function(je){ye.set(je,Ve)})}});for(var Ze=o(function(){var Ye=ze.shift(),Je=ae.get(Ye);Je.forEach(function(Ve){if(ye.get(Ve.id)je&&(je=mt),Stkt&&(kt=St)}}catch(Qn){xt=!0,it=Qn}finally{try{!at&&dt.return&&dt.return()}finally{if(xt)throw it}}var gr=(Je+je)/2-(Ve+kt)/2,xn=!0,jt=!1,rn=void 0;try{for(var Er=tt[Symbol.iterator](),Kn;!(xn=(Kn=Er.next()).done);xn=!0){var hn=Kn.value;ye.set(hn,ye.get(hn)+gr)}}catch(Qn){jt=!0,rn=Qn}finally{try{!xn&&Er.return&&Er.return()}finally{if(jt)throw rn}}})}return ye},"findAppropriatePositionForRelativePlacement"),N=o(function(ae){var we=0,Te=0,Ce=0,Ae=0;if(ae.forEach(function(He){He.left?b[x.get(He.left)]-b[x.get(He.right)]>=0?we++:Te++:w[x.get(He.top)]-w[x.get(He.bottom)]>=0?Ce++:Ae++}),we>Te&&Ce>Ae)for(var Ge=0;GeTe)for(var Me=0;MeAe)for(var ye=0;ye1)y.fixedNodeConstraint.forEach(function(ne,ae){O[ae]=[ne.position.x,ne.position.y],D[ae]=[b[x.get(ne.nodeId)],w[x.get(ne.nodeId)]]}),P=!0;else if(y.alignmentConstraint)(function(){var ne=0;if(y.alignmentConstraint.vertical){for(var ae=y.alignmentConstraint.vertical,we=o(function(ye){var He=new Set;ae[ye].forEach(function(gt){He.add(gt)});var ze=new Set([].concat(u(He)).filter(function(gt){return B.has(gt)})),Ze=void 0;ze.size>0?Ze=b[x.get(ze.values().next().value)]:Ze=L(He).x,ae[ye].forEach(function(gt){O[ne]=[Ze,w[x.get(gt)]],D[ne]=[b[x.get(gt)],w[x.get(gt)]],ne++})},"_loop2"),Te=0;Te0?Ze=b[x.get(ze.values().next().value)]:Ze=L(He).y,Ce[ye].forEach(function(gt){O[ne]=[b[x.get(gt)],Ze],D[ne]=[b[x.get(gt)],w[x.get(gt)]],ne++})},"_loop3"),Ge=0;GeQ&&(Q=Y[ie].length,X=ie);if(Q0){var Ue={x:0,y:0};y.fixedNodeConstraint.forEach(function(ne,ae){var we={x:b[x.get(ne.nodeId)],y:w[x.get(ne.nodeId)]},Te=ne.position,Ce=A(Te,we);Ue.x+=Ce.x,Ue.y+=Ce.y}),Ue.x/=y.fixedNodeConstraint.length,Ue.y/=y.fixedNodeConstraint.length,b.forEach(function(ne,ae){b[ae]+=Ue.x}),w.forEach(function(ne,ae){w[ae]+=Ue.y}),y.fixedNodeConstraint.forEach(function(ne){b[x.get(ne.nodeId)]=ne.position.x,w[x.get(ne.nodeId)]=ne.position.y})}if(y.alignmentConstraint){if(y.alignmentConstraint.vertical)for(var Pe=y.alignmentConstraint.vertical,_e=o(function(ae){var we=new Set;Pe[ae].forEach(function(Ae){we.add(Ae)});var Te=new Set([].concat(u(we)).filter(function(Ae){return B.has(Ae)})),Ce=void 0;Te.size>0?Ce=b[x.get(Te.values().next().value)]:Ce=L(we).x,we.forEach(function(Ae){B.has(Ae)||(b[x.get(Ae)]=Ce)})},"_loop4"),me=0;me0?Ce=w[x.get(Te.values().next().value)]:Ce=L(we).y,we.forEach(function(Ae){B.has(Ae)||(w[x.get(Ae)]=Ce)})},"_loop5"),ge=0;ge{a.exports=t}},r={};function n(a){var s=r[a];if(s!==void 0)return s.exports;var l=r[a]={exports:{}};return e[a](l,l.exports,n),l.exports}o(n,"__webpack_require__");var i=n(45);return i})()})});var X1e=gi((xb,JB)=>{"use strict";o(function(e,r){typeof xb=="object"&&typeof JB=="object"?JB.exports=r(ZB()):typeof define=="function"&&define.amd?define(["cose-base"],r):typeof xb=="object"?xb.cytoscapeFcose=r(ZB()):e.cytoscapeFcose=r(e.coseBase)},"webpackUniversalModuleDefinition")(xb,function(t){return(()=>{"use strict";var e={658:a=>{a.exports=Object.assign!=null?Object.assign.bind(Object):function(s){for(var l=arguments.length,u=Array(l>1?l-1:0),h=1;h{var u=function(){function d(p,m){var g=[],y=!0,v=!1,x=void 0;try{for(var b=p[Symbol.iterator](),w;!(y=(w=b.next()).done)&&(g.push(w.value),!(m&&g.length===m));y=!0);}catch(S){v=!0,x=S}finally{try{!y&&b.return&&b.return()}finally{if(v)throw x}}return g}return o(d,"sliceIterator"),function(p,m){if(Array.isArray(p))return p;if(Symbol.iterator in Object(p))return d(p,m);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),h=l(140).layoutBase.LinkedList,f={};f.getTopMostNodes=function(d){for(var p={},m=0;m0&&P.merge(z)});for(var F=0;F1){w=x[0],S=w.connectedEdges().length,x.forEach(function(D){D.connectedEdges().length0&&g.set("dummy"+(g.size+1),_),A},f.relocateComponent=function(d,p,m){if(!m.fixedNodeConstraint){var g=Number.POSITIVE_INFINITY,y=Number.NEGATIVE_INFINITY,v=Number.POSITIVE_INFINITY,x=Number.NEGATIVE_INFINITY;if(m.quality=="draft"){var b=!0,w=!1,S=void 0;try{for(var T=p.nodeIndexes[Symbol.iterator](),E;!(b=(E=T.next()).done);b=!0){var _=E.value,A=u(_,2),L=A[0],M=A[1],N=m.cy.getElementById(L);if(N){var k=N.boundingBox(),I=p.xCoords[M]-k.w/2,C=p.xCoords[M]+k.w/2,O=p.yCoords[M]-k.h/2,D=p.yCoords[M]+k.h/2;Iy&&(y=C),Ox&&(x=D)}}}catch(z){w=!0,S=z}finally{try{!b&&T.return&&T.return()}finally{if(w)throw S}}var P=d.x-(y+g)/2,F=d.y-(x+v)/2;p.xCoords=p.xCoords.map(function(z){return z+P}),p.yCoords=p.yCoords.map(function(z){return z+F})}else{Object.keys(p).forEach(function(z){var Y=p[z],Q=Y.getRect().x,X=Y.getRect().x+Y.getRect().width,ie=Y.getRect().y,j=Y.getRect().y+Y.getRect().height;Qy&&(y=X),iex&&(x=j)});var B=d.x-(y+g)/2,$=d.y-(x+v)/2;Object.keys(p).forEach(function(z){var Y=p[z];Y.setCenter(Y.getCenterX()+B,Y.getCenterY()+$)})}}},f.calcBoundingBox=function(d,p,m,g){for(var y=Number.MAX_SAFE_INTEGER,v=Number.MIN_SAFE_INTEGER,x=Number.MAX_SAFE_INTEGER,b=Number.MIN_SAFE_INTEGER,w=void 0,S=void 0,T=void 0,E=void 0,_=d.descendants().not(":parent"),A=_.length,L=0;Lw&&(y=w),vT&&(x=T),b{var u=l(548),h=l(140).CoSELayout,f=l(140).CoSENode,d=l(140).layoutBase.PointD,p=l(140).layoutBase.DimensionD,m=l(140).layoutBase.LayoutConstants,g=l(140).layoutBase.FDLayoutConstants,y=l(140).CoSEConstants,v=o(function(b,w){var S=b.cy,T=b.eles,E=T.nodes(),_=T.edges(),A=void 0,L=void 0,M=void 0,N={};b.randomize&&(A=w.nodeIndexes,L=w.xCoords,M=w.yCoords);var k=o(function(z){return typeof z=="function"},"isFn"),I=o(function(z,Y){return k(z)?z(Y):z},"optFn"),C=u.calcParentsWithoutChildren(S,T),O=o(function $(z,Y,Q,X){for(var ie=Y.length,j=0;j0){var se=void 0;se=Q.getGraphManager().add(Q.newGraph(),H),$(se,Z,Q,X)}}},"processChildrenList"),D=o(function(z,Y,Q){for(var X=0,ie=0,j=0;j0?y.DEFAULT_EDGE_LENGTH=g.DEFAULT_EDGE_LENGTH=X/ie:k(b.idealEdgeLength)?y.DEFAULT_EDGE_LENGTH=g.DEFAULT_EDGE_LENGTH=50:y.DEFAULT_EDGE_LENGTH=g.DEFAULT_EDGE_LENGTH=b.idealEdgeLength,y.MIN_REPULSION_DIST=g.MIN_REPULSION_DIST=g.DEFAULT_EDGE_LENGTH/10,y.DEFAULT_RADIAL_SEPARATION=g.DEFAULT_EDGE_LENGTH)},"processEdges"),P=o(function(z,Y){Y.fixedNodeConstraint&&(z.constraints.fixedNodeConstraint=Y.fixedNodeConstraint),Y.alignmentConstraint&&(z.constraints.alignmentConstraint=Y.alignmentConstraint),Y.relativePlacementConstraint&&(z.constraints.relativePlacementConstraint=Y.relativePlacementConstraint)},"processConstraints");b.nestingFactor!=null&&(y.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR=g.PER_LEVEL_IDEAL_EDGE_LENGTH_FACTOR=b.nestingFactor),b.gravity!=null&&(y.DEFAULT_GRAVITY_STRENGTH=g.DEFAULT_GRAVITY_STRENGTH=b.gravity),b.numIter!=null&&(y.MAX_ITERATIONS=g.MAX_ITERATIONS=b.numIter),b.gravityRange!=null&&(y.DEFAULT_GRAVITY_RANGE_FACTOR=g.DEFAULT_GRAVITY_RANGE_FACTOR=b.gravityRange),b.gravityCompound!=null&&(y.DEFAULT_COMPOUND_GRAVITY_STRENGTH=g.DEFAULT_COMPOUND_GRAVITY_STRENGTH=b.gravityCompound),b.gravityRangeCompound!=null&&(y.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR=g.DEFAULT_COMPOUND_GRAVITY_RANGE_FACTOR=b.gravityRangeCompound),b.initialEnergyOnIncremental!=null&&(y.DEFAULT_COOLING_FACTOR_INCREMENTAL=g.DEFAULT_COOLING_FACTOR_INCREMENTAL=b.initialEnergyOnIncremental),b.tilingCompareBy!=null&&(y.TILING_COMPARE_BY=b.tilingCompareBy),b.quality=="proof"?m.QUALITY=2:m.QUALITY=0,y.NODE_DIMENSIONS_INCLUDE_LABELS=g.NODE_DIMENSIONS_INCLUDE_LABELS=m.NODE_DIMENSIONS_INCLUDE_LABELS=b.nodeDimensionsIncludeLabels,y.DEFAULT_INCREMENTAL=g.DEFAULT_INCREMENTAL=m.DEFAULT_INCREMENTAL=!b.randomize,y.ANIMATE=g.ANIMATE=m.ANIMATE=b.animate,y.TILE=b.tile,y.TILING_PADDING_VERTICAL=typeof b.tilingPaddingVertical=="function"?b.tilingPaddingVertical.call():b.tilingPaddingVertical,y.TILING_PADDING_HORIZONTAL=typeof b.tilingPaddingHorizontal=="function"?b.tilingPaddingHorizontal.call():b.tilingPaddingHorizontal,y.DEFAULT_INCREMENTAL=g.DEFAULT_INCREMENTAL=m.DEFAULT_INCREMENTAL=!0,y.PURE_INCREMENTAL=!b.randomize,m.DEFAULT_UNIFORM_LEAF_NODE_SIZES=b.uniformNodeDimensions,b.step=="transformed"&&(y.TRANSFORM_ON_CONSTRAINT_HANDLING=!0,y.ENFORCE_CONSTRAINTS=!1,y.APPLY_LAYOUT=!1),b.step=="enforced"&&(y.TRANSFORM_ON_CONSTRAINT_HANDLING=!1,y.ENFORCE_CONSTRAINTS=!0,y.APPLY_LAYOUT=!1),b.step=="cose"&&(y.TRANSFORM_ON_CONSTRAINT_HANDLING=!1,y.ENFORCE_CONSTRAINTS=!1,y.APPLY_LAYOUT=!0),b.step=="all"&&(b.randomize?y.TRANSFORM_ON_CONSTRAINT_HANDLING=!0:y.TRANSFORM_ON_CONSTRAINT_HANDLING=!1,y.ENFORCE_CONSTRAINTS=!0,y.APPLY_LAYOUT=!0),b.fixedNodeConstraint||b.alignmentConstraint||b.relativePlacementConstraint?y.TREE_REDUCTION_ON_INCREMENTAL=!1:y.TREE_REDUCTION_ON_INCREMENTAL=!0;var F=new h,B=F.newGraphManager();return O(B.addRoot(),u.getTopMostNodes(E),F,b),D(F,B,_),P(F,b),F.runLayout(),N},"coseLayout");a.exports={coseLayout:v}},212:(a,s,l)=>{var u=function(){function b(w,S){for(var T=0;T0)if(D){var B=d.getTopMostNodes(T.eles.nodes());if(k=d.connectComponents(E,T.eles,B),k.forEach(function(oe){var ke=oe.boundingBox();I.push({x:ke.x1+ke.w/2,y:ke.y1+ke.h/2})}),T.randomize&&k.forEach(function(oe){T.eles=oe,A.push(m(T))}),T.quality=="default"||T.quality=="proof"){var $=E.collection();if(T.tile){var z=new Map,Y=[],Q=[],X=0,ie={nodeIndexes:z,xCoords:Y,yCoords:Q},j=[];if(k.forEach(function(oe,ke){oe.edges().length==0&&(oe.nodes().forEach(function(Ie,Se){$.merge(oe.nodes()[Se]),Ie.isParent()||(ie.nodeIndexes.set(oe.nodes()[Se].id(),X++),ie.xCoords.push(oe.nodes()[0].position().x),ie.yCoords.push(oe.nodes()[0].position().y))}),j.push(ke))}),$.length>1){var J=$.boundingBox();I.push({x:J.x1+J.w/2,y:J.y1+J.h/2}),k.push($),A.push(ie);for(var Z=j.length-1;Z>=0;Z--)k.splice(j[Z],1),A.splice(j[Z],1),I.splice(j[Z],1)}}k.forEach(function(oe,ke){T.eles=oe,N.push(y(T,A[ke])),d.relocateComponent(I[ke],N[ke],T)})}else k.forEach(function(oe,ke){d.relocateComponent(I[ke],A[ke],T)});var H=new Set;if(k.length>1){var q=[],K=_.filter(function(oe){return oe.css("display")=="none"});k.forEach(function(oe,ke){var Ie=void 0;if(T.quality=="draft"&&(Ie=A[ke].nodeIndexes),oe.nodes().not(K).length>0){var Se={};Se.edges=[],Se.nodes=[];var Ue=void 0;oe.nodes().not(K).forEach(function(Pe){if(T.quality=="draft")if(!Pe.isParent())Ue=Ie.get(Pe.id()),Se.nodes.push({x:A[ke].xCoords[Ue]-Pe.boundingbox().w/2,y:A[ke].yCoords[Ue]-Pe.boundingbox().h/2,width:Pe.boundingbox().w,height:Pe.boundingbox().h});else{var _e=d.calcBoundingBox(Pe,A[ke].xCoords,A[ke].yCoords,Ie);Se.nodes.push({x:_e.topLeftX,y:_e.topLeftY,width:_e.width,height:_e.height})}else N[ke][Pe.id()]&&Se.nodes.push({x:N[ke][Pe.id()].getLeft(),y:N[ke][Pe.id()].getTop(),width:N[ke][Pe.id()].getWidth(),height:N[ke][Pe.id()].getHeight()})}),oe.edges().forEach(function(Pe){var _e=Pe.source(),me=Pe.target();if(_e.css("display")!="none"&&me.css("display")!="none")if(T.quality=="draft"){var W=Ie.get(_e.id()),fe=Ie.get(me.id()),ge=[],re=[];if(_e.isParent()){var he=d.calcBoundingBox(_e,A[ke].xCoords,A[ke].yCoords,Ie);ge.push(he.topLeftX+he.width/2),ge.push(he.topLeftY+he.height/2)}else ge.push(A[ke].xCoords[W]),ge.push(A[ke].yCoords[W]);if(me.isParent()){var ne=d.calcBoundingBox(me,A[ke].xCoords,A[ke].yCoords,Ie);re.push(ne.topLeftX+ne.width/2),re.push(ne.topLeftY+ne.height/2)}else re.push(A[ke].xCoords[fe]),re.push(A[ke].yCoords[fe]);Se.edges.push({startX:ge[0],startY:ge[1],endX:re[0],endY:re[1]})}else N[ke][_e.id()]&&N[ke][me.id()]&&Se.edges.push({startX:N[ke][_e.id()].getCenterX(),startY:N[ke][_e.id()].getCenterY(),endX:N[ke][me.id()].getCenterX(),endY:N[ke][me.id()].getCenterY()})}),Se.nodes.length>0&&(q.push(Se),H.add(ke))}});var se=O.packComponents(q,T.randomize).shifts;if(T.quality=="draft")A.forEach(function(oe,ke){var Ie=oe.xCoords.map(function(Ue){return Ue+se[ke].dx}),Se=oe.yCoords.map(function(Ue){return Ue+se[ke].dy});oe.xCoords=Ie,oe.yCoords=Se});else{var ce=0;H.forEach(function(oe){Object.keys(N[oe]).forEach(function(ke){var Ie=N[oe][ke];Ie.setCenter(Ie.getCenterX()+se[ce].dx,Ie.getCenterY()+se[ce].dy)}),ce++})}}}else{var P=T.eles.boundingBox();if(I.push({x:P.x1+P.w/2,y:P.y1+P.h/2}),T.randomize){var F=m(T);A.push(F)}T.quality=="default"||T.quality=="proof"?(N.push(y(T,A[0])),d.relocateComponent(I[0],N[0],T)):d.relocateComponent(I[0],A[0],T)}var ue=o(function(ke,Ie){if(T.quality=="default"||T.quality=="proof"){typeof ke=="number"&&(ke=Ie);var Se=void 0,Ue=void 0,Pe=ke.data("id");return N.forEach(function(me){Pe in me&&(Se={x:me[Pe].getRect().getCenterX(),y:me[Pe].getRect().getCenterY()},Ue=me[Pe])}),T.nodeDimensionsIncludeLabels&&(Ue.labelWidth&&(Ue.labelPosHorizontal=="left"?Se.x+=Ue.labelWidth/2:Ue.labelPosHorizontal=="right"&&(Se.x-=Ue.labelWidth/2)),Ue.labelHeight&&(Ue.labelPosVertical=="top"?Se.y+=Ue.labelHeight/2:Ue.labelPosVertical=="bottom"&&(Se.y-=Ue.labelHeight/2))),Se==null&&(Se={x:ke.position("x"),y:ke.position("y")}),{x:Se.x,y:Se.y}}else{var _e=void 0;return A.forEach(function(me){var W=me.nodeIndexes.get(ke.id());W!=null&&(_e={x:me.xCoords[W],y:me.yCoords[W]})}),_e==null&&(_e={x:ke.position("x"),y:ke.position("y")}),{x:_e.x,y:_e.y}}},"getPositions");if(T.quality=="default"||T.quality=="proof"||T.randomize){var te=d.calcParentsWithoutChildren(E,_),De=_.filter(function(oe){return oe.css("display")=="none"});T.eles=_.not(De),_.nodes().not(":parent").not(De).layoutPositions(S,T,ue),te.length>0&&te.forEach(function(oe){oe.position(ue(oe))})}else console.log("If randomize option is set to false, then quality option must be 'default' or 'proof'.")},"run")}]),b}();a.exports=x},657:(a,s,l)=>{var u=l(548),h=l(140).layoutBase.Matrix,f=l(140).layoutBase.SVD,d=o(function(m){var g=m.cy,y=m.eles,v=y.nodes(),x=y.nodes(":parent"),b=new Map,w=new Map,S=new Map,T=[],E=[],_=[],A=[],L=[],M=[],N=[],k=[],I=void 0,C=void 0,O=1e8,D=1e-9,P=m.piTol,F=m.samplingType,B=m.nodeSeparation,$=void 0,z=o(function(){for(var we=0,Te=0,Ce=!1;Te<$;){we=Math.floor(Math.random()*C),Ce=!1;for(var Ae=0;Ae=Ge;){ye=Ae[Ge++];for(var tt=T[ye],Ye=0;YeZe&&(Ze=L[Ve],gt=Ve)}return gt},"BFS"),Q=o(function(we){var Te=void 0;if(we){Te=Math.floor(Math.random()*C),I=Te;for(var Ae=0;Ae=1)break;Ze=ze}for(var tt=0;tt=1)break;Ze=ze}for(var Je=0;Je0&&(Te.isParent()?T[we].push(S.get(Te.id())):T[we].push(Te.id()))})});var te=o(function(we){var Te=w.get(we),Ce=void 0;b.get(we).forEach(function(Ae){g.getElementById(Ae).isParent()?Ce=S.get(Ae):Ce=Ae,T[Te].push(Ce),T[w.get(Ce)].push(we)})},"_loop"),De=!0,oe=!1,ke=void 0;try{for(var Ie=b.keys()[Symbol.iterator](),Se;!(De=(Se=Ie.next()).done);De=!0){var Ue=Se.value;te(Ue)}}catch(ae){oe=!0,ke=ae}finally{try{!De&&Ie.return&&Ie.return()}finally{if(oe)throw ke}}C=w.size;var Pe=void 0;if(C>2){$=C{var u=l(212),h=o(function(d){d&&d("layout","fcose",u)},"register");typeof cytoscape<"u"&&h(cytoscape),a.exports=h},140:a=>{a.exports=t}},r={};function n(a){var s=r[a];if(s!==void 0)return s.exports;var l=r[a]={exports:{}};return e[a](l,l.exports,n),l.exports}o(n,"__webpack_require__");var i=n(579);return i})()})});var T1,J0,eF=R(()=>{"use strict";V1();T1=o(t=>`${t}`,"wrapIcon"),J0={prefix:"mermaid-architecture",height:80,width:80,icons:{database:{body:T1('')},server:{body:T1('')},disk:{body:T1('')},internet:{body:T1('')},cloud:{body:T1('')},unknown:FC,blank:{body:T1("")}}}});var j1e,K1e,Q1e,Z1e,J1e=R(()=>{"use strict";V1();_t();Al();gb();eF();cC();j1e=o(async function(t,e){let r=Ci("padding"),n=Ci("iconSize"),i=n/2,a=n/6,s=a/2;await Promise.all(e.edges().map(async l=>{let{source:u,sourceDir:h,sourceArrow:f,sourceGroup:d,target:p,targetDir:m,targetArrow:g,targetGroup:y,label:v}=lC(l),{x,y:b}=l[0].sourceEndpoint(),{x:w,y:S}=l[0].midpoint(),{x:T,y:E}=l[0].targetEndpoint(),_=r+4;if(d&&(cs(h)?x+=h==="L"?-_:_:b+=h==="T"?-_:_+18),y&&(cs(m)?T+=m==="L"?-_:_:E+=m==="T"?-_:_+18),!d&&Z0.getNode(u)?.type==="junction"&&(cs(h)?x+=h==="L"?i:-i:b+=h==="T"?i:-i),!y&&Z0.getNode(p)?.type==="junction"&&(cs(m)?T+=m==="L"?i:-i:E+=m==="T"?i:-i),l[0]._private.rscratch){let A=t.insert("g");if(A.insert("path").attr("d",`M ${x},${b} L ${w},${S} L${T},${E} `).attr("class","edge"),f){let L=cs(h)?pb[h](x,a):x-s,M=Wc(h)?pb[h](b,a):b-s;A.insert("polygon").attr("points",qB[h](a)).attr("transform",`translate(${L},${M})`).attr("class","arrow")}if(g){let L=cs(m)?pb[m](T,a):T-s,M=Wc(m)?pb[m](E,a):E-s;A.insert("polygon").attr("points",qB[m](a)).attr("transform",`translate(${L},${M})`).attr("class","arrow")}if(v){let L=oC(h,m)?"XY":cs(h)?"X":"Y",M=0;L==="X"?M=Math.abs(x-T):L==="Y"?M=Math.abs(b-E)/1.5:M=Math.abs(x-T)/2;let N=A.append("g");if(await ta(N,v,{useHtmlLabels:!1,width:M,classes:"architecture-service-label"},de()),N.attr("dy","1em").attr("alignment-baseline","middle").attr("dominant-baseline","middle").attr("text-anchor","middle"),L==="X")N.attr("transform","translate("+w+", "+S+")");else if(L==="Y")N.attr("transform","translate("+w+", "+S+") rotate(-90)");else if(L==="XY"){let k=mb(h,m);if(k&&F1e(k)){let I=N.node().getBoundingClientRect(),[C,O]=G1e(k);N.attr("dominant-baseline","auto").attr("transform",`rotate(${-1*C*O*45})`);let D=N.node().getBoundingClientRect();N.attr("transform",` + translate(${w}, ${S-I.height/2}) + translate(${C*D.width/2}, ${O*D.height/2}) + rotate(${-1*C*O*45}, 0, ${I.height/2}) + `)}}}}}))},"drawEdges"),K1e=o(async function(t,e){let n=Ci("padding")*.75,i=Ci("fontSize"),s=Ci("iconSize")/2;await Promise.all(e.nodes().map(async l=>{let u=If(l);if(u.type==="group"){let{h,w:f,x1:d,y1:p}=l.boundingBox();t.append("rect").attr("x",d+s).attr("y",p+s).attr("width",f).attr("height",h).attr("class","node-bkg");let m=t.append("g"),g=d,y=p;if(u.icon){let v=m.append("g");v.html(`${await zb(u.icon,{height:n,width:n,fallbackPrefix:J0.prefix})}`),v.attr("transform","translate("+(g+s+1)+", "+(y+s+1)+")"),g+=n,y+=i/2-1-2}if(u.label){let v=m.append("g");await ta(v,u.label,{useHtmlLabels:!1,width:f,classes:"architecture-service-label"},de()),v.attr("dy","1em").attr("alignment-baseline","middle").attr("dominant-baseline","start").attr("text-anchor","start"),v.attr("transform","translate("+(g+s+4)+", "+(y+s+2)+")")}}}))},"drawGroups"),Q1e=o(async function(t,e,r){for(let n of r){let i=e.append("g"),a=Ci("iconSize");if(n.title){let h=i.append("g");await ta(h,n.title,{useHtmlLabels:!1,width:a*1.5,classes:"architecture-service-label"},de()),h.attr("dy","1em").attr("alignment-baseline","middle").attr("dominant-baseline","middle").attr("text-anchor","middle"),h.attr("transform","translate("+a/2+", "+a+")")}let s=i.append("g");if(n.icon)s.html(`${await zb(n.icon,{height:a,width:a,fallbackPrefix:J0.prefix})}`);else if(n.iconText){s.html(`${await zb("blank",{height:a,width:a,fallbackPrefix:J0.prefix})}`);let d=s.append("g").append("foreignObject").attr("width",a).attr("height",a).append("div").attr("class","node-icon-text").attr("style",`height: ${a}px;`).append("div").html(n.iconText),p=parseInt(window.getComputedStyle(d.node(),null).getPropertyValue("font-size").replace(/\D/g,""))??16;d.attr("style",`-webkit-line-clamp: ${Math.floor((a-2)/p)};`)}else s.append("path").attr("class","node-bkg").attr("id","node-"+n.id).attr("d",`M0 ${a} v${-a} q0,-5 5,-5 h${a} q5,0 5,5 v${a} H0 Z`);i.attr("class","architecture-service");let{width:l,height:u}=i._groups[0][0].getBBox();n.width=l,n.height=u,t.setElementForId(n.id,i)}return 0},"drawServices"),Z1e=o(function(t,e,r){r.forEach(n=>{let i=e.append("g"),a=Ci("iconSize");i.append("g").append("rect").attr("id","node-"+n.id).attr("fill-opacity","0").attr("width",a).attr("height",a),i.attr("class","architecture-junction");let{width:l,height:u}=i._groups[0][0].getBBox();i.width=l,i.height=u,t.setElementForId(n.id,i)})},"drawJunctions")});function iet(t,e){t.forEach(r=>{e.add({group:"nodes",data:{type:"service",id:r.id,icon:r.icon,label:r.title,parent:r.in,width:Ci("iconSize"),height:Ci("iconSize")},classes:"node-service"})})}function aet(t,e){t.forEach(r=>{e.add({group:"nodes",data:{type:"junction",id:r.id,parent:r.in,width:Ci("iconSize"),height:Ci("iconSize")},classes:"node-junction"})})}function set(t,e){e.nodes().map(r=>{let n=If(r);if(n.type==="group")return;n.x=r.position().x,n.y=r.position().y,t.getElementById(n.id).attr("transform","translate("+(n.x||0)+","+(n.y||0)+")")})}function oet(t,e){t.forEach(r=>{e.add({group:"nodes",data:{type:"group",id:r.id,icon:r.icon,label:r.title,parent:r.in},classes:"node-group"})})}function cet(t,e){t.forEach(r=>{let{lhsId:n,rhsId:i,lhsInto:a,lhsGroup:s,rhsInto:l,lhsDir:u,rhsDir:h,rhsGroup:f,title:d}=r,p=oC(r.lhsDir,r.rhsDir)?"segments":"straight",m={id:`${n}-${i}`,label:d,source:n,sourceDir:u,sourceArrow:a,sourceGroup:s,sourceEndpoint:u==="L"?"0 50%":u==="R"?"100% 50%":u==="T"?"50% 0":"50% 100%",target:i,targetDir:h,targetArrow:l,targetGroup:f,targetEndpoint:h==="L"?"0 50%":h==="R"?"100% 50%":h==="T"?"50% 0":"50% 100%"};e.add({group:"edges",data:m,classes:p})})}function uet(t){let e=t.map(i=>{let a={},s={};return Object.entries(i).forEach(([l,[u,h]])=>{a[h]||(a[h]=[]),s[u]||(s[u]=[]),a[h].push(l),s[u].push(l)}),{horiz:Object.values(a).filter(l=>l.length>1),vert:Object.values(s).filter(l=>l.length>1)}}),[r,n]=e.reduce(([i,a],{horiz:s,vert:l})=>[[...i,...s],[...a,...l]],[[],[]]);return{horizontal:r,vertical:n}}function het(t){let e=[],r=o(i=>`${i[0]},${i[1]}`,"posToStr"),n=o(i=>i.split(",").map(a=>parseInt(a)),"strToPos");return t.forEach(i=>{let a=Object.fromEntries(Object.entries(i).map(([h,f])=>[r(f),h])),s=[r([0,0])],l={},u={L:[-1,0],R:[1,0],T:[0,1],B:[0,-1]};for(;s.length>0;){let h=s.shift();if(h){l[h]=1;let f=a[h];if(f){let d=n(h);Object.entries(u).forEach(([p,m])=>{let g=r([d[0]+m[0],d[1]+m[1]]),y=a[g];y&&!l[g]&&(s.push(g),e.push({[WB[p]]:y,[WB[B1e(p)]]:f,gap:1.5*Ci("iconSize")}))})}}}}),e}function fet(t,e,r,n,{spatialMaps:i}){return new Promise(a=>{let s=$e("body").append("div").attr("id","cy").attr("style","display:none"),l=rl({container:document.getElementById("cy"),style:[{selector:"edge",style:{"curve-style":"straight",label:"data(label)","source-endpoint":"data(sourceEndpoint)","target-endpoint":"data(targetEndpoint)"}},{selector:"edge.segments",style:{"curve-style":"segments","segment-weights":"0","segment-distances":[.5],"edge-distances":"endpoints","source-endpoint":"data(sourceEndpoint)","target-endpoint":"data(targetEndpoint)"}},{selector:"node",style:{"compound-sizing-wrt-labels":"include"}},{selector:"node[label]",style:{"text-valign":"bottom","text-halign":"center","font-size":`${Ci("fontSize")}px`}},{selector:".node-service",style:{label:"data(label)",width:"data(width)",height:"data(height)"}},{selector:".node-junction",style:{width:"data(width)",height:"data(height)"}},{selector:".node-group",style:{padding:`${Ci("padding")}px`}}]});s.remove(),oet(r,l),iet(t,l),aet(e,l),cet(n,l);let u=uet(i),h=het(i),f=l.layout({name:"fcose",quality:"proof",styleEnabled:!1,animate:!1,nodeDimensionsIncludeLabels:!1,idealEdgeLength(d){let[p,m]=d.connectedNodes(),{parent:g}=If(p),{parent:y}=If(m);return g===y?1.5*Ci("iconSize"):.5*Ci("iconSize")},edgeElasticity(d){let[p,m]=d.connectedNodes(),{parent:g}=If(p),{parent:y}=If(m);return g===y?.45:.001},alignmentConstraint:u,relativePlacementConstraint:h});f.one("layoutstop",()=>{function d(p,m,g,y){let v,x,{x:b,y:w}=p,{x:S,y:T}=m;x=(y-w+(b-g)*(w-T)/(b-S))/Math.sqrt(1+Math.pow((w-T)/(b-S),2)),v=Math.sqrt(Math.pow(y-w,2)+Math.pow(g-b,2)-Math.pow(x,2));let E=Math.sqrt(Math.pow(S-b,2)+Math.pow(T-w,2));v=v/E;let _=(S-b)*(y-w)-(T-w)*(g-b);switch(!0){case _>=0:_=1;break;case _<0:_=-1;break}let A=(S-b)*(g-b)+(T-w)*(y-w);switch(!0){case A>=0:A=1;break;case A<0:A=-1;break}return x=Math.abs(x)*_,v=v*A,{distances:x,weights:v}}o(d,"getSegmentWeights"),l.startBatch();for(let p of Object.values(l.edges()))if(p.data?.()){let{x:m,y:g}=p.source().position(),{x:y,y:v}=p.target().position();if(m!==y&&g!==v){let x=p.sourceEndpoint(),b=p.targetEndpoint(),{sourceDir:w}=lC(p),[S,T]=Wc(w)?[x.x,b.y]:[b.x,x.y],{weights:E,distances:_}=d(x,b,S,T);p.style("segment-distances",_),p.style("segment-weights",E)}}l.endBatch(),f.run()}),f.run(),l.ready(d=>{V.info("Ready",d),a(l)})})}var eye,det,tye,rye=R(()=>{"use strict";V1();vB();eye=Xi(X1e(),1);Zt();ut();pf();Yn();gb();eF();cC();J1e();Fb([{name:J0.prefix,icons:J0}]);rl.use(eye.default);o(iet,"addServices");o(aet,"addJunctions");o(set,"positionNodes");o(oet,"addGroups");o(cet,"addEdges");o(uet,"getAlignments");o(het,"getRelativeConstraints");o(fet,"layoutArchitecture");det=o(async(t,e,r,n)=>{let i=n.db,a=i.getServices(),s=i.getJunctions(),l=i.getGroups(),u=i.getEdges(),h=i.getDataStructures(),f=Ps(e),d=f.append("g");d.attr("class","architecture-edges");let p=f.append("g");p.attr("class","architecture-services");let m=f.append("g");m.attr("class","architecture-groups"),await Q1e(i,p,a),Z1e(i,p,s);let g=await fet(a,s,l,u,h);await j1e(d,g),await K1e(m,g),set(i,g),Lo(void 0,f,Ci("padding"),Ci("useMaxWidth"))},"draw"),tye={draw:det}});var nye={};hr(nye,{diagram:()=>pet});var pet,iye=R(()=>{"use strict";Y1e();gb();q1e();rye();pet={parser:H1e,db:Z0,renderer:tye,styles:W1e}});var knt={};hr(knt,{default:()=>Tnt});V1();zC();Hf();var BX="c4",mCe=o(t=>/^\s*C4Context|C4Container|C4Component|C4Dynamic|C4Deployment/.test(t),"detector"),gCe=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(PX(),OX));return{id:BX,diagram:t}},"loader"),yCe={id:BX,detector:mCe,loader:gCe},FX=yCe;var Zre="flowchart",SNe=o((t,e)=>e?.flowchart?.defaultRenderer==="dagre-wrapper"||e?.flowchart?.defaultRenderer==="elk"?!1:/^\s*graph/.test(t),"detector"),ANe=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(uT(),cT));return{id:Zre,diagram:t}},"loader"),_Ne={id:Zre,detector:SNe,loader:ANe},Jre=_Ne;var ene="flowchart-v2",LNe=o((t,e)=>e?.flowchart?.defaultRenderer==="dagre-d3"?!1:(e?.flowchart?.defaultRenderer==="elk"&&(e.layout="elk"),/^\s*graph/.test(t)&&e?.flowchart?.defaultRenderer==="dagre-wrapper"?!0:/^\s*flowchart/.test(t)),"detector"),DNe=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(uT(),cT));return{id:ene,diagram:t}},"loader"),RNe={id:ene,detector:LNe,loader:DNe},tne=RNe;var Dne="er",sMe=o(t=>/^\s*erDiagram/.test(t),"detector"),oMe=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(Lne(),_ne));return{id:Dne,diagram:t}},"loader"),lMe={id:Dne,detector:sMe,loader:oMe},Rne=lMe;var $le="gitGraph",NBe=o(t=>/^\s*gitGraph/.test(t),"detector"),MBe=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(Gle(),zle));return{id:$le,diagram:t}},"loader"),IBe={id:$le,detector:NBe,loader:MBe},Vle=IBe;var vce="gantt",wFe=o(t=>/^\s*gantt/.test(t),"detector"),TFe=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(yce(),gce));return{id:vce,diagram:t}},"loader"),kFe={id:vce,detector:wFe,loader:TFe},xce=kFe;var _ce="info",LFe=o(t=>/^\s*info/.test(t),"detector"),DFe=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(Ace(),Sce));return{id:_ce,diagram:t}},"loader"),Lce={id:_ce,detector:LFe,loader:DFe};var zce="pie",UFe=o(t=>/^\s*pie/.test(t),"detector"),HFe=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(Fce(),Bce));return{id:zce,diagram:t}},"loader"),Gce={id:zce,detector:UFe,loader:HFe};var Jce="quadrantChart",lze=o(t=>/^\s*quadrantChart/.test(t),"detector"),cze=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(Zce(),Qce));return{id:Jce,diagram:t}},"loader"),uze={id:Jce,detector:lze,loader:cze},eue=uze;var Aue="xychart",Sze=o(t=>/^\s*xychart-beta/.test(t),"detector"),Aze=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(Sue(),Cue));return{id:Aue,diagram:t}},"loader"),_ze={id:Aue,detector:Sze,loader:Aze},_ue=_ze;var Hue="requirement",rGe=o(t=>/^\s*requirement(Diagram)?/.test(t),"detector"),nGe=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(Uue(),Vue));return{id:Hue,diagram:t}},"loader"),iGe={id:Hue,detector:rGe,loader:nGe},Yue=iGe;var vhe="sequence",o$e=o(t=>/^\s*sequenceDiagram/.test(t),"detector"),l$e=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(yhe(),ghe));return{id:vhe,diagram:t}},"loader"),c$e={id:vhe,detector:o$e,loader:l$e},xhe=c$e;var Ihe="class",U$e=o((t,e)=>e?.class?.defaultRenderer==="dagre-wrapper"?!1:/^\s*classDiagram/.test(t),"detector"),H$e=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(Mhe(),Nhe));return{id:Ihe,diagram:t}},"loader"),Y$e={id:Ihe,detector:U$e,loader:H$e},Ohe=Y$e;var cfe="classDiagram",vVe=o((t,e)=>/^\s*classDiagram/.test(t)&&e?.class?.defaultRenderer==="dagre-wrapper"?!0:/^\s*classDiagram-v2/.test(t),"detector"),xVe=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(lfe(),ofe));return{id:cfe,diagram:t}},"loader"),bVe={id:cfe,detector:vVe,loader:xVe},ufe=bVe;var tde="state",mUe=o((t,e)=>e?.state?.defaultRenderer==="dagre-wrapper"?!1:/^\s*stateDiagram/.test(t),"detector"),gUe=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(ede(),Jfe));return{id:tde,diagram:t}},"loader"),yUe={id:tde,detector:mUe,loader:gUe},rde=yUe;var ade="stateDiagram",xUe=o((t,e)=>!!(/^\s*stateDiagram-v2/.test(t)||/^\s*stateDiagram/.test(t)&&e?.state?.defaultRenderer==="dagre-wrapper"),"detector"),bUe=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(ide(),nde));return{id:ade,diagram:t}},"loader"),wUe={id:ade,detector:xUe,loader:bUe},sde=wUe;var Tde="journey",VUe=o(t=>/^\s*journey/.test(t),"detector"),UUe=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(wde(),bde));return{id:Tde,diagram:t}},"loader"),HUe={id:Tde,detector:VUe,loader:UUe},kde=HUe;ut();pf();Yn();var YUe=o((t,e,r)=>{V.debug(`rendering svg for syntax error +`);let n=Ps(e),i=n.append("g");n.attr("viewBox","0 0 2412 512"),Sr(n,100,512,!0),i.append("path").attr("class","error-icon").attr("d","m411.313,123.313c6.25-6.25 6.25-16.375 0-22.625s-16.375-6.25-22.625,0l-32,32-9.375,9.375-20.688-20.688c-12.484-12.5-32.766-12.5-45.25,0l-16,16c-1.261,1.261-2.304,2.648-3.31,4.051-21.739-8.561-45.324-13.426-70.065-13.426-105.867,0-192,86.133-192,192s86.133,192 192,192 192-86.133 192-192c0-24.741-4.864-48.327-13.426-70.065 1.402-1.007 2.79-2.049 4.051-3.31l16-16c12.5-12.492 12.5-32.758 0-45.25l-20.688-20.688 9.375-9.375 32.001-31.999zm-219.313,100.687c-52.938,0-96,43.063-96,96 0,8.836-7.164,16-16,16s-16-7.164-16-16c0-70.578 57.422-128 128-128 8.836,0 16,7.164 16,16s-7.164,16-16,16z"),i.append("path").attr("class","error-icon").attr("d","m459.02,148.98c-6.25-6.25-16.375-6.25-22.625,0s-6.25,16.375 0,22.625l16,16c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688 6.25-6.25 6.25-16.375 0-22.625l-16.001-16z"),i.append("path").attr("class","error-icon").attr("d","m340.395,75.605c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688 6.25-6.25 6.25-16.375 0-22.625l-16-16c-6.25-6.25-16.375-6.25-22.625,0s-6.25,16.375 0,22.625l15.999,16z"),i.append("path").attr("class","error-icon").attr("d","m400,64c8.844,0 16-7.164 16-16v-32c0-8.836-7.156-16-16-16-8.844,0-16,7.164-16,16v32c0,8.836 7.156,16 16,16z"),i.append("path").attr("class","error-icon").attr("d","m496,96.586h-32c-8.844,0-16,7.164-16,16 0,8.836 7.156,16 16,16h32c8.844,0 16-7.164 16-16 0-8.836-7.156-16-16-16z"),i.append("path").attr("class","error-icon").attr("d","m436.98,75.605c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688l32-32c6.25-6.25 6.25-16.375 0-22.625s-16.375-6.25-22.625,0l-32,32c-6.251,6.25-6.251,16.375-0.001,22.625z"),i.append("text").attr("class","error-text").attr("x",1440).attr("y",250).attr("font-size","150px").style("text-anchor","middle").text("Syntax error in text"),i.append("text").attr("class","error-text").attr("x",1250).attr("y",400).attr("font-size","100px").style("text-anchor","middle").text(`mermaid version ${r}`)},"draw"),fP={draw:YUe},Ede=fP;var WUe={db:{},renderer:fP,parser:{parse:o(()=>{},"parse")}},Cde=WUe;var Sde="flowchart-elk",qUe=o((t,e={})=>/^\s*flowchart-elk/.test(t)||/^\s*flowchart|graph/.test(t)&&e?.flowchart?.defaultRenderer==="elk"?(e.layout="elk",!0):!1,"detector"),XUe=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(uT(),cT));return{id:Sde,diagram:t}},"loader"),jUe={id:Sde,detector:qUe,loader:XUe},Ade=jUe;var Jde="timeline",pHe=o(t=>/^\s*timeline/.test(t),"detector"),mHe=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(Zde(),Qde));return{id:Jde,diagram:t}},"loader"),gHe={id:Jde,detector:pHe,loader:mHe},e0e=gHe;var vge="mindmap",TZe=o(t=>/^\s*mindmap/.test(t),"detector"),kZe=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(yge(),gge));return{id:vge,diagram:t}},"loader"),EZe={id:vge,detector:TZe,loader:kZe},xge=EZe;var Zge="sankey",WZe=o(t=>/^\s*sankey-beta/.test(t),"detector"),qZe=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(Qge(),Kge));return{id:Zge,diagram:t}},"loader"),XZe={id:Zge,detector:WZe,loader:qZe},Jge=XZe;var c1e="packet",oJe=o(t=>/^\s*packet-beta/.test(t),"detector"),lJe=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(l1e(),o1e));return{id:c1e,diagram:t}},"loader"),u1e={id:c1e,detector:oJe,loader:lJe};var O1e="block",FJe=o(t=>/^\s*block-beta/.test(t),"detector"),zJe=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(I1e(),M1e));return{id:O1e,diagram:t}},"loader"),GJe={id:O1e,detector:FJe,loader:zJe},P1e=GJe;var aye="architecture",met=o(t=>/^\s*architecture/.test(t),"detector"),get=o(async()=>{let{diagram:t}=await Promise.resolve().then(()=>(iye(),nye));return{id:aye,diagram:t}},"loader"),yet={id:aye,detector:met,loader:get},sye=yet;Hf();_t();var oye=!1,k1=o(()=>{oye||(oye=!0,Jf("error",Cde,t=>t.toLowerCase().trim()==="error"),Jf("---",{db:{clear:o(()=>{},"clear")},styles:{},renderer:{draw:o(()=>{},"draw")},parser:{parse:o(()=>{throw new Error("Diagrams beginning with --- are not valid. If you were trying to use a YAML front-matter, please ensure that you've correctly opened and closed the YAML front-matter with un-indented `---` blocks")},"parse")},init:o(()=>null,"init")},t=>t.toLowerCase().trimStart().startsWith("---")),Ub(FX,ufe,Ohe,Rne,xce,Lce,Gce,Yue,xhe,Ade,tne,Jre,xge,e0e,Vle,sde,rde,kde,eue,Jge,u1e,_ue,P1e,sye))},"addDiagrams");ut();Hf();_t();var lye=o(async()=>{V.debug("Loading registered diagrams");let e=(await Promise.allSettled(Object.entries(Uf).map(async([r,{detector:n,loader:i}])=>{if(i)try{cy(r)}catch{try{let{diagram:a,id:s}=await i();Jf(s,a,n)}catch(a){throw V.error(`Failed to load external diagram with key ${r}. Removing from detectors.`),delete Uf[r],a}}}))).filter(r=>r.status==="rejected");if(e.length>0){V.error(`Failed to load ${e.length} external diagrams`);for(let r of e)V.error(r);throw new Error(`Failed to load ${e.length} external diagrams`)}},"loadRegisteredDiagrams");ut();Zt();var uC="comm",hC="rule",fC="decl";var cye="@import";var uye="@keyframes";var hye="@layer";var tF=Math.abs,bb=String.fromCharCode;function dC(t){return t.trim()}o(dC,"trim");function wb(t,e,r){return t.replace(e,r)}o(wb,"replace");function fye(t,e,r){return t.indexOf(e,r)}o(fye,"indexof");function ep(t,e){return t.charCodeAt(e)|0}o(ep,"charat");function Of(t,e,r){return t.slice(e,r)}o(Of,"substr");function wo(t){return t.length}o(wo,"strlen");function dye(t){return t.length}o(dye,"sizeof");function E1(t,e){return e.push(t),t}o(E1,"append");var pC=1,C1=1,pye=0,nl=0,Si=0,A1="";function mC(t,e,r,n,i,a,s,l){return{value:t,root:e,parent:r,type:n,props:i,children:a,line:pC,column:C1,length:s,return:"",siblings:l}}o(mC,"node");function mye(){return Si}o(mye,"char");function gye(){return Si=nl>0?ep(A1,--nl):0,C1--,Si===10&&(C1=1,pC--),Si}o(gye,"prev");function il(){return Si=nl2||S1(Si)>3?"":" "}o(xye,"whitespace");function bye(t,e){for(;--e&&il()&&!(Si<48||Si>102||Si>57&&Si<65||Si>70&&Si<97););return gC(t,Tb()+(e<6&&th()==32&&il()==32))}o(bye,"escaping");function rF(t){for(;il();)switch(Si){case t:return nl;case 34:case 39:t!==34&&t!==39&&rF(Si);break;case 40:t===41&&rF(t);break;case 92:il();break}return nl}o(rF,"delimiter");function wye(t,e){for(;il()&&t+Si!==57;)if(t+Si===84&&th()===47)break;return"/*"+gC(e,nl-1)+"*"+bb(t===47?t:il())}o(wye,"commenter");function Tye(t){for(;!S1(th());)il();return gC(t,nl)}o(Tye,"identifier");function Cye(t){return vye(vC("",null,null,null,[""],t=yye(t),0,[0],t))}o(Cye,"compile");function vC(t,e,r,n,i,a,s,l,u){for(var h=0,f=0,d=s,p=0,m=0,g=0,y=1,v=1,x=1,b=0,w="",S=i,T=a,E=n,_=w;v;)switch(g=b,b=il()){case 40:if(g!=108&&ep(_,d-1)==58){fye(_+=wb(yC(b),"&","&\f"),"&\f",tF(h?l[h-1]:0))!=-1&&(x=-1);break}case 34:case 39:case 91:_+=yC(b);break;case 9:case 10:case 13:case 32:_+=xye(g);break;case 92:_+=bye(Tb()-1,7);continue;case 47:switch(th()){case 42:case 47:E1(vet(wye(il(),Tb()),e,r,u),u),(S1(g||1)==5||S1(th()||1)==5)&&wo(_)&&Of(_,-1,void 0)!==" "&&(_+=" ");break;default:_+="/"}break;case 123*y:l[h++]=wo(_)*x;case 125*y:case 59:case 0:switch(b){case 0:case 125:v=0;case 59+f:x==-1&&(_=wb(_,/\f/g,"")),m>0&&(wo(_)-d||y===0&&g===47)&&E1(m>32?Eye(_+";",n,r,d-1,u):Eye(wb(_," ","")+";",n,r,d-2,u),u);break;case 59:_+=";";default:if(E1(E=kye(_,e,r,h,f,i,l,w,S=[],T=[],d,a),a),b===123)if(f===0)vC(_,e,E,E,S,a,d,l,T);else switch(p===99&&ep(_,3)===110?100:p){case 100:case 108:case 109:case 115:vC(t,E,E,n&&E1(kye(t,E,E,0,0,i,l,w,i,S=[],d,T),T),i,T,d,l,n?S:T);break;default:vC(_,E,E,E,[""],T,0,l,T)}}h=f=m=0,y=x=1,w=_="",d=s;break;case 58:d=1+wo(_),m=g;default:if(y<1){if(b==123)--y;else if(b==125&&y++==0&&gye()==125)continue}switch(_+=bb(b),b*y){case 38:x=f>0?1:(_+="\f",-1);break;case 44:l[h++]=(wo(_)-1)*x,x=1;break;case 64:th()===45&&(_+=yC(il())),p=th(),f=d=wo(w=_+=Tye(Tb())),b++;break;case 45:g===45&&wo(_)==2&&(y=0)}}return a}o(vC,"parse");function kye(t,e,r,n,i,a,s,l,u,h,f,d){for(var p=i-1,m=i===0?a:[""],g=dye(m),y=0,v=0,x=0;y0?m[b]+" "+w:wb(w,/&\f/g,m[b])))&&(u[x++]=S);return mC(t,e,r,i===0?hC:l,u,h,f,d)}o(kye,"ruleset");function vet(t,e,r,n){return mC(t,e,r,uC,bb(mye()),Of(t,2,-2),0,n)}o(vet,"comment");function Eye(t,e,r,n,i){return mC(t,e,r,fC,Of(t,0,n),Of(t,n+1,-1),n,i)}o(Eye,"declaration");function xC(t,e){for(var r="",n=0;n{Lye.forEach(t=>{t()}),Lye=[]},"attachFunctions");ut();var Rye=o(t=>t.replace(/^\s*%%(?!{)[^\n]+\n?/gm,"").trimStart(),"cleanupComments");Vb();function qye(t){return typeof t>"u"||t===null}o(qye,"isNothing");function bet(t){return typeof t=="object"&&t!==null}o(bet,"isObject");function wet(t){return Array.isArray(t)?t:qye(t)?[]:[t]}o(wet,"toArray");function Tet(t,e){var r,n,i,a;if(e)for(a=Object.keys(e),r=0,n=a.length;rl&&(a=" ... ",e=n-l+a.length),r-n>l&&(s=" ...",r=n+l-s.length),{str:a+t.slice(e,r).replace(/\t/g,"\u2192")+s,pos:n-e+a.length}}o(nF,"getLine");function iF(t,e){return Wi.repeat(" ",e-t.length)+t}o(iF,"padStart");function Net(t,e){if(e=Object.create(e||null),!t.buffer)return null;e.maxLength||(e.maxLength=79),typeof e.indent!="number"&&(e.indent=1),typeof e.linesBefore!="number"&&(e.linesBefore=3),typeof e.linesAfter!="number"&&(e.linesAfter=2);for(var r=/\r?\n|\r|\0/g,n=[0],i=[],a,s=-1;a=r.exec(t.buffer);)i.push(a.index),n.push(a.index+a[0].length),t.position<=a.index&&s<0&&(s=n.length-2);s<0&&(s=n.length-1);var l="",u,h,f=Math.min(t.line+e.linesAfter,i.length).toString().length,d=e.maxLength-(e.indent+f+3);for(u=1;u<=e.linesBefore&&!(s-u<0);u++)h=nF(t.buffer,n[s-u],i[s-u],t.position-(n[s]-n[s-u]),d),l=Wi.repeat(" ",e.indent)+iF((t.line-u+1).toString(),f)+" | "+h.str+` +`+l;for(h=nF(t.buffer,n[s],i[s],t.position,d),l+=Wi.repeat(" ",e.indent)+iF((t.line+1).toString(),f)+" | "+h.str+` +`,l+=Wi.repeat("-",e.indent+f+3+h.pos)+`^ +`,u=1;u<=e.linesAfter&&!(s+u>=i.length);u++)h=nF(t.buffer,n[s+u],i[s+u],t.position-(n[s]-n[s+u]),d),l+=Wi.repeat(" ",e.indent)+iF((t.line+u+1).toString(),f)+" | "+h.str+` +`;return l.replace(/\n$/,"")}o(Net,"makeSnippet");var Met=Net,Iet=["kind","multi","resolve","construct","instanceOf","predicate","represent","representName","defaultStyle","styleAliases"],Oet=["scalar","sequence","mapping"];function Pet(t){var e={};return t!==null&&Object.keys(t).forEach(function(r){t[r].forEach(function(n){e[String(n)]=r})}),e}o(Pet,"compileStyleAliases");function Bet(t,e){if(e=e||{},Object.keys(e).forEach(function(r){if(Iet.indexOf(r)===-1)throw new $s('Unknown option "'+r+'" is met in definition of "'+t+'" YAML type.')}),this.options=e,this.tag=t,this.kind=e.kind||null,this.resolve=e.resolve||function(){return!0},this.construct=e.construct||function(r){return r},this.instanceOf=e.instanceOf||null,this.predicate=e.predicate||null,this.represent=e.represent||null,this.representName=e.representName||null,this.defaultStyle=e.defaultStyle||null,this.multi=e.multi||!1,this.styleAliases=Pet(e.styleAliases||null),Oet.indexOf(this.kind)===-1)throw new $s('Unknown kind "'+this.kind+'" is specified for "'+t+'" YAML type.')}o(Bet,"Type$1");var Va=Bet;function Nye(t,e){var r=[];return t[e].forEach(function(n){var i=r.length;r.forEach(function(a,s){a.tag===n.tag&&a.kind===n.kind&&a.multi===n.multi&&(i=s)}),r[i]=n}),r}o(Nye,"compileList");function Fet(){var t={scalar:{},sequence:{},mapping:{},fallback:{},multi:{scalar:[],sequence:[],mapping:[],fallback:[]}},e,r;function n(i){i.multi?(t.multi[i.kind].push(i),t.multi.fallback.push(i)):t[i.kind][i.tag]=t.fallback[i.tag]=i}for(o(n,"collectType"),e=0,r=arguments.length;e=0?"0b"+t.toString(2):"-0b"+t.toString(2).slice(1)},"binary"),octal:o(function(t){return t>=0?"0o"+t.toString(8):"-0o"+t.toString(8).slice(1)},"octal"),decimal:o(function(t){return t.toString(10)},"decimal"),hexadecimal:o(function(t){return t>=0?"0x"+t.toString(16).toUpperCase():"-0x"+t.toString(16).toUpperCase().slice(1)},"hexadecimal")},defaultStyle:"decimal",styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}}),att=new RegExp("^(?:[-+]?(?:[0-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$");function stt(t){return!(t===null||!att.test(t)||t[t.length-1]==="_")}o(stt,"resolveYamlFloat");function ott(t){var e,r;return e=t.replace(/_/g,"").toLowerCase(),r=e[0]==="-"?-1:1,"+-".indexOf(e[0])>=0&&(e=e.slice(1)),e===".inf"?r===1?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:e===".nan"?NaN:r*parseFloat(e,10)}o(ott,"constructYamlFloat");var ltt=/^[-+]?[0-9]+e/;function ctt(t,e){var r;if(isNaN(t))switch(e){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===t)switch(e){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===t)switch(e){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(Wi.isNegativeZero(t))return"-0.0";return r=t.toString(10),ltt.test(r)?r.replace("e",".e"):r}o(ctt,"representYamlFloat");function utt(t){return Object.prototype.toString.call(t)==="[object Number]"&&(t%1!==0||Wi.isNegativeZero(t))}o(utt,"isFloat");var htt=new Va("tag:yaml.org,2002:float",{kind:"scalar",resolve:stt,construct:ott,predicate:utt,represent:ctt,defaultStyle:"lowercase"}),jye=Uet.extend({implicit:[qet,Qet,itt,htt]}),ftt=jye,Kye=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"),Qye=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$");function dtt(t){return t===null?!1:Kye.exec(t)!==null||Qye.exec(t)!==null}o(dtt,"resolveYamlTimestamp");function ptt(t){var e,r,n,i,a,s,l,u=0,h=null,f,d,p;if(e=Kye.exec(t),e===null&&(e=Qye.exec(t)),e===null)throw new Error("Date resolve error");if(r=+e[1],n=+e[2]-1,i=+e[3],!e[4])return new Date(Date.UTC(r,n,i));if(a=+e[4],s=+e[5],l=+e[6],e[7]){for(u=e[7].slice(0,3);u.length<3;)u+="0";u=+u}return e[9]&&(f=+e[10],d=+(e[11]||0),h=(f*60+d)*6e4,e[9]==="-"&&(h=-h)),p=new Date(Date.UTC(r,n,i,a,s,l,u)),h&&p.setTime(p.getTime()-h),p}o(ptt,"constructYamlTimestamp");function mtt(t){return t.toISOString()}o(mtt,"representYamlTimestamp");var gtt=new Va("tag:yaml.org,2002:timestamp",{kind:"scalar",resolve:dtt,construct:ptt,instanceOf:Date,represent:mtt});function ytt(t){return t==="<<"||t===null}o(ytt,"resolveYamlMerge");var vtt=new Va("tag:yaml.org,2002:merge",{kind:"scalar",resolve:ytt}),hF=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/= +\r`;function xtt(t){if(t===null)return!1;var e,r,n=0,i=t.length,a=hF;for(r=0;r64)){if(e<0)return!1;n+=6}return n%8===0}o(xtt,"resolveYamlBinary");function btt(t){var e,r,n=t.replace(/[\r\n=]/g,""),i=n.length,a=hF,s=0,l=[];for(e=0;e>16&255),l.push(s>>8&255),l.push(s&255)),s=s<<6|a.indexOf(n.charAt(e));return r=i%4*6,r===0?(l.push(s>>16&255),l.push(s>>8&255),l.push(s&255)):r===18?(l.push(s>>10&255),l.push(s>>2&255)):r===12&&l.push(s>>4&255),new Uint8Array(l)}o(btt,"constructYamlBinary");function wtt(t){var e="",r=0,n,i,a=t.length,s=hF;for(n=0;n>18&63],e+=s[r>>12&63],e+=s[r>>6&63],e+=s[r&63]),r=(r<<8)+t[n];return i=a%3,i===0?(e+=s[r>>18&63],e+=s[r>>12&63],e+=s[r>>6&63],e+=s[r&63]):i===2?(e+=s[r>>10&63],e+=s[r>>4&63],e+=s[r<<2&63],e+=s[64]):i===1&&(e+=s[r>>2&63],e+=s[r<<4&63],e+=s[64],e+=s[64]),e}o(wtt,"representYamlBinary");function Ttt(t){return Object.prototype.toString.call(t)==="[object Uint8Array]"}o(Ttt,"isBinary");var ktt=new Va("tag:yaml.org,2002:binary",{kind:"scalar",resolve:xtt,construct:btt,predicate:Ttt,represent:wtt}),Ett=Object.prototype.hasOwnProperty,Ctt=Object.prototype.toString;function Stt(t){if(t===null)return!0;var e=[],r,n,i,a,s,l=t;for(r=0,n=l.length;r>10)+55296,(t-65536&1023)+56320)}o(Htt,"charFromCodepoint");var nve=new Array(256),ive=new Array(256);for(tp=0;tp<256;tp++)nve[tp]=Oye(tp)?1:0,ive[tp]=Oye(tp);var tp;function Ytt(t,e){this.input=t,this.filename=e.filename||null,this.schema=e.schema||Zye,this.onWarning=e.onWarning||null,this.legacy=e.legacy||!1,this.json=e.json||!1,this.listener=e.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=t.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.firstTabInLine=-1,this.documents=[]}o(Ytt,"State$1");function ave(t,e){var r={name:t.filename,buffer:t.input.slice(0,-1),position:t.position,line:t.line,column:t.position-t.lineStart};return r.snippet=Met(r),new $s(e,r)}o(ave,"generateError");function Gt(t,e){throw ave(t,e)}o(Gt,"throwError");function TC(t,e){t.onWarning&&t.onWarning.call(null,ave(t,e))}o(TC,"throwWarning");var Pye={YAML:o(function(e,r,n){var i,a,s;e.version!==null&&Gt(e,"duplication of %YAML directive"),n.length!==1&&Gt(e,"YAML directive accepts exactly one argument"),i=/^([0-9]+)\.([0-9]+)$/.exec(n[0]),i===null&&Gt(e,"ill-formed argument of the YAML directive"),a=parseInt(i[1],10),s=parseInt(i[2],10),a!==1&&Gt(e,"unacceptable YAML version of the document"),e.version=n[0],e.checkLineBreaks=s<2,s!==1&&s!==2&&TC(e,"unsupported YAML version of the document")},"handleYamlDirective"),TAG:o(function(e,r,n){var i,a;n.length!==2&&Gt(e,"TAG directive accepts exactly two arguments"),i=n[0],a=n[1],tve.test(i)||Gt(e,"ill-formed tag handle (first argument) of the TAG directive"),Bf.call(e.tagMap,i)&&Gt(e,'there is a previously declared suffix for "'+i+'" tag handle'),rve.test(a)||Gt(e,"ill-formed tag prefix (second argument) of the TAG directive");try{a=decodeURIComponent(a)}catch{Gt(e,"tag prefix is malformed: "+a)}e.tagMap[i]=a},"handleTagDirective")};function Pf(t,e,r,n){var i,a,s,l;if(e1&&(t.result+=Wi.repeat(` +`,e-1))}o(dF,"writeFoldedLines");function Wtt(t,e,r){var n,i,a,s,l,u,h,f,d=t.kind,p=t.result,m;if(m=t.input.charCodeAt(t.position),Vs(m)||D1(m)||m===35||m===38||m===42||m===33||m===124||m===62||m===39||m===34||m===37||m===64||m===96||(m===63||m===45)&&(i=t.input.charCodeAt(t.position+1),Vs(i)||r&&D1(i)))return!1;for(t.kind="scalar",t.result="",a=s=t.position,l=!1;m!==0;){if(m===58){if(i=t.input.charCodeAt(t.position+1),Vs(i)||r&&D1(i))break}else if(m===35){if(n=t.input.charCodeAt(t.position-1),Vs(n))break}else{if(t.position===t.lineStart&&CC(t)||r&&D1(m))break;if(qc(m))if(u=t.line,h=t.lineStart,f=t.lineIndent,Ai(t,!1,-1),t.lineIndent>=e){l=!0,m=t.input.charCodeAt(t.position);continue}else{t.position=s,t.line=u,t.lineStart=h,t.lineIndent=f;break}}l&&(Pf(t,a,s,!1),dF(t,t.line-u),a=s=t.position,l=!1),rp(m)||(s=t.position+1),m=t.input.charCodeAt(++t.position)}return Pf(t,a,s,!1),t.result?!0:(t.kind=d,t.result=p,!1)}o(Wtt,"readPlainScalar");function qtt(t,e){var r,n,i;if(r=t.input.charCodeAt(t.position),r!==39)return!1;for(t.kind="scalar",t.result="",t.position++,n=i=t.position;(r=t.input.charCodeAt(t.position))!==0;)if(r===39)if(Pf(t,n,t.position,!0),r=t.input.charCodeAt(++t.position),r===39)n=t.position,t.position++,i=t.position;else return!0;else qc(r)?(Pf(t,n,i,!0),dF(t,Ai(t,!1,e)),n=i=t.position):t.position===t.lineStart&&CC(t)?Gt(t,"unexpected end of the document within a single quoted scalar"):(t.position++,i=t.position);Gt(t,"unexpected end of the stream within a single quoted scalar")}o(qtt,"readSingleQuotedScalar");function Xtt(t,e){var r,n,i,a,s,l;if(l=t.input.charCodeAt(t.position),l!==34)return!1;for(t.kind="scalar",t.result="",t.position++,r=n=t.position;(l=t.input.charCodeAt(t.position))!==0;){if(l===34)return Pf(t,r,t.position,!0),t.position++,!0;if(l===92){if(Pf(t,r,t.position,!0),l=t.input.charCodeAt(++t.position),qc(l))Ai(t,!1,e);else if(l<256&&nve[l])t.result+=ive[l],t.position++;else if((s=Vtt(l))>0){for(i=s,a=0;i>0;i--)l=t.input.charCodeAt(++t.position),(s=$tt(l))>=0?a=(a<<4)+s:Gt(t,"expected hexadecimal character");t.result+=Htt(a),t.position++}else Gt(t,"unknown escape sequence");r=n=t.position}else qc(l)?(Pf(t,r,n,!0),dF(t,Ai(t,!1,e)),r=n=t.position):t.position===t.lineStart&&CC(t)?Gt(t,"unexpected end of the document within a double quoted scalar"):(t.position++,n=t.position)}Gt(t,"unexpected end of the stream within a double quoted scalar")}o(Xtt,"readDoubleQuotedScalar");function jtt(t,e){var r=!0,n,i,a,s=t.tag,l,u=t.anchor,h,f,d,p,m,g=Object.create(null),y,v,x,b;if(b=t.input.charCodeAt(t.position),b===91)f=93,m=!1,l=[];else if(b===123)f=125,m=!0,l={};else return!1;for(t.anchor!==null&&(t.anchorMap[t.anchor]=l),b=t.input.charCodeAt(++t.position);b!==0;){if(Ai(t,!0,e),b=t.input.charCodeAt(t.position),b===f)return t.position++,t.tag=s,t.anchor=u,t.kind=m?"mapping":"sequence",t.result=l,!0;r?b===44&&Gt(t,"expected the node content, but found ','"):Gt(t,"missed comma between flow collection entries"),v=y=x=null,d=p=!1,b===63&&(h=t.input.charCodeAt(t.position+1),Vs(h)&&(d=p=!0,t.position++,Ai(t,!0,e))),n=t.line,i=t.lineStart,a=t.position,N1(t,e,bC,!1,!0),v=t.tag,y=t.result,Ai(t,!0,e),b=t.input.charCodeAt(t.position),(p||t.line===n)&&b===58&&(d=!0,b=t.input.charCodeAt(++t.position),Ai(t,!0,e),N1(t,e,bC,!1,!0),x=t.result),m?R1(t,l,g,v,y,x,n,i,a):d?l.push(R1(t,null,g,v,y,x,n,i,a)):l.push(y),Ai(t,!0,e),b=t.input.charCodeAt(t.position),b===44?(r=!0,b=t.input.charCodeAt(++t.position)):r=!1}Gt(t,"unexpected end of the stream within a flow collection")}o(jtt,"readFlowCollection");function Ktt(t,e){var r,n,i=aF,a=!1,s=!1,l=e,u=0,h=!1,f,d;if(d=t.input.charCodeAt(t.position),d===124)n=!1;else if(d===62)n=!0;else return!1;for(t.kind="scalar",t.result="";d!==0;)if(d=t.input.charCodeAt(++t.position),d===43||d===45)aF===i?i=d===43?Mye:Btt:Gt(t,"repeat of a chomping mode identifier");else if((f=Utt(d))>=0)f===0?Gt(t,"bad explicit indentation width of a block scalar; it cannot be less than one"):s?Gt(t,"repeat of an indentation width identifier"):(l=e+f-1,s=!0);else break;if(rp(d)){do d=t.input.charCodeAt(++t.position);while(rp(d));if(d===35)do d=t.input.charCodeAt(++t.position);while(!qc(d)&&d!==0)}for(;d!==0;){for(fF(t),t.lineIndent=0,d=t.input.charCodeAt(t.position);(!s||t.lineIndentl&&(l=t.lineIndent),qc(d)){u++;continue}if(t.lineIndente)&&u!==0)Gt(t,"bad indentation of a sequence entry");else if(t.lineIndente)&&(v&&(s=t.line,l=t.lineStart,u=t.position),N1(t,e,wC,!0,i)&&(v?g=t.result:y=t.result),v||(R1(t,d,p,m,g,y,s,l,u),m=g=y=null),Ai(t,!0,-1),b=t.input.charCodeAt(t.position)),(t.line===a||t.lineIndent>e)&&b!==0)Gt(t,"bad indentation of a mapping entry");else if(t.lineIndente?u=1:t.lineIndent===e?u=0:t.lineIndente?u=1:t.lineIndent===e?u=0:t.lineIndent tag; it should be "scalar", not "'+t.kind+'"'),d=0,p=t.implicitTypes.length;d"),t.result!==null&&g.kind!==t.kind&&Gt(t,"unacceptable node kind for !<"+t.tag+'> tag; it should be "'+g.kind+'", not "'+t.kind+'"'),g.resolve(t.result,t.tag)?(t.result=g.construct(t.result,t.tag),t.anchor!==null&&(t.anchorMap[t.anchor]=t.result)):Gt(t,"cannot resolve a node with !<"+t.tag+"> explicit tag")}return t.listener!==null&&t.listener("close",t),t.tag!==null||t.anchor!==null||f}o(N1,"composeNode");function trt(t){var e=t.position,r,n,i,a=!1,s;for(t.version=null,t.checkLineBreaks=t.legacy,t.tagMap=Object.create(null),t.anchorMap=Object.create(null);(s=t.input.charCodeAt(t.position))!==0&&(Ai(t,!0,-1),s=t.input.charCodeAt(t.position),!(t.lineIndent>0||s!==37));){for(a=!0,s=t.input.charCodeAt(++t.position),r=t.position;s!==0&&!Vs(s);)s=t.input.charCodeAt(++t.position);for(n=t.input.slice(r,t.position),i=[],n.length<1&&Gt(t,"directive name must not be less than one character in length");s!==0;){for(;rp(s);)s=t.input.charCodeAt(++t.position);if(s===35){do s=t.input.charCodeAt(++t.position);while(s!==0&&!qc(s));break}if(qc(s))break;for(r=t.position;s!==0&&!Vs(s);)s=t.input.charCodeAt(++t.position);i.push(t.input.slice(r,t.position))}s!==0&&fF(t),Bf.call(Pye,n)?Pye[n](t,n,i):TC(t,'unknown document directive "'+n+'"')}if(Ai(t,!0,-1),t.lineIndent===0&&t.input.charCodeAt(t.position)===45&&t.input.charCodeAt(t.position+1)===45&&t.input.charCodeAt(t.position+2)===45?(t.position+=3,Ai(t,!0,-1)):a&&Gt(t,"directives end mark is expected"),N1(t,t.lineIndent-1,wC,!1,!0),Ai(t,!0,-1),t.checkLineBreaks&&ztt.test(t.input.slice(e,t.position))&&TC(t,"non-ASCII line breaks are interpreted as content"),t.documents.push(t.result),t.position===t.lineStart&&CC(t)){t.input.charCodeAt(t.position)===46&&(t.position+=3,Ai(t,!0,-1));return}if(t.position"u"&&(r=e,e=null);var n=sve(t,r);if(typeof e!="function")return n;for(var i=0,a=n.length;i=55296&&r<=56319&&e+1=56320&&n<=57343)?(r-55296)*1024+n-56320+65536:r}o(kb,"codePointAt");function mve(t){var e=/^\n* /;return e.test(t)}o(mve,"needIndentIndicator");var gve=1,cF=2,yve=3,vve=4,L1=5;function Rrt(t,e,r,n,i,a,s,l){var u,h=0,f=null,d=!1,p=!1,m=n!==-1,g=-1,y=Lrt(kb(t,0))&&Drt(kb(t,t.length-1));if(e||s)for(u=0;u=65536?u+=2:u++){if(h=kb(t,u),!Ab(h))return L1;y=y&&$ye(h,f,l),f=h}else{for(u=0;u=65536?u+=2:u++){if(h=kb(t,u),h===Cb)d=!0,m&&(p=p||u-g-1>n&&t[g+1]!==" ",g=u);else if(!Ab(h))return L1;y=y&&$ye(h,f,l),f=h}p=p||m&&u-g-1>n&&t[g+1]!==" "}return!d&&!p?y&&!s&&!i(t)?gve:a===Sb?L1:cF:r>9&&mve(t)?L1:s?a===Sb?L1:cF:p?vve:yve}o(Rrt,"chooseScalarStyle");function Nrt(t,e,r,n,i){t.dump=function(){if(e.length===0)return t.quotingType===Sb?'""':"''";if(!t.noCompatMode&&(Trt.indexOf(e)!==-1||krt.test(e)))return t.quotingType===Sb?'"'+e+'"':"'"+e+"'";var a=t.indent*Math.max(1,r),s=t.lineWidth===-1?-1:Math.max(Math.min(t.lineWidth,40),t.lineWidth-a),l=n||t.flowLevel>-1&&r>=t.flowLevel;function u(h){return _rt(t,h)}switch(o(u,"testAmbiguity"),Rrt(e,l,t.indent,s,u,t.quotingType,t.forceQuotes&&!n,i)){case gve:return e;case cF:return"'"+e.replace(/'/g,"''")+"'";case yve:return"|"+Vye(e,t.indent)+Uye(zye(e,a));case vve:return">"+Vye(e,t.indent)+Uye(zye(Mrt(e,s),a));case L1:return'"'+Irt(e)+'"';default:throw new $s("impossible error: invalid scalar style")}}()}o(Nrt,"writeScalar");function Vye(t,e){var r=mve(t)?String(e):"",n=t[t.length-1]===` +`,i=n&&(t[t.length-2]===` +`||t===` +`),a=i?"+":n?"":"-";return r+a+` +`}o(Vye,"blockHeader");function Uye(t){return t[t.length-1]===` +`?t.slice(0,-1):t}o(Uye,"dropEndingNewline");function Mrt(t,e){for(var r=/(\n+)([^\n]*)/g,n=function(){var h=t.indexOf(` +`);return h=h!==-1?h:t.length,r.lastIndex=h,Hye(t.slice(0,h),e)}(),i=t[0]===` +`||t[0]===" ",a,s;s=r.exec(t);){var l=s[1],u=s[2];a=u[0]===" ",n+=l+(!i&&!a&&u!==""?` +`:"")+Hye(u,e),i=a}return n}o(Mrt,"foldString");function Hye(t,e){if(t===""||t[0]===" ")return t;for(var r=/ [^ ]/g,n,i=0,a,s=0,l=0,u="";n=r.exec(t);)l=n.index,l-i>e&&(a=s>i?s:l,u+=` +`+t.slice(i,a),i=a+1),s=l;return u+=` +`,t.length-i>e&&s>i?u+=t.slice(i,s)+` +`+t.slice(s+1):u+=t.slice(i),u.slice(1)}o(Hye,"foldLine");function Irt(t){for(var e="",r=0,n,i=0;i=65536?i+=2:i++)r=kb(t,i),n=Ua[r],!n&&Ab(r)?(e+=t[i],r>=65536&&(e+=t[i+1])):e+=n||Crt(r);return e}o(Irt,"escapeString");function Ort(t,e,r){var n="",i=t.tag,a,s,l;for(a=0,s=r.length;a"u"&&rh(t,e,null,!1,!1))&&(n!==""&&(n+=","+(t.condenseFlow?"":" ")),n+=t.dump);t.tag=i,t.dump="["+n+"]"}o(Ort,"writeFlowSequence");function Yye(t,e,r,n){var i="",a=t.tag,s,l,u;for(s=0,l=r.length;s"u"&&rh(t,e+1,null,!0,!0,!1,!0))&&((!n||i!=="")&&(i+=lF(t,e)),t.dump&&Cb===t.dump.charCodeAt(0)?i+="-":i+="- ",i+=t.dump);t.tag=a,t.dump=i||"[]"}o(Yye,"writeBlockSequence");function Prt(t,e,r){var n="",i=t.tag,a=Object.keys(r),s,l,u,h,f;for(s=0,l=a.length;s1024&&(f+="? "),f+=t.dump+(t.condenseFlow?'"':"")+":"+(t.condenseFlow?"":" "),rh(t,e,h,!1,!1)&&(f+=t.dump,n+=f));t.tag=i,t.dump="{"+n+"}"}o(Prt,"writeFlowMapping");function Brt(t,e,r,n){var i="",a=t.tag,s=Object.keys(r),l,u,h,f,d,p;if(t.sortKeys===!0)s.sort();else if(typeof t.sortKeys=="function")s.sort(t.sortKeys);else if(t.sortKeys)throw new $s("sortKeys must be a boolean or a function");for(l=0,u=s.length;l1024,d&&(t.dump&&Cb===t.dump.charCodeAt(0)?p+="?":p+="? "),p+=t.dump,d&&(p+=lF(t,e)),rh(t,e+1,f,!0,d)&&(t.dump&&Cb===t.dump.charCodeAt(0)?p+=":":p+=": ",p+=t.dump,i+=p));t.tag=a,t.dump=i||"{}"}o(Brt,"writeBlockMapping");function Wye(t,e,r){var n,i,a,s,l,u;for(i=r?t.explicitTypes:t.implicitTypes,a=0,s=i.length;a tag resolver accepts not "'+u+'" style');t.dump=n}return!0}return!1}o(Wye,"detectType");function rh(t,e,r,n,i,a,s){t.tag=null,t.dump=r,Wye(t,r,!1)||Wye(t,r,!0);var l=lve.call(t.dump),u=n,h;n&&(n=t.flowLevel<0||t.flowLevel>e);var f=l==="[object Object]"||l==="[object Array]",d,p;if(f&&(d=t.duplicates.indexOf(r),p=d!==-1),(t.tag!==null&&t.tag!=="?"||p||t.indent!==2&&e>0)&&(i=!1),p&&t.usedDuplicates[d])t.dump="*ref_"+d;else{if(f&&p&&!t.usedDuplicates[d]&&(t.usedDuplicates[d]=!0),l==="[object Object]")n&&Object.keys(t.dump).length!==0?(Brt(t,e,t.dump,i),p&&(t.dump="&ref_"+d+t.dump)):(Prt(t,e,t.dump),p&&(t.dump="&ref_"+d+" "+t.dump));else if(l==="[object Array]")n&&t.dump.length!==0?(t.noArrayIndent&&!s&&e>0?Yye(t,e-1,t.dump,i):Yye(t,e,t.dump,i),p&&(t.dump="&ref_"+d+t.dump)):(Ort(t,e,t.dump),p&&(t.dump="&ref_"+d+" "+t.dump));else if(l==="[object String]")t.tag!=="?"&&Nrt(t,t.dump,e,a,u);else{if(l==="[object Undefined]")return!1;if(t.skipInvalid)return!1;throw new $s("unacceptable kind of an object to dump "+l)}t.tag!==null&&t.tag!=="?"&&(h=encodeURI(t.tag[0]==="!"?t.tag.slice(1):t.tag).replace(/!/g,"%21"),t.tag[0]==="!"?h="!"+h:h.slice(0,18)==="tag:yaml.org,2002:"?h="!!"+h.slice(18):h="!<"+h+">",t.dump=h+" "+t.dump)}return!0}o(rh,"writeNode");function Frt(t,e){var r=[],n=[],i,a;for(uF(t,r,n),i=0,a=n.length;it.replace(/\r\n?/g,` +`).replace(/<(\w+)([^>]*)>/g,(e,r,n)=>"<"+r+n.replace(/="([^"]*)"/g,"='$1'")+">"),"cleanupText"),Hrt=o(t=>{let{text:e,metadata:r}=wve(t),{displayMode:n,title:i,config:a={}}=r;return n&&(a.gantt||(a.gantt={}),a.gantt.displayMode=n),{title:i,config:a,text:e}},"processFrontmatter"),Yrt=o(t=>{let e=Lt.detectInit(t)??{},r=Lt.detectDirective(t,"wrap");return Array.isArray(r)?e.wrap=r.some(({type:n})=>n==="wrap"):r?.type==="wrap"&&(e.wrap=!0),{text:EX(t),directive:e}},"processDirectives");function gF(t){let e=Urt(t),r=Hrt(e),n=Yrt(r.text),i=Ts(r.config,n.directive);return t=Rye(n.text),{code:t,title:r.title,config:i}}o(gF,"preprocessDiagram");Z7();Kb();xr();function Tve(t){let e=new TextEncoder().encode(t),r=Array.from(e,n=>String.fromCodePoint(n)).join("");return btoa(r)}o(Tve,"toBase64");var Wrt=5e4,qrt="graph TB;a[Maximum text size in diagram exceeded];style a fill:#faa",Xrt="sandbox",jrt="loose",Krt="http://www.w3.org/2000/svg",Qrt="http://www.w3.org/1999/xlink",Zrt="http://www.w3.org/1999/xhtml",Jrt="100%",ent="100%",tnt="border:0;margin:0;",rnt="margin:0",nnt="allow-top-navigation-by-user-activation allow-popups",int='The "iframe" tag is not supported by your browser.',ant=["foreignobject"],snt=["dominant-baseline"];function Ave(t){let e=gF(t);return Q1(),jz(e.config??{}),e}o(Ave,"processAndSetConfigs");async function ont(t,e){k1();try{let{code:r,config:n}=Ave(t);return{diagramType:(await _ve(r)).type,config:n}}catch(r){if(e?.suppressErrors)return!1;throw r}}o(ont,"parse");var kve=o((t,e,r=[])=>` +.${t} ${e} { ${r.join(" !important; ")} !important; }`,"cssImportantStyles"),lnt=o((t,e=new Map)=>{let r="";if(t.themeCSS!==void 0&&(r+=` +${t.themeCSS}`),t.fontFamily!==void 0&&(r+=` +:root { --mermaid-font-family: ${t.fontFamily}}`),t.altFontFamily!==void 0&&(r+=` +:root { --mermaid-alt-font-family: ${t.altFontFamily}}`),e instanceof Map){let s=t.htmlLabels??t.flowchart?.htmlLabels?["> *","span"]:["rect","polygon","ellipse","circle","path"];e.forEach(l=>{Qt(l.styles)||s.forEach(u=>{r+=kve(l.id,u,l.styles)}),Qt(l.textStyles)||(r+=kve(l.id,"tspan",(l?.textStyles||[]).map(u=>u.replace("color","fill"))))})}return r},"createCssStyles"),cnt=o((t,e,r,n)=>{let i=lnt(t,r),a=D$(e,i,t.themeVariables);return xC(Cye(`${n}{${a}}`),Sye)},"createUserStyles"),unt=o((t="",e,r)=>{let n=t;return!r&&!e&&(n=n.replace(/marker-end="url\([\d+./:=?A-Za-z-]*?#/g,'marker-end="url(#')),n=to(n),n=n.replace(/
    /g,"
    "),n},"cleanUpSvgCode"),hnt=o((t="",e)=>{let r=e?.viewBox?.baseVal?.height?e.viewBox.baseVal.height+"px":ent,n=Tve(`${t}`);return``},"putIntoIFrame"),Eve=o((t,e,r,n,i)=>{let a=t.append("div");a.attr("id",r),n&&a.attr("style",n);let s=a.append("svg").attr("id",e).attr("width","100%").attr("xmlns",Krt);return i&&s.attr("xmlns:xlink",i),s.append("g"),t},"appendDivSvgG");function Cve(t,e){return t.append("iframe").attr("id",e).attr("style","width: 100%; height: 100%;").attr("sandbox","")}o(Cve,"sandboxedIframe");var fnt=o((t,e,r,n)=>{t.getElementById(e)?.remove(),t.getElementById(r)?.remove(),t.getElementById(n)?.remove()},"removeExistingElements"),dnt=o(async function(t,e,r){k1();let n=Ave(e);e=n.code;let i=Or();V.debug(i),e.length>(i?.maxTextSize??Wrt)&&(e=qrt);let a="#"+t,s="i"+t,l="#"+s,u="d"+t,h="#"+u,f=o(()=>{let I=$e(p?l:h).node();I&&"remove"in I&&I.remove()},"removeTempElements"),d=$e("body"),p=i.securityLevel===Xrt,m=i.securityLevel===jrt,g=i.fontFamily;if(r!==void 0){if(r&&(r.innerHTML=""),p){let k=Cve($e(r),s);d=$e(k.nodes()[0].contentDocument.body),d.node().style.margin=0}else d=$e(r);Eve(d,t,u,`font-family: ${g}`,Qrt)}else{if(fnt(document,t,u,s),p){let k=Cve($e("body"),s);d=$e(k.nodes()[0].contentDocument.body),d.node().style.margin=0}else d=$e("body");Eve(d,t,u)}let y,v;try{y=await _1.fromText(e,{title:n.title})}catch(k){if(i.suppressErrorRendering)throw f(),k;y=await _1.fromText("error"),v=k}let x=d.select(h).node(),b=y.type,w=x.firstChild,S=w.firstChild,T=y.renderer.getClasses?.(e,y),E=cnt(i,b,T,a),_=document.createElement("style");_.innerHTML=E,w.insertBefore(_,S);try{await y.renderer.draw(e,t,fx,y)}catch(k){throw i.suppressErrorRendering?f():Ede.draw(e,t,fx),k}let A=d.select(`${h} svg`),L=y.db.getAccTitle?.(),M=y.db.getAccDescription?.();mnt(b,A,L,M),d.select(`[id="${t}"]`).selectAll("foreignobject > *").attr("xmlns",Zrt);let N=d.select(h).node().innerHTML;if(V.debug("config.arrowMarkerAbsolute",i.arrowMarkerAbsolute),N=unt(N,p,yr(i.arrowMarkerAbsolute)),p){let k=d.select(h+" svg").node();N=hnt(N,k)}else m||(N=Sve.default.sanitize(N,{ADD_TAGS:ant,ADD_ATTR:snt}));if(Dye(),v)throw v;return f(),{diagramType:b,svg:N,bindFunctions:y.db.bindFunctions}},"render");function pnt(t={}){let e=On({},t);e?.fontFamily&&!e.themeVariables?.fontFamily&&(e.themeVariables||(e.themeVariables={}),e.themeVariables.fontFamily=e.fontFamily),Wz(e),e?.theme&&e.theme in Co?e.themeVariables=Co[e.theme].getThemeVariables(e.themeVariables):e&&(e.themeVariables=Co.default.getThemeVariables(e.themeVariables));let r=typeof e=="object"?n7(e):i7();$1(r.logLevel),k1()}o(pnt,"initialize");var _ve=o((t,e={})=>{let{code:r}=gF(t);return _1.fromText(r,e)},"getDiagramFromText");function mnt(t,e,r,n){Aye(e,t),_ye(e,r,n,e.attr("id"))}o(mnt,"addA11yInfo");var Ff=Object.freeze({render:dnt,parse:ont,getDiagramFromText:_ve,initialize:pnt,getConfig:Or,setConfig:Zb,getSiteConfig:i7,updateSiteConfig:qz,reset:o(()=>{Q1()},"reset"),globalReset:o(()=>{Q1(uh)},"globalReset"),defaultConfig:uh});$1(Or().logLevel);Q1(Or());oT();xr();var gnt=o((t,e,r)=>{V.warn(t),r9(t)?(r&&r(t.str,t.hash),e.push({...t,message:t.str,error:t})):(r&&r(t),t instanceof Error&&e.push({str:t.message,message:t.message,hash:t.name,error:t}))},"handleError"),Lve=o(async function(t={querySelector:".mermaid"}){try{await ynt(t)}catch(e){if(r9(e)&&V.error(e.str),nh.parseError&&nh.parseError(e),!t.suppressErrors)throw V.error("Use the suppressErrors option to suppress these errors"),e}},"run"),ynt=o(async function({postRenderCallback:t,querySelector:e,nodes:r}={querySelector:".mermaid"}){let n=Ff.getConfig();V.debug(`${t?"":"No "}Callback function found`);let i;if(r)i=r;else if(e)i=document.querySelectorAll(e);else throw new Error("Nodes and querySelector are both undefined");V.debug(`Found ${i.length} diagrams`),n?.startOnLoad!==void 0&&(V.debug("Start On Load: "+n?.startOnLoad),Ff.updateSiteConfig({startOnLoad:n?.startOnLoad}));let a=new Lt.InitIDGenerator(n.deterministicIds,n.deterministicIDSeed),s,l=[];for(let u of Array.from(i)){V.info("Rendering diagram: "+u.id);if(u.getAttribute("data-processed"))continue;u.setAttribute("data-processed","true");let h=`mermaid-${a.next()}`;s=u.innerHTML,s=Gb(Lt.entityDecode(s)).trim().replace(//gi,"
    ");let f=Lt.detectInit(s);f&&V.debug("Detected early reinit: ",f);try{let{svg:d,bindFunctions:p}=await Mve(h,s,u);u.innerHTML=d,t&&await t(h),p&&p(u)}catch(d){gnt(d,l,nh.parseError)}}if(l.length>0)throw l[0]},"runThrowsErrors"),Dve=o(function(t){Ff.initialize(t)},"initialize"),vnt=o(async function(t,e,r){V.warn("mermaid.init is deprecated. Please use run instead."),t&&Dve(t);let n={postRenderCallback:r,querySelector:".mermaid"};typeof e=="string"?n.querySelector=e:e&&(e instanceof HTMLElement?n.nodes=[e]:n.nodes=e),await Lve(n)},"init"),xnt=o(async(t,{lazyLoad:e=!0}={})=>{k1(),Ub(...t),e===!1&&await lye()},"registerExternalDiagrams"),Rve=o(function(){if(nh.startOnLoad){let{startOnLoad:t}=Ff.getConfig();t&&nh.run().catch(e=>V.error("Mermaid failed to initialize",e))}},"contentLoaded");if(typeof document<"u"){window.addEventListener("load",Rve,!1)}var bnt=o(function(t){nh.parseError=t},"setParseErrorHandler"),SC=[],yF=!1,Nve=o(async()=>{if(!yF){for(yF=!0;SC.length>0;){let t=SC.shift();if(t)try{await t()}catch(e){V.error("Error executing queue",e)}}yF=!1}},"executeQueue"),wnt=o(async(t,e)=>new Promise((r,n)=>{let i=o(()=>new Promise((a,s)=>{Ff.parse(t,e).then(l=>{a(l),r(l)},l=>{V.error("Error parsing",l),nh.parseError?.(l),s(l),n(l)})}),"performCall");SC.push(i),Nve().catch(n)}),"parse"),Mve=o((t,e,r)=>new Promise((n,i)=>{let a=o(()=>new Promise((s,l)=>{Ff.render(t,e,r).then(u=>{s(u),n(u)},u=>{V.error("Error parsing",u),nh.parseError?.(u),l(u),i(u)})}),"performCall");SC.push(a),Nve().catch(i)}),"render"),nh={startOnLoad:!0,mermaidAPI:Ff,parse:wnt,render:Mve,init:vnt,run:Lve,registerExternalDiagrams:xnt,registerLayoutLoaders:gD,initialize:Dve,parseError:void 0,contentLoaded:Rve,setParseErrorHandler:bnt,detectType:lp,registerIconPacks:Fb},Tnt=nh;return $ve(knt);})(); +/*! Check if previously processed */ +/*! + * Wait for document loaded before starting the execution + */ +/*! Bundled license information: + +dompurify/dist/purify.js: + (*! @license DOMPurify 3.1.6 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.1.6/LICENSE *) + +lodash-es/lodash.js: + (** + * @license + * Lodash (Custom Build) + * Build: `lodash modularize exports="es" -o ./` + * Copyright OpenJS Foundation and other contributors + * Released under MIT license + * Based on Underscore.js 1.8.3 + * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + *) + +cytoscape/dist/cytoscape.esm.mjs: + (*! + Embeddable Minimum Strictly-Compliant Promises/A+ 1.1.1 Thenable + Copyright (c) 2013-2014 Ralf S. Engelschall (http://engelschall.com) + Licensed under The MIT License (http://opensource.org/licenses/MIT) + *) + (*! + Event object based on jQuery events, MIT license + + https://jquery.org/license/ + https://tldrlegal.com/license/mit-license + https://github.com/jquery/jquery/blob/master/src/event.js + *) + (*! Bezier curve function generator. Copyright Gaetan Renaudeau. MIT License: http://en.wikipedia.org/wiki/MIT_License *) + (*! Runge-Kutta spring physics function generator. Adapted from Framer.js, copyright Koen Bok. MIT License: http://en.wikipedia.org/wiki/MIT_License *) + +js-yaml/dist/js-yaml.mjs: + (*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT *) +*/ +globalThis.mermaid = globalThis.__esbuild_esm_mermaid.default; diff --git a/packages/contracts-bedrock/book/src/SUMMARY.md b/packages/contracts-bedrock/book/src/SUMMARY.md new file mode 100644 index 00000000000..9fa9538ae2a --- /dev/null +++ b/packages/contracts-bedrock/book/src/SUMMARY.md @@ -0,0 +1,15 @@ +# Summary + +- [Introduction](./introduction.md) + +# Contributing Guide + +- [Style Guide](./contributing/style-guide.md) +- [Interfaces](./contributing/interfaces.md) +- [OPCM](./contributing/opcm.md) + +# Policies + +- [Solidity Upgrades](./policies/solidity-upgrades.md) +- [Code Freezes](./policies/code-freezes.md) +- [Versioning](./policies/versioning.md) \ No newline at end of file diff --git a/packages/contracts-bedrock/book/src/contributing/code-freezes.md b/packages/contracts-bedrock/book/src/contributing/code-freezes.md new file mode 100644 index 00000000000..9b49560dae8 --- /dev/null +++ b/packages/contracts-bedrock/book/src/contributing/code-freezes.md @@ -0,0 +1 @@ +# Code Freezes diff --git a/packages/contracts-bedrock/meta/INTERFACES.md b/packages/contracts-bedrock/book/src/contributing/interfaces.md similarity index 99% rename from packages/contracts-bedrock/meta/INTERFACES.md rename to packages/contracts-bedrock/book/src/contributing/interfaces.md index 89219c43f67..5eb18caaf97 100644 --- a/packages/contracts-bedrock/meta/INTERFACES.md +++ b/packages/contracts-bedrock/book/src/contributing/interfaces.md @@ -1,6 +1,4 @@ -# INTERFACES.md - -## Introduction +# Interfaces This document outlines the guidelines and best practices for using and creating interfaces in the `contracts-bedrock` package. diff --git a/packages/contracts-bedrock/book/src/contributing/opcm.md b/packages/contracts-bedrock/book/src/contributing/opcm.md new file mode 100644 index 00000000000..7ade8025ac5 --- /dev/null +++ b/packages/contracts-bedrock/book/src/contributing/opcm.md @@ -0,0 +1,98 @@ +# OP Contracts Manager (OPCM) + +The OPCM is an important smart contract that is used to orchestrate OP Chain deployments and upgrades. It is responsible +for the following: + +1. Keeping track of the canonical implementation contracts for each [contracts release][versioning]. +2. Deploying new L1 contracts for each OP Chain. +3. Upgrading from one contract release to another. +4. Maintaining the fault proof system by adding game types or updating prestates. + +All contract upgrades that touch live chains **must** be performed via the OPCM. This guide will walk you through +the OPCM's architecture, and how to hook your contracts into it. + +[versioning]: ../policies/versioning.md + +## Architecture + +The OPCM consists of multiple contracts: + +1. `OPContractsManager`, which serves as the entry point. +2. `OPContractsManagerGameTypeAdder`, which is used to add new game types and update prestates. +3. `OPContractsManagerDeployer`, which is used to deploy new OP Chains. +4. `OPContractsManagerUpgrader`, which is used to upgrade existing OP Chains. +5. `OPContractsManagerContractsContainer`, which is a repository for contract implementations and blueprints. + +They fit together like the diagram below: + +```mermaid +stateDiagram-v2 + state OpContractsManager { + direction LR + deploy() --> OpContractsManagerDeployer: staticcall + upgrade() --> OpContractsManagerUpgrader: delegatecall + addGameType() --> OpContractsManagerGameTypeAdder: delegatecall + updatePrestate() --> OpContractsManagerGameTypeAdder: delegatecall + } + + state Logic { + OpContractsManagerDeployer --> OpContractsManagerContractsContainer: getImplementations()/getBlueprints() + OpContractsManagerUpgrader --> OpContractsManagerContractsContainer: getImplementations()/getBlueprints() + OpContractsManagerGameTypeAdder --> OpContractsManagerContractsContainer: getImplementations()/getBlueprints() + } + + state Implementations { + OpContractsManagerContractsContainer + } +``` + +One OPCM is deployed per smart contract release per chain. Each OPCM supports deploying a new chain at its +corresponding smart contract release, and upgrading existing chains from one version prior to its corresponding +smart contract release. Chains that are multiple versions behind must be upgraded in multiple stages across multiple +OPCMs. + +The OPCM supports upgrading Superchain-wide contracts like `ProtocolVersions` and the `SuperchainConfig`. The OPCM will +perform the upgrade when the user calling the `upgrade` method is also the `UpgradeController`. + +## Usage + +Typically, users do not call into the OPCM directly. Instead, they use [OP Deployer][deployer] to either directly +call `deploy` when spinning up a new chain or generate calldate for use with `upgrade`. + +If you want to call the OPCM directly, check out the implementation to see exactly what the inputs and outputs are +to each method. This changes between releases, and will not be covered directly here. + +[deployer]: https://devdocs.optimism.io/op-deployer + +## Updating the OPCM + +Whenever you make updates to in-protocol contracts, you'll need to make corresponding changes inside the OPCM. While +the details of each change will vary, we've included some general guidelines below. + +### Updating Logic Contracts + +As their name implies, the logic contracts contain the actual logic used to deploy or upgrade contracts. When +modifying these contracts keep the following tips in mind: + +- The `deploy` method can typically be modified in-place since the deployment process doesn't change much from + release to release. For example, most changes to the `deploy` method will involve either adding a new contract or + modifying the constructor/initializer for existing contracts. You can use the existing implementation as a guide. +- The `upgrade` method changes much more frequently. That said, you can still use the existing implementation as a + guide. Just make sure to delete any old upgrade code that is no longer needed. The `OPContractsManagerUpgrader` + logic contract also contains helpers for things like deploying new dispute games and upgrading proxies to new + implementations. See the `upgradeTo` method for an example. +- The `upgrade` method will _always_ set the RC on the OPCM to false when called by the upgrade controller. It will + only sometimes (depending on your specific upgrade) upgrade Superchain contracts. + +### Fork Tests + +The OPCM is tested using fork tests. These tests fork mainnet and "run" the upgrade against OP Mainnet. This allows +us to validate that the upgrades work as expected in CI prior to deploying them to betanets or production. + +To run fork tests, run `just test-upgrade`. You will need to set `ETH_RPC_URL` to an archival mainnet node. + +When multiple upgrades are in flight at the same time, the fork tests stack upgrades on top of one another. Since the +tip of `develop` must contain the implementation for the latest upgrade only, fork tests that run upgrades prior to +the latest one must use deployed instances of the OPCM. For example, as of this writing upgrades 13, 14, and 15 are +all in flight. Therefore, the fork tests will use deployed versions of the OPCM for upgrades 13 and 14 and whatever +is on `develop` for upgrade 15. See `OPContractsManager.t.sol` for the implementation of the fork tests. \ No newline at end of file diff --git a/packages/contracts-bedrock/meta/STYLE_GUIDE.md b/packages/contracts-bedrock/book/src/contributing/style-guide.md similarity index 99% rename from packages/contracts-bedrock/meta/STYLE_GUIDE.md rename to packages/contracts-bedrock/book/src/contributing/style-guide.md index 00af13d5a5b..0a7e9257717 100644 --- a/packages/contracts-bedrock/meta/STYLE_GUIDE.md +++ b/packages/contracts-bedrock/book/src/contributing/style-guide.md @@ -127,7 +127,7 @@ Additionally, contracts MUST use the following versioning scheme when incrementi - `minor` releases are to be used for changes that modify bytecode OR changes that expand the contract ABI provided that these changes do NOT break the existing interface. - `major` releases are to be used for changes that break the existing contract interface OR changes that modify the security model of a contract. -The remainder of the contract versioning and release process can be found in [`VERSIONING.md](./VERSIONING.md). +The remainder of the contract versioning and release process can be found in [`VERSIONING.md](../policies/VERSIONING.md). #### Exceptions diff --git a/packages/contracts-bedrock/book/src/introduction.md b/packages/contracts-bedrock/book/src/introduction.md new file mode 100644 index 00000000000..a6c0c841926 --- /dev/null +++ b/packages/contracts-bedrock/book/src/introduction.md @@ -0,0 +1,56 @@ +# Introduction + +This package contains the L1 and L2 smart contracts for the OP Stack. Detailed specifications for the contracts +contained within this package can be found at [specs.optimism.io][specs]. High-level information about these contracts +can be found within this book and within the [Optimism Developer Docs][docs]. For more information about contributing +to OP Stack smart contract development, read on in this book. + +[specs]: https://specs.optimism.io +[docs]: https://docs.optimism.io + +## Contributing + +### Contributing Guide + +Contributions to the OP Stack are always welcome. Please refer to the [CONTRIBUTING.md][contrib] for general information +about how to contribute to the OP Stack monorepo. + +[contrib]: https://github.com/ethereum-optimism/optimism/blob/develop/CONTRIBUTING.md + +When contributing to the `contracts-bedrock` package there are some additional steps you should follow. These have been +conveniently packaged into a just command which you should run before pushing your changes. + +```bash +just pre-pr +``` + +### Style Guide + +OP Stack smart contracts should be written according to the [style guide][style-guide] found within this book. +Maintaining a consistent code style makes code easier to review and maintain, ultimately making the development process +safer. + +[style-guide]: ./contributing/style-guide.md + +### Contract Interfaces + +OP Stack smart contracts use contract interfaces in a relatively unique way. Please refer to the [interfaces guide] +[ifaces] to read more about how the OP Stack uses contract interfaces. + +[ifaces]: ./contributing/interfaces.md + +### Solidity Versioning + +OP Stack smart contracts are designed to utilize a single, consistent Solidity version. Please refer to +the [Solidity upgrades][solidity-upgrades] guide to understand the process for updating to newer Solidity versions. + +[solidity-upgrades]: ./policies/solidity-upgrades.md + +### Frozen Code + +From time to time we need to ensure that certain files remain frozen, as they may be under audit or a large PR is in the +works and we wish to avoid a large rebase. In order to enforce this, a hardcoded list of contracts is stored in +`./scripts/checks/check-frozen-files.sh`. Any change which affects the resulting init or source code of a contract which +is not allowed to be modified will prevent merging to the `develop` branch. + +In order to remove a file from the freeze it must be removed from the check file. \ No newline at end of file diff --git a/packages/contracts-bedrock/meta/CODE_FREEZES.md b/packages/contracts-bedrock/book/src/policies/code-freezes.md similarity index 100% rename from packages/contracts-bedrock/meta/CODE_FREEZES.md rename to packages/contracts-bedrock/book/src/policies/code-freezes.md diff --git a/packages/contracts-bedrock/meta/SOLIDITY_UPGRADES.md b/packages/contracts-bedrock/book/src/policies/solidity-upgrades.md similarity index 100% rename from packages/contracts-bedrock/meta/SOLIDITY_UPGRADES.md rename to packages/contracts-bedrock/book/src/policies/solidity-upgrades.md diff --git a/packages/contracts-bedrock/meta/VERSIONING.md b/packages/contracts-bedrock/book/src/policies/versioning.md similarity index 97% rename from packages/contracts-bedrock/meta/VERSIONING.md rename to packages/contracts-bedrock/book/src/policies/versioning.md index 836eff618e9..c1272aaa7fe 100644 --- a/packages/contracts-bedrock/meta/VERSIONING.md +++ b/packages/contracts-bedrock/book/src/policies/versioning.md @@ -5,7 +5,7 @@ However, there are some changes to accommodate the unique nature of smart contra There are five parts to the versioning and release process: -- [Semver Rules](#semver-rules): Follows the rules defined in the [style guide](./STYLE_GUIDE.md#versioning) for when to bump major, minor, and patch versions in individual contracts. +- [Semver Rules](#semver-rules): Follows the rules defined in the [style guide](contributing/STYLE_GUIDE.mdLE_GUIDE.md#versioning) for when to bump major, minor, and patch versions in individual contracts. - [Individual Contract Versioning](#individual-contract-versioning): The versioning scheme for individual contracts and includes beta, release candidate, and feature tags. - [Monorepo Contracts Release Versioning](#monorepo-contracts-release-versioning): The versioning scheme for monorepo smart contract releases. - [Release Process](#release-process): The process for deploying contracts, creating a governance proposal, and the required associated releases. @@ -18,7 +18,7 @@ There are five parts to the versioning and release process: ## Semver Rules -Version increments follow the [style guide rules](./STYLE_GUIDE.md#versioning) for when to bump major, minor, and patch versions in individual contracts: +Version increments follow the [style guide rules](contributing/STYLE_GUIDE.mdLE_GUIDE.md#versioning) for when to bump major, minor, and patch versions in individual contracts: > - `patch` releases are to be used only for changes that do NOT modify contract bytecode (such as updating comments). > - `minor` releases are to be used for changes that modify bytecode OR changes that expand the contract ABI provided that these changes do NOT break the existing interface. diff --git a/packages/contracts-bedrock/meta/POLICY.md b/packages/contracts-bedrock/meta/POLICY.md deleted file mode 100644 index 9813379f31b..00000000000 --- a/packages/contracts-bedrock/meta/POLICY.md +++ /dev/null @@ -1,41 +0,0 @@ -**Table of Contents** - - - -- [Policy](#policy) - - [Contributing](#contributing) - - [Versioning Policy](#versioning-policy) - - [Code Freeze Policy](#code-freeze-policy) - - [Upgrade Policy](#upgrade-policy) - - [Style Guide](#style-guide) - - [Revert Data](#revert-data) - - - -# Policy - -This document outlines upgrade policies regarding the OP Stack codebase. - -## Contributing - -For any policies on contributing, please see [CONTRIBUTING](./CONTRIBUTING.md) - -## Versioning Policy - -For our versioning policy, please see our policy on [VERSIONING](./VERSIONING.md) - -## Code Freeze Policy - -For our code freeze policy, please see our doc on [CODE FREEZES](./CODE_FREEZES.md) - -## Upgrade Policy - -For the solidity upgrade policy, please see our doc on [SOLIDITY UPGRADES](./SOLIDITY_UPGRADES.md) - -## Style Guide - -For an indepth review of the code style used in the OP Stack contracts, please see our [STYLE GUIDE](./STYLE_GUIDE.md) - -## Revert Data - -Revert data may be changed in the future, and is not a reliable interface for external consumers. Contracts should not depend on specific revert data returned by OP Stack contracts, which can be changed during any future OP Stack contract upgrades. Revert data includes both custom errors returned by contracts, as a well as revert strings. From 2123532c3be498199db3bc73c9b518a15ff57355 Mon Sep 17 00:00:00 2001 From: Maurelian Date: Wed, 5 Mar 2025 09:58:15 -0500 Subject: [PATCH 069/130] Adress and remove todo (#14646) --- packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol index 337adcdc8eb..6b6f60bf778 100644 --- a/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol +++ b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol @@ -49,9 +49,6 @@ contract OptimismPortal2_Test is CommonTest { assertEq(address(opImpl.systemConfig()), address(0)); assertEq(address(opImpl.superchainConfig()), address(0)); assertEq(opImpl.respectedGameType().raw(), deploy.cfg().respectedGameType()); - - // TODO(opcm upgrades): remove skip once upgrade path is implemented - returnIfForkTest("OptimismPortal2_Test: l2Sender is nonzero on OP mainnet"); assertEq(opImpl.l2Sender(), address(0)); } From 43640aeb15a20fdb755502f32ae9ba6f8c31be01 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:58:08 +0000 Subject: [PATCH 070/130] dependabot(gomod): bump github.com/klauspost/compress (#14436) Bumps [github.com/klauspost/compress](https://github.com/klauspost/compress) from 1.17.11 to 1.18.0. - [Release notes](https://github.com/klauspost/compress/releases) - [Changelog](https://github.com/klauspost/compress/blob/master/.goreleaser.yml) - [Commits](https://github.com/klauspost/compress/compare/v1.17.11...v1.18.0) --- updated-dependencies: - dependency-name: github.com/klauspost/compress dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index e1304a0328d..4e94b594e5e 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/holiman/uint256 v1.3.2 github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ds-leveldb v0.5.0 - github.com/klauspost/compress v1.17.11 + github.com/klauspost/compress v1.18.0 github.com/kurtosis-tech/kurtosis/api/golang v1.4.4 github.com/libp2p/go-libp2p v0.36.2 github.com/libp2p/go-libp2p-mplex v0.9.0 diff --git a/go.sum b/go.sum index e1c0086e5ce..c63ef245350 100644 --- a/go.sum +++ b/go.sum @@ -438,8 +438,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= From 8de0d080979bc45d3cbc3668e670f87db7402501 Mon Sep 17 00:00:00 2001 From: smartcontracts Date: Wed, 5 Mar 2025 10:00:03 -0600 Subject: [PATCH 071/130] feat: update message passing for EIP7623 (#14608) * feat: update message passing for EIP7623 Updates the way that gas limits are handled in the message passing system to account for EIP-7623. Most significant change is to make the baseGas calculation inside of CrossDomainMessenger more principled. * fix: clean up XDM tests * fix: apply EF byte change to all code * fix: add OPPrestateUpdater to allow list * fix: add changes to OPCM upgrade --- .../universal/ICrossDomainMessenger.sol | 3 + .../scripts/checks/check-frozen-files.sh | 1 + .../snapshots/abi/L1CrossDomainMessenger.json | 39 ++++ .../snapshots/abi/L2CrossDomainMessenger.json | 39 ++++ .../snapshots/semver-lock.json | 40 ++--- .../src/L1/L1CrossDomainMessenger.sol | 4 +- .../src/L1/L1ERC721Bridge.sol | 4 +- .../src/L1/L1StandardBridge.sol | 4 +- .../src/L1/OPContractsManager.sol | 18 +- .../src/L1/OptimismPortal2.sol | 6 +- .../src/L1/OptimismPortalInterop.sol | 4 +- .../src/L2/L2CrossDomainMessenger.sol | 4 +- .../src/L2/L2ERC721Bridge.sol | 4 +- .../src/L2/L2StandardBridge.sol | 4 +- .../src/L2/L2StandardBridgeInterop.sol | 4 +- .../contracts-bedrock/src/libraries/EOA.sol | 5 +- .../src/universal/CrossDomainMessenger.sol | 70 ++++++-- .../test/L1/L1CrossDomainMessenger.t.sol | 167 +++++++++++++++++- .../test/libraries/EOA.t.sol | 23 +++ .../contracts-bedrock/test/mocks/Callers.sol | 32 ++++ .../test/universal/CrossDomainMessenger.t.sol | 95 ++++++++++ .../test/universal/Specs.t.sol | 3 + 22 files changed, 513 insertions(+), 60 deletions(-) diff --git a/packages/contracts-bedrock/interfaces/universal/ICrossDomainMessenger.sol b/packages/contracts-bedrock/interfaces/universal/ICrossDomainMessenger.sol index 256b09fa56e..3f0b7774002 100644 --- a/packages/contracts-bedrock/interfaces/universal/ICrossDomainMessenger.sol +++ b/packages/contracts-bedrock/interfaces/universal/ICrossDomainMessenger.sol @@ -17,6 +17,9 @@ interface ICrossDomainMessenger { function RELAY_CONSTANT_OVERHEAD() external view returns (uint64); function RELAY_GAS_CHECK_BUFFER() external view returns (uint64); function RELAY_RESERVED_GAS() external view returns (uint64); + function TX_BASE_GAS() external view returns (uint64); + function FLOOR_CALLDATA_OVERHEAD() external view returns (uint64); + function ENCODING_OVERHEAD() external view returns (uint64); function baseGas(bytes memory _message, uint32 _minGasLimit) external pure returns (uint64); function failedMessages(bytes32) external view returns (bool); function messageNonce() external view returns (uint256); diff --git a/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh b/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh index f400ab5cab5..77eec5660b8 100755 --- a/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh +++ b/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh @@ -54,6 +54,7 @@ ALLOWED_FILES=( "src/L1/L1StandardBridge.sol" "src/L1/OPContractsManager.sol" "src/L1/OPContractsManagerInterop.sol" + "src/L1/OPPrestateUpdater.sol" "src/L1/OptimismPortal2.sol" "src/L1/OptimismPortalInterop.sol" "src/L1/ProtocolVersions.sol" diff --git a/packages/contracts-bedrock/snapshots/abi/L1CrossDomainMessenger.json b/packages/contracts-bedrock/snapshots/abi/L1CrossDomainMessenger.json index ac33f91e24c..6cce51733d3 100644 --- a/packages/contracts-bedrock/snapshots/abi/L1CrossDomainMessenger.json +++ b/packages/contracts-bedrock/snapshots/abi/L1CrossDomainMessenger.json @@ -4,6 +4,32 @@ "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [], + "name": "ENCODING_OVERHEAD", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "FLOOR_CALLDATA_OVERHEAD", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "MESSAGE_VERSION", @@ -134,6 +160,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "TX_BASE_GAS", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { diff --git a/packages/contracts-bedrock/snapshots/abi/L2CrossDomainMessenger.json b/packages/contracts-bedrock/snapshots/abi/L2CrossDomainMessenger.json index 717bbb6eb6f..b9f04f127fb 100644 --- a/packages/contracts-bedrock/snapshots/abi/L2CrossDomainMessenger.json +++ b/packages/contracts-bedrock/snapshots/abi/L2CrossDomainMessenger.json @@ -4,6 +4,32 @@ "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [], + "name": "ENCODING_OVERHEAD", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "FLOOR_CALLDATA_OVERHEAD", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "MESSAGE_VERSION", @@ -121,6 +147,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "TX_BASE_GAS", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 71146521dad..68d3e79e9fb 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -4,28 +4,28 @@ "sourceCodeHash": "0xe772f7db8033e4a738850cb28ac4849d3a454c93732135a8a10d4f7cb498088e" }, "src/L1/L1CrossDomainMessenger.sol": { - "initCodeHash": "0x03a3c0eb2418aba9f93bb89723ba2ee7cb9e1988ca00f380503c960149c85b7a", - "sourceCodeHash": "0x5907cdb82ec5f6bef2184558a511d049ab3ee65388cf44d0c20d0f234ef8ca44" + "initCodeHash": "0x5a6272f6bd4346da460b7ff2ebc426d158d7ffc65dc0f2c0651a9e6d545bfd03", + "sourceCodeHash": "0xf11fa72481dbe43dad4e7a48645fcf92d0feeefa0b98b282042bdda568508372" }, "src/L1/L1ERC721Bridge.sol": { - "initCodeHash": "0xa54dbc482f557966a636158bc3a566c351cb04ee8b1cb13aa2de61b9a0ddb064", - "sourceCodeHash": "0xe9c85de57005fe964acb9c62f5009aa5a4ba0eacc24bec67a0e4a043d2db5757" + "initCodeHash": "0x819fcccb74bef53241d7d651f73949115e5706b61c7cb8cdb9b6d9d34ef39e89", + "sourceCodeHash": "0x5036f59ae3414d1106f148765bba9d1759844c9c2d3e18ab5c81bb49cf59eab8" }, "src/L1/L1StandardBridge.sol": { - "initCodeHash": "0xe95886a6408f77fb433687049d85dd3c2200dfacc2dd6c5fd7f9554f6fc5b11b", - "sourceCodeHash": "0x7f614d0c5251365580c02dd898900a583ece2aa28579adea74a3808b575949b4" + "initCodeHash": "0xbd4a03a3611a0de9669c4c8556f90c5aaef666b899d9ded1c07abc60263da8d6", + "sourceCodeHash": "0x44797707aea8c63dec049a02d69ea056662a06e5cf320028ab8b388634bf1c67" }, "src/L1/OPContractsManager.sol": { - "initCodeHash": "0x7ad29e286feabd0ef333f8969be8e766b3c68e97fe7b75e4c8a9699dd49e87f8", - "sourceCodeHash": "0xa412132a08809d03088690b7275c8118327d69d1e42b9d033c34cc283dea77a0" + "initCodeHash": "0xc50bce08abbfc66d7f902b5b1f5504c99cbdca17ca4ff98f67801e40c0751f6b", + "sourceCodeHash": "0xff40fd58a78fdfa90e8ac84bed278e6af8329fd826b478fb4fcd6641f62ae1b6" }, "src/L1/OptimismPortal2.sol": { - "initCodeHash": "0x93c5e8ff52ff8b1cedd985b4a8890c12b56d5959832405c7622615c3541908f5", - "sourceCodeHash": "0x753465f9317d89f8ea64bcd3fc3fb1164d040b9be2d5ba3d401d951f2ceff023" + "initCodeHash": "0xd1651b8a6f4d25611a0105d5cc7c1da3921417bd44da870ec63bf5ccd1bc7c63", + "sourceCodeHash": "0xce7373d8c7df47caa8b090f3afb3d2539677f12cb3eff7fc0ab1fd85638f05c1" }, "src/L1/OptimismPortalInterop.sol": { - "initCodeHash": "0x12ecac882d3a45901993f93661a5e8ada1fb964d25e7843cfb9f56b2a30aa7b4", - "sourceCodeHash": "0xa031a40aa6f722466a76a36871989942f34351a327227a62266bb9a26c439fe2" + "initCodeHash": "0xd59854648bf205dfbea96b483b2937441c32e9ef66b002468c2c14c0d6661728", + "sourceCodeHash": "0xd00b267dcf125e77c10b28c088be4378ec779927e3bcfeb6aa9a7f3d51370490" }, "src/L1/ProtocolVersions.sol": { "initCodeHash": "0x5a76c8530cb24cf23d3baacc6eefaac226382af13f1e2a35535d2ec2b0573b29", @@ -72,20 +72,20 @@ "sourceCodeHash": "0xd0471c328c1d17c5863261322bf8d5aff2e7e9e3a1135631a993aa75667621df" }, "src/L2/L2CrossDomainMessenger.sol": { - "initCodeHash": "0x6117d2ca80029c802b1e5cc36341f03ec48efd07df0251121d3faf5e93aa5901", - "sourceCodeHash": "0x48001529220d274c5cd2e84787239b6d2244780d23894e0a8e96550a40be18fe" + "initCodeHash": "0xe160be403df12709c371c33195d1b9c3b5e9499e902e86bdabc8eed749c3fd61", + "sourceCodeHash": "0x12ea125038b87e259a0d203e119faa6e9726ab2bdbc30430f820ccd48fe87e14" }, "src/L2/L2ERC721Bridge.sol": { - "initCodeHash": "0xe0c4646e3372d53c294028ecbeff97b1ecb14cf7e44361b112c62b0c0d956ea2", - "sourceCodeHash": "0xc3c7fe397f91baa0b89567b8ecb2c0aff522000fd4889346e1dfec2a281486fe" + "initCodeHash": "0x863f0f5b410983f3e51cd97c60a3a42915141b7452864d0e176571d640002b81", + "sourceCodeHash": "0xc05bfcfadfd09a56cfea68e7c1853faa36d114d9a54cd307348be143e442c35a" }, "src/L2/L2StandardBridge.sol": { - "initCodeHash": "0x0b5ec1511a7059f8eed938e68a2258485a3e24ac9ebef1f386e8dd6725b1a7c4", - "sourceCodeHash": "0xf59e693939236d00dbc5e21a5ba3e94eb442967e6a4533973d805c391e554465" + "initCodeHash": "0xba5b288a396b34488ba7be68473305529c7da7c43e5f1cfc48d6a4aecd014103", + "sourceCodeHash": "0x9dd26676cd1276c807ffd4747236783c5170d0919c70693e70b7e4c4c2675429" }, "src/L2/L2StandardBridgeInterop.sol": { - "initCodeHash": "0xa34db426a412a36c06b7a172a107af4b075d1df6997e9bb5619e14f8e52de49c", - "sourceCodeHash": "0xc2c5c34bfe7bf2833c2b4dec73f403b4c3a57d26ef0b19a9b6e4958dc0908090" + "initCodeHash": "0xa7a2e7efe8116ebb21f47ee06c1e62d3b2f5a046478094611a2ab4b714154030", + "sourceCodeHash": "0xde724da82ecf3c96b330c2876a7285b6e2b933ac599241eaa3174c443ebbe33a" }, "src/L2/L2ToL1MessagePasser.sol": { "initCodeHash": "0xf9d82084dcef31a3737a76d8ee4e5842ea190d0f77ed4678adb3bbb95217050f", diff --git a/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol b/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol index 5e475c715c8..c1cd2efc0ce 100644 --- a/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol +++ b/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol @@ -31,8 +31,8 @@ contract L1CrossDomainMessenger is CrossDomainMessenger, ISemver { address private spacer_253_0_20; /// @notice Semantic version. - /// @custom:semver 2.5.0 - string public constant version = "2.5.0"; + /// @custom:semver 2.6.0 + string public constant version = "2.6.0"; /// @notice Constructs the L1CrossDomainMessenger contract. constructor() { diff --git a/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol b/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol index 78afeef759c..51f51c8ea10 100644 --- a/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol +++ b/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol @@ -28,8 +28,8 @@ contract L1ERC721Bridge is ERC721Bridge, ISemver { ISuperchainConfig public superchainConfig; /// @notice Semantic version. - /// @custom:semver 2.3.1 - string public constant version = "2.3.1"; + /// @custom:semver 2.4.0 + string public constant version = "2.4.0"; /// @notice Constructs the L1ERC721Bridge contract. constructor() ERC721Bridge() { diff --git a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol index 3a22ef24adc..d893370a022 100644 --- a/packages/contracts-bedrock/src/L1/L1StandardBridge.sol +++ b/packages/contracts-bedrock/src/L1/L1StandardBridge.sol @@ -74,8 +74,8 @@ contract L1StandardBridge is StandardBridge, ISemver { ); /// @notice Semantic version. - /// @custom:semver 2.2.2 - string public constant version = "2.2.2"; + /// @custom:semver 2.3.0 + string public constant version = "2.3.0"; /// @notice Address of the SuperchainConfig contract. ISuperchainConfig public superchainConfig; diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index b70b4179a09..eb858c76709 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -532,6 +532,20 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { assertValidOpChainConfig(_opChainConfigs[i]); ISystemConfig.Addresses memory opChainAddrs = _opChainConfigs[i].systemConfigProxy.getAddresses(); + // -------- Upgrade Contracts Stored in SystemConfig -------- + + // OptimismPortal and L1CrossDomainMessenger are being upgraded to include the fixes + // for EIP-7623 (minimum gas limits for L1 -> L2 messages). + upgradeTo(_opChainConfigs[i].proxyAdmin, opChainAddrs.optimismPortal, impls.optimismPortalImpl); + upgradeTo( + _opChainConfigs[i].proxyAdmin, opChainAddrs.l1CrossDomainMessenger, impls.l1CrossDomainMessengerImpl + ); + + // L1ERC721Bridge and L1StandardBridge are being upgraded to include the tweaks to the + // EOA checking code for EIP-7702 (code length == 23). + upgradeTo(_opChainConfigs[i].proxyAdmin, opChainAddrs.l1ERC721Bridge, impls.l1ERC721BridgeImpl); + upgradeTo(_opChainConfigs[i].proxyAdmin, opChainAddrs.l1StandardBridge, impls.l1StandardBridgeImpl); + // -------- Discover and Upgrade Proofs Contracts -------- // All chains have the Permissioned Dispute Game. @@ -1216,9 +1230,9 @@ contract OPContractsManager is ISemver { // -------- Constants and Variables -------- - /// @custom:semver 1.7.0 + /// @custom:semver 1.8.0 function version() public pure virtual returns (string memory) { - return "1.7.0"; + return "1.8.0"; } OPContractsManagerGameTypeAdder public immutable opcmGameTypeAdder; diff --git a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol index 0573afdf46f..0b43241ea4f 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol @@ -178,9 +178,9 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { } /// @notice Semantic version. - /// @custom:semver 3.13.0 + /// @custom:semver 3.14.0 function version() public pure virtual returns (string memory) { - return "3.13.0"; + return "3.14.0"; } /// @notice Constructs the OptimismPortal contract. @@ -255,7 +255,7 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { /// @param _byteCount Number of bytes in the calldata. /// @return The minimum gas limit for a deposit. function minimumGasLimit(uint64 _byteCount) public pure returns (uint64) { - return _byteCount * 16 + 21000; + return _byteCount * 40 + 21000; } /// @notice Accepts value so that users can send ETH directly to this contract and have the diff --git a/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol b/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol index 5993b7f27dc..7920a4931b1 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol @@ -25,9 +25,9 @@ contract OptimismPortalInterop is OptimismPortal2 { OptimismPortal2(_proofMaturityDelaySeconds, _disputeGameFinalityDelaySeconds) { } - /// @custom:semver +interop.2 + /// @custom:semver +interop.3 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop.2"); + return string.concat(super.version(), "+interop.3"); } /// @notice Sets static configuration options for the L2 system. diff --git a/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol b/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol index 8c5af32f016..3e33c55dd0c 100644 --- a/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol +++ b/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol @@ -19,8 +19,8 @@ import { IL2ToL1MessagePasser } from "interfaces/L2/IL2ToL1MessagePasser.sol"; /// L2 on the L2 side. Users are generally encouraged to use this contract instead of lower /// level message passing contracts. contract L2CrossDomainMessenger is CrossDomainMessenger, ISemver { - /// @custom:semver 2.1.1-beta.8 - string public constant version = "2.1.1-beta.8"; + /// @custom:semver 2.2.0 + string public constant version = "2.2.0"; /// @notice Constructs the L2CrossDomainMessenger contract. constructor() { diff --git a/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol b/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol index 5f56ca1bf1f..e080f694751 100644 --- a/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol +++ b/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol @@ -24,8 +24,8 @@ import { ISemver } from "interfaces/universal/ISemver.sol"; /// **WARNING**: Do not bridge an ERC721 that was originally deployed on Optimism. This /// bridge ONLY supports ERC721s originally deployed on Ethereum. contract L2ERC721Bridge is ERC721Bridge, ISemver { - /// @custom:semver 1.9.0 - string public constant version = "1.9.0"; + /// @custom:semver 1.10.0 + string public constant version = "1.10.0"; /// @notice Constructs the L2ERC721Bridge contract. constructor() ERC721Bridge() { diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridge.sol b/packages/contracts-bedrock/src/L2/L2StandardBridge.sol index 53418eec973..1e9c2ac15f3 100644 --- a/packages/contracts-bedrock/src/L2/L2StandardBridge.sol +++ b/packages/contracts-bedrock/src/L2/L2StandardBridge.sol @@ -57,9 +57,9 @@ contract L2StandardBridge is StandardBridge, ISemver { ); /// @notice Semantic version. - /// @custom:semver 1.12.0 + /// @custom:semver 1.13.0 function version() public pure virtual returns (string memory) { - return "1.12.0"; + return "1.13.0"; } /// @notice Constructs the L2StandardBridge contract. diff --git a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol index 485d862a293..8dd1230e12e 100644 --- a/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol +++ b/packages/contracts-bedrock/src/L2/L2StandardBridgeInterop.sol @@ -39,9 +39,9 @@ contract L2StandardBridgeInterop is L2StandardBridge { event Converted(address indexed from, address indexed to, address indexed caller, uint256 amount); /// @notice Semantic version. - /// @custom:semver +interop.9 + /// @custom:semver +interop.10 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop.9"); + return string.concat(super.version(), "+interop.10"); } /// @notice Converts `amount` of `from` token to `to` token. diff --git a/packages/contracts-bedrock/src/libraries/EOA.sol b/packages/contracts-bedrock/src/libraries/EOA.sol index 7d9d5cb7197..254cf6eabe8 100644 --- a/packages/contracts-bedrock/src/libraries/EOA.sol +++ b/packages/contracts-bedrock/src/libraries/EOA.sol @@ -9,7 +9,7 @@ library EOA { function isSenderEOA() internal view returns (bool isEOA_) { if (msg.sender == tx.origin) { isEOA_ = true; - } else { + } else if (address(msg.sender).code.length == 23) { // If the sender is not the origin, check for 7702 delegated EOAs. assembly { let ptr := mload(0x40) @@ -17,6 +17,9 @@ library EOA { extcodecopy(caller(), ptr, 0, 0x20) isEOA_ := eq(shr(232, mload(ptr)), 0xEF0100) } + } else { + // If more or less than 23 bytes of code, not a 7702 delegated EOA. + isEOA_ = false; } } } diff --git a/packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol b/packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol index 65b781707fe..ba21a0c7c51 100644 --- a/packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol +++ b/packages/contracts-bedrock/src/universal/CrossDomainMessenger.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.15; // Libraries import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; import { Hashing } from "src/libraries/Hashing.sol"; import { Encoding } from "src/libraries/Encoding.sol"; @@ -115,6 +116,19 @@ abstract contract CrossDomainMessenger is /// call in `relayMessage`. uint64 public constant RELAY_GAS_CHECK_BUFFER = 5_000; + /// @notice Base gas required for any transaction in the EVM. + uint64 public constant TX_BASE_GAS = 21_000; + + /// @notice Floor overhead per byte of non-zero calldata in a message. Calldata floor was + /// introduced in EIP-7623. + uint64 public constant FLOOR_CALLDATA_OVERHEAD = 40; + + /// @notice Overhead added to the internal message data when the full call to relayMessage is + /// ABI encoded. This is a constant value that is specific to the V1 message encoding + /// scheme. 260 is an upper bound, actual overhead can be as low as 228 bytes for an + /// empty message. + uint64 public constant ENCODING_OVERHEAD = 260; + /// @notice Mapping of message hashes to boolean receipt values. Note that a message will only /// be present in this mapping if it has successfully been relayed on this chain, and /// can therefore not be relayed again. @@ -340,23 +354,45 @@ abstract contract CrossDomainMessenger is /// @param _message Message to compute the amount of required gas for. /// @param _minGasLimit Minimum desired gas limit when message goes to target. /// @return Amount of gas required to guarantee message receipt. - function baseGas(bytes calldata _message, uint32 _minGasLimit) public pure returns (uint64) { - return - // Constant overhead - RELAY_CONSTANT_OVERHEAD - // Calldata overhead - + (uint64(_message.length) * MIN_GAS_CALLDATA_OVERHEAD) - // Dynamic overhead (EIP-150) - + ((_minGasLimit * MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR) / MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR) - // Gas reserved for the worst-case cost of 3/5 of the `CALL` opcode's dynamic gas - // factors. (Conservative) - + RELAY_CALL_OVERHEAD - // Relay reserved gas (to ensure execution of `relayMessage` completes after the - // subcontext finishes executing) (Conservative) - + RELAY_RESERVED_GAS - // Gas reserved for the execution between the `hasMinGas` check and the `CALL` - // opcode. (Conservative) - + RELAY_GAS_CHECK_BUFFER; + function baseGas(bytes memory _message, uint32 _minGasLimit) public pure returns (uint64) { + // Base gas should really be computed on the fully encoded message but that would break the + // expected API, so we instead just add the encoding overhead to the message length inside + // of this function. + + // We need a minimum amount of execution gas to ensure that the message will be received on + // the other side without running out of gas (stored within the failedMessages mapping). + // If we get beyond the hasMinGas check, then we *must* supply more than minGasLimit to + // the external call. + uint64 executionGas = uint64( + // Constant costs for relayMessage + RELAY_CONSTANT_OVERHEAD + // Covers dynamic parts of the CALL opcode + + RELAY_CALL_OVERHEAD + // Ensures execution of relayMessage completes after call + + RELAY_RESERVED_GAS + // Buffer between hasMinGas check and the CALL + + RELAY_GAS_CHECK_BUFFER + // Minimum gas limit, multiplied by 64/63 to account for EIP-150. + + ((_minGasLimit * MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR) / MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR) + ); + + // Total message size is the result of properly ABI encoding the call to relayMessage. + // Since we only get the message data and not the rest of the calldata, we use the + // ENCODING_OVERHEAD constant to conservatively account for the remaining bytes. + uint64 totalMessageSize = uint64(_message.length + ENCODING_OVERHEAD); + + // Finally, replicate the transaction cost formula as defined after EIP-7623. This is + // mostly relevant in the L1 -> L2 case because we need to be able to cover the intrinsic + // cost of the message but it doesn't hurt in the L2 -> L1 case. After EIP-7623, the cost + // of a transaction is floored by its calldata size. We don't need to account for the + // contract creation case because this is always a call to relayMessage. + return TX_BASE_GAS + + uint64( + Math.max( + executionGas + (totalMessageSize * MIN_GAS_CALLDATA_OVERHEAD), + (totalMessageSize * FLOOR_CALLDATA_OVERHEAD) + ) + ); } /// @notice Initializer. diff --git a/packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol b/packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol index 762ac3ed190..39d8e8c86fa 100644 --- a/packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol +++ b/packages/contracts-bedrock/test/L1/L1CrossDomainMessenger.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.15; // Testing utilities import { CommonTest } from "test/setup/CommonTest.sol"; -import { Reverter } from "test/mocks/Callers.sol"; +import { Reverter, GasBurner } from "test/mocks/Callers.sol"; import { stdError } from "forge-std/StdError.sol"; // Libraries @@ -18,6 +18,23 @@ import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.s import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +contract Encoding_Harness { + function encodeCrossDomainMessage( + uint256 nonce, + address sender, + address target, + uint256 value, + uint256 gasLimit, + bytes memory data + ) + external + pure + returns (bytes memory) + { + return Encoding.encodeCrossDomainMessage(nonce, sender, target, value, gasLimit, data); + } +} + contract L1CrossDomainMessenger_Test is CommonTest { /// @dev The receiver address address recipient = address(0xabbaacdc); @@ -25,9 +42,13 @@ contract L1CrossDomainMessenger_Test is CommonTest { /// @dev The storage slot of the l2Sender uint256 senderSlotIndex; + /// @dev Encoding library harness. + Encoding_Harness encoding; + function setUp() public override { super.setUp(); senderSlotIndex = ForgeArtifacts.getSlot("OptimismPortal2", "l2Sender").slot; + encoding = new Encoding_Harness(); } /// @dev Tests that the implementation is initialized correctly. @@ -275,6 +296,95 @@ contract L1CrossDomainMessenger_Test is CommonTest { ); } + /// @notice Tests that the relayMessage function on L2 will always succeed for any potential + /// message, regardless of the size of the message, the minimum gas limit, or the + /// amount of gas used by the target contract. + function testFuzz_relayMessage_baseGasSufficient_succeeds( + uint24 _messageLength, + uint32 _minGasLimit, + uint32 _gasToUse + ) + external + { + // TODO(#14609): Update this test to use default.isolate = true once a new stable Foundry + // release is available that includes #9904. That will allow us to use this test to check + // for changes to the EVM itself that might cause our gas formula to be incorrect. + + // Skip if this is a fork test, won't work. + skipIfForkTest("L2CrossDomainMessenger doesn't exist on L1 in forked test"); + + // Using smaller uint so the fuzzer doesn't give as many massive values that get bounded. + // TODO: Known issue, messages above 34k can actually OOG on the receiving side if the + // target uses all available gas. Can be resolved by capping data sizes on the XDM or by + // increasing the amount of available relay gas to ~100k. If increasing relay gas, should + // have the relay gas only increase when the calldata size is large to avoid disrupting + // in-flight L2 -> L1 messages. + _messageLength = uint24(bound(_messageLength, 0, 34_000)); + + // Need more than 500 since GasBurner requires it. + // It's ok to try to use more than minGasLimit since the L2CrossDomainMessenger should + // catch the revert and store the message hash in the failedMessages mapping. + _gasToUse = uint32(bound(_gasToUse, 500, type(uint32).max)); + + // Generate random bytes, more useful than having a _message parameter. + bytes memory _message = vm.randomBytes(_messageLength); + + // Compute the base gas. + // Base gas should really be computed on the fully encoded message but that would break the + // expected API, so we instead just add the encoding overhead to the message length inside + // of the baseGas function. + uint64 baseGas = l1CrossDomainMessenger.baseGas(_message, _minGasLimit); + + // Deploy a gas burner. + address target = address(new GasBurner(_gasToUse)); + + // Encode the message. + bytes memory encoded = Encoding.encodeCrossDomainMessage( + Encoding.encodeVersionedNonce(0, 1), // nonce + alice, // Sender doesn't matter + target, + 0, // Value doesn't matter + _minGasLimit, + _message + ); + + // Count the number of non-zero bytes in the message. + uint256 zeroBytesInCalldata = 0; + uint256 nonzeroBytesInCalldata = 0; + for (uint256 i = 0; i < encoded.length; i++) { + if (encoded[i] != bytes1(0)) { + nonzeroBytesInCalldata++; + } else { + zeroBytesInCalldata++; + } + } + + // Base gas must always be sufficient to cover the floor cost from EIP-7623. + assertGt(baseGas, 21000 + ((zeroBytesInCalldata + nonzeroBytesInCalldata * 4) * 10)); + + // Actual gas on L2 will be the base gas minus the intrinsic gas cost. Note that even after + // EIP-7623, we still deduct 21k + 16 gas per calldata token from the gas limit before + // execution happens. After execution, if the message didn't spend enough in execution gas, + // the EVM will floor the cost of the transaction to 21k + 40 gas per calldata token. + uint256 gasSupplied = baseGas - (21000 + ((zeroBytesInCalldata + nonzeroBytesInCalldata * 4) * 4)); + + // We'll trigger the L2CrossDomainMessenger as if we're the L1CrossDomainMessenger + address caller = AddressAliasHelper.applyL1ToL2Alias(address(l1CrossDomainMessenger)); + vm.prank(caller); + + // Trigger the L2CrossDomainMessenger. + // Should NOT fail. + (bool success,) = address(l2CrossDomainMessenger).call{ gas: gasSupplied }(encoded); + assertTrue(success, "L2CrossDomainMessenger call should not fail"); + + // Message should either be in the failed or successful messages mapping. + bool inFailedMessages = l2CrossDomainMessenger.failedMessages(keccak256(encoded)); + bool inSuccessfulMessages = l2CrossDomainMessenger.successfulMessages(keccak256(encoded)); + assertTrue( + inFailedMessages || inSuccessfulMessages, "message should be in either failed or successful messages" + ); + } + /// @dev Tests that the relayMessage function reverts if eth is /// sent from a contract other than the standard bridge. function test_replayMessage_withValue_reverts() external { @@ -288,6 +398,61 @@ contract L1CrossDomainMessenger_Test is CommonTest { ); } + /// @notice Tests that the ENCODING_OVERHEAD is always greater than or equal to the number of + /// bytes in an encoded message OTHER than the size of the data field. + /// ENCODING_OVERHEAD needs to account for these other bytes so that the total message + /// size used in the baseGas function is accurate. This test ensures that if the + /// encoding ever changes, this test will fail and the developer will need to update + /// the ENCODING_OVERHEAD constant. + /// @param _nonce The nonce to encode into the message. + /// @param _version The version to encode into the message. + /// @param _sender The sender to encode into the message. + /// @param _target The target to encode into the message. + /// @param _value The value to encode into the message. + /// @param _minGasLimit The minimum gas limit to encode into the message. + /// @param _message The message to encode into the message. + function testFuzz_encodingOverhead_sufficient_succeeds( + uint240 _nonce, + uint16 _version, + address _sender, + address _target, + uint256 _value, + uint256 _minGasLimit, + bytes memory _message + ) + external + { + // Make sure that unexpected nonces aren't being used right now. + // Prevents people from forgetting to update this test if a new version is ever used. + if (_version > 2) { + vm.expectRevert("Encoding: unknown cross domain message version"); + encoding.encodeCrossDomainMessage( + Encoding.encodeVersionedNonce({ _nonce: 0, _version: _version }), + _sender, + _target, + _value, + _minGasLimit, + _message + ); + } + + // Clamp the version to 0 or 1. + _version = _version % 2; + + // Encode the message. + bytes memory encoded = encoding.encodeCrossDomainMessage( + Encoding.encodeVersionedNonce({ _nonce: _nonce, _version: _version }), + _sender, + _target, + _value, + _minGasLimit, + _message + ); + + // Total size should always be greater than or equal to the overhead bytes. + assertGe(l1CrossDomainMessenger.ENCODING_OVERHEAD(), encoded.length - _message.length); + } + /// @dev Tests that the xDomainMessageSender is reset to the original value /// after a message is relayed. function test_xDomainMessageSender_reset_succeeds() external { diff --git a/packages/contracts-bedrock/test/libraries/EOA.t.sol b/packages/contracts-bedrock/test/libraries/EOA.t.sol index ddead83ffe3..845fb540e6c 100644 --- a/packages/contracts-bedrock/test/libraries/EOA.t.sol +++ b/packages/contracts-bedrock/test/libraries/EOA.t.sol @@ -81,4 +81,27 @@ contract EOA_isEOA_Test is Test { vm.prank(sender); assertEq(harness.isSenderEOA(), false); } + + /// @notice Tests that a contract with 23 bytes of code is not detected as an EOA. + /// @param _privateKey The private key of the sender. + function testFuzz_isEOA_isContract23BytesNot7702_succeeds(uint256 _privateKey) external { + // Make sure that the private key is in the range of a valid secp256k1 private key. + _privateKey = boundPrivateKey(_privateKey); + + // Generate a random 23 byte code. + bytes memory code = vm.randomBytes(23); + + // If the code happens to be EOF code, change it! + if (code[0] == 0xEF) { + code[0] = 0xFE; // Anything but EF! + } + + // Make sure that the sender is a contract. + address sender = vm.addr(_privateKey); + vm.etch(sender, code); + + // Should not be considered an EOA. + vm.prank(sender); + assertEq(harness.isSenderEOA(), false); + } } diff --git a/packages/contracts-bedrock/test/mocks/Callers.sol b/packages/contracts-bedrock/test/mocks/Callers.sol index c64eb423520..965d1eaa1e4 100644 --- a/packages/contracts-bedrock/test/mocks/Callers.sol +++ b/packages/contracts-bedrock/test/mocks/Callers.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +// Libraries +import { Burn } from "src/libraries/Burn.sol"; + contract CallRecorder { struct CallInfo { address sender; @@ -50,3 +53,32 @@ contract DelegateCaller { } } } + +/// @title GasBurner +/// @notice Contract that burns a specified amount of gas on receive or fallback. +contract GasBurner { + /// @notice The amount of gas to burn on receive or fallback. + uint256 immutable GAS_TO_BURN; + + /// @notice Constructor. + /// @param _gas The amount of gas to burn on receive or fallback. + constructor(uint256 _gas) { + // 500 gas buffer for Solidity overhead. + GAS_TO_BURN = _gas - 500; + } + + /// @notice Receive function that burns the specified amount of gas. + receive() external payable { + _burn(); + } + + /// @notice Fallback function that burns the specified amount of gas. + fallback() external payable { + _burn(); + } + + /// @notice Internal function that burns the specified amount of gas. + function _burn() internal view { + Burn.gas(GAS_TO_BURN); + } +} diff --git a/packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol b/packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol index 88c48dfed2c..01a7ccdb9ba 100644 --- a/packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol +++ b/packages/contracts-bedrock/test/universal/CrossDomainMessenger.t.sol @@ -6,6 +6,7 @@ import { Test } from "forge-std/Test.sol"; import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { Hashing } from "src/libraries/Hashing.sol"; import { Encoding } from "src/libraries/Encoding.sol"; @@ -39,6 +40,100 @@ contract CrossDomainMessenger_BaseGas_Test is CommonTest { uint64 minGasLimit = optimismPortal2.minimumGasLimit(uint64(_data.length)); assertTrue(baseGas >= minGasLimit); } + + /// @notice Test that baseGas returns at least the floor cost for calldata + function test_baseGas_floor_succeeds() external view { + // Create a message large enough that the floor cost would be higher than the execution gas + bytes memory largeMessage = new bytes(100_000); + + uint64 baseGasResult = l1CrossDomainMessenger.baseGas(largeMessage, 0); + + // Calculate the expected floor cost + uint64 expectedFloorCost = l1CrossDomainMessenger.TX_BASE_GAS() + + ( + uint64(largeMessage.length + l1CrossDomainMessenger.ENCODING_OVERHEAD()) + * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD() + ); + + // Verify that the result is at least the floor cost + assertTrue(baseGasResult >= expectedFloorCost, "baseGas should return at least the floor cost"); + } + + /// @notice Test that baseGas returns the execution gas when it's higher than the floor cost + function test_baseGas_executionGas_succeeds() external view { + // Create a small message where execution gas would be higher than floor cost + bytes memory smallMessage = new bytes(10); + uint32 highGasLimit = 1_000_000; + + uint64 baseGasResult = l1CrossDomainMessenger.baseGas(smallMessage, highGasLimit); + + // Calculate the expected floor cost + uint64 floorCost = l1CrossDomainMessenger.TX_BASE_GAS() + + ( + uint64(smallMessage.length + l1CrossDomainMessenger.ENCODING_OVERHEAD()) + * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD() + ); + + // Calculate the expected execution gas (simplified version of what's in the contract) + uint64 executionGas = l1CrossDomainMessenger.RELAY_CONSTANT_OVERHEAD() + + l1CrossDomainMessenger.RELAY_CALL_OVERHEAD() + l1CrossDomainMessenger.RELAY_RESERVED_GAS() + + l1CrossDomainMessenger.RELAY_GAS_CHECK_BUFFER() + + ( + (highGasLimit * l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR()) + / l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR() + ); + + uint64 expectedExecutionGasWithOverhead = l1CrossDomainMessenger.TX_BASE_GAS() + executionGas + + ( + uint64(smallMessage.length + l1CrossDomainMessenger.ENCODING_OVERHEAD()) + * l1CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD() + ); + + // Verify that the result is the execution gas (which should be higher than floor cost) + assertTrue( + baseGasResult >= expectedExecutionGasWithOverhead, "baseGas should return at least the execution gas" + ); + assertTrue( + expectedExecutionGasWithOverhead > floorCost, "Execution gas should be higher than floor cost for this test" + ); + } + + /// @notice Fuzz test to verify the baseGas function correctly implements the Math.max logic + /// @param _message The message to test + /// @param _minGasLimit The minimum gas limit to test + function testFuzz_baseGas_maxLogic_succeeds(bytes calldata _message, uint32 _minGasLimit) external view { + uint64 baseGasResult = l1CrossDomainMessenger.baseGas(_message, _minGasLimit); + + // Calculate the expected execution gas + uint64 executionGas = l1CrossDomainMessenger.RELAY_CONSTANT_OVERHEAD() + + l1CrossDomainMessenger.RELAY_CALL_OVERHEAD() + l1CrossDomainMessenger.RELAY_RESERVED_GAS() + + l1CrossDomainMessenger.RELAY_GAS_CHECK_BUFFER() + + ( + (_minGasLimit * l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR()) + / l1CrossDomainMessenger.MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR() + ); + + uint64 executionGasWithOverhead = executionGas + + ( + uint64(_message.length + l1CrossDomainMessenger.ENCODING_OVERHEAD()) + * l1CrossDomainMessenger.MIN_GAS_CALLDATA_OVERHEAD() + ); + + // The result should be at least the maximum of the two calculations + uint64 expectedMinimum = uint64( + Math.max( + executionGasWithOverhead, + uint64(_message.length + l1CrossDomainMessenger.ENCODING_OVERHEAD()) + * l1CrossDomainMessenger.FLOOR_CALLDATA_OVERHEAD() + ) + ); + expectedMinimum += l1CrossDomainMessenger.TX_BASE_GAS(); + + assertTrue( + baseGasResult >= expectedMinimum, + "baseGas should return at least the maximum of execution gas and floor cost" + ); + } } /// @title ExternalRelay diff --git a/packages/contracts-bedrock/test/universal/Specs.t.sol b/packages/contracts-bedrock/test/universal/Specs.t.sol index 63a0b733c0e..90ac8ae5373 100644 --- a/packages/contracts-bedrock/test/universal/Specs.t.sol +++ b/packages/contracts-bedrock/test/universal/Specs.t.sol @@ -116,6 +116,9 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "L1CrossDomainMessenger", _sel: _getSel("RELAY_CONSTANT_OVERHEAD()") }); _addSpec({ _name: "L1CrossDomainMessenger", _sel: _getSel("RELAY_GAS_CHECK_BUFFER()") }); _addSpec({ _name: "L1CrossDomainMessenger", _sel: _getSel("RELAY_RESERVED_GAS()") }); + _addSpec({ _name: "L1CrossDomainMessenger", _sel: _getSel("TX_BASE_GAS()") }); + _addSpec({ _name: "L1CrossDomainMessenger", _sel: _getSel("FLOOR_CALLDATA_OVERHEAD()") }); + _addSpec({ _name: "L1CrossDomainMessenger", _sel: _getSel("ENCODING_OVERHEAD()") }); _addSpec({ _name: "L1CrossDomainMessenger", _sel: _getSel("baseGas(bytes,uint32)") }); _addSpec({ _name: "L1CrossDomainMessenger", _sel: _getSel("failedMessages(bytes32)") }); _addSpec({ _name: "L1CrossDomainMessenger", _sel: _getSel("initialize(address,address)") }); From be43d0fae50c2fe3600545e39ca667bb2909122c Mon Sep 17 00:00:00 2001 From: Maurelian Date: Wed, 5 Mar 2025 12:05:15 -0500 Subject: [PATCH 072/130] Add upgrade 13 audit (#14647) --- .../2025_02-Upgrade13-Spearbit.pdf | Bin 0 -> 202279 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/security-reviews/2025_02-Upgrade13-Spearbit.pdf diff --git a/docs/security-reviews/2025_02-Upgrade13-Spearbit.pdf b/docs/security-reviews/2025_02-Upgrade13-Spearbit.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c730546c57c418e3fcb60b433fce6a220a14c7c1 GIT binary patch literal 202279 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

    emXQKYrzj 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 z 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%0A(8@kd0OsuAFZKM(4=NvgXOdn zt4VuBz`F|@pFH{;0R}aoGiDwZ4`%guTOLS&(KY$q1n7<7tO>%#R^>T(LZzfj;9pTL z)b9{hy7Y%UN>V@PI!*G(075tTW525&E6eYp_hb&$DfpT~jhq+gBA3}tZhtq>&pql) z?N0fcpIjS%7tG`uS!F>ilNBG7hdbzTT}`3mfnmSDLk zE1R~TzpxTfDbC{KTMi%_9V+<3a$t-z?vIei`sTv7L9E87s8YfNq^VTyC;To)yJGnt zNlzFbFz(Cjm`<;8=w@c{!JzkzK7ftMze~spvl+ ze}>LuCqGBi7dGf4-gqvE65*?S3lnl8RN4S>!u?QM=-3zbugCS=8IRC?k?$XKOU2iT zvROt&$h(5&Yn1~)Qt}nhXg`nltu-FyEL=djcbVvh2_1~8{f_vN0tf|h*vjjcz3iVA zX=q9v*EAdyd(#y-_-FelLR=)yqdMMKv+wH(drRk{eFFVaw#l%;szqRv&gP_Q!jrWn z>&m7Fmcc);(hQ_7W2A^|b!dzYni&9h!H-i>X^D##uC*LjiV`8S`VVjOB|OCz8PRPR zi8j=I9IFDKV@HIJcMqnl;yfIgp9kbh?wHE;XH^;B}l(%={FIC5!}jdZI#@z3pb- zPq$bAVy^Z6ZWXN8q?1^__>;)pQ@3^nrFO3XA)!3=+}cyE`ri-rWS=YK^vp=Xc~VNL z&UZW);ZWQ}ix4FA~CX2}YhR+Z$%mm|%PNpEGYzbBa zv33!MjwedfA|+Zbl88xenNBbA)}T57CQZAFHE{E9#E?nhV1Hx1>+OMTAEjz{8K{vW z>jBSL8;2#pJG=eL%ia6KIhrt&kDS_v?%Vb<4F$lW4%K>1CfR!-!LmyB4Q;f@$0sAD zvU^cs)_*DZ_#y=~yI8opW1ym><$RG7wS=}41QfABud{y0g0tGhNLtjpv+I{P6zBq;`53Wc2QL>5hIs7RWCi)&G>j?9~A0WM4y0s;xl7XSQxfJ6#&Q6m;#9dBlDko}9G){qSsr<7DatNSrF<`x z%g|rLP{bfxA~KPz(fp6VY2)3;`SN*O4#je;rz@dds%u)~oO|Pq+^M9lZ-k9^i*h?~ z+u0@(=+K7puw`)UCgJTkru5%YFB?#$>&rJ!*-fS6vFu(A_gI#QnhbLzbm-8N8cbyBLZIog@dDDliIO|TgvcMsD&=NbUXnZS#9A1#YDHF8t z%x4miTh6GVpUi3I22{Qh31SyZFWOCsuo9FG#2N~VX zsjRfi(kDBx0@jIbGK(@tHON^3OeN5qxC*m)*NV%9mGHEY@DyrZSjIQ@d`ZRmARGFHNxv7{gzZ*mw}eDHhrAzwnaZ78E+St5vYzN#Th1Y zcbOqrSj3kCoaonTsYv>SJ8UvkAwuYm*sg1vs0?32MS0ujCTmBL#fyFz=D1kxtBJ-K z|G{J&|NjOSws3mc>i*8&sgkfpemwMUaT>h-a)3b~YLgbvw0O|rB$l!FYqi#24B`bb_yj(_V457tThU0;yu-OzWypY`J zxH&|x01UQQ1@qyNG$y6eB#5kCkDmkjlMucv2Ebm}%7OQj zT(TLYjk9zRez#eLqrZ@SduxkNsvve#?&Q+8{7`5@)5n`0pocZ>8Akm3+p;uwHI0Fm z0w7vC=s%miT89z#R)w&OOFvRgl^WOgDMBcZ*mA72$71q)1AKVcr(Fj*0@Kcz&giM} z5pEhglf0h5mJ(HqArR>UWhzv}VC(T#@;#%I><<)SO{@sfS}6w97ijRGf&jKfq>YUs z1sVRC;s6mcbrsXe&}ac;2r{q^;F0F_HtDY&5uD(Rf}{x|tqu7IAh~CW-=4ZA|CL6O zJf{rhEC*}qdhFi)zZ1!~+U#DG<8*>qJx;a2k?pKI>q{dGLmV8Hdh5+fCs6vxc}A?i zAI`FH2EJ4Cos&ZLdKh_MGxqFr5`aEDAJ#hNq4j0NLuYAv0@u2KX<{~KZ*~8ll^I#1 zpYr2ibPQmyfQg+s!TJXr3r!0HIKSonLnCc6L9EZ^Joq2vSF6vfY|hRTgr97ZE7R(? zZp|C?`y^gZ+#^fPtHS%vdHg@F&_o#X#fm3ueG+jZPt%;DuU> z)|lZ@2a&x`sE9o8;kakC$)6>lA21QU_>A+Hj`VmX6Tb7Uy<7I{w zq!L*cH1ue~=kWf)77VIjgK-danW#q#BMk0mzNiD+A1GPOo3$s315NOSiTkP5mc|Q1 z347Ry`uxsO6y4PR#MHqf@ z$>4Xop&>z6x?R_Lcfto!FxLRHFE;aELHAU2-jFCQYjE(IH-1TgCTg1TR_!$wCkhiM zk@su**!v~zXeYUn+8<~_5V(MMFXfF7xPn(6jd$!o29DN>Ua2l9aCRa;~M9i3JUTz~`0x%Jcg zhL_}Hyi!ebFCIAIoi0?YTm7PVgi`&?Z+p{nY_)UNpIhDv;EGE~Lrz2^(1?oVMIHgs zt_VZiG6Hc6GwDZh^9PnX9_xsMewtI;Hp-_!OGZsJkBt}8tkg;PMcG!o>nE}W0!TX} zyk8}#QoOL0DxRj%!8ta^BqA)!!9@acI#?iUzi?uC$l*UUO1q;lIqd@2ke0H|w6Tkz zY4Uqvq|8STzZrV&m*}%nO1?hl+N?^guMPvA@Z8z_mn{>|n1U{Pml6o4a=3ythc5es zj>{sg7ALCf#i*e4k(U}@bMzQ>AzXUt&e=I3eJ4ck%NgB^$U!}8aHC=sEX}K_PpT2> z@EsZ&HP;x-gC}annW-mR$Obqf;K-zN1?*SW`yN&1dWMt5x+NOAx+uZCSSlOw@7_8= zNX%p0Rsw?-!^|w*rR|&5v)7pBRa&=ApyjECY*gxk8ej3OjZ^QxUk0Gv%mzbbAFA*; z6)5n{Q@GsoXDE-a+=o|8ubY4Nj3WhR%b z%cdk#=yI7qOMv+r#z;NqY5dnC-Es(X7r@y~(Z1QJBFgU z&^pYBr)_s+^uU;L(D6a*Np+iU@Z-PfHftpRH2JMAE#HW?Uvh98>KDSsbis&;o|uJ& zPlmHe7&NyFqX1+|6Vy>z`T8+>cOu^gA%5>pVS@CY9Ocr*C5!Jo)H{yxQ1TGM&(&wK zBkxz0?XpPNkT*dyc%^^yceR%(k!Hf%dklFx4>)T+O~bNrB#r^UA!24 z%3(?barxfdCsrnlb<)^%JLNC&z;ov>x=&?LiLy515VJ zIFIbH1`YTa7fduV1#-rk^6np0PXib$GrOLB6+zE2NPZ@`$ZJ26VZT0d6e#pf)N2?b zBpT0guFc4HQZ10#{~s@d7}#ozWx?GRd<<$Y@w=Q-31o&w>q+wP7?>{32A)2`NZ6T~ zL`jzg;T5mwijEo&Y1zSQ$xcIUh5|&Q z6;WQ?g`Qa56_USW6QSsxCH|V~%*@h*foa{3crtnHO<6z96WnFX2RHNF4$S0RPfwg# zXHsBVV1irOIKaH=RBd|Ser0}nvzq`I#CE?u$W{(#N0Mq5PH$!63IQ@g@LR@6%t2+} zow)&~@%KQ}3C8cD>l|EC#q?r#bWgKiQZHKj_+FhvS!cFhLa%CpCFr|myjN3y@zUT- zAbVS^-r?)VP?cDT7JoHv>|yYw_|$*g(k6To<^Nav1F3yTGiU?nsDjaPo_Z;V7+h4W zN6g{9pgw{`{I(vt0JqDr+stxSZpNO&@bU8tA<5w2R|ii3+$l4{B`Q!Ojj5Jq9NRry zr`ku(!JpqLpOyMjprx7eON=+pqSBhLC)XoNV0RG5mIhISPhLxePln}Yh&{0;mnp+4b9Ks3JcyL_8BdE1E?;aYfOJKTxADzNXFbzDcR36-(WzPyJ z&pveo_qGN7m!U3m&s%%QpGQmrtuDiby$!3}-cH}IosM$5+y(;6h5#q|MG_@ zf5so~wxnr#I?$B>;P{u_B|GhDy~(4F{?CsgB33vGEkcE+IB5x;X%27OUBW^8M>b;i z?-{4Hxl330a^70rMCncHBO_oTwgW@pKXBP{%naun=KSbiDLSEca8p!-

    7-seNx- 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 z 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

      0Tk_*Da_m&RE#;ctyr0;2r~ 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 h5ETFVAJNfC-@(ZZB#{Wr001W&2Ld^{u&fBe{{x Date: Wed, 5 Mar 2025 18:06:14 +0100 Subject: [PATCH 073/130] op-supervisor: cleanup stale todo comment (#14648) --- op-supervisor/supervisor/backend/syncnode/node_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/op-supervisor/supervisor/backend/syncnode/node_test.go b/op-supervisor/supervisor/backend/syncnode/node_test.go index be656999688..5ea018881ff 100644 --- a/op-supervisor/supervisor/backend/syncnode/node_test.go +++ b/op-supervisor/supervisor/backend/syncnode/node_test.go @@ -60,8 +60,6 @@ func TestEventResponse(t *testing.T) { return nil } - // TODO(#13595): rework node-reset, and include testing for it here - node.Start() // send events and continue to do so until at least one of each type has been received From 63da401391e9be93517d242da5da24905aa5b84c Mon Sep 17 00:00:00 2001 From: JosepBove Date: Wed, 5 Mar 2025 18:33:34 +0100 Subject: [PATCH 074/130] Test upgrades on a list of chains (#14358) * prepare upgrade parameters * Archive rpcs * change var type rerun * rerun ci * rerun ci * bump block number * remove context * set cached block number * try to solve error * move block number to justfile * update just file * update blocknumber on upgrades ci * remove last config edit * fix config * Update packages/contracts-bedrock/justfile Co-authored-by: Maurelian * move logic to pinnedblocknumber * fix * fix just file * fix again * back to just * remove char to rerun CI * readd char to rerun CI * add env to pinned block * back to og name for merge * only op-mainnet * use current block number --------- Co-authored-by: Maurelian --- .circleci/config.yml | 29 +++++++++++++++++++++++++---- packages/contracts-bedrock/justfile | 15 +++++++++++++-- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 45e080ca9a3..dbd20b4d7a3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -809,6 +809,18 @@ jobs: contracts-bedrock-tests-upgrade: circleci_ip_ranges: true + parameters: + fork_op_chain: + description: Fork OP Chain + type: string + default: "op" + fork_base_chain: + description: Fork Base Chain + type: string + default: "mainnet" + fork_base_rpc: + description: Fork Base RPC + type: string docker: - image: <> resource_class: large @@ -835,10 +847,12 @@ jobs: command: | just print-pinned-block-number > ./pinnedBlockNumber.txt cat pinnedBlockNumber.txt + environment: + FORK_BASE_CHAIN: <> working_directory: packages/contracts-bedrock - restore_cache: name: Restore forked state - key: forked-state-contracts-bedrock-tests-upgrade-{{ checksum "packages/contracts-bedrock/pinnedBlockNumber.txt" }} + key: forked-state-contracts-bedrock-tests-upgrade-<>-<>-{{ checksum "packages/contracts-bedrock/pinnedBlockNumber.txt" }} - run: name: Run tests command: just test-upgrade @@ -846,7 +860,9 @@ jobs: FOUNDRY_FUZZ_SEED: 42424242 FOUNDRY_FUZZ_RUNS: 1 FOUNDRY_PROFILE: ci - ETH_RPC_URL: https://ci-mainnet-l1-archive.optimism.io + ETH_RPC_URL: <> + FORK_OP_CHAIN: <> + FORK_BASE_CHAIN: <> working_directory: packages/contracts-bedrock no_output_timeout: 15m - run: @@ -857,7 +873,9 @@ jobs: FOUNDRY_FUZZ_SEED: 42424242 FOUNDRY_FUZZ_RUNS: 1 FOUNDRY_PROFILE: ci - ETH_RPC_URL: https://ci-mainnet-l1-archive.optimism.io + ETH_RPC_URL: <> + FORK_OP_CHAIN: <> + FORK_BASE_CHAIN: <> working_directory: packages/contracts-bedrock when: on_fail - save_cache: @@ -867,7 +885,7 @@ jobs: - "/root/.cache/go-build" - save_cache: name: Save forked state - key: forked-state-contracts-bedrock-tests-upgrade-{{ checksum "packages/contracts-bedrock/pinnedBlockNumber.txt" }} + key: forked-state-contracts-bedrock-tests-upgrade-<>-<>-{{ checksum "packages/contracts-bedrock/pinnedBlockNumber.txt" }} when: always paths: - "/root/.foundry/cache" @@ -1454,6 +1472,9 @@ workflows: - contracts-bedrock-build - contracts-bedrock-tests-upgrade: name: contracts-bedrock-tests-upgrade + fork_op_chain: op + fork_base_chain: mainnet + fork_base_rpc: https://ci-mainnet-l1-archive.optimism.io - contracts-bedrock-checks: requires: - contracts-bedrock-build diff --git a/packages/contracts-bedrock/justfile b/packages/contracts-bedrock/justfile index 4f1c59913b8..09c0f1f8a23 100644 --- a/packages/contracts-bedrock/justfile +++ b/packages/contracts-bedrock/justfile @@ -63,7 +63,18 @@ test-dev *ARGS: build-go-ffi FOUNDRY_PROFILE=lite forge test {{ARGS}} # Default block number for the forked upgrade path. -export pinnedBlockNumber := env_var_or_default('FORK_BLOCK_NUMBER', '21971446') + +export sepoliaBlockNumber := "7701807" +export mainnetBlockNumber := "21971446" + +export pinnedBlockNumber := if env_var_or_default("FORK_BASE_CHAIN", "") == "mainnet" { + mainnetBlockNumber +} else if env_var_or_default("FORK_BASE_CHAIN", "") == "sepolia" { + sepoliaBlockNumber +} else { + mainnetBlockNumber +} + print-pinned-block-number: echo $pinnedBlockNumber @@ -76,8 +87,8 @@ print-pinned-block-number: # when the L1 chain is upgraded. prepare-upgrade-env *ARGS : build-go-ffi #!/bin/bash - echo "Running upgrade tests at block $pinnedBlockNumber" export FORK_BLOCK_NUMBER=$pinnedBlockNumber + echo "Running upgrade tests at block $FORK_BLOCK_NUMBER" export FORK_RPC_URL=$ETH_RPC_URL export FORK_TEST=true export USE_MT_CANNON=true From 636ad830f50b859366f9e15654c34d5f875c83a8 Mon Sep 17 00:00:00 2001 From: Yuwen Zhang Date: Wed, 5 Mar 2025 11:44:06 -0800 Subject: [PATCH 075/130] isthmus: operator fee (#12166) * feat: update IL1Block and info * feat: update SystemConfig * feat: add operator fee vault * feat: handle transactions at Istmus activation * feat: update GasPriceOracle * feat: update deployment * chore: update snapshots and semvers * fix: GasPriceOracle nits * fix: operator fee scalars ordering * fix: handle feedbacks * feat: remove operator fee vault constructor arguments * chore: update e2e TestFees to handle operator fee * chore: update snapshots * fix: remove operator fee vault parameters on deployment * feat: handle overflow for operator fee in GasPriceOracle * chore: fee test * chore: add e2e test to ensure operator fee doesn't ends up minting or burning eth * fix: tests * chore: use operator fee OP Geth branch for CI * fix: tests * fix(op-node): ordering of network upgrade txs * fix: semver lock * fix: sys config interop * fix: semver lock * fix: arithmetic return args * fix: semver lock fix: semver lock fix: semver lock fix: semver lock fix: semver lock * update system config interop * fix: sys config interop semver lock * fix: trailing newline * chore: update tag * fix: point to Isthmus by default * fix: TestParseL1InfoDepositTxData * Update test to catch Alice balance underflow * fix: Test_Operator_Fee_Constistency chore: update go.mod * chore: update tag * fix: operator fee are nil in receipts if both are 0 * chore: set the correct op-geth tag * fix: handle sebastianst feedbacks * fix: another round of feedbacks * nit: useless cast * chore: remove beta in contract versions chore: update contracts versions * chore: more comments on contract versions * fix semgrep * preinstall compilers * schema * indent * add yes pipe * ignore yes code * try echo * try wiping out the svm dir --------- Co-authored-by: leruaa Co-authored-by: refcell Co-authored-by: Teddy Knox Co-authored-by: Matthew Slipper --- .circleci/config.yml | 9 + go.mod | 2 +- go.sum | 4 +- op-chain-ops/genesis/config.go | 21 +- .../testdata/test-deploy-config-full.json | 2 + op-deployer/pkg/deployer/opcm/opchain.go | 3 + op-deployer/pkg/deployer/pipeline/opchain.go | 2 + .../pkg/deployer/state/chain_intent.go | 2 + .../pkg/deployer/state/deploy_config.go | 6 +- op-e2e/actions/proofs/operator_fee_test.go | 135 ++++++++++ op-e2e/actions/upgrades/isthmus_fork_test.go | 63 ++++- op-e2e/bindings/gaspriceoracle.go | 87 ++++++- op-e2e/bindings/systemconfig.go | 233 ++++++++---------- op-e2e/config/init.go | 2 + op-e2e/system/fees/fees_test.go | 116 ++++++--- .../derive/isthmus_upgrade_transactions.go | 160 +++++++++++- .../isthmus_upgrade_transactions_test.go | 88 ++++++- op-node/rollup/derive/l1_block_info.go | 186 ++++++++++++-- op-node/rollup/derive/l1_block_info_test.go | 51 +++- op-node/rollup/derive/payload_util.go | 7 + op-node/rollup/derive/system_config.go | 17 ++ op-node/rollup/derive/system_config_test.go | 25 +- op-service/eth/types.go | 28 +++ op-service/eth/types_test.go | 27 +- op-service/predeploys/addresses.go | 3 + op-service/solabi/util.go | 11 + .../interfaces/L1/ISystemConfig.sol | 6 +- .../interfaces/L1/ISystemConfigInterop.sol | 3 + .../interfaces/L2/IGasPriceOracle.sol | 3 + .../interfaces/L2/IL1Block.sol | 3 + .../interfaces/L2/IL1BlockInterop.sol | 3 + .../interfaces/L2/IOperatorFeeVault.sol | 25 ++ .../contracts-bedrock/scripts/Artifacts.s.sol | 2 + .../contracts-bedrock/scripts/L2Genesis.s.sol | 31 ++- .../scripts/deploy/DeployConfig.s.sol | 4 + .../scripts/deploy/DeployOPChain.s.sol | 15 ++ .../snapshots/abi/GasPriceOracle.json | 39 +++ .../snapshots/abi/L1Block.json | 33 +++ .../snapshots/abi/L1BlockInterop.json | 33 +++ .../snapshots/abi/OperatorFeeVault.json | 178 +++++++++++++ .../snapshots/abi/SystemConfig.json | 44 ++++ .../snapshots/abi/SystemConfigInterop.json | 44 ++++ .../snapshots/semver-lock.json | 24 +- .../storageLayout/GasPriceOracle.json | 7 + .../snapshots/storageLayout/L1Block.json | 14 ++ .../storageLayout/L1BlockInterop.json | 16 +- .../storageLayout/OperatorFeeVault.json | 16 ++ .../snapshots/storageLayout/SystemConfig.json | 14 ++ .../storageLayout/SystemConfigInterop.json | 14 ++ .../contracts-bedrock/src/L1/SystemConfig.sol | 29 ++- .../src/L1/SystemConfigInterop.sol | 4 +- .../src/L2/GasPriceOracle.sol | 30 ++- packages/contracts-bedrock/src/L2/L1Block.sol | 50 +++- .../src/L2/L1BlockInterop.sol | 4 +- .../src/L2/OperatorFeeVault.sol | 26 ++ .../src/libraries/Arithmetic.sol | 26 ++ .../src/libraries/Encoding.sol | 46 ++++ .../src/libraries/Predeploys.sol | 12 +- .../test/L2/GasPriceOracle.t.sol | 45 ++++ .../contracts-bedrock/test/L2/L1Block.t.sol | 105 ++++++++ .../contracts-bedrock/test/L2/L2Genesis.t.sol | 6 +- .../test/L2/OperatorFeeVault.t.sol | 22 ++ .../test/L2/Predeploys.t.sol | 4 +- .../contracts-bedrock/test/setup/Setup.sol | 3 + .../test/universal/Specs.t.sol | 14 ++ 65 files changed, 2037 insertions(+), 250 deletions(-) create mode 100644 op-e2e/actions/proofs/operator_fee_test.go create mode 100644 packages/contracts-bedrock/interfaces/L2/IOperatorFeeVault.sol create mode 100644 packages/contracts-bedrock/snapshots/abi/OperatorFeeVault.json create mode 100644 packages/contracts-bedrock/snapshots/storageLayout/OperatorFeeVault.json create mode 100644 packages/contracts-bedrock/src/L2/OperatorFeeVault.sol create mode 100644 packages/contracts-bedrock/test/L2/OperatorFeeVault.t.sol diff --git a/.circleci/config.yml b/.circleci/config.yml index dbd20b4d7a3..f2c78ad5dea 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -121,6 +121,15 @@ commands: install-contracts-dependencies: description: "Install the dependencies for the smart contracts" steps: + - run: + name: Install SVM compilers + command: | + # Have to wipe out the SVM directory to avoid issues with the SVM version + rm -rf ~/.svm/* + svm install 0.8.15 + svm install 0.8.19 + svm install 0.8.25 + svm install 0.8.28 - run: name: Install dependencies command: | diff --git a/go.mod b/go.mod index 4e94b594e5e..6750dd3e95a 100644 --- a/go.mod +++ b/go.mod @@ -256,7 +256,7 @@ require ( rsc.io/tmplfunc v0.0.3 // indirect ) -replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101500.2-rc.1 +replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101500.2-rc.2 //replace github.com/ethereum/go-ethereum => ../op-geth diff --git a/go.sum b/go.sum index c63ef245350..e7d7380581f 100644 --- a/go.sum +++ b/go.sum @@ -192,8 +192,8 @@ github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/u github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs= -github.com/ethereum-optimism/op-geth v1.101500.2-rc.1 h1:CCQtyKKobjMbK9jYd676YbH6Xcvntdn6jIOxFTCb5B8= -github.com/ethereum-optimism/op-geth v1.101500.2-rc.1/go.mod h1:OMpyVMMy5zpAAHlR5s/aGbXRk+7cIKczUEIJj54APbY= +github.com/ethereum-optimism/op-geth v1.101500.2-rc.2 h1:sUPXJ07X4Ud0QCtVWFeuhWe0y9WP5QXEduo8ZRVOeXs= +github.com/ethereum-optimism/op-geth v1.101500.2-rc.2/go.mod h1:OMpyVMMy5zpAAHlR5s/aGbXRk+7cIKczUEIJj54APbY= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250205201532-8ff62ada16e1 h1:OqRYDcjiOx5QCLn5krpd3BK1CW+VfSZx7YIa6zY9ePE= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250205201532-8ff62ada16e1/go.mod h1:NZ816PzLU1TLv1RdAvYAb6KWOj4Zm5aInT0YpDVml2Y= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250226225001-eda352a0be39 h1:PKn3dL9W0FyGC/oynhP6T43P0Xm80W+woW53lRUp2nE= diff --git a/op-chain-ops/genesis/config.go b/op-chain-ops/genesis/config.go index be37cb44030..3cd7f9d677e 100644 --- a/op-chain-ops/genesis/config.go +++ b/op-chain-ops/genesis/config.go @@ -233,6 +233,10 @@ type GasPriceOracleDeployConfig struct { GasPriceOracleBaseFeeScalar uint32 `json:"gasPriceOracleBaseFeeScalar" evm:"basefeeScalar"` // GasPriceOracleBlobBaseFeeScalar represents the value of the blob base fee scalar used for fee calculations. GasPriceOracleBlobBaseFeeScalar uint32 `json:"gasPriceOracleBlobBaseFeeScalar" evm:"blobbasefeeScalar"` + // GasPriceOracleOperatorFeeScalar represents the value of the operator fee scalar used for fee calculations. + GasPriceOracleOperatorFeeScalar uint32 `json:"gasPriceOracleOperatorFeeScalar" evm:"operatorfeeScalar"` + // GasPriceOracleOperatorFeeConstant represents the value of the operator fee constant used for fee calculations. + GasPriceOracleOperatorFeeConstant uint64 `json:"gasPriceOracleOperatorFeeConstant" evm:"operatorfeeConstant"` } var _ ConfigChecker = (*GasPriceOracleDeployConfig)(nil) @@ -259,6 +263,14 @@ func (d *GasPriceOracleDeployConfig) FeeScalar() [32]byte { }) } +// OperatorFeeParams returns the raw serialized operator fee params. +func (d *GasPriceOracleDeployConfig) OperatorFeeParams() [32]byte { + return eth.EncodeOperatorFeeParams(eth.OperatorFeeParams{ + Scalar: d.GasPriceOracleOperatorFeeScalar, + Constant: d.GasPriceOracleOperatorFeeConstant, + }) +} + // GasTokenDeployConfig configures the optional custom gas token functionality. type GasTokenDeployConfig struct { // UseCustomGasToken is a flag to indicate that a custom gas token should be used @@ -1036,10 +1048,11 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *types.Header, l2GenesisBlockHa // Overhead value is considered a noop. func (d *DeployConfig) GenesisSystemConfig() eth.SystemConfig { return eth.SystemConfig{ - BatcherAddr: d.BatchSenderAddress, - Overhead: eth.Bytes32(common.BigToHash(new(big.Int).SetUint64(d.GasPriceOracleOverhead))), - Scalar: d.FeeScalar(), - GasLimit: uint64(d.L2GenesisBlockGasLimit), + BatcherAddr: d.BatchSenderAddress, + Overhead: eth.Bytes32(common.BigToHash(new(big.Int).SetUint64(d.GasPriceOracleOverhead))), + Scalar: d.FeeScalar(), + GasLimit: uint64(d.L2GenesisBlockGasLimit), + OperatorFeeParams: d.OperatorFeeParams(), } } diff --git a/op-chain-ops/genesis/testdata/test-deploy-config-full.json b/op-chain-ops/genesis/testdata/test-deploy-config-full.json index 7fe9a78e715..0f332842204 100644 --- a/op-chain-ops/genesis/testdata/test-deploy-config-full.json +++ b/op-chain-ops/genesis/testdata/test-deploy-config-full.json @@ -55,6 +55,8 @@ "protocolVersionsProxy": "0x0000000000000000000000000000000000000000", "gasPriceOracleBaseFeeScalar": 0, "gasPriceOracleBlobBaseFeeScalar": 0, + "gasPriceOracleOperatorFeeScalar": 0, + "gasPriceOracleOperatorFeeConstant": 0, "gasPriceOracleOverhead": 2100, "gasPriceOracleScalar": 1000000, "enableGovernance": true, diff --git a/op-deployer/pkg/deployer/opcm/opchain.go b/op-deployer/pkg/deployer/opcm/opchain.go index 632ca3a053e..a9015044b9e 100644 --- a/op-deployer/pkg/deployer/opcm/opchain.go +++ b/op-deployer/pkg/deployer/opcm/opchain.go @@ -40,6 +40,9 @@ type DeployOPChainInput struct { DisputeClockExtension uint64 DisputeMaxClockDuration uint64 AllowCustomDisputeParameters bool + + OperatorFeeScalar uint32 + OperatorFeeConstant uint64 } func (input *DeployOPChainInput) InputSet() bool { diff --git a/op-deployer/pkg/deployer/pipeline/opchain.go b/op-deployer/pkg/deployer/pipeline/opchain.go index dadbb34820d..03d5b375671 100644 --- a/op-deployer/pkg/deployer/pipeline/opchain.go +++ b/op-deployer/pkg/deployer/pipeline/opchain.go @@ -107,6 +107,8 @@ func makeDCI(intent *state.Intent, thisIntent *state.ChainIntent, chainID common DisputeClockExtension: proofParams.DisputeClockExtension, // 3 hours (input in seconds) DisputeMaxClockDuration: proofParams.DisputeMaxClockDuration, // 3.5 days (input in seconds) AllowCustomDisputeParameters: proofParams.DangerouslyAllowCustomDisputeParameters, + OperatorFeeScalar: thisIntent.OperatorFeeScalar, + OperatorFeeConstant: thisIntent.OperatorFeeConstant, }, nil } diff --git a/op-deployer/pkg/deployer/state/chain_intent.go b/op-deployer/pkg/deployer/state/chain_intent.go index aac1d27affe..f374466ea41 100644 --- a/op-deployer/pkg/deployer/state/chain_intent.go +++ b/op-deployer/pkg/deployer/state/chain_intent.go @@ -47,6 +47,8 @@ type ChainIntent struct { DeployOverrides map[string]any `json:"deployOverrides" toml:"deployOverrides"` DangerousAltDAConfig genesis.AltDADeployConfig `json:"dangerousAltDAConfig,omitempty" toml:"dangerousAltDAConfig,omitempty"` AdditionalDisputeGames []AdditionalDisputeGame `json:"dangerousAdditionalDisputeGames" toml:"dangerousAdditionalDisputeGames,omitempty"` + OperatorFeeScalar uint32 `json:"operatorFeeScalar,omitempty" toml:"operatorFeeScalar,omitempty"` + OperatorFeeConstant uint64 `json:"operatorFeeConstant,omitempty" toml:"operatorFeeConstant,omitempty"` } type ChainRoles struct { diff --git a/op-deployer/pkg/deployer/state/deploy_config.go b/op-deployer/pkg/deployer/state/deploy_config.go index 1cf8fc913c8..50b2f128415 100644 --- a/op-deployer/pkg/deployer/state/deploy_config.go +++ b/op-deployer/pkg/deployer/state/deploy_config.go @@ -67,8 +67,10 @@ func CombineDeployConfig(intent *Intent, chainIntent *ChainIntent, state *State, GovernanceTokenOwner: common.HexToAddress("0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAdDEad"), }, GasPriceOracleDeployConfig: genesis.GasPriceOracleDeployConfig{ - GasPriceOracleBaseFeeScalar: 1368, - GasPriceOracleBlobBaseFeeScalar: 810949, + GasPriceOracleBaseFeeScalar: 1368, + GasPriceOracleBlobBaseFeeScalar: 810949, + GasPriceOracleOperatorFeeScalar: chainIntent.OperatorFeeScalar, + GasPriceOracleOperatorFeeConstant: chainIntent.OperatorFeeConstant, }, EIP1559DeployConfig: genesis.EIP1559DeployConfig{ EIP1559Denominator: chainIntent.Eip1559Denominator, diff --git a/op-e2e/actions/proofs/operator_fee_test.go b/op-e2e/actions/proofs/operator_fee_test.go new file mode 100644 index 00000000000..476d50b0857 --- /dev/null +++ b/op-e2e/actions/proofs/operator_fee_test.go @@ -0,0 +1,135 @@ +package proofs + +import ( + "math/big" + "testing" + + actionsHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" + "github.com/ethereum-optimism/optimism/op-e2e/actions/proofs/helpers" + "github.com/ethereum-optimism/optimism/op-e2e/bindings" + "github.com/ethereum-optimism/optimism/op-program/client/claim" + "github.com/ethereum-optimism/optimism/op-service/predeploys" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func Test_Operator_Fee_Constistency(gt *testing.T) { + + const testOperatorFeeScalar = uint32(20000) + const testOperatorFeeConstant = uint64(500) + + runIsthmusDerivationTest := func(gt *testing.T, testCfg *helpers.TestCfg[any]) { + t := actionsHelpers.NewDefaultTesting(gt) + + env := helpers.NewL2FaultProofEnv(t, testCfg, helpers.NewTestParams(), helpers.NewBatcherCfg()) + + balanceAt := func(a common.Address) *big.Int { + t.Helper() + bal, err := env.Engine.EthClient().BalanceAt(t.Ctx(), a, nil) + require.NoError(t, err) + return bal + } + + t.Logf("L2 Genesis Time: %d, IsthmusTime: %d ", env.Sequencer.RollupCfg.Genesis.L2Time, *env.Sequencer.RollupCfg.IsthmusTime) + + sysCfgContract, err := bindings.NewSystemConfig(env.Sd.RollupCfg.L1SystemConfigAddress, env.Miner.EthClient()) + require.NoError(t, err) + + sysCfgOwner, err := bind.NewKeyedTransactorWithChainID(env.Dp.Secrets.Deployer, env.Sd.RollupCfg.L1ChainID) + require.NoError(t, err) + + // Update the operator fee parameters + _, err = sysCfgContract.SetOperatorFeeScalars(sysCfgOwner, testOperatorFeeScalar, testOperatorFeeConstant) + require.NoError(t, err) + + env.Miner.ActL1StartBlock(12)(t) + env.Miner.ActL1IncludeTx(env.Dp.Addresses.Deployer)(t) + env.Miner.ActL1EndBlock(t) + + // sequence L2 blocks, and submit with new batcher + env.Sequencer.ActL1HeadSignal(t) + env.Sequencer.ActBuildToL1Head(t) + env.Batcher.ActSubmitAll(t) + env.Miner.ActL1StartBlock(12)(t) + env.Miner.ActL1EndBlock(t) + + aliceInitialBalance := balanceAt(env.Alice.Address()) + operatorFeeVaultInitialBalance := balanceAt(predeploys.OperatorFeeVaultAddr) + + require.Equal(t, operatorFeeVaultInitialBalance.Sign(), 0) + + env.Sequencer.ActL2StartBlock(t) + // Send an L2 tx + env.Alice.L2.ActResetTxOpts(t) + env.Alice.L2.ActSetTxToAddr(&env.Dp.Addresses.Bob) + env.Alice.L2.ActMakeTx(t) + env.Engine.ActL2IncludeTx(env.Alice.Address())(t) + env.Sequencer.ActL2EndBlock(t) + + receipt := env.Alice.L2.LastTxReceipt(t) + + // Check that the operator fee was applied + require.Equal(t, testOperatorFeeScalar, uint32(*receipt.OperatorFeeScalar)) + require.Equal(t, testOperatorFeeConstant, *receipt.OperatorFeeConstant) + + l1FeeVaultBalance := balanceAt(predeploys.L1FeeVaultAddr) + baseFeeVaultBalance := balanceAt(predeploys.BaseFeeVaultAddr) + sequencerFeeVaultBalance := balanceAt(predeploys.SequencerFeeVaultAddr) + operatorFeeVaultFinalBalance := balanceAt(predeploys.OperatorFeeVaultAddr) + aliceFinalBalance := balanceAt(env.Alice.Address()) + + require.True(t, aliceFinalBalance.Cmp(aliceInitialBalance) < 0, "Alice's balance should decrease") + + // Check that the operator fee sent to the vault is correct + require.Equal(t, + new(big.Int).Add( + new(big.Int).Div( + new(big.Int).Mul( + new(big.Int).SetUint64(receipt.GasUsed), + new(big.Int).SetUint64(uint64(testOperatorFeeScalar)), + ), + new(big.Int).SetUint64(1e6), + ), + new(big.Int).SetUint64(testOperatorFeeConstant), + ), + operatorFeeVaultFinalBalance, + ) + + // Check that no Ether has been minted or burned + // All vault balances are 0 at the beginning of the test + finalTotalBalance := new(big.Int).Add( + aliceFinalBalance, + new(big.Int).Add( + new(big.Int).Add(l1FeeVaultBalance, sequencerFeeVaultBalance), + new(big.Int).Add(operatorFeeVaultFinalBalance, baseFeeVaultBalance), + ), + ) + + require.Equal(t, aliceInitialBalance, finalTotalBalance) + + l2SafeHead := env.Sequencer.L2Safe() + + env.RunFaultProofProgram(t, l2SafeHead.Number, testCfg.CheckResult, testCfg.InputParams...) + } + + matrix := helpers.NewMatrix[any]() + defer matrix.Run(gt) + + matrix.AddTestCase( + "HonestClaim-OperatorFeeConstistency", + nil, + helpers.NewForkMatrix(helpers.Isthmus), + runIsthmusDerivationTest, + helpers.ExpectNoError(), + ) + + matrix.AddTestCase( + "JunkClaim-OperatorFeeConstistency", + nil, + helpers.NewForkMatrix(helpers.Isthmus), + runIsthmusDerivationTest, + helpers.ExpectError(claim.ErrClaimNotValid), + helpers.WithL2Claim(common.HexToHash("0xdeadbeef")), + ) +} diff --git a/op-e2e/actions/upgrades/isthmus_fork_test.go b/op-e2e/actions/upgrades/isthmus_fork_test.go index ce774be1a1b..21759709210 100644 --- a/op-e2e/actions/upgrades/isthmus_fork_test.go +++ b/op-e2e/actions/upgrades/isthmus_fork_test.go @@ -6,11 +6,13 @@ import ( "testing" "time" + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" "github.com/ethereum-optimism/optimism/op-e2e/bindings" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/predeploys" @@ -26,6 +28,12 @@ import ( "github.com/stretchr/testify/require" ) +var ( + isthmusL1BlockCodeHash = common.HexToHash("0xe59074b8d4c08924ce463087b05485b835650652528383a32ef009fb2b6d4050") + isthmusGasPriceOracleCodeHash = common.HexToHash("0x9279e9e0535a7b63939d670e7faec536256b63d4ff353eb521a3342c51ce26e5") + isthmusOperatorFeeVaultCodeHash = common.HexToHash("0x9ee0fa5ab86f13f58fcb8f798f3a74401a8493d99d1c5b3bad19a8dff4b3194f") +) + func TestIsthmusActivationAtGenesis(gt *testing.T) { t := helpers.NewDefaultTesting(gt) env := helpers.SetupEnv(t, helpers.WithActiveGenesisFork(rollup.Isthmus)) @@ -341,6 +349,16 @@ func TestIsthmusNetworkUpgradeTransactions(gt *testing.T) { sequencer.ActL2PipelineFull(t) verifier.ActL2PipelineFull(t) + // Get gas price from oracle + gasPriceOracle, err := bindings.NewGasPriceOracleCaller(predeploys.GasPriceOracleAddr, ethCl) + require.NoError(t, err) + + // Get current implementations addresses (by slot) for L1Block + GasPriceOracle + initialL1BlockAddress, err := ethCl.StorageAt(context.Background(), predeploys.L1BlockAddr, genesis.ImplementationSlot, nil) + require.NoError(t, err) + initialGasPriceOracleAddress, err := ethCl.StorageAt(context.Background(), predeploys.GasPriceOracleAddr, genesis.ImplementationSlot, nil) + require.NoError(t, err) + // Build to the isthmus block sequencer.ActBuildL2ToIsthmus(t) @@ -353,14 +371,47 @@ func TestIsthmusNetworkUpgradeTransactions(gt *testing.T) { // L1Block: 1 set-L1-info + 1 deploy // See [derive.IsthmusNetworkUpgradeTransactions] - require.Equal(t, 2, len(transactions)) + require.Equal(t, 9, len(transactions)) + + // All transactions are successful + for i := 1; i < 9; i++ { + txn := transactions[i] + receipt, err := ethCl.TransactionReceipt(context.Background(), txn.Hash()) + require.NoError(t, err) + require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status) + require.NotEmpty(t, txn.Data(), "upgrade tx must provide input data") + } + + expectedL1BlockAddress := crypto.CreateAddress(derive.L1BlockIsthmusDeployerAddress, 0) + + // L1 Block Proxy is updated + updatedL1BlockAddress, err := ethCl.StorageAt(context.Background(), predeploys.L1BlockAddr, genesis.ImplementationSlot, latestBlock.Number()) + require.NoError(t, err) + require.Equal(t, expectedL1BlockAddress, common.BytesToAddress(updatedL1BlockAddress)) + require.NotEqualf(t, initialL1BlockAddress, updatedL1BlockAddress, "Gas L1 Block address should have changed") + verifyCodeHashMatches(t, ethCl, expectedL1BlockAddress, isthmusL1BlockCodeHash) + + expectedGasPriceOracleAddress := crypto.CreateAddress(derive.GasPriceOracleIsthmusDeployerAddress, 0) + + // Gas Price Oracle Proxy is updated + updatedGasPriceOracleAddress, err := ethCl.StorageAt(context.Background(), predeploys.GasPriceOracleAddr, genesis.ImplementationSlot, latestBlock.Number()) + require.NoError(t, err) + require.Equal(t, expectedGasPriceOracleAddress, common.BytesToAddress(updatedGasPriceOracleAddress)) + require.NotEqualf(t, initialGasPriceOracleAddress, updatedGasPriceOracleAddress, "Gas Price Oracle Proxy address should have changed") + verifyCodeHashMatches(t, ethCl, expectedGasPriceOracleAddress, isthmusGasPriceOracleCodeHash) + + // Check that Isthmus was activated + isIsthmus, err := gasPriceOracle.IsIsthmus(nil) + require.NoError(t, err) + require.True(t, isIsthmus) + + expectedOperatorFeeVaultAddress := crypto.CreateAddress(derive.OperatorFeeVaultDeployerAddress, 0) - // Contract deployment transaction - txn := transactions[1] - receipt, err := ethCl.TransactionReceipt(context.Background(), txn.Hash()) + // Operator Fee vault is updated + updatedOperatorFeeVaultAddress, err := ethCl.StorageAt(context.Background(), predeploys.OperatorFeeVaultAddr, genesis.ImplementationSlot, latestBlock.Number()) require.NoError(t, err) - require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status, "block hashes deployment tx must pass") - require.NotEmpty(t, txn.Data(), "upgrade tx must provide input data") + require.Equal(t, expectedOperatorFeeVaultAddress, common.BytesToAddress(updatedOperatorFeeVaultAddress)) + verifyCodeHashMatches(t, ethCl, expectedOperatorFeeVaultAddress, isthmusOperatorFeeVaultCodeHash) // EIP-2935 contract is deployed expectedBlockHashAddress := crypto.CreateAddress(predeploys.EIP2935ContractDeployer, 0) diff --git a/op-e2e/bindings/gaspriceoracle.go b/op-e2e/bindings/gaspriceoracle.go index 00f3194cb6a..c50bf674be9 100644 --- a/op-e2e/bindings/gaspriceoracle.go +++ b/op-e2e/bindings/gaspriceoracle.go @@ -30,8 +30,8 @@ var ( // GasPriceOracleMetaData contains all meta data concerning the GasPriceOracle contract. var GasPriceOracleMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"function\",\"name\":\"DECIMALS\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"baseFee\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"baseFeeScalar\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"blobBaseFee\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"blobBaseFeeScalar\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"decimals\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"pure\"},{\"type\":\"function\",\"name\":\"gasPrice\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getL1Fee\",\"inputs\":[{\"name\":\"_data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getL1FeeUpperBound\",\"inputs\":[{\"name\":\"_unsignedTxSize\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getL1GasUsed\",\"inputs\":[{\"name\":\"_data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isEcotone\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isFjord\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"l1BaseFee\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"overhead\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"scalar\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setEcotone\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setFjord\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"version\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"}]", - Bin: "0x608060405234801561001057600080fd5b506117f6806100206000396000f3fe608060405234801561001057600080fd5b50600436106101365760003560e01c80636ef25c3a116100b2578063de26c4a111610081578063f45e65d811610066578063f45e65d81461025b578063f820614014610263578063fe173b971461020d57600080fd5b8063de26c4a114610235578063f1c7a58b1461024857600080fd5b80636ef25c3a1461020d5780638e98b10614610213578063960e3a231461021b578063c59859181461022d57600080fd5b806349948e0e11610109578063519b4bd3116100ee578063519b4bd31461019f57806354fd4d50146101a757806368d5dca6146101f057600080fd5b806349948e0e1461016f5780634ef6e2241461018257600080fd5b80630c18c1621461013b57806322b90ab3146101565780632e0f262514610160578063313ce56714610168575b600080fd5b61014361026b565b6040519081526020015b60405180910390f35b61015e61038c565b005b610143600681565b6006610143565b61014361017d3660046112a1565b610515565b60005461018f9060ff1681565b604051901515815260200161014d565b610143610552565b6101e36040518060400160405280600581526020017f312e332e3000000000000000000000000000000000000000000000000000000081525081565b60405161014d9190611370565b6101f86105b3565b60405163ffffffff909116815260200161014d565b48610143565b61015e610638565b60005461018f90610100900460ff1681565b6101f8610832565b6101436102433660046112a1565b610893565b6101436102563660046113e3565b61098d565b610143610a69565b610143610b5c565b6000805460ff1615610304576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f47617350726963654f7261636c653a206f76657268656164282920697320646560448201527f707265636174656400000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa158015610363573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061038791906113fc565b905090565b3373deaddeaddeaddeaddeaddeaddeaddeaddead000114610455576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e2073657420697345636f746f6e6520666c6160648201527f6700000000000000000000000000000000000000000000000000000000000000608482015260a4016102fb565b60005460ff16156104e8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a2045636f746f6e6520616c72656164792060448201527f616374697665000000000000000000000000000000000000000000000000000060648201526084016102fb565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b60008054610100900460ff16156105355761052f82610bbd565b92915050565b60005460ff16156105495761052f82610bdc565b61052f82610c80565b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16635cf249696040518163ffffffff1660e01b8152600401602060405180830381865afa158015610363573d6000803e3d6000fd5b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff166368d5dca66040518163ffffffff1660e01b8152600401602060405180830381865afa158015610614573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103879190611415565b3373deaddeaddeaddeaddeaddeaddeaddeaddead0001146106db576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603f60248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e20736574206973466a6f726420666c61670060648201526084016102fb565b60005460ff1661076d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f47617350726963654f7261636c653a20466a6f72642063616e206f6e6c79206260448201527f65206163746976617465642061667465722045636f746f6e650000000000000060648201526084016102fb565b600054610100900460ff1615610804576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f47617350726963654f7261636c653a20466a6f726420616c726561647920616360448201527f746976650000000000000000000000000000000000000000000000000000000060648201526084016102fb565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff16610100179055565b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663c59859186040518163ffffffff1660e01b8152600401602060405180830381865afa158015610614573d6000803e3d6000fd5b60008054610100900460ff16156108da57620f42406108c56108b484610dd4565b516108c090604461146a565b6110f1565b6108d0906010611482565b61052f91906114bf565b60006108e583611150565b60005490915060ff16156108f95792915050565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa158015610958573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061097c91906113fc565b610986908261146a565b9392505050565b60008054610100900460ff16610a25576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603660248201527f47617350726963654f7261636c653a206765744c314665655570706572426f7560448201527f6e64206f6e6c7920737570706f72747320466a6f72640000000000000000000060648201526084016102fb565b6000610a3283604461146a565b90506000610a4160ff836114bf565b610a4b908361146a565b610a5690601061146a565b9050610a61816111e0565b949350505050565b6000805460ff1615610afd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a207363616c61722829206973206465707260448201527f656361746564000000000000000000000000000000000000000000000000000060648201526084016102fb565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16639e8c49666040518163ffffffff1660e01b8152600401602060405180830381865afa158015610363573d6000803e3d6000fd5b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663f82061406040518163ffffffff1660e01b8152600401602060405180830381865afa158015610363573d6000803e3d6000fd5b600061052f610bcb83610dd4565b51610bd790604461146a565b6111e0565b600080610be883611150565b90506000610bf4610552565b610bfc610832565b610c079060106114fa565b63ffffffff16610c179190611482565b90506000610c23610b5c565b610c2b6105b3565b63ffffffff16610c3b9190611482565b90506000610c49828461146a565b610c539085611482565b9050610c616006600a611646565b610c6c906010611482565b610c7690826114bf565b9695505050505050565b600080610c8c83611150565b9050600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16639e8c49666040518163ffffffff1660e01b8152600401602060405180830381865afa158015610cef573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d1391906113fc565b610d1b610552565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d7a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d9e91906113fc565b610da8908561146a565b610db29190611482565b610dbc9190611482565b9050610dca6006600a611646565b610a6190826114bf565b6060610f63565b818153600101919050565b600082840393505b838110156109865782810151828201511860001a1590930292600101610dee565b825b60208210610e5b578251610e26601f83610ddb565b52602092909201917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090910190602101610e11565b8115610986578251610e706001840383610ddb565b520160010192915050565b60006001830392505b6101078210610ebc57610eae8360ff16610ea960fd610ea98760081c60e00189610ddb565b610ddb565b935061010682039150610e84565b60078210610ee957610ee28360ff16610ea960078503610ea98760081c60e00189610ddb565b9050610986565b610a618360ff16610ea98560081c8560051b0187610ddb565b610f5b828203610f3f610f2f84600081518060001a8160011a60081b178160021a60101b17915050919050565b639e3779b90260131c611fff1690565b8060021b6040510182815160e01c1860e01b8151188152505050565b600101919050565b6180003860405139618000604051016020830180600d8551820103826002015b81811015611096576000805b50508051604051600082901a600183901a60081b1760029290921a60101b91909117639e3779b9810260111c617ffc16909101805160e081811c878603811890911b90911890915284019081830390848410610feb5750611026565b600184019350611fff8211611020578251600081901a600182901a60081b1760029190911a60101b1781036110205750611026565b50610f8f565b838310611034575050611096565b600183039250858311156110525761104f8787888603610e0f565b96505b611066600985016003850160038501610de6565b9150611073878284610e7b565b96505061108b8461108686848601610f02565b610f02565b915050809350610f83565b50506110a88383848851850103610e0f565b925050506040519150618000820180820391508183526020830160005b838110156110dd5782810151828201526020016110c5565b506000920191825250602001604052919050565b60008061110183620cc394611482565b61112b907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd763200611652565b905061113b6064620f42406116c6565b81121561052f576109866064620f42406116c6565b80516000908190815b818110156111d35784818151811061117357611173611782565b01602001517fff00000000000000000000000000000000000000000000000000000000000000166000036111b3576111ac60048461146a565b92506111c1565b6111be60108461146a565b92505b806111cb816117b1565b915050611159565b50610a618261044061146a565b6000806111ec836110f1565b905060006111f8610b5c565b6112006105b3565b63ffffffff166112109190611482565b611218610552565b611220610832565b61122b9060106114fa565b63ffffffff1661123b9190611482565b611245919061146a565b905061125360066002611482565b61125e90600a611646565b6112688284611482565b610a6191906114bf565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000602082840312156112b357600080fd5b813567ffffffffffffffff808211156112cb57600080fd5b818401915084601f8301126112df57600080fd5b8135818111156112f1576112f1611272565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561133757611337611272565b8160405282815287602084870101111561135057600080fd5b826020860160208301376000928101602001929092525095945050505050565b600060208083528351808285015260005b8181101561139d57858101830151858201604001528201611381565b818111156113af576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b6000602082840312156113f557600080fd5b5035919050565b60006020828403121561140e57600080fd5b5051919050565b60006020828403121561142757600080fd5b815163ffffffff8116811461098657600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000821982111561147d5761147d61143b565b500190565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156114ba576114ba61143b565b500290565b6000826114f5577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b600063ffffffff8083168185168183048111821515161561151d5761151d61143b565b02949350505050565b600181815b8085111561157f57817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048211156115655761156561143b565b8085161561157257918102915b93841c939080029061152b565b509250929050565b6000826115965750600161052f565b816115a35750600061052f565b81600181146115b957600281146115c3576115df565b600191505061052f565b60ff8411156115d4576115d461143b565b50506001821b61052f565b5060208310610133831016604e8410600b8410161715611602575081810a61052f565b61160c8383611526565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0482111561163e5761163e61143b565b029392505050565b60006109868383611587565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0384138115161561168c5761168c61143b565b827f80000000000000000000000000000000000000000000000000000000000000000384128116156116c0576116c061143b565b50500190565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6000841360008413858304851182821616156117075761170761143b565b7f800000000000000000000000000000000000000000000000000000000000000060008712868205881281841616156117425761174261143b565b6000871292508782058712848416161561175e5761175e61143b565b878505871281841616156117745761177461143b565b505050929093029392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036117e2576117e261143b565b506001019056fea164736f6c634300080f000a", + ABI: "[{\"inputs\":[],\"name\":\"DECIMALS\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"baseFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"baseFeeScalar\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"blobBaseFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"blobBaseFeeScalar\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"gasPrice\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"getL1Fee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_unsignedTxSize\",\"type\":\"uint256\"}],\"name\":\"getL1FeeUpperBound\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"getL1GasUsed\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_gasUsed\",\"type\":\"uint256\"}],\"name\":\"getOperatorFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"isEcotone\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"isFjord\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"isIsthmus\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"l1BaseFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"overhead\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"scalar\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"setEcotone\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"setFjord\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"setIsthmus\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]", + Bin: "0x608060405234801561001057600080fd5b50611c08806100206000396000f3fe608060405234801561001057600080fd5b50600436106101775760003560e01c806368d5dca6116100d8578063c59859181161008c578063f45e65d811610066578063f45e65d8146102ca578063f8206140146102d2578063fe173b971461026957600080fd5b8063c59859181461029c578063de26c4a1146102a4578063f1c7a58b146102b757600080fd5b80638e98b106116100bd5780638e98b1061461026f578063960e3a2314610277578063b54501bc1461028957600080fd5b806368d5dca61461024c5780636ef25c3a1461026957600080fd5b8063313ce5671161012f5780634ef6e224116101145780634ef6e224146101de578063519b4bd3146101fb57806354fd4d501461020357600080fd5b8063313ce567146101c457806349948e0e146101cb57600080fd5b8063275aedd211610160578063275aedd2146101a1578063291b0383146101b45780632e0f2625146101bc57600080fd5b80630c18c1621461017c57806322b90ab314610197575b600080fd5b6101846102da565b6040519081526020015b60405180910390f35b61019f6103fb565b005b6101846101af36600461165a565b610584565b61019f6106db565b610184600681565b6006610184565b6101846101d93660046116a2565b610903565b6000546101eb9060ff1681565b604051901515815260200161018e565b61018461093a565b61023f6040518060400160405280600c81526020017f312e332e312d626574612e35000000000000000000000000000000000000000081525081565b60405161018e9190611771565b61025461099b565b60405163ffffffff909116815260200161018e565b48610184565b61019f610a20565b6000546101eb90610100900460ff1681565b6000546101eb9062010000900460ff1681565b610254610c1a565b6101846102b23660046116a2565b610c7b565b6101846102c536600461165a565b610d75565b610184610e51565b610184610f44565b6000805460ff1615610373576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f47617350726963654f7261636c653a206f76657268656164282920697320646560448201527f707265636174656400000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103d2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103f691906117e4565b905090565b3373deaddeaddeaddeaddeaddeaddeaddeaddead0001146104c4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e2073657420697345636f746f6e6520666c6160648201527f6700000000000000000000000000000000000000000000000000000000000000608482015260a40161036a565b60005460ff1615610557576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a2045636f746f6e6520616c72656164792060448201527f6163746976650000000000000000000000000000000000000000000000000000606482015260840161036a565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b6000805462010000900460ff1661059d57506000919050565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff166316d3bc7f6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105fc573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061062091906117fd565b67ffffffffffffffff16620f424073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16634d5d9a2a6040518163ffffffff1660e01b8152600401602060405180830381865afa15801561068d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106b19190611827565b6106c19063ffffffff168561187c565b6106cb91906118b9565b6106d591906118f4565b92915050565b3373deaddeaddeaddeaddeaddeaddeaddeaddead0001146107a4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e20736574206973497374686d757320666c6160648201527f6700000000000000000000000000000000000000000000000000000000000000608482015260a40161036a565b600054610100900460ff1661083b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f47617350726963654f7261636c653a20497374686d75732063616e206f6e6c7960448201527f2062652061637469766174656420616674657220466a6f726400000000000000606482015260840161036a565b60005462010000900460ff16156108d4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a20497374686d757320616c72656164792060448201527f6163746976650000000000000000000000000000000000000000000000000000606482015260840161036a565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffff1662010000179055565b60008054610100900460ff161561091d576106d582610fa5565b60005460ff1615610931576106d582610fc4565b6106d582611068565b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16635cf249696040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103d2573d6000803e3d6000fd5b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff166368d5dca66040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109fc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103f69190611827565b3373deaddeaddeaddeaddeaddeaddeaddeaddead000114610ac3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603f60248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e20736574206973466a6f726420666c616700606482015260840161036a565b60005460ff16610b55576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f47617350726963654f7261636c653a20466a6f72642063616e206f6e6c79206260448201527f65206163746976617465642061667465722045636f746f6e6500000000000000606482015260840161036a565b600054610100900460ff1615610bec576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f47617350726963654f7261636c653a20466a6f726420616c726561647920616360448201527f7469766500000000000000000000000000000000000000000000000000000000606482015260840161036a565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff16610100179055565b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663c59859186040518163ffffffff1660e01b8152600401602060405180830381865afa1580156109fc573d6000803e3d6000fd5b60008054610100900460ff1615610cc257620f4240610cad610c9c846111bc565b51610ca89060446118f4565b6114d9565b610cb890601061187c565b6106d591906118b9565b6000610ccd83611538565b60005490915060ff1615610ce15792915050565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d40573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d6491906117e4565b610d6e90826118f4565b9392505050565b60008054610100900460ff16610e0d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603660248201527f47617350726963654f7261636c653a206765744c314665655570706572426f7560448201527f6e64206f6e6c7920737570706f72747320466a6f726400000000000000000000606482015260840161036a565b6000610e1a8360446118f4565b90506000610e2960ff836118b9565b610e3390836118f4565b610e3e9060106118f4565b9050610e49816115c8565b949350505050565b6000805460ff1615610ee5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a207363616c61722829206973206465707260448201527f6563617465640000000000000000000000000000000000000000000000000000606482015260840161036a565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16639e8c49666040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103d2573d6000803e3d6000fd5b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663f82061406040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103d2573d6000803e3d6000fd5b60006106d5610fb3836111bc565b51610fbf9060446118f4565b6115c8565b600080610fd083611538565b90506000610fdc61093a565b610fe4610c1a565b610fef90601061190c565b63ffffffff16610fff919061187c565b9050600061100b610f44565b61101361099b565b63ffffffff16611023919061187c565b9050600061103182846118f4565b61103b908561187c565b90506110496006600a611a58565b61105490601061187c565b61105e90826118b9565b9695505050505050565b60008061107483611538565b9050600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16639e8c49666040518163ffffffff1660e01b8152600401602060405180830381865afa1580156110d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110fb91906117e4565b61110361093a565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa158015611162573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061118691906117e4565b61119090856118f4565b61119a919061187c565b6111a4919061187c565b90506111b26006600a611a58565b610e4990826118b9565b606061134b565b818153600101919050565b600082840393505b83811015610d6e5782810151828201511860001a15909302926001016111d6565b825b6020821061124357825161120e601f836111c3565b52602092909201917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909101906021016111f9565b8115610d6e57825161125860018403836111c3565b520160010192915050565b60006001830392505b61010782106112a4576112968360ff1661129160fd6112918760081c60e001896111c3565b6111c3565b93506101068203915061126c565b600782106112d1576112ca8360ff16611291600785036112918760081c60e001896111c3565b9050610d6e565b610e498360ff166112918560081c8560051b01876111c3565b61134382820361132761131784600081518060001a8160011a60081b178160021a60101b17915050919050565b639e3779b90260131c611fff1690565b8060021b6040510182815160e01c1860e01b8151188152505050565b600101919050565b6180003860405139618000604051016020830180600d8551820103826002015b8181101561147e576000805b50508051604051600082901a600183901a60081b1760029290921a60101b91909117639e3779b9810260111c617ffc16909101805160e081811c878603811890911b909118909152840190818303908484106113d3575061140e565b600184019350611fff8211611408578251600081901a600182901a60081b1760029190911a60101b178103611408575061140e565b50611377565b83831061141c57505061147e565b6001830392508583111561143a5761143787878886036111f7565b96505b61144e6009850160038501600385016111ce565b915061145b878284611263565b9650506114738461146e868486016112ea565b6112ea565b91505080935061136b565b505061149083838488518501036111f7565b925050506040519150618000820180820391508183526020830160005b838110156114c55782810151828201526020016114ad565b506000920191825250602001604052919050565b6000806114e983620cc39461187c565b611513907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd763200611a64565b90506115236064620f4240611ad8565b8112156106d557610d6e6064620f4240611ad8565b80516000908190815b818110156115bb5784818151811061155b5761155b611b94565b01602001517fff000000000000000000000000000000000000000000000000000000000000001660000361159b576115946004846118f4565b92506115a9565b6115a66010846118f4565b92505b806115b381611bc3565b915050611541565b50610e49826104406118f4565b6000806115d4836114d9565b905060006115e0610f44565b6115e861099b565b63ffffffff166115f8919061187c565b61160061093a565b611608610c1a565b61161390601061190c565b63ffffffff16611623919061187c565b61162d91906118f4565b905061163b6006600261187c565b61164690600a611a58565b611650828461187c565b610e4991906118b9565b60006020828403121561166c57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000602082840312156116b457600080fd5b813567ffffffffffffffff808211156116cc57600080fd5b818401915084601f8301126116e057600080fd5b8135818111156116f2576116f2611673565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561173857611738611673565b8160405282815287602084870101111561175157600080fd5b826020860160208301376000928101602001929092525095945050505050565b600060208083528351808285015260005b8181101561179e57858101830151858201604001528201611782565b818111156117b0576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b6000602082840312156117f657600080fd5b5051919050565b60006020828403121561180f57600080fd5b815167ffffffffffffffff81168114610d6e57600080fd5b60006020828403121561183957600080fd5b815163ffffffff81168114610d6e57600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04831182151516156118b4576118b461184d565b500290565b6000826118ef577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b600082198211156119075761190761184d565b500190565b600063ffffffff8083168185168183048111821515161561192f5761192f61184d565b02949350505050565b600181815b8085111561199157817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048211156119775761197761184d565b8085161561198457918102915b93841c939080029061193d565b509250929050565b6000826119a8575060016106d5565b816119b5575060006106d5565b81600181146119cb57600281146119d5576119f1565b60019150506106d5565b60ff8411156119e6576119e661184d565b50506001821b6106d5565b5060208310610133831016604e8410600b8410161715611a14575081810a6106d5565b611a1e8383611938565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04821115611a5057611a5061184d565b029392505050565b6000610d6e8383611999565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03841381151615611a9e57611a9e61184d565b827f8000000000000000000000000000000000000000000000000000000000000000038412811615611ad257611ad261184d565b50500190565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600084136000841385830485118282161615611b1957611b1961184d565b7f80000000000000000000000000000000000000000000000000000000000000006000871286820588128184161615611b5457611b5461184d565b60008712925087820587128484161615611b7057611b7061184d565b87850587128184161615611b8657611b8661184d565b505050929093029392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611bf457611bf461184d565b506001019056fea164736f6c634300080f000a", } // GasPriceOracleABI is the input ABI used to generate the binding from. @@ -511,6 +511,37 @@ func (_GasPriceOracle *GasPriceOracleCallerSession) GetL1GasUsed(_data []byte) ( return _GasPriceOracle.Contract.GetL1GasUsed(&_GasPriceOracle.CallOpts, _data) } +// GetOperatorFee is a free data retrieval call binding the contract method 0x275aedd2. +// +// Solidity: function getOperatorFee(uint256 _gasUsed) view returns(uint256) +func (_GasPriceOracle *GasPriceOracleCaller) GetOperatorFee(opts *bind.CallOpts, _gasUsed *big.Int) (*big.Int, error) { + var out []interface{} + err := _GasPriceOracle.contract.Call(opts, &out, "getOperatorFee", _gasUsed) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetOperatorFee is a free data retrieval call binding the contract method 0x275aedd2. +// +// Solidity: function getOperatorFee(uint256 _gasUsed) view returns(uint256) +func (_GasPriceOracle *GasPriceOracleSession) GetOperatorFee(_gasUsed *big.Int) (*big.Int, error) { + return _GasPriceOracle.Contract.GetOperatorFee(&_GasPriceOracle.CallOpts, _gasUsed) +} + +// GetOperatorFee is a free data retrieval call binding the contract method 0x275aedd2. +// +// Solidity: function getOperatorFee(uint256 _gasUsed) view returns(uint256) +func (_GasPriceOracle *GasPriceOracleCallerSession) GetOperatorFee(_gasUsed *big.Int) (*big.Int, error) { + return _GasPriceOracle.Contract.GetOperatorFee(&_GasPriceOracle.CallOpts, _gasUsed) +} + // IsEcotone is a free data retrieval call binding the contract method 0x4ef6e224. // // Solidity: function isEcotone() view returns(bool) @@ -573,6 +604,37 @@ func (_GasPriceOracle *GasPriceOracleCallerSession) IsFjord() (bool, error) { return _GasPriceOracle.Contract.IsFjord(&_GasPriceOracle.CallOpts) } +// IsIsthmus is a free data retrieval call binding the contract method 0xb54501bc. +// +// Solidity: function isIsthmus() view returns(bool) +func (_GasPriceOracle *GasPriceOracleCaller) IsIsthmus(opts *bind.CallOpts) (bool, error) { + var out []interface{} + err := _GasPriceOracle.contract.Call(opts, &out, "isIsthmus") + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// IsIsthmus is a free data retrieval call binding the contract method 0xb54501bc. +// +// Solidity: function isIsthmus() view returns(bool) +func (_GasPriceOracle *GasPriceOracleSession) IsIsthmus() (bool, error) { + return _GasPriceOracle.Contract.IsIsthmus(&_GasPriceOracle.CallOpts) +} + +// IsIsthmus is a free data retrieval call binding the contract method 0xb54501bc. +// +// Solidity: function isIsthmus() view returns(bool) +func (_GasPriceOracle *GasPriceOracleCallerSession) IsIsthmus() (bool, error) { + return _GasPriceOracle.Contract.IsIsthmus(&_GasPriceOracle.CallOpts) +} + // L1BaseFee is a free data retrieval call binding the contract method 0x519b4bd3. // // Solidity: function l1BaseFee() view returns(uint256) @@ -738,3 +800,24 @@ func (_GasPriceOracle *GasPriceOracleSession) SetFjord() (*types.Transaction, er func (_GasPriceOracle *GasPriceOracleTransactorSession) SetFjord() (*types.Transaction, error) { return _GasPriceOracle.Contract.SetFjord(&_GasPriceOracle.TransactOpts) } + +// SetIsthmus is a paid mutator transaction binding the contract method 0x291b0383. +// +// Solidity: function setIsthmus() returns() +func (_GasPriceOracle *GasPriceOracleTransactor) SetIsthmus(opts *bind.TransactOpts) (*types.Transaction, error) { + return _GasPriceOracle.contract.Transact(opts, "setIsthmus") +} + +// SetIsthmus is a paid mutator transaction binding the contract method 0x291b0383. +// +// Solidity: function setIsthmus() returns() +func (_GasPriceOracle *GasPriceOracleSession) SetIsthmus() (*types.Transaction, error) { + return _GasPriceOracle.Contract.SetIsthmus(&_GasPriceOracle.TransactOpts) +} + +// SetIsthmus is a paid mutator transaction binding the contract method 0x291b0383. +// +// Solidity: function setIsthmus() returns() +func (_GasPriceOracle *GasPriceOracleTransactorSession) SetIsthmus() (*types.Transaction, error) { + return _GasPriceOracle.Contract.SetIsthmus(&_GasPriceOracle.TransactOpts) +} diff --git a/op-e2e/bindings/systemconfig.go b/op-e2e/bindings/systemconfig.go index a6ff5ce1480..05d74beadbe 100644 --- a/op-e2e/bindings/systemconfig.go +++ b/op-e2e/bindings/systemconfig.go @@ -46,12 +46,11 @@ type SystemConfigAddresses struct { DisputeGameFactory common.Address OptimismPortal common.Address OptimismMintableERC20Factory common.Address - GasPayingToken common.Address } // SystemConfigMetaData contains all meta data concerning the SystemConfig contract. var SystemConfigMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"constructor\",\"inputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"BATCH_INBOX_SLOT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"DISPUTE_GAME_FACTORY_SLOT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"L1_CROSS_DOMAIN_MESSENGER_SLOT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"L1_ERC_721_BRIDGE_SLOT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"L1_STANDARD_BRIDGE_SLOT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"OPTIMISM_PORTAL_SLOT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"START_BLOCK_SLOT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"UNSAFE_BLOCK_SIGNER_SLOT\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"VERSION\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"basefeeScalar\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"batchInbox\",\"inputs\":[],\"outputs\":[{\"name\":\"addr_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"batcherHash\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"blobbasefeeScalar\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"disputeGameFactory\",\"inputs\":[],\"outputs\":[{\"name\":\"addr_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"eip1559Denominator\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"eip1559Elasticity\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"gasLimit\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"gasPayingToken\",\"inputs\":[],\"outputs\":[{\"name\":\"addr_\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"decimals_\",\"type\":\"uint8\",\"internalType\":\"uint8\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"gasPayingTokenName\",\"inputs\":[],\"outputs\":[{\"name\":\"name_\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"gasPayingTokenSymbol\",\"inputs\":[],\"outputs\":[{\"name\":\"symbol_\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_basefeeScalar\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"_blobbasefeeScalar\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"_batcherHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"_gasLimit\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"_unsafeBlockSigner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_config\",\"type\":\"tuple\",\"internalType\":\"structIResourceMetering.ResourceConfig\",\"components\":[{\"name\":\"maxResourceLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"elasticityMultiplier\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"baseFeeMaxChangeDenominator\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"minimumBaseFee\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"systemTxMaxGas\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maximumBaseFee\",\"type\":\"uint128\",\"internalType\":\"uint128\"}]},{\"name\":\"_batchInbox\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_addresses\",\"type\":\"tuple\",\"internalType\":\"structSystemConfig.Addresses\",\"components\":[{\"name\":\"l1CrossDomainMessenger\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"l1ERC721Bridge\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"l1StandardBridge\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"disputeGameFactory\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"optimismPortal\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"optimismMintableERC20Factory\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"gasPayingToken\",\"type\":\"address\",\"internalType\":\"address\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"isCustomGasToken\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"l1CrossDomainMessenger\",\"inputs\":[],\"outputs\":[{\"name\":\"addr_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"l1ERC721Bridge\",\"inputs\":[],\"outputs\":[{\"name\":\"addr_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"l1StandardBridge\",\"inputs\":[],\"outputs\":[{\"name\":\"addr_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"maximumGasLimit\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"pure\"},{\"type\":\"function\",\"name\":\"minimumGasLimit\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"optimismMintableERC20Factory\",\"inputs\":[],\"outputs\":[{\"name\":\"addr_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"optimismPortal\",\"inputs\":[],\"outputs\":[{\"name\":\"addr_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"overhead\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"renounceOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"resourceConfig\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structIResourceMetering.ResourceConfig\",\"components\":[{\"name\":\"maxResourceLimit\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"elasticityMultiplier\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"baseFeeMaxChangeDenominator\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"minimumBaseFee\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"systemTxMaxGas\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"maximumBaseFee\",\"type\":\"uint128\",\"internalType\":\"uint128\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"scalar\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setBatcherHash\",\"inputs\":[{\"name\":\"_batcherHash\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setEIP1559Params\",\"inputs\":[{\"name\":\"_denominator\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"_elasticity\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setGasConfig\",\"inputs\":[{\"name\":\"_overhead\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"_scalar\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setGasConfigEcotone\",\"inputs\":[{\"name\":\"_basefeeScalar\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"_blobbasefeeScalar\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setGasLimit\",\"inputs\":[{\"name\":\"_gasLimit\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setUnsafeBlockSigner\",\"inputs\":[{\"name\":\"_unsafeBlockSigner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"startBlock\",\"inputs\":[],\"outputs\":[{\"name\":\"startBlock_\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"unsafeBlockSigner\",\"inputs\":[],\"outputs\":[{\"name\":\"addr_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"version\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"pure\"},{\"type\":\"event\",\"name\":\"ConfigUpdate\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint256\",\"indexed\":true,\"internalType\":\"uint256\"},{\"name\":\"updateType\",\"type\":\"uint8\",\"indexed\":true,\"internalType\":\"enumSystemConfig.UpdateType\"},{\"name\":\"data\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Initialized\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint8\",\"indexed\":false,\"internalType\":\"uint8\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferred\",\"inputs\":[{\"name\":\"previousOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false}]", + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"BATCH_INBOX_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DISPUTE_GAME_FACTORY_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"L1_CROSS_DOMAIN_MESSENGER_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"L1_ERC_721_BRIDGE_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"L1_STANDARD_BRIDGE_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OPTIMISM_PORTAL_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"START_BLOCK_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"UNSAFE_BLOCK_SIGNER_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"VERSION\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"basefeeScalar\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"batchInbox\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"batcherHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"blobbasefeeScalar\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"disputeGameFactory\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"eip1559Denominator\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"eip1559Elasticity\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"gasLimit\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAddresses\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"l1CrossDomainMessenger\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"l1ERC721Bridge\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"l1StandardBridge\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"disputeGameFactory\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"optimismPortal\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"optimismMintableERC20Factory\",\"type\":\"address\"}],\"internalType\":\"structSystemConfig.Addresses\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"_basefeeScalar\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"_blobbasefeeScalar\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"_batcherHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"_gasLimit\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"_unsafeBlockSigner\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"maxResourceLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint8\",\"name\":\"elasticityMultiplier\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"baseFeeMaxChangeDenominator\",\"type\":\"uint8\"},{\"internalType\":\"uint32\",\"name\":\"minimumBaseFee\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"systemTxMaxGas\",\"type\":\"uint32\"},{\"internalType\":\"uint128\",\"name\":\"maximumBaseFee\",\"type\":\"uint128\"}],\"internalType\":\"structIResourceMetering.ResourceConfig\",\"name\":\"_config\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"_batchInbox\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"l1CrossDomainMessenger\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"l1ERC721Bridge\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"l1StandardBridge\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"disputeGameFactory\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"optimismPortal\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"optimismMintableERC20Factory\",\"type\":\"address\"}],\"internalType\":\"structSystemConfig.Addresses\",\"name\":\"_addresses\",\"type\":\"tuple\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"l1CrossDomainMessenger\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"l1ERC721Bridge\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"l1StandardBridge\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maximumGasLimit\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minimumGasLimit\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"operatorFeeConstant\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"operatorFeeScalar\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimismMintableERC20Factory\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimismPortal\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"overhead\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"resourceConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"maxResourceLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint8\",\"name\":\"elasticityMultiplier\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"baseFeeMaxChangeDenominator\",\"type\":\"uint8\"},{\"internalType\":\"uint32\",\"name\":\"minimumBaseFee\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"systemTxMaxGas\",\"type\":\"uint32\"},{\"internalType\":\"uint128\",\"name\":\"maximumBaseFee\",\"type\":\"uint128\"}],\"internalType\":\"structIResourceMetering.ResourceConfig\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"scalar\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_batcherHash\",\"type\":\"bytes32\"}],\"name\":\"setBatcherHash\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_denominator\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"_elasticity\",\"type\":\"uint32\"}],\"name\":\"setEIP1559Params\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_overhead\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_scalar\",\"type\":\"uint256\"}],\"name\":\"setGasConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_basefeeScalar\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"_blobbasefeeScalar\",\"type\":\"uint32\"}],\"name\":\"setGasConfigEcotone\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"_gasLimit\",\"type\":\"uint64\"}],\"name\":\"setGasLimit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_operatorFeeScalar\",\"type\":\"uint32\"},{\"internalType\":\"uint64\",\"name\":\"_operatorFeeConstant\",\"type\":\"uint64\"}],\"name\":\"setOperatorFeeScalars\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_unsafeBlockSigner\",\"type\":\"address\"}],\"name\":\"setUnsafeBlockSigner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"startBlock\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"startBlock_\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unsafeBlockSigner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"enumSystemConfig.UpdateType\",\"name\":\"updateType\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"ConfigUpdate\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"}]", } // SystemConfigABI is the input ABI used to generate the binding from. @@ -758,142 +757,35 @@ func (_SystemConfig *SystemConfigCallerSession) GasLimit() (uint64, error) { return _SystemConfig.Contract.GasLimit(&_SystemConfig.CallOpts) } -// GasPayingToken is a free data retrieval call binding the contract method 0x4397dfef. +// GetAddresses is a free data retrieval call binding the contract method 0xa39fac12. // -// Solidity: function gasPayingToken() view returns(address addr_, uint8 decimals_) -func (_SystemConfig *SystemConfigCaller) GasPayingToken(opts *bind.CallOpts) (struct { - Addr common.Address - Decimals uint8 -}, error) { +// Solidity: function getAddresses() view returns((address,address,address,address,address,address)) +func (_SystemConfig *SystemConfigCaller) GetAddresses(opts *bind.CallOpts) (SystemConfigAddresses, error) { var out []interface{} - err := _SystemConfig.contract.Call(opts, &out, "gasPayingToken") + err := _SystemConfig.contract.Call(opts, &out, "getAddresses") - outstruct := new(struct { - Addr common.Address - Decimals uint8 - }) if err != nil { - return *outstruct, err + return *new(SystemConfigAddresses), err } - outstruct.Addr = *abi.ConvertType(out[0], new(common.Address)).(*common.Address) - outstruct.Decimals = *abi.ConvertType(out[1], new(uint8)).(*uint8) - - return *outstruct, err - -} - -// GasPayingToken is a free data retrieval call binding the contract method 0x4397dfef. -// -// Solidity: function gasPayingToken() view returns(address addr_, uint8 decimals_) -func (_SystemConfig *SystemConfigSession) GasPayingToken() (struct { - Addr common.Address - Decimals uint8 -}, error) { - return _SystemConfig.Contract.GasPayingToken(&_SystemConfig.CallOpts) -} - -// GasPayingToken is a free data retrieval call binding the contract method 0x4397dfef. -// -// Solidity: function gasPayingToken() view returns(address addr_, uint8 decimals_) -func (_SystemConfig *SystemConfigCallerSession) GasPayingToken() (struct { - Addr common.Address - Decimals uint8 -}, error) { - return _SystemConfig.Contract.GasPayingToken(&_SystemConfig.CallOpts) -} - -// GasPayingTokenName is a free data retrieval call binding the contract method 0xd8444715. -// -// Solidity: function gasPayingTokenName() view returns(string name_) -func (_SystemConfig *SystemConfigCaller) GasPayingTokenName(opts *bind.CallOpts) (string, error) { - var out []interface{} - err := _SystemConfig.contract.Call(opts, &out, "gasPayingTokenName") - - if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) + out0 := *abi.ConvertType(out[0], new(SystemConfigAddresses)).(*SystemConfigAddresses) return out0, err } -// GasPayingTokenName is a free data retrieval call binding the contract method 0xd8444715. +// GetAddresses is a free data retrieval call binding the contract method 0xa39fac12. // -// Solidity: function gasPayingTokenName() view returns(string name_) -func (_SystemConfig *SystemConfigSession) GasPayingTokenName() (string, error) { - return _SystemConfig.Contract.GasPayingTokenName(&_SystemConfig.CallOpts) -} - -// GasPayingTokenName is a free data retrieval call binding the contract method 0xd8444715. -// -// Solidity: function gasPayingTokenName() view returns(string name_) -func (_SystemConfig *SystemConfigCallerSession) GasPayingTokenName() (string, error) { - return _SystemConfig.Contract.GasPayingTokenName(&_SystemConfig.CallOpts) -} - -// GasPayingTokenSymbol is a free data retrieval call binding the contract method 0x550fcdc9. -// -// Solidity: function gasPayingTokenSymbol() view returns(string symbol_) -func (_SystemConfig *SystemConfigCaller) GasPayingTokenSymbol(opts *bind.CallOpts) (string, error) { - var out []interface{} - err := _SystemConfig.contract.Call(opts, &out, "gasPayingTokenSymbol") - - if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) - - return out0, err - -} - -// GasPayingTokenSymbol is a free data retrieval call binding the contract method 0x550fcdc9. -// -// Solidity: function gasPayingTokenSymbol() view returns(string symbol_) -func (_SystemConfig *SystemConfigSession) GasPayingTokenSymbol() (string, error) { - return _SystemConfig.Contract.GasPayingTokenSymbol(&_SystemConfig.CallOpts) -} - -// GasPayingTokenSymbol is a free data retrieval call binding the contract method 0x550fcdc9. -// -// Solidity: function gasPayingTokenSymbol() view returns(string symbol_) -func (_SystemConfig *SystemConfigCallerSession) GasPayingTokenSymbol() (string, error) { - return _SystemConfig.Contract.GasPayingTokenSymbol(&_SystemConfig.CallOpts) -} - -// IsCustomGasToken is a free data retrieval call binding the contract method 0x21326849. -// -// Solidity: function isCustomGasToken() view returns(bool) -func (_SystemConfig *SystemConfigCaller) IsCustomGasToken(opts *bind.CallOpts) (bool, error) { - var out []interface{} - err := _SystemConfig.contract.Call(opts, &out, "isCustomGasToken") - - if err != nil { - return *new(bool), err - } - - out0 := *abi.ConvertType(out[0], new(bool)).(*bool) - - return out0, err - +// Solidity: function getAddresses() view returns((address,address,address,address,address,address)) +func (_SystemConfig *SystemConfigSession) GetAddresses() (SystemConfigAddresses, error) { + return _SystemConfig.Contract.GetAddresses(&_SystemConfig.CallOpts) } -// IsCustomGasToken is a free data retrieval call binding the contract method 0x21326849. +// GetAddresses is a free data retrieval call binding the contract method 0xa39fac12. // -// Solidity: function isCustomGasToken() view returns(bool) -func (_SystemConfig *SystemConfigSession) IsCustomGasToken() (bool, error) { - return _SystemConfig.Contract.IsCustomGasToken(&_SystemConfig.CallOpts) -} - -// IsCustomGasToken is a free data retrieval call binding the contract method 0x21326849. -// -// Solidity: function isCustomGasToken() view returns(bool) -func (_SystemConfig *SystemConfigCallerSession) IsCustomGasToken() (bool, error) { - return _SystemConfig.Contract.IsCustomGasToken(&_SystemConfig.CallOpts) +// Solidity: function getAddresses() view returns((address,address,address,address,address,address)) +func (_SystemConfig *SystemConfigCallerSession) GetAddresses() (SystemConfigAddresses, error) { + return _SystemConfig.Contract.GetAddresses(&_SystemConfig.CallOpts) } // L1CrossDomainMessenger is a free data retrieval call binding the contract method 0xa7119869. @@ -1051,6 +943,68 @@ func (_SystemConfig *SystemConfigCallerSession) MinimumGasLimit() (uint64, error return _SystemConfig.Contract.MinimumGasLimit(&_SystemConfig.CallOpts) } +// OperatorFeeConstant is a free data retrieval call binding the contract method 0x16d3bc7f. +// +// Solidity: function operatorFeeConstant() view returns(uint64) +func (_SystemConfig *SystemConfigCaller) OperatorFeeConstant(opts *bind.CallOpts) (uint64, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "operatorFeeConstant") + + if err != nil { + return *new(uint64), err + } + + out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) + + return out0, err + +} + +// OperatorFeeConstant is a free data retrieval call binding the contract method 0x16d3bc7f. +// +// Solidity: function operatorFeeConstant() view returns(uint64) +func (_SystemConfig *SystemConfigSession) OperatorFeeConstant() (uint64, error) { + return _SystemConfig.Contract.OperatorFeeConstant(&_SystemConfig.CallOpts) +} + +// OperatorFeeConstant is a free data retrieval call binding the contract method 0x16d3bc7f. +// +// Solidity: function operatorFeeConstant() view returns(uint64) +func (_SystemConfig *SystemConfigCallerSession) OperatorFeeConstant() (uint64, error) { + return _SystemConfig.Contract.OperatorFeeConstant(&_SystemConfig.CallOpts) +} + +// OperatorFeeScalar is a free data retrieval call binding the contract method 0x4d5d9a2a. +// +// Solidity: function operatorFeeScalar() view returns(uint32) +func (_SystemConfig *SystemConfigCaller) OperatorFeeScalar(opts *bind.CallOpts) (uint32, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "operatorFeeScalar") + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + +} + +// OperatorFeeScalar is a free data retrieval call binding the contract method 0x4d5d9a2a. +// +// Solidity: function operatorFeeScalar() view returns(uint32) +func (_SystemConfig *SystemConfigSession) OperatorFeeScalar() (uint32, error) { + return _SystemConfig.Contract.OperatorFeeScalar(&_SystemConfig.CallOpts) +} + +// OperatorFeeScalar is a free data retrieval call binding the contract method 0x4d5d9a2a. +// +// Solidity: function operatorFeeScalar() view returns(uint32) +func (_SystemConfig *SystemConfigCallerSession) OperatorFeeScalar() (uint32, error) { + return _SystemConfig.Contract.OperatorFeeScalar(&_SystemConfig.CallOpts) +} + // OptimismMintableERC20Factory is a free data retrieval call binding the contract method 0x9b7d7f0a. // // Solidity: function optimismMintableERC20Factory() view returns(address addr_) @@ -1330,23 +1284,23 @@ func (_SystemConfig *SystemConfigCallerSession) Version() (string, error) { return _SystemConfig.Contract.Version(&_SystemConfig.CallOpts) } -// Initialize is a paid mutator transaction binding the contract method 0xdb9040fa. +// Initialize is a paid mutator transaction binding the contract method 0xca407f0c. // -// Solidity: function initialize(address _owner, uint32 _basefeeScalar, uint32 _blobbasefeeScalar, bytes32 _batcherHash, uint64 _gasLimit, address _unsafeBlockSigner, (uint32,uint8,uint8,uint32,uint32,uint128) _config, address _batchInbox, (address,address,address,address,address,address,address) _addresses) returns() +// Solidity: function initialize(address _owner, uint32 _basefeeScalar, uint32 _blobbasefeeScalar, bytes32 _batcherHash, uint64 _gasLimit, address _unsafeBlockSigner, (uint32,uint8,uint8,uint32,uint32,uint128) _config, address _batchInbox, (address,address,address,address,address,address) _addresses) returns() func (_SystemConfig *SystemConfigTransactor) Initialize(opts *bind.TransactOpts, _owner common.Address, _basefeeScalar uint32, _blobbasefeeScalar uint32, _batcherHash [32]byte, _gasLimit uint64, _unsafeBlockSigner common.Address, _config IResourceMeteringResourceConfig, _batchInbox common.Address, _addresses SystemConfigAddresses) (*types.Transaction, error) { return _SystemConfig.contract.Transact(opts, "initialize", _owner, _basefeeScalar, _blobbasefeeScalar, _batcherHash, _gasLimit, _unsafeBlockSigner, _config, _batchInbox, _addresses) } -// Initialize is a paid mutator transaction binding the contract method 0xdb9040fa. +// Initialize is a paid mutator transaction binding the contract method 0xca407f0c. // -// Solidity: function initialize(address _owner, uint32 _basefeeScalar, uint32 _blobbasefeeScalar, bytes32 _batcherHash, uint64 _gasLimit, address _unsafeBlockSigner, (uint32,uint8,uint8,uint32,uint32,uint128) _config, address _batchInbox, (address,address,address,address,address,address,address) _addresses) returns() +// Solidity: function initialize(address _owner, uint32 _basefeeScalar, uint32 _blobbasefeeScalar, bytes32 _batcherHash, uint64 _gasLimit, address _unsafeBlockSigner, (uint32,uint8,uint8,uint32,uint32,uint128) _config, address _batchInbox, (address,address,address,address,address,address) _addresses) returns() func (_SystemConfig *SystemConfigSession) Initialize(_owner common.Address, _basefeeScalar uint32, _blobbasefeeScalar uint32, _batcherHash [32]byte, _gasLimit uint64, _unsafeBlockSigner common.Address, _config IResourceMeteringResourceConfig, _batchInbox common.Address, _addresses SystemConfigAddresses) (*types.Transaction, error) { return _SystemConfig.Contract.Initialize(&_SystemConfig.TransactOpts, _owner, _basefeeScalar, _blobbasefeeScalar, _batcherHash, _gasLimit, _unsafeBlockSigner, _config, _batchInbox, _addresses) } -// Initialize is a paid mutator transaction binding the contract method 0xdb9040fa. +// Initialize is a paid mutator transaction binding the contract method 0xca407f0c. // -// Solidity: function initialize(address _owner, uint32 _basefeeScalar, uint32 _blobbasefeeScalar, bytes32 _batcherHash, uint64 _gasLimit, address _unsafeBlockSigner, (uint32,uint8,uint8,uint32,uint32,uint128) _config, address _batchInbox, (address,address,address,address,address,address,address) _addresses) returns() +// Solidity: function initialize(address _owner, uint32 _basefeeScalar, uint32 _blobbasefeeScalar, bytes32 _batcherHash, uint64 _gasLimit, address _unsafeBlockSigner, (uint32,uint8,uint8,uint32,uint32,uint128) _config, address _batchInbox, (address,address,address,address,address,address) _addresses) returns() func (_SystemConfig *SystemConfigTransactorSession) Initialize(_owner common.Address, _basefeeScalar uint32, _blobbasefeeScalar uint32, _batcherHash [32]byte, _gasLimit uint64, _unsafeBlockSigner common.Address, _config IResourceMeteringResourceConfig, _batchInbox common.Address, _addresses SystemConfigAddresses) (*types.Transaction, error) { return _SystemConfig.Contract.Initialize(&_SystemConfig.TransactOpts, _owner, _basefeeScalar, _blobbasefeeScalar, _batcherHash, _gasLimit, _unsafeBlockSigner, _config, _batchInbox, _addresses) } @@ -1477,6 +1431,27 @@ func (_SystemConfig *SystemConfigTransactorSession) SetGasLimit(_gasLimit uint64 return _SystemConfig.Contract.SetGasLimit(&_SystemConfig.TransactOpts, _gasLimit) } +// SetOperatorFeeScalars is a paid mutator transaction binding the contract method 0x155b6c6f. +// +// Solidity: function setOperatorFeeScalars(uint32 _operatorFeeScalar, uint64 _operatorFeeConstant) returns() +func (_SystemConfig *SystemConfigTransactor) SetOperatorFeeScalars(opts *bind.TransactOpts, _operatorFeeScalar uint32, _operatorFeeConstant uint64) (*types.Transaction, error) { + return _SystemConfig.contract.Transact(opts, "setOperatorFeeScalars", _operatorFeeScalar, _operatorFeeConstant) +} + +// SetOperatorFeeScalars is a paid mutator transaction binding the contract method 0x155b6c6f. +// +// Solidity: function setOperatorFeeScalars(uint32 _operatorFeeScalar, uint64 _operatorFeeConstant) returns() +func (_SystemConfig *SystemConfigSession) SetOperatorFeeScalars(_operatorFeeScalar uint32, _operatorFeeConstant uint64) (*types.Transaction, error) { + return _SystemConfig.Contract.SetOperatorFeeScalars(&_SystemConfig.TransactOpts, _operatorFeeScalar, _operatorFeeConstant) +} + +// SetOperatorFeeScalars is a paid mutator transaction binding the contract method 0x155b6c6f. +// +// Solidity: function setOperatorFeeScalars(uint32 _operatorFeeScalar, uint64 _operatorFeeConstant) returns() +func (_SystemConfig *SystemConfigTransactorSession) SetOperatorFeeScalars(_operatorFeeScalar uint32, _operatorFeeConstant uint64) (*types.Transaction, error) { + return _SystemConfig.Contract.SetOperatorFeeScalars(&_SystemConfig.TransactOpts, _operatorFeeScalar, _operatorFeeConstant) +} + // SetUnsafeBlockSigner is a paid mutator transaction binding the contract method 0x18d13918. // // Solidity: function setUnsafeBlockSigner(address _unsafeBlockSigner) returns() diff --git a/op-e2e/config/init.go b/op-e2e/config/init.go index d2cd5728cfa..b13a6002342 100644 --- a/op-e2e/config/init.go +++ b/op-e2e/config/init.go @@ -396,6 +396,8 @@ func defaultIntent(root string, loc *artifacts.Locator, deployer common.Address, "gasPriceOracleScalar": 1000000, "gasPriceOracleBaseFeeScalar": 1368, "gasPriceOracleBlobBaseFeeScalar": 810949, + "gasPriceOracleOperatorFeeScalar": 0, + "gasPriceOracleOperatorFeeConstant": 0, "l1CancunTimeOffset": "0x0", "faultGameAbsolutePrestate": defaultPrestate.Hex(), "faultGameMaxDepth": 50, diff --git a/op-e2e/system/fees/fees_test.go b/op-e2e/system/fees/fees_test.go index 6296b0ee7ab..80f5febc19e 100644 --- a/op-e2e/system/fees/fees_test.go +++ b/op-e2e/system/fees/fees_test.go @@ -68,14 +68,27 @@ func TestFees(t *testing.T) { }) t.Run("fjord", func(t *testing.T) { op_e2e.InitParallel(t) - cfg := e2esys.DefaultSystemConfig(t) + cfg := e2esys.FjordSystemConfig(t, new(hexutil.Uint64)) cfg.DeployConfig.L1GenesisBlockBaseFeePerGas = (*hexutil.Big)(big.NewInt(7)) - cfg.DeployConfig.L2GenesisRegolithTimeOffset = new(hexutil.Uint64) - cfg.DeployConfig.L2GenesisCanyonTimeOffset = new(hexutil.Uint64) - cfg.DeployConfig.L2GenesisDeltaTimeOffset = new(hexutil.Uint64) - cfg.DeployConfig.L2GenesisEcotoneTimeOffset = new(hexutil.Uint64) - cfg.DeployConfig.L2GenesisFjordTimeOffset = new(hexutil.Uint64) + testFees(t, cfg) + }) + + t.Run("isthmus without operator fee", func(t *testing.T) { + op_e2e.InitParallel(t) + cfg := e2esys.IsthmusSystemConfig(t, new(hexutil.Uint64)) + cfg.DeployConfig.L1GenesisBlockBaseFeePerGas = (*hexutil.Big)(big.NewInt(7)) + + testFees(t, cfg) + }) + + t.Run("isthmus with operator fee", func(t *testing.T) { + op_e2e.InitParallel(t) + cfg := e2esys.IsthmusSystemConfig(t, new(hexutil.Uint64)) + cfg.DeployConfig.L1GenesisBlockBaseFeePerGas = (*hexutil.Big)(big.NewInt(7)) + cfg.DeployConfig.GasPriceOracleOperatorFeeScalar = 1439103868 + cfg.DeployConfig.GasPriceOracleOperatorFeeConstant = 12564178266093314607 + testFees(t, cfg) }) } @@ -88,6 +101,13 @@ func testFees(t *testing.T, cfg e2esys.SystemConfig) { l2Verif := sys.NodeClient("verifier") l1 := sys.NodeClient("l1") + balanceAt := func(a common.Address, blockNumber *big.Int) *big.Int { + t.Helper() + bal, err := l2Seq.BalanceAt(context.Background(), a, blockNumber) + require.NoError(t, err) + return bal + } + // Wait for first block after genesis. The genesis block has zero L1Block values and will throw off the GPO checks _, err = geth.WaitForBlock(big.NewInt(1), l2Verif) require.NoError(t, err) @@ -101,6 +121,7 @@ func testFees(t *testing.T, cfg e2esys.SystemConfig) { } l1CostFn := types.NewL1CostFunc(config, sga) + operatorFeeFn := types.NewOperatorCostFunc(config, sga) // Transactor Account ethPrivKey := cfg.Secrets.Alice @@ -134,26 +155,24 @@ func testFees(t *testing.T, cfg e2esys.SystemConfig) { require.Equal(t, decimals.Uint64(), uint64(6), "wrong gpo decimals") - // BaseFee Recipient - baseFeeRecipientStartBalance, err := l2Seq.BalanceAt(context.Background(), predeploys.BaseFeeVaultAddr, big.NewInt(rpc.EarliestBlockNumber.Int64())) - require.Nil(t, err) - - // L1Fee Recipient - l1FeeRecipientStartBalance, err := l2Seq.BalanceAt(context.Background(), predeploys.L1FeeVaultAddr, big.NewInt(rpc.EarliestBlockNumber.Int64())) - require.Nil(t, err) + baseFeeRecipientStartBalance := balanceAt(predeploys.BaseFeeVaultAddr, big.NewInt(rpc.EarliestBlockNumber.Int64())) + l1FeeRecipientStartBalance := balanceAt(predeploys.L1FeeVaultAddr, big.NewInt(rpc.EarliestBlockNumber.Int64())) + sequencerFeeVaultStartBalance := balanceAt(predeploys.SequencerFeeVaultAddr, big.NewInt(rpc.EarliestBlockNumber.Int64())) + operatorFeeVaultStartBalance := balanceAt(predeploys.OperatorFeeVaultAddr, big.NewInt(rpc.EarliestBlockNumber.Int64())) - sequencerFeeVaultStartBalance, err := l2Seq.BalanceAt(context.Background(), predeploys.SequencerFeeVaultAddr, big.NewInt(rpc.EarliestBlockNumber.Int64())) - require.Nil(t, err) + require.Equal(t, baseFeeRecipientStartBalance.Sign(), 0, "base fee vault should be empty") + require.Equal(t, l1FeeRecipientStartBalance.Sign(), 0, "l1 fee vault should be empty") + require.Equal(t, sequencerFeeVaultStartBalance.Sign(), 0, "sequencer fee vault should be empty") + require.Equal(t, operatorFeeVaultStartBalance.Sign(), 0, "operator fee vault should be empty") genesisBlock, err := l2Seq.BlockByNumber(context.Background(), big.NewInt(rpc.EarliestBlockNumber.Int64())) require.NoError(t, err) - coinbaseStartBalance, err := l2Seq.BalanceAt(context.Background(), genesisBlock.Coinbase(), big.NewInt(rpc.EarliestBlockNumber.Int64())) - require.NoError(t, err) + coinbaseStartBalance := balanceAt(genesisBlock.Coinbase(), big.NewInt(rpc.EarliestBlockNumber.Int64())) // Simple transfer from signer to random account - startBalance, err := l2Seq.BalanceAt(context.Background(), fromAddr, big.NewInt(rpc.EarliestBlockNumber.Int64())) - require.Nil(t, err) + startBalance := balanceAt(fromAddr, big.NewInt(rpc.EarliestBlockNumber.Int64())) + require.Greater(t, startBalance.Uint64(), big.NewInt(params.Ether).Uint64()) transferAmount := big.NewInt(params.Ether) @@ -172,38 +191,33 @@ func testFees(t *testing.T, cfg e2esys.SystemConfig) { header, err := l2Seq.HeaderByNumber(context.Background(), receipt.BlockNumber) require.Nil(t, err) - coinbaseEndBalance, err := l2Seq.BalanceAt(context.Background(), header.Coinbase, header.Number) - require.Nil(t, err) - - endBalance, err := l2Seq.BalanceAt(context.Background(), fromAddr, header.Number) - require.Nil(t, err) - - baseFeeRecipientEndBalance, err := l2Seq.BalanceAt(context.Background(), predeploys.BaseFeeVaultAddr, header.Number) - require.Nil(t, err) + coinbaseEndBalance := balanceAt(header.Coinbase, header.Number) + endBalance := balanceAt(fromAddr, header.Number) + baseFeeRecipientEndBalance := balanceAt(predeploys.BaseFeeVaultAddr, header.Number) l1Header, err := l1.HeaderByNumber(context.Background(), nil) require.Nil(t, err) - l1FeeRecipientEndBalance, err := l2Seq.BalanceAt(context.Background(), predeploys.L1FeeVaultAddr, header.Number) - require.Nil(t, err) - - sequencerFeeVaultEndBalance, err := l2Seq.BalanceAt(context.Background(), predeploys.SequencerFeeVaultAddr, header.Number) - require.Nil(t, err) + l1FeeRecipientEndBalance := balanceAt(predeploys.L1FeeVaultAddr, header.Number) + sequencerFeeVaultEndBalance := balanceAt(predeploys.SequencerFeeVaultAddr, header.Number) + operatorFeeVaultEndBalance := balanceAt(predeploys.OperatorFeeVaultAddr, header.Number) // Diff fee recipient + coinbase balances baseFeeRecipientDiff := new(big.Int).Sub(baseFeeRecipientEndBalance, baseFeeRecipientStartBalance) l1FeeRecipientDiff := new(big.Int).Sub(l1FeeRecipientEndBalance, l1FeeRecipientStartBalance) sequencerFeeVaultDiff := new(big.Int).Sub(sequencerFeeVaultEndBalance, sequencerFeeVaultStartBalance) + operatorFeeVaultDiff := new(big.Int).Sub(operatorFeeVaultEndBalance, operatorFeeVaultStartBalance) coinbaseDiff := new(big.Int).Sub(coinbaseEndBalance, coinbaseStartBalance) // Tally L2 Fee - l2Fee := gasTip.Mul(gasTip, new(big.Int).SetUint64(receipt.GasUsed)) + gasUsed := new(big.Int).SetUint64(receipt.GasUsed) + l2Fee := gasTip.Mul(gasTip, gasUsed) require.Equal(t, sequencerFeeVaultDiff, coinbaseDiff, "coinbase is always sequencer fee vault") require.Equal(t, l2Fee, coinbaseDiff, "l2 fee mismatch") require.Equal(t, l2Fee, sequencerFeeVaultDiff) // Tally BaseFee - baseFee := new(big.Int).Mul(header.BaseFee, new(big.Int).SetUint64(receipt.GasUsed)) + baseFee := new(big.Int).Mul(header.BaseFee, gasUsed) require.Equal(t, baseFee, baseFeeRecipientDiff, "base fee mismatch") // Tally L1 Fee @@ -215,6 +229,9 @@ func testFees(t *testing.T, cfg e2esys.SystemConfig) { l1Fee := l1CostFn(tx.RollupCostData(), header.Time) require.Equalf(t, l1Fee, l1FeeRecipientDiff, "L1 fee mismatch: start balance %v, end balance %v", l1FeeRecipientStartBalance, l1FeeRecipientEndBalance) + operatorFee := operatorFeeFn(receipt.GasUsed, header.Time) + require.True(t, operatorFeeVaultDiff.Cmp(operatorFee.ToBig()) == 0, "operator fee mismatch: start balance %v, end balance %v", operatorFeeVaultStartBalance, operatorFeeVaultEndBalance) + gpoEcotone, err := gpoContract.IsEcotone(nil) require.NoError(t, err) require.Equal(t, sys.RollupConfig.IsEcotone(header.Time), gpoEcotone, "GPO and chain must have same ecotone view") @@ -223,6 +240,10 @@ func testFees(t *testing.T, cfg e2esys.SystemConfig) { require.NoError(t, err) require.Equal(t, sys.RollupConfig.IsFjord(header.Time), gpoFjord, "GPO and chain must have same fjord view") + gpoIsthmus, err := gpoContract.IsIsthmus(nil) + require.NoError(t, err) + require.Equal(t, sys.RollupConfig.IsIsthmus(header.Time), gpoIsthmus, "GPO and chain must have same isthmus view") + gpoL1Fee, err := gpoContract.GetL1Fee(&bind.CallOpts{}, bytes) require.Nil(t, err) @@ -256,9 +277,36 @@ func testFees(t *testing.T, cfg e2esys.SystemConfig) { new(big.Float).SetInt(receipt.L1Fee), "fee field in receipt matches gas used times scalar times base fee") } + expectedOperatorFee := new(big.Int).Add( + new(big.Int).Div( + new(big.Int).Mul( + gasUsed, + new(big.Int).SetUint64(uint64(cfg.DeployConfig.GasPriceOracleOperatorFeeScalar)), + ), + new(big.Int).SetUint64(uint64(1e6)), + ), + new(big.Int).SetUint64(cfg.DeployConfig.GasPriceOracleOperatorFeeConstant), + ) + + if sys.RollupConfig.IsIsthmus(header.Time) { + require.True(t, expectedOperatorFee.Cmp(operatorFee.ToBig()) == 0, + "operator fee is correct", + ) + + if cfg.DeployConfig.GasPriceOracleOperatorFeeScalar == 0 && cfg.DeployConfig.GasPriceOracleOperatorFeeConstant == 0 { + // if both operator fee params are 0, they aren't added to receipts. + require.Nil(t, receipt.OperatorFeeScalar, "operator fee constant in receipt is nil") + require.Nil(t, receipt.OperatorFeeConstant, "operator fee constant in receipt is nil") + } else { + require.Equal(t, cfg.DeployConfig.GasPriceOracleOperatorFeeScalar, uint32(*receipt.OperatorFeeScalar), "operator fee constant in receipt is correct") + require.Equal(t, cfg.DeployConfig.GasPriceOracleOperatorFeeConstant, *receipt.OperatorFeeConstant, "operator fee constant in receipt is correct") + } + } + // Calculate total fee baseFeeRecipientDiff.Add(baseFeeRecipientDiff, coinbaseDiff) totalFee := new(big.Int).Add(baseFeeRecipientDiff, l1FeeRecipientDiff) + totalFee = new(big.Int).Add(totalFee, operatorFeeVaultDiff) balanceDiff := new(big.Int).Sub(startBalance, endBalance) balanceDiff.Sub(balanceDiff, transferAmount) require.Equal(t, balanceDiff, totalFee, "balances should add up") diff --git a/op-node/rollup/derive/isthmus_upgrade_transactions.go b/op-node/rollup/derive/isthmus_upgrade_transactions.go index 595bf50fd94..31c575af469 100644 --- a/op-node/rollup/derive/isthmus_upgrade_transactions.go +++ b/op-node/rollup/derive/isthmus_upgrade_transactions.go @@ -7,14 +7,170 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" ) var ( + // L1Block Parameters + deployIsthmusL1BlockSource = UpgradeDepositSource{Intent: "Isthmus: L1 Block Deployment"} + updateIsthmusL1BlockProxySource = UpgradeDepositSource{Intent: "Isthmus: L1 Block Proxy Update"} + L1BlockIsthmusDeployerAddress = common.HexToAddress("0x4210000000000000000000000000000000000003") + isthmusL1BlockAddress = crypto.CreateAddress(L1BlockIsthmusDeployerAddress, 0) + + // Gas Price Oracle Parameters + deployIsthmusGasPriceOracleSource = UpgradeDepositSource{Intent: "Isthmus: Gas Price Oracle Deployment"} + updateIsthmusGasPriceOracleSource = UpgradeDepositSource{Intent: "Isthmus: Gas Price Oracle Proxy Update"} + GasPriceOracleIsthmusDeployerAddress = common.HexToAddress("0x4210000000000000000000000000000000000004") + isthmusGasPriceOracleAddress = crypto.CreateAddress(GasPriceOracleIsthmusDeployerAddress, 0) + + // Operator fee vault Parameters + deployOperatorFeeVaultSource = UpgradeDepositSource{Intent: "Isthmus: Operator Fee Vault Deployment"} + updateOperatorFeeVaultSource = UpgradeDepositSource{Intent: "Isthmus: Operator Fee Vault Proxy Update"} + OperatorFeeVaultDeployerAddress = common.HexToAddress("0x4210000000000000000000000000000000000005") + OperatorFeeVaultAddress = crypto.CreateAddress(OperatorFeeVaultDeployerAddress, 0) + + // Bytecodes + l1BlockIsthmusDeploymentBytecode = common.FromHex("0x608060405234801561001057600080fd5b506106ae806100206000396000f3fe608060405234801561001057600080fd5b50600436106101825760003560e01c806364ca23ef116100d8578063b80777ea1161008c578063e591b28211610066578063e591b282146103b0578063e81b2c6d146103d2578063f8206140146103db57600080fd5b8063b80777ea14610337578063c598591814610357578063d84447151461037757600080fd5b80638381f58a116100bd5780638381f58a146103115780638b239f73146103255780639e8c49661461032e57600080fd5b806364ca23ef146102e157806368d5dca6146102f557600080fd5b80634397dfef1161013a57806354fd4d501161011457806354fd4d501461025d578063550fcdc91461029f5780635cf24969146102d857600080fd5b80634397dfef146101fc578063440a5e20146102245780634d5d9a2a1461022c57600080fd5b806309bd5a601161016b57806309bd5a60146101a457806316d3bc7f146101c057806321326849146101ed57600080fd5b8063015d8eb914610187578063098999be1461019c575b600080fd5b61019a6101953660046105bc565b6103e4565b005b61019a610523565b6101ad60025481565b6040519081526020015b60405180910390f35b6008546101d49067ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020016101b7565b604051600081526020016101b7565b6040805173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee815260126020820152016101b7565b61019a61052d565b6008546102489068010000000000000000900463ffffffff1681565b60405163ffffffff90911681526020016101b7565b60408051808201909152600c81527f312e352e312d626574612e37000000000000000000000000000000000000000060208201525b6040516101b7919061062e565b60408051808201909152600381527f45544800000000000000000000000000000000000000000000000000000000006020820152610292565b6101ad60015481565b6003546101d49067ffffffffffffffff1681565b6003546102489068010000000000000000900463ffffffff1681565b6000546101d49067ffffffffffffffff1681565b6101ad60055481565b6101ad60065481565b6000546101d49068010000000000000000900467ffffffffffffffff1681565b600354610248906c01000000000000000000000000900463ffffffff1681565b60408051808201909152600581527f45746865720000000000000000000000000000000000000000000000000000006020820152610292565b60405173deaddeaddeaddeaddeaddeaddeaddeaddead000181526020016101b7565b6101ad60045481565b6101ad60075481565b3373deaddeaddeaddeaddeaddeaddeaddeaddead00011461048b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f4c31426c6f636b3a206f6e6c7920746865206465706f7369746f72206163636f60448201527f756e742063616e20736574204c3120626c6f636b2076616c7565730000000000606482015260840160405180910390fd5b6000805467ffffffffffffffff98891668010000000000000000027fffffffffffffffffffffffffffffffff00000000000000000000000000000000909116998916999099179890981790975560019490945560029290925560038054919094167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009190911617909255600491909155600555600655565b61052b610535565b565b61052b610548565b61053d610548565b60a43560a01c600855565b73deaddeaddeaddeaddeaddeaddeaddeaddead000133811461057257633cc50b456000526004601cfd5b60043560801c60035560143560801c60005560243560015560443560075560643560025560843560045550565b803567ffffffffffffffff811681146105b757600080fd5b919050565b600080600080600080600080610100898b0312156105d957600080fd5b6105e28961059f565b97506105f060208a0161059f565b9650604089013595506060890135945061060c60808a0161059f565b979a969950949793969560a0850135955060c08501359460e001359350915050565b600060208083528351808285015260005b8181101561065b5785810183015185820160400152820161063f565b8181111561066d576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01692909201604001939250505056fea164736f6c634300080f000a") + gasPriceOracleIsthmusDeploymentBytecode = common.FromHex("0x608060405234801561001057600080fd5b50611c3c806100206000396000f3fe608060405234801561001057600080fd5b50600436106101775760003560e01c806368d5dca6116100d8578063c59859181161008c578063f45e65d811610066578063f45e65d8146102ca578063f8206140146102d2578063fe173b971461026957600080fd5b8063c59859181461029c578063de26c4a1146102a4578063f1c7a58b146102b757600080fd5b80638e98b106116100bd5780638e98b1061461026f578063960e3a2314610277578063b54501bc1461028957600080fd5b806368d5dca61461024c5780636ef25c3a1461026957600080fd5b8063313ce5671161012f5780634ef6e224116101145780634ef6e224146101de578063519b4bd3146101fb57806354fd4d501461020357600080fd5b8063313ce567146101c457806349948e0e146101cb57600080fd5b8063275aedd211610160578063275aedd2146101a1578063291b0383146101b45780632e0f2625146101bc57600080fd5b80630c18c1621461017c57806322b90ab314610197575b600080fd5b6101846102da565b6040519081526020015b60405180910390f35b61019f6103fb565b005b6101846101af36600461168e565b610584565b61019f61070f565b610184600681565b6006610184565b6101846101d93660046116d6565b610937565b6000546101eb9060ff1681565b604051901515815260200161018e565b61018461096e565b61023f6040518060400160405280600c81526020017f312e332e312d626574612e35000000000000000000000000000000000000000081525081565b60405161018e91906117a5565b6102546109cf565b60405163ffffffff909116815260200161018e565b48610184565b61019f610a54565b6000546101eb90610100900460ff1681565b6000546101eb9062010000900460ff1681565b610254610c4e565b6101846102b23660046116d6565b610caf565b6101846102c536600461168e565b610da9565b610184610e85565b610184610f78565b6000805460ff1615610373576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f47617350726963654f7261636c653a206f76657268656164282920697320646560448201527f707265636174656400000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103d2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103f69190611818565b905090565b3373deaddeaddeaddeaddeaddeaddeaddeaddead0001146104c4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e2073657420697345636f746f6e6520666c6160648201527f6700000000000000000000000000000000000000000000000000000000000000608482015260a40161036a565b60005460ff1615610557576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a2045636f746f6e6520616c72656164792060448201527f6163746976650000000000000000000000000000000000000000000000000000606482015260840161036a565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b6000805462010000900460ff1661059d57506000919050565b610709620f42406106668473420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16634d5d9a2a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610607573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061062b9190611831565b63ffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821583830293840490921491909117011790565b6106709190611886565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff166316d3bc7f6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156106cf573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106f391906118c1565b67ffffffffffffffff1681019081106000031790565b92915050565b3373deaddeaddeaddeaddeaddeaddeaddeaddead0001146107d8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e20736574206973497374686d757320666c6160648201527f6700000000000000000000000000000000000000000000000000000000000000608482015260a40161036a565b600054610100900460ff1661086f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f47617350726963654f7261636c653a20497374686d75732063616e206f6e6c7960448201527f2062652061637469766174656420616674657220466a6f726400000000000000606482015260840161036a565b60005462010000900460ff1615610908576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a20497374686d757320616c72656164792060448201527f6163746976650000000000000000000000000000000000000000000000000000606482015260840161036a565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffff1662010000179055565b60008054610100900460ff16156109515761070982610fd9565b60005460ff16156109655761070982610ff8565b6107098261109c565b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16635cf249696040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103d2573d6000803e3d6000fd5b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff166368d5dca66040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a30573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103f69190611831565b3373deaddeaddeaddeaddeaddeaddeaddeaddead000114610af7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603f60248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e20736574206973466a6f726420666c616700606482015260840161036a565b60005460ff16610b89576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f47617350726963654f7261636c653a20466a6f72642063616e206f6e6c79206260448201527f65206163746976617465642061667465722045636f746f6e6500000000000000606482015260840161036a565b600054610100900460ff1615610c20576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f47617350726963654f7261636c653a20466a6f726420616c726561647920616360448201527f7469766500000000000000000000000000000000000000000000000000000000606482015260840161036a565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff16610100179055565b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663c59859186040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a30573d6000803e3d6000fd5b60008054610100900460ff1615610cf657620f4240610ce1610cd0846111f0565b51610cdc9060446118eb565b61150d565b610cec906010611903565b6107099190611886565b6000610d018361156c565b60005490915060ff1615610d155792915050565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d74573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d989190611818565b610da290826118eb565b9392505050565b60008054610100900460ff16610e41576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603660248201527f47617350726963654f7261636c653a206765744c314665655570706572426f7560448201527f6e64206f6e6c7920737570706f72747320466a6f726400000000000000000000606482015260840161036a565b6000610e4e8360446118eb565b90506000610e5d60ff83611886565b610e6790836118eb565b610e729060106118eb565b9050610e7d816115fc565b949350505050565b6000805460ff1615610f19576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a207363616c61722829206973206465707260448201527f6563617465640000000000000000000000000000000000000000000000000000606482015260840161036a565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16639e8c49666040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103d2573d6000803e3d6000fd5b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663f82061406040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103d2573d6000803e3d6000fd5b6000610709610fe7836111f0565b51610ff39060446118eb565b6115fc565b6000806110048361156c565b9050600061101061096e565b611018610c4e565b611023906010611940565b63ffffffff166110339190611903565b9050600061103f610f78565b6110476109cf565b63ffffffff166110579190611903565b9050600061106582846118eb565b61106f9085611903565b905061107d6006600a611a8c565b611088906010611903565b6110929082611886565b9695505050505050565b6000806110a88361156c565b9050600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16639e8c49666040518163ffffffff1660e01b8152600401602060405180830381865afa15801561110b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061112f9190611818565b61113761096e565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa158015611196573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111ba9190611818565b6111c490856118eb565b6111ce9190611903565b6111d89190611903565b90506111e66006600a611a8c565b610e7d9082611886565b606061137f565b818153600101919050565b600082840393505b83811015610da25782810151828201511860001a159093029260010161120a565b825b60208210611277578251611242601f836111f7565b52602092909201917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09091019060210161122d565b8115610da257825161128c60018403836111f7565b520160010192915050565b60006001830392505b61010782106112d8576112ca8360ff166112c560fd6112c58760081c60e001896111f7565b6111f7565b9350610106820391506112a0565b60078210611305576112fe8360ff166112c5600785036112c58760081c60e001896111f7565b9050610da2565b610e7d8360ff166112c58560081c8560051b01876111f7565b61137782820361135b61134b84600081518060001a8160011a60081b178160021a60101b17915050919050565b639e3779b90260131c611fff1690565b8060021b6040510182815160e01c1860e01b8151188152505050565b600101919050565b6180003860405139618000604051016020830180600d8551820103826002015b818110156114b2576000805b50508051604051600082901a600183901a60081b1760029290921a60101b91909117639e3779b9810260111c617ffc16909101805160e081811c878603811890911b909118909152840190818303908484106114075750611442565b600184019350611fff821161143c578251600081901a600182901a60081b1760029190911a60101b17810361143c5750611442565b506113ab565b8383106114505750506114b2565b6001830392508583111561146e5761146b878788860361122b565b96505b611482600985016003850160038501611202565b915061148f878284611297565b9650506114a7846114a28684860161131e565b61131e565b91505080935061139f565b50506114c4838384885185010361122b565b925050506040519150618000820180820391508183526020830160005b838110156114f95782810151828201526020016114e1565b506000920191825250602001604052919050565b60008061151d83620cc394611903565b611547907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd763200611a98565b90506115576064620f4240611b0c565b81121561070957610da26064620f4240611b0c565b80516000908190815b818110156115ef5784818151811061158f5761158f611bc8565b01602001517fff00000000000000000000000000000000000000000000000000000000000000166000036115cf576115c86004846118eb565b92506115dd565b6115da6010846118eb565b92505b806115e781611bf7565b915050611575565b50610e7d826104406118eb565b6000806116088361150d565b90506000611614610f78565b61161c6109cf565b63ffffffff1661162c9190611903565b61163461096e565b61163c610c4e565b611647906010611940565b63ffffffff166116579190611903565b61166191906118eb565b905061166f60066002611903565b61167a90600a611a8c565b6116848284611903565b610e7d9190611886565b6000602082840312156116a057600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000602082840312156116e857600080fd5b813567ffffffffffffffff8082111561170057600080fd5b818401915084601f83011261171457600080fd5b813581811115611726576117266116a7565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561176c5761176c6116a7565b8160405282815287602084870101111561178557600080fd5b826020860160208301376000928101602001929092525095945050505050565b600060208083528351808285015260005b818110156117d2578581018301518582016040015282016117b6565b818111156117e4576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b60006020828403121561182a57600080fd5b5051919050565b60006020828403121561184357600080fd5b815163ffffffff81168114610da257600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000826118bc577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b6000602082840312156118d357600080fd5b815167ffffffffffffffff81168114610da257600080fd5b600082198211156118fe576118fe611857565b500190565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161561193b5761193b611857565b500290565b600063ffffffff8083168185168183048111821515161561196357611963611857565b02949350505050565b600181815b808511156119c557817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048211156119ab576119ab611857565b808516156119b857918102915b93841c9390800290611971565b509250929050565b6000826119dc57506001610709565b816119e957506000610709565b81600181146119ff5760028114611a0957611a25565b6001915050610709565b60ff841115611a1a57611a1a611857565b50506001821b610709565b5060208310610133831016604e8410600b8410161715611a48575081810a610709565b611a52838361196c565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04821115611a8457611a84611857565b029392505050565b6000610da283836119cd565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03841381151615611ad257611ad2611857565b827f8000000000000000000000000000000000000000000000000000000000000000038412811615611b0657611b06611857565b50500190565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600084136000841385830485118282161615611b4d57611b4d611857565b7f80000000000000000000000000000000000000000000000000000000000000006000871286820588128184161615611b8857611b88611857565b60008712925087820587128484161615611ba457611ba4611857565b87850587128184161615611bba57611bba611857565b505050929093029392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611c2857611c28611857565b506001019056fea164736f6c634300080f000a") + operatorFeeVaultDeploymentBytecode = common.FromHex("0x60e060405234801561001057600080fd5b5073420000000000000000000000000000000000001960a0526000608052600160c05260805160a05160c0516107ef6100a7600039600081816101b3015281816102450152818161044b015261048601526000818160b8015281816101800152818161039a01528181610429015281816104c201526105b70152600081816101ef01528181610279015261029d01526107ef6000f3fe60806040526004361061009a5760003560e01c806382356d8a1161006957806384411d651161004e57806384411d651461021d578063d0e12f9014610233578063d3e5792b1461026757600080fd5b806382356d8a146101a45780638312f149146101e057600080fd5b80630d9019e1146100a65780633ccfd60b1461010457806354fd4d501461011b57806366d003ac1461017157600080fd5b366100a157005b600080fd5b3480156100b257600080fd5b506100da7f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561011057600080fd5b5061011961029b565b005b34801561012757600080fd5b506101646040518060400160405280600c81526020017f312e352e302d626574612e35000000000000000000000000000000000000000081525081565b6040516100fb9190610671565b34801561017d57600080fd5b507f00000000000000000000000000000000000000000000000000000000000000006100da565b3480156101b057600080fd5b507f00000000000000000000000000000000000000000000000000000000000000005b6040516100fb919061074e565b3480156101ec57600080fd5b507f00000000000000000000000000000000000000000000000000000000000000005b6040519081526020016100fb565b34801561022957600080fd5b5061020f60005481565b34801561023f57600080fd5b506101d37f000000000000000000000000000000000000000000000000000000000000000081565b34801561027357600080fd5b5061020f7f000000000000000000000000000000000000000000000000000000000000000081565b7f0000000000000000000000000000000000000000000000000000000000000000471015610376576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f4665655661756c743a207769746864726177616c20616d6f756e74206d75737460448201527f2062652067726561746572207468616e206d696e696d756d207769746864726160648201527f77616c20616d6f756e7400000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b60004790508060008082825461038c9190610762565b9091555050604080518281527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166020820152338183015290517fc8a211cc64b6ed1b50595a9fcb1932b6d1e5a6e8ef15b60e5b1f988ea9086bba9181900360600190a17f38e04cbeb8c10f8f568618aa75be0f10b6729b8b4237743b4de20cbcde2839ee817f0000000000000000000000000000000000000000000000000000000000000000337f000000000000000000000000000000000000000000000000000000000000000060405161047a94939291906107a1565b60405180910390a160017f000000000000000000000000000000000000000000000000000000000000000060018111156104b6576104b66106e4565b0361057a5760006104e77f000000000000000000000000000000000000000000000000000000000000000083610649565b905080610576576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f4665655661756c743a206661696c656420746f2073656e642045544820746f2060448201527f4c322066656520726563697069656e7400000000000000000000000000000000606482015260840161036d565b5050565b6040517fc2b3e5ac00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016600482015262061a80602482015260606044820152600060648201527342000000000000000000000000000000000000169063c2b3e5ac9083906084016000604051808303818588803b15801561062d57600080fd5b505af1158015610641573d6000803e3d6000fd5b505050505050565b6000610656835a8461065d565b9392505050565b6000806000806000858888f1949350505050565b600060208083528351808285015260005b8181101561069e57858101830151858201604001528201610682565b818111156106b0576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b6002811061074a577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b9052565b6020810161075c8284610713565b92915050565b6000821982111561079c577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b500190565b84815273ffffffffffffffffffffffffffffffffffffffff848116602083015283166040820152608081016107d96060830184610713565b9594505050505056fea164736f6c634300080f000a") + + // Enable Isthmus Parameters + enableIsthmusSource = UpgradeDepositSource{Intent: "Isthmus: Gas Price Oracle Set Isthmus"} + enableIsthmusInput = crypto.Keccak256([]byte("setIsthmus()"))[:4] + blockHashDeployerSource = UpgradeDepositSource{Intent: "Isthmus: EIP-2935 Contract Deployment"} blockHashDeploymentBytecode = common.FromHex("0x60538060095f395ff33373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500") ) func IsthmusNetworkUpgradeTransactions() ([]hexutil.Bytes, error) { + upgradeTxns := make([]hexutil.Bytes, 0, 8) + + // Deploy L1 Block transaction + deployL1BlockTransaction, err := types.NewTx(&types.DepositTx{ + SourceHash: deployIsthmusL1BlockSource.SourceHash(), + From: L1BlockIsthmusDeployerAddress, + To: nil, + Mint: big.NewInt(0), + Value: big.NewInt(0), + Gas: 425_000, + IsSystemTransaction: false, + Data: l1BlockIsthmusDeploymentBytecode, + }).MarshalBinary() + + if err != nil { + return nil, err + } + + upgradeTxns = append(upgradeTxns, deployL1BlockTransaction) + + // Deploy Gas Price Oracle transaction + deployGasPriceOracle, err := types.NewTx(&types.DepositTx{ + SourceHash: deployIsthmusGasPriceOracleSource.SourceHash(), + From: GasPriceOracleIsthmusDeployerAddress, + To: nil, + Mint: big.NewInt(0), + Value: big.NewInt(0), + Gas: 1_625_000, + IsSystemTransaction: false, + Data: gasPriceOracleIsthmusDeploymentBytecode, + }).MarshalBinary() + + if err != nil { + return nil, err + } + + upgradeTxns = append(upgradeTxns, deployGasPriceOracle) + + // Deploy Operator Fee vault transaction + deployOperatorFeeVault, err := types.NewTx(&types.DepositTx{ + SourceHash: deployOperatorFeeVaultSource.SourceHash(), + From: OperatorFeeVaultDeployerAddress, + To: nil, + Mint: big.NewInt(0), + Value: big.NewInt(0), + Gas: 500_000, + IsSystemTransaction: false, + Data: operatorFeeVaultDeploymentBytecode, + }).MarshalBinary() + + if err != nil { + return nil, err + } + + upgradeTxns = append(upgradeTxns, deployOperatorFeeVault) + + // Deploy L1 Block Proxy upgrade transaction + updateL1BlockProxy, err := types.NewTx(&types.DepositTx{ + SourceHash: updateIsthmusL1BlockProxySource.SourceHash(), + From: common.Address{}, + To: &predeploys.L1BlockAddr, + Mint: big.NewInt(0), + Value: big.NewInt(0), + Gas: 50_000, + IsSystemTransaction: false, + Data: upgradeToCalldata(isthmusL1BlockAddress), + }).MarshalBinary() + + if err != nil { + return nil, err + } + + upgradeTxns = append(upgradeTxns, updateL1BlockProxy) + + // Deploy Gas Price Oracle Proxy upgrade transaction + updateGasPriceOracleProxy, err := types.NewTx(&types.DepositTx{ + SourceHash: updateIsthmusGasPriceOracleSource.SourceHash(), + From: common.Address{}, + To: &predeploys.GasPriceOracleAddr, + Mint: big.NewInt(0), + Value: big.NewInt(0), + Gas: 50_000, + IsSystemTransaction: false, + Data: upgradeToCalldata(isthmusGasPriceOracleAddress), + }).MarshalBinary() + + if err != nil { + return nil, err + } + + upgradeTxns = append(upgradeTxns, updateGasPriceOracleProxy) + + // Deploy Operator Fee Vault Proxy upgrade transaction + updateOperatorFeeVaultProxy, err := types.NewTx(&types.DepositTx{ + SourceHash: updateOperatorFeeVaultSource.SourceHash(), + From: common.Address{}, + To: &predeploys.OperatorFeeVaultAddr, + Mint: big.NewInt(0), + Value: big.NewInt(0), + Gas: 50_000, + IsSystemTransaction: false, + Data: upgradeToCalldata(OperatorFeeVaultAddress), + }).MarshalBinary() + + if err != nil { + return nil, err + } + + upgradeTxns = append(upgradeTxns, updateOperatorFeeVaultProxy) + + // Enable Isthmus transaction + enableIsthmus, err := types.NewTx(&types.DepositTx{ + SourceHash: enableIsthmusSource.SourceHash(), + From: L1InfoDepositerAddress, + To: &predeploys.GasPriceOracleAddr, + Mint: big.NewInt(0), + Value: big.NewInt(0), + Gas: 90_000, + IsSystemTransaction: false, + Data: enableIsthmusInput, + }).MarshalBinary() + + if err != nil { + return nil, err + } + + upgradeTxns = append(upgradeTxns, enableIsthmus) + deployHistoricalBlockHashesContract, err := types.NewTx(&types.DepositTx{ SourceHash: blockHashDeployerSource.SourceHash(), From: predeploys.EIP2935ContractDeployer, @@ -30,5 +186,7 @@ func IsthmusNetworkUpgradeTransactions() ([]hexutil.Bytes, error) { return nil, err } - return []hexutil.Bytes{deployHistoricalBlockHashesContract}, nil + upgradeTxns = append(upgradeTxns, deployHistoricalBlockHashesContract) + + return upgradeTxns, nil } diff --git a/op-node/rollup/derive/isthmus_upgrade_transactions_test.go b/op-node/rollup/derive/isthmus_upgrade_transactions_test.go index 5527e76e20e..c005d57b161 100644 --- a/op-node/rollup/derive/isthmus_upgrade_transactions_test.go +++ b/op-node/rollup/derive/isthmus_upgrade_transactions_test.go @@ -4,9 +4,8 @@ import ( "testing" "github.com/ethereum-optimism/optimism/op-service/predeploys" - "github.com/stretchr/testify/require" - "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" ) func TestIsthmusSourcesMatchSpec(t *testing.T) { @@ -18,6 +17,34 @@ func TestIsthmusSourcesMatchSpec(t *testing.T) { source: blockHashDeployerSource, expectedHash: "0xbfb734dae514c5974ddf803e54c1bc43d5cdb4a48ae27e1d9b875a5a150b553a", }, + { + source: deployIsthmusL1BlockSource, + expectedHash: "0x3b2d0821ca2411ad5cd3595804d1213d15737188ae4cbd58aa19c821a6c211bf", + }, + { + source: deployIsthmusGasPriceOracleSource, + expectedHash: "0xfc70b48424763fa3fab9844253b4f8d508f91eb1f7cb11a247c9baec0afb8035", + }, + { + source: deployOperatorFeeVaultSource, + expectedHash: "0x107a570d3db75e6110817eb024f09f3172657e920634111ce9875d08a16daa96", + }, + { + source: updateIsthmusL1BlockProxySource, + expectedHash: "0xebe8b5cb10ca47e0d8bda8f5355f2d66711a54ddeb0ef1d30e29418c9bf17a0e", + }, + { + source: updateIsthmusGasPriceOracleSource, + expectedHash: "0xecf2d9161d26c54eda6b7bfdd9142719b1e1199a6e5641468d1bf705bc531ab0", + }, + { + source: updateOperatorFeeVaultSource, + expectedHash: "0xad74e1adb877ccbe176b8fa1cc559388a16e090ddbe8b512f5b37d07d887a927", + }, + { + source: enableIsthmusSource, + expectedHash: "0x3ddf4b1302548dd92939826e970f260ba36167f4c25f18390a5e8b194b295319", + }, } { require.Equal(t, common.HexToHash(test.expectedHash), test.source.SourceHash()) } @@ -26,9 +53,62 @@ func TestIsthmusSourcesMatchSpec(t *testing.T) { func TestIsthmusNetworkTransactions(t *testing.T) { upgradeTxns, err := IsthmusNetworkUpgradeTransactions() require.NoError(t, err) - require.Len(t, upgradeTxns, 1) + require.Len(t, upgradeTxns, 8) + + deployL1BlockSender, deployL1Block := toDepositTxn(t, upgradeTxns[0]) + require.Equal(t, deployL1BlockSender, common.HexToAddress("0x4210000000000000000000000000000000000003")) + require.Equal(t, deployIsthmusL1BlockSource.SourceHash(), deployL1Block.SourceHash()) + require.Nil(t, deployL1Block.To()) + require.Equal(t, uint64(425_000), deployL1Block.Gas()) // TODO + require.Equal(t, l1BlockIsthmusDeploymentBytecode, deployL1Block.Data()) + + deployGasPriceOracleSender, deployGasPriceOracle := toDepositTxn(t, upgradeTxns[1]) + require.Equal(t, deployGasPriceOracleSender, common.HexToAddress("0x4210000000000000000000000000000000000004")) + require.Equal(t, deployIsthmusGasPriceOracleSource.SourceHash(), deployGasPriceOracle.SourceHash()) + require.Nil(t, deployGasPriceOracle.To()) + require.Equal(t, uint64(1_625_000), deployGasPriceOracle.Gas()) + require.Equal(t, gasPriceOracleIsthmusDeploymentBytecode, deployGasPriceOracle.Data()) + + deployOperatorFeeVaultSender, deployOperatorFeeVault := toDepositTxn(t, upgradeTxns[2]) + require.Equal(t, deployOperatorFeeVaultSender, common.HexToAddress("0x4210000000000000000000000000000000000005")) + require.Equal(t, deployOperatorFeeVaultSource.SourceHash(), deployOperatorFeeVault.SourceHash()) + require.Nil(t, deployOperatorFeeVault.To()) + require.Equal(t, uint64(500_000), deployOperatorFeeVault.Gas()) + require.Equal(t, operatorFeeVaultDeploymentBytecode, deployOperatorFeeVault.Data()) + + updateL1BlockProxySender, updateL1BlockProxy := toDepositTxn(t, upgradeTxns[3]) + require.Equal(t, updateL1BlockProxySender, common.Address{}) + require.Equal(t, updateIsthmusL1BlockProxySource.SourceHash(), updateL1BlockProxy.SourceHash()) + require.NotNil(t, updateL1BlockProxy.To()) + require.Equal(t, *updateL1BlockProxy.To(), common.HexToAddress("0x4200000000000000000000000000000000000015")) + require.Equal(t, uint64(50_000), updateL1BlockProxy.Gas()) + require.Equal(t, common.FromHex("3659cfe6000000000000000000000000ff256497d61dcd71a9e9ff43967c13fde1f72d12"), updateL1BlockProxy.Data()) + + updateGasPriceOracleSender, updateGasPriceOracle := toDepositTxn(t, upgradeTxns[4]) + require.Equal(t, updateGasPriceOracleSender, common.Address{}) + require.Equal(t, updateIsthmusGasPriceOracleSource.SourceHash(), updateGasPriceOracle.SourceHash()) + require.NotNil(t, updateGasPriceOracle.To()) + require.Equal(t, *updateGasPriceOracle.To(), common.HexToAddress("0x420000000000000000000000000000000000000F")) + require.Equal(t, uint64(50_000), updateGasPriceOracle.Gas()) + require.Equal(t, common.FromHex("3659cfe600000000000000000000000093e57a196454cb919193fa9946f14943cf733845"), updateGasPriceOracle.Data()) + + updateOperatorFeeSender, updateOperatorFee := toDepositTxn(t, upgradeTxns[5]) + require.Equal(t, updateOperatorFeeSender, common.Address{}) + require.Equal(t, updateOperatorFeeVaultSource.SourceHash(), updateOperatorFee.SourceHash()) + require.NotNil(t, updateOperatorFee.To()) + require.Equal(t, *updateOperatorFee.To(), common.HexToAddress("0x420000000000000000000000000000000000001b")) + require.Equal(t, uint64(50_000), updateOperatorFee.Gas()) + require.Equal(t, common.FromHex("3659cfe60000000000000000000000004fa2be8cd41504037f1838bce3bcc93bc68ff537"), updateOperatorFee.Data()) + + gpoSetIsthmusSender, gpoSetIsthmus := toDepositTxn(t, upgradeTxns[6]) + require.Equal(t, gpoSetIsthmusSender, common.HexToAddress("0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001")) + require.Equal(t, enableIsthmusSource.SourceHash(), gpoSetIsthmus.SourceHash()) + require.NotNil(t, gpoSetIsthmus.To()) + require.Equal(t, *gpoSetIsthmus.To(), common.HexToAddress("0x420000000000000000000000000000000000000F")) + require.Equal(t, uint64(90_000), gpoSetIsthmus.Gas()) + require.Equal(t, common.FromHex("291b0383"), gpoSetIsthmus.Data()) - deployBlockHashesSender, deployBlockHashesContract := toDepositTxn(t, upgradeTxns[0]) + deployBlockHashesSender, deployBlockHashesContract := toDepositTxn(t, upgradeTxns[7]) require.Equal(t, deployBlockHashesSender, predeploys.EIP2935ContractDeployer) require.Equal(t, blockHashDeployerSource.SourceHash(), deployBlockHashesContract.SourceHash()) require.Nil(t, deployBlockHashesContract.To()) diff --git a/op-node/rollup/derive/l1_block_info.go b/op-node/rollup/derive/l1_block_info.go index a01fe5bca6b..0fae5de8118 100644 --- a/op-node/rollup/derive/l1_block_info.go +++ b/op-node/rollup/derive/l1_block_info.go @@ -20,12 +20,14 @@ import ( const ( L1InfoFuncBedrockSignature = "setL1BlockValues(uint64,uint64,uint256,bytes32,uint64,bytes32,uint256,uint256)" L1InfoFuncEcotoneSignature = "setL1BlockValuesEcotone()" + L1InfoFuncIsthmusSignature = "setL1BlockValuesIsthmus()" L1InfoFuncInteropSignature = "setL1BlockValuesInterop()" DepositsCompleteSignature = "depositsComplete()" L1InfoArguments = 8 L1InfoBedrockLen = 4 + 32*L1InfoArguments - L1InfoEcotoneLen = 4 + 32*5 // after Ecotone upgrade, args are packed into 5 32-byte slots - DepositsCompleteLen = 4 // only the selector + L1InfoEcotoneLen = 4 + 32*5 // after Ecotone upgrade, args are packed into 5 32-byte slots + L1InfoIsthmusLen = 4 + 32*5 + 4 + 8 // after Isthmus upgrade, additionally pack in operator fee scalar and constant + DepositsCompleteLen = 4 // only the selector // DepositsCompleteGas allocates 21k gas for intrinsic tx costs, and // an additional 15k to ensure that the DepositsComplete call does not run out of gas. // GasBenchMark_L1BlockInterop_DepositsComplete:test_depositsComplete_benchmark() (gas: 7768) @@ -37,11 +39,13 @@ const ( var ( L1InfoFuncBedrockBytes4 = crypto.Keccak256([]byte(L1InfoFuncBedrockSignature))[:4] L1InfoFuncEcotoneBytes4 = crypto.Keccak256([]byte(L1InfoFuncEcotoneSignature))[:4] + L1InfoFuncIsthmusBytes4 = crypto.Keccak256([]byte(L1InfoFuncIsthmusSignature))[:4] L1InfoFuncInteropBytes4 = crypto.Keccak256([]byte(L1InfoFuncInteropSignature))[:4] DepositsCompleteBytes4 = crypto.Keccak256([]byte(DepositsCompleteSignature))[:4] L1InfoDepositerAddress = common.HexToAddress("0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001") L1BlockAddress = predeploys.L1BlockAddr ErrInvalidFormat = errors.New("invalid ecotone l1 block info format") + ErrInvalidIsthmusFormat = errors.New("invalid isthmus l1 block info format") ) const ( @@ -66,6 +70,9 @@ type L1BlockInfo struct { BlobBaseFee *big.Int // added by Ecotone upgrade BaseFeeScalar uint32 // added by Ecotone upgrade BlobBaseFeeScalar uint32 // added by Ecotone upgrade + + OperatorFeeScalar uint32 // added by Isthmus upgrade + OperatorFeeConstant uint64 // added by Isthmus upgrade } // Bedrock Binary Format @@ -155,7 +162,7 @@ func (info *L1BlockInfo) unmarshalBinaryBedrock(data []byte) error { return nil } -// Interop & Ecotone Binary Format +// Ecotone Binary Format // +---------+--------------------------+ // | Bytes | Field | // +---------+--------------------------+ @@ -172,9 +179,111 @@ func (info *L1BlockInfo) unmarshalBinaryBedrock(data []byte) error { // +---------+--------------------------+ func (info *L1BlockInfo) marshalBinaryEcotone() ([]byte, error) { - out, err := marshalBinaryWithSignature(info, L1InfoFuncEcotoneBytes4) + w := bytes.NewBuffer(make([]byte, 0, L1InfoEcotoneLen)) // Ecotone and Interop have the same length + if err := solabi.WriteSignature(w, L1InfoFuncEcotoneBytes4); err != nil { + return nil, err + } + if err := binary.Write(w, binary.BigEndian, info.BaseFeeScalar); err != nil { + return nil, err + } + if err := binary.Write(w, binary.BigEndian, info.BlobBaseFeeScalar); err != nil { + return nil, err + } + if err := binary.Write(w, binary.BigEndian, info.SequenceNumber); err != nil { + return nil, err + } + if err := binary.Write(w, binary.BigEndian, info.Time); err != nil { + return nil, err + } + if err := binary.Write(w, binary.BigEndian, info.Number); err != nil { + return nil, err + } + if err := solabi.WriteUint256(w, info.BaseFee); err != nil { + return nil, err + } + blobBasefee := info.BlobBaseFee + if blobBasefee == nil { + blobBasefee = big.NewInt(1) // set to 1, to match the min blob basefee as defined in EIP-4844 + } + if err := solabi.WriteUint256(w, blobBasefee); err != nil { + return nil, err + } + if err := solabi.WriteHash(w, info.BlockHash); err != nil { + return nil, err + } + // ABI encoding will perform the left-padding with zeroes to 32 bytes, matching the "batcherHash" SystemConfig format and version 0 byte. + if err := solabi.WriteAddress(w, info.BatcherAddr); err != nil { + return nil, err + } + return w.Bytes(), nil +} + +func (info *L1BlockInfo) unmarshalBinaryEcotone(data []byte) error { + if len(data) != L1InfoEcotoneLen { + return fmt.Errorf("data is unexpected length: %d", len(data)) + } + r := bytes.NewReader(data) + + var err error + if _, err := solabi.ReadAndValidateSignature(r, L1InfoFuncEcotoneBytes4); err != nil { + return err + } + if err := binary.Read(r, binary.BigEndian, &info.BaseFeeScalar); err != nil { + return ErrInvalidFormat + } + if err := binary.Read(r, binary.BigEndian, &info.BlobBaseFeeScalar); err != nil { + return ErrInvalidFormat + } + if err := binary.Read(r, binary.BigEndian, &info.SequenceNumber); err != nil { + return ErrInvalidFormat + } + if err := binary.Read(r, binary.BigEndian, &info.Time); err != nil { + return ErrInvalidFormat + } + if err := binary.Read(r, binary.BigEndian, &info.Number); err != nil { + return ErrInvalidFormat + } + if info.BaseFee, err = solabi.ReadUint256(r); err != nil { + return err + } + if info.BlobBaseFee, err = solabi.ReadUint256(r); err != nil { + return err + } + if info.BlockHash, err = solabi.ReadHash(r); err != nil { + return err + } + // The "batcherHash" will be correctly parsed as address, since the version 0 and left-padding matches the ABI encoding format. + if info.BatcherAddr, err = solabi.ReadAddress(r); err != nil { + return err + } + if !solabi.EmptyReader(r) { + return errors.New("too many bytes") + } + return nil +} + +// Interop & Isthmus Binary Format +// +---------+--------------------------+ +// | Bytes | Field | +// +---------+--------------------------+ +// | 4 | Function signature | +// | 4 | BaseFeeScalar | +// | 4 | BlobBaseFeeScalar | +// | 8 | SequenceNumber | +// | 8 | Timestamp | +// | 8 | L1BlockNumber | +// | 32 | BaseFee | +// | 32 | BlobBaseFee | +// | 32 | BlockHash | +// | 32 | BatcherHash | +// | 4 | OperatorFeeScalar | +// | 8 | OperatorFeeConstant | +// +---------+--------------------------+ + +func (info *L1BlockInfo) marshalBinaryIsthmus() ([]byte, error) { + out, err := marshalBinaryWithSignature(info, L1InfoFuncIsthmusBytes4) if err != nil { - return nil, fmt.Errorf("failed to marshal Ecotone l1 block info: %w", err) + return nil, fmt.Errorf("failed to marshal Isthmus l1 block info: %w", err) } return out, nil } @@ -188,7 +297,7 @@ func (info *L1BlockInfo) marshalBinaryInterop() ([]byte, error) { } func marshalBinaryWithSignature(info *L1BlockInfo, signature []byte) ([]byte, error) { - w := bytes.NewBuffer(make([]byte, 0, L1InfoEcotoneLen)) // Ecotone and Interop have the same length + w := bytes.NewBuffer(make([]byte, 0, L1InfoIsthmusLen)) if err := solabi.WriteSignature(w, signature); err != nil { return nil, err } @@ -224,19 +333,21 @@ func marshalBinaryWithSignature(info *L1BlockInfo, signature []byte) ([]byte, er if err := solabi.WriteAddress(w, info.BatcherAddr); err != nil { return nil, err } + if err := binary.Write(w, binary.BigEndian, info.OperatorFeeScalar); err != nil { + return nil, err + } + if err := binary.Write(w, binary.BigEndian, info.OperatorFeeConstant); err != nil { + return nil, err + } return w.Bytes(), nil } -func (info *L1BlockInfo) unmarshalBinaryEcotone(data []byte) error { - return unmarshalBinaryWithSignatureAndData(info, L1InfoFuncEcotoneBytes4, data) -} - func (info *L1BlockInfo) unmarshalBinaryInterop(data []byte) error { return unmarshalBinaryWithSignatureAndData(info, L1InfoFuncInteropBytes4, data) } func unmarshalBinaryWithSignatureAndData(info *L1BlockInfo, signature []byte, data []byte) error { - if len(data) != L1InfoEcotoneLen { + if len(data) != L1InfoIsthmusLen { return fmt.Errorf("data is unexpected length: %d", len(data)) } r := bytes.NewReader(data) @@ -246,19 +357,19 @@ func unmarshalBinaryWithSignatureAndData(info *L1BlockInfo, signature []byte, da return err } if err := binary.Read(r, binary.BigEndian, &info.BaseFeeScalar); err != nil { - return ErrInvalidFormat + return ErrInvalidIsthmusFormat } if err := binary.Read(r, binary.BigEndian, &info.BlobBaseFeeScalar); err != nil { - return ErrInvalidFormat + return ErrInvalidIsthmusFormat } if err := binary.Read(r, binary.BigEndian, &info.SequenceNumber); err != nil { - return ErrInvalidFormat + return ErrInvalidIsthmusFormat } if err := binary.Read(r, binary.BigEndian, &info.Time); err != nil { - return ErrInvalidFormat + return ErrInvalidIsthmusFormat } if err := binary.Read(r, binary.BigEndian, &info.Number); err != nil { - return ErrInvalidFormat + return ErrInvalidIsthmusFormat } if info.BaseFee, err = solabi.ReadUint256(r); err != nil { return err @@ -273,12 +384,22 @@ func unmarshalBinaryWithSignatureAndData(info *L1BlockInfo, signature []byte, da if info.BatcherAddr, err = solabi.ReadAddress(r); err != nil { return err } + if err := binary.Read(r, binary.BigEndian, &info.OperatorFeeScalar); err != nil { + return ErrInvalidIsthmusFormat + } + if err := binary.Read(r, binary.BigEndian, &info.OperatorFeeConstant); err != nil { + return ErrInvalidIsthmusFormat + } if !solabi.EmptyReader(r) { return errors.New("too many bytes") } return nil } +func (info *L1BlockInfo) unmarshalBinaryIsthmus(data []byte) error { + return unmarshalBinaryWithSignatureAndData(info, L1InfoFuncIsthmusBytes4, data) +} + // isEcotoneButNotFirstBlock returns whether the specified block is subject to the Ecotone upgrade, // but is not the activation block itself. func isEcotoneButNotFirstBlock(rollupCfg *rollup.Config, l2Timestamp uint64) bool { @@ -295,6 +416,12 @@ func isInteropButNotFirstBlock(rollupCfg *rollup.Config, l2Timestamp uint64) boo return rollupCfg.IsInterop(l2Timestamp) && !rollupCfg.IsInteropActivationBlock(l2Timestamp) } +// isIsthmusButNotFirstBlock returns whether the specified block is subject to the Isthmus upgrade, +// but is not the activation block itself. +func isIsthmusButNotFirstBlock(rollupCfg *rollup.Config, l2Timestamp uint64) bool { + return rollupCfg.IsIsthmus(l2Timestamp) && !rollupCfg.IsIsthmusActivationBlock(l2Timestamp) +} + // L1BlockInfoFromBytes is the inverse of L1InfoDeposit, to see where the L2 chain is derived from func L1BlockInfoFromBytes(rollupCfg *rollup.Config, l2BlockTime uint64, data []byte) (*L1BlockInfo, error) { var info L1BlockInfo @@ -302,6 +429,9 @@ func L1BlockInfoFromBytes(rollupCfg *rollup.Config, l2BlockTime uint64, data []b if isInteropButNotFirstBlock(rollupCfg, l2BlockTime) { return &info, info.unmarshalBinaryInterop(data) } + if isIsthmusButNotFirstBlock(rollupCfg, l2BlockTime) { + return &info, info.unmarshalBinaryIsthmus(data) + } if isEcotoneButNotFirstBlock(rollupCfg, l2BlockTime) { return &info, info.unmarshalBinaryEcotone(data) } @@ -321,6 +451,7 @@ func L1InfoDeposit(rollupCfg *rollup.Config, sysCfg eth.SystemConfig, seqNumber } var data []byte if isEcotoneButNotFirstBlock(rollupCfg, l2Timestamp) { + isIsthmusActivated := isIsthmusButNotFirstBlock(rollupCfg, l2Timestamp) l1BlockInfo.BlobBaseFee = block.BlobBaseFee() if l1BlockInfo.BlobBaseFee == nil { // The L2 spec states to use the MIN_BLOB_GASPRICE from EIP-4844 if not yet active on L1. @@ -332,6 +463,13 @@ func L1InfoDeposit(rollupCfg *rollup.Config, sysCfg eth.SystemConfig, seqNumber } l1BlockInfo.BlobBaseFeeScalar = scalars.BlobBaseFeeScalar l1BlockInfo.BaseFeeScalar = scalars.BaseFeeScalar + + if isIsthmusActivated { + operatorFee := sysCfg.OperatorFee() + l1BlockInfo.OperatorFeeScalar = operatorFee.Scalar + l1BlockInfo.OperatorFeeConstant = operatorFee.Constant + } + if isInteropButNotFirstBlock(rollupCfg, l2Timestamp) { out, err := l1BlockInfo.marshalBinaryInterop() if err != nil { @@ -339,11 +477,19 @@ func L1InfoDeposit(rollupCfg *rollup.Config, sysCfg eth.SystemConfig, seqNumber } data = out } else { - out, err := l1BlockInfo.marshalBinaryEcotone() - if err != nil { - return nil, fmt.Errorf("failed to marshal Ecotone l1 block info: %w", err) + if isIsthmusActivated { + out, err := l1BlockInfo.marshalBinaryIsthmus() + if err != nil { + return nil, fmt.Errorf("failed to marshal Isthmus l1 block info: %w", err) + } + data = out + } else { + out, err := l1BlockInfo.marshalBinaryEcotone() + if err != nil { + return nil, fmt.Errorf("failed to marshal Ecotone l1 block info: %w", err) + } + data = out } - data = out } } else { l1BlockInfo.L1FeeOverhead = sysCfg.Overhead diff --git a/op-node/rollup/derive/l1_block_info_test.go b/op-node/rollup/derive/l1_block_info_test.go index 3f7dd0647e6..03e1b4ec652 100644 --- a/op-node/rollup/derive/l1_block_info_test.go +++ b/op-node/rollup/derive/l1_block_info_test.go @@ -154,6 +154,45 @@ func TestParseL1InfoDepositTxData(t *testing.T) { require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) require.Equal(t, L1InfoEcotoneLen, len(depTx.Data)) }) + t.Run("isthmus", func(t *testing.T) { + rng := rand.New(rand.NewSource(1234)) + info := testutils.MakeBlockInfo(nil)(rng) + rollupCfg := rollup.Config{BlockTime: 2, Genesis: rollup.Genesis{L2Time: 1000}} + rollupCfg.ActivateAtGenesis(rollup.Isthmus) + // run 1 block after isthmus transition + timestamp := rollupCfg.Genesis.L2Time + rollupCfg.BlockTime + depTx, err := L1InfoDeposit(&rollupCfg, randomL1Cfg(rng, info), randomSeqNr(rng), info, timestamp) + require.NoError(t, err) + require.False(t, depTx.IsSystemTransaction) + require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) + require.Equal(t, L1InfoIsthmusLen, len(depTx.Data)) + }) + t.Run("activation-block isthmus", func(t *testing.T) { + rng := rand.New(rand.NewSource(1234)) + info := testutils.MakeBlockInfo(nil)(rng) + rollupCfg := rollup.Config{BlockTime: 2, Genesis: rollup.Genesis{L2Time: 1000}} + rollupCfg.ActivateAtGenesis(rollup.Granite) + isthmusTime := rollupCfg.Genesis.L2Time + rollupCfg.BlockTime // activate isthmus just after genesis + rollupCfg.InteropTime = &isthmusTime + depTx, err := L1InfoDeposit(&rollupCfg, randomL1Cfg(rng, info), randomSeqNr(rng), info, isthmusTime) + require.NoError(t, err) + require.False(t, depTx.IsSystemTransaction) + require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) + // Isthmus activates, but ecotone L1 info is still used at this upgrade block + require.Equal(t, L1InfoEcotoneLen, len(depTx.Data)) + require.Equal(t, L1InfoFuncEcotoneBytes4, depTx.Data[:4]) + }) + t.Run("genesis-block isthmus", func(t *testing.T) { + rng := rand.New(rand.NewSource(1234)) + info := testutils.MakeBlockInfo(nil)(rng) + rollupCfg := rollup.Config{BlockTime: 2, Genesis: rollup.Genesis{L2Time: 1000}} + rollupCfg.ActivateAtGenesis(rollup.Isthmus) + depTx, err := L1InfoDeposit(&rollupCfg, randomL1Cfg(rng, info), randomSeqNr(rng), info, rollupCfg.Genesis.L2Time) + require.NoError(t, err) + require.False(t, depTx.IsSystemTransaction) + require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) + require.Equal(t, L1InfoIsthmusLen, len(depTx.Data)) + }) t.Run("interop", func(t *testing.T) { rng := rand.New(rand.NewSource(1234)) info := testutils.MakeBlockInfo(nil)(rng) @@ -165,23 +204,23 @@ func TestParseL1InfoDepositTxData(t *testing.T) { require.NoError(t, err) require.False(t, depTx.IsSystemTransaction) require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) - require.Equal(t, L1InfoEcotoneLen, len(depTx.Data), "the length is same in interop") + require.Equal(t, L1InfoIsthmusLen, len(depTx.Data), "the length is same in interop") require.Equal(t, L1InfoFuncInteropBytes4, depTx.Data[:4], "upgrade is active, need interop signature") }) t.Run("activation-block interop", func(t *testing.T) { rng := rand.New(rand.NewSource(1234)) info := testutils.MakeBlockInfo(nil)(rng) rollupCfg := rollup.Config{BlockTime: 2, Genesis: rollup.Genesis{L2Time: 1000}} - rollupCfg.ActivateAtGenesis(rollup.Fjord) + rollupCfg.ActivateAtGenesis(rollup.Isthmus) interopTime := rollupCfg.Genesis.L2Time + rollupCfg.BlockTime // activate interop just after genesis rollupCfg.InteropTime = &interopTime depTx, err := L1InfoDeposit(&rollupCfg, randomL1Cfg(rng, info), randomSeqNr(rng), info, interopTime) require.NoError(t, err) require.False(t, depTx.IsSystemTransaction) require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) - // Interop activates, but ecotone L1 info is still used at this upgrade block - require.Equal(t, L1InfoEcotoneLen, len(depTx.Data)) - require.Equal(t, L1InfoFuncEcotoneBytes4, depTx.Data[:4]) + // Interop activates, but isthmus L1 info is still used at this upgrade block + require.Equal(t, L1InfoIsthmusLen, len(depTx.Data)) + require.Equal(t, L1InfoFuncIsthmusBytes4, depTx.Data[:4]) }) t.Run("genesis-block interop", func(t *testing.T) { rng := rand.New(rand.NewSource(1234)) @@ -192,7 +231,7 @@ func TestParseL1InfoDepositTxData(t *testing.T) { require.NoError(t, err) require.False(t, depTx.IsSystemTransaction) require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) - require.Equal(t, L1InfoEcotoneLen, len(depTx.Data)) + require.Equal(t, L1InfoIsthmusLen, len(depTx.Data)) }) } diff --git a/op-node/rollup/derive/payload_util.go b/op-node/rollup/derive/payload_util.go index 6da1a952b5f..eb02d1afa45 100644 --- a/op-node/rollup/derive/payload_util.go +++ b/op-node/rollup/derive/payload_util.go @@ -97,5 +97,12 @@ func PayloadToSystemConfig(rollupCfg *rollup.Config, payload *eth.ExecutionPaylo d, e := eip1559.DecodeHoloceneExtraData(payload.ExtraData) copy(r.EIP1559Params[:], eip1559.EncodeHolocene1559Params(d, e)) } + + if rollupCfg.IsIsthmus(uint64(payload.Timestamp)) { + r.OperatorFeeParams = eth.EncodeOperatorFeeParams(eth.OperatorFeeParams{ + Scalar: info.OperatorFeeScalar, + Constant: info.OperatorFeeConstant, + }) + } return r, nil } diff --git a/op-node/rollup/derive/system_config.go b/op-node/rollup/derive/system_config.go index 72c4e713c30..64e21ff15f3 100644 --- a/op-node/rollup/derive/system_config.go +++ b/op-node/rollup/derive/system_config.go @@ -22,6 +22,7 @@ var ( SystemConfigUpdateGasLimit = common.Hash{31: 2} SystemConfigUpdateUnsafeBlockSigner = common.Hash{31: 3} SystemConfigUpdateEIP1559Params = common.Hash{31: 4} + SystemConfigUpdateOperatorFeeParams = common.Hash{31: 5} ) var ( @@ -157,6 +158,22 @@ func ProcessSystemConfigUpdateLogEvent(destSysCfg *eth.SystemConfig, ev *types.L } copy(destSysCfg.EIP1559Params[:], params[24:32]) return nil + case SystemConfigUpdateOperatorFeeParams: + if pointer, err := solabi.ReadUint64(reader); err != nil || pointer != 32 { + return NewCriticalError(errors.New("invalid pointer field")) + } + if length, err := solabi.ReadUint64(reader); err != nil || length != 32 { + return NewCriticalError(errors.New("invalid length field")) + } + params, err := solabi.ReadEthBytes32(reader) + if err != nil { + return NewCriticalError(errors.New("could not read operator fee params")) + } + if !solabi.EmptyReader(reader) { + return NewCriticalError(errors.New("too many bytes")) + } + destSysCfg.OperatorFeeParams = params + return nil case SystemConfigUpdateUnsafeBlockSigner: // Ignored in derivation. This configurable applies to runtime configuration outside of the derivation. return nil diff --git a/op-node/rollup/derive/system_config_test.go b/op-node/rollup/derive/system_config_test.go index add17909387..5906f26cbea 100644 --- a/op-node/rollup/derive/system_config_test.go +++ b/op-node/rollup/derive/system_config_test.go @@ -32,7 +32,8 @@ var ( oneUint256 = abi.Arguments{ {Type: uint256T}, } - eip1559Params = []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8} + eip1559Params = []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8} + operatorFeeParams = []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xd, 0x8} ) // TestProcessSystemConfigUpdateLogEvent tests the parsing of an event and mutating the @@ -208,6 +209,28 @@ func TestProcessSystemConfigUpdateLogEvent(t *testing.T) { }, err: false, }, + { + name: "SystemConfigUpdateOperatorFeeParams", + log: &types.Log{ + Topics: []common.Hash{ + ConfigUpdateEventABIHash, + ConfigUpdateEventVersion0, + SystemConfigUpdateOperatorFeeParams, + }, + }, + hook: func(t *testing.T, log *types.Log) *types.Log { + numberData, err := oneUint256.Pack(new(big.Int).SetBytes(operatorFeeParams)) + require.NoError(t, err) + data, err := bytesArgs.Pack(numberData) + require.NoError(t, err) + log.Data = data + return log + }, + config: eth.SystemConfig{ + OperatorFeeParams: eth.Bytes32(operatorFeeParams), + }, + err: false, + }, } for _, test := range tests { diff --git a/op-service/eth/types.go b/op-service/eth/types.go index ae458b1e0e2..c64c93e6516 100644 --- a/op-service/eth/types.go +++ b/op-service/eth/types.go @@ -517,6 +517,8 @@ type SystemConfig struct { // value will be 0 if Holocene is not active, or if derivation has yet to // process any EIP_1559_PARAMS system config update events. EIP1559Params Bytes8 `json:"eip1559Params"` + // OperatorFeeParams identifies the operator fee parameters. + OperatorFeeParams Bytes32 `json:"operatorFeeParams"` // More fields can be added for future SystemConfig versions. // MarshalPreHolocene indicates whether or not this struct should be @@ -627,6 +629,32 @@ func CheckEcotoneL1SystemConfigScalar(scalar [32]byte) error { } } +type OperatorFeeParams struct { + Scalar uint32 + Constant uint64 +} + +func (sysCfg *SystemConfig) OperatorFee() OperatorFeeParams { + return DecodeOperatorFeeParams(sysCfg.OperatorFeeParams) +} + +// DecodeScalar decodes the operatorFeeScalar and operatorFeeConstant from a 32-byte scalar value. +// It uses the first byte to determine the scalar format. +func DecodeOperatorFeeParams(scalar [32]byte) OperatorFeeParams { + return OperatorFeeParams{ + Scalar: binary.BigEndian.Uint32(scalar[20:24]), + Constant: binary.BigEndian.Uint64(scalar[24:32]), + } +} + +// EncodeOperatorFeeParams encodes the OperatorFeeParams into a 32-byte value +func EncodeOperatorFeeParams(params OperatorFeeParams) (scalar [32]byte) { + + binary.BigEndian.PutUint32(scalar[20:24], params.Scalar) + binary.BigEndian.PutUint64(scalar[24:32], params.Constant) + return +} + type Bytes48 [48]byte func (b *Bytes48) UnmarshalJSON(text []byte) error { diff --git a/op-service/eth/types_test.go b/op-service/eth/types_test.go index 5c24330f0d4..ea1d40d930f 100644 --- a/op-service/eth/types_test.go +++ b/op-service/eth/types_test.go @@ -67,6 +67,13 @@ func TestEcotoneScalars(t *testing.T) { } } +func TestOperatorFeeScalars(t *testing.T) { + sysConfig := SystemConfig{OperatorFeeParams: Bytes32{0: 0, 20: 4, 29: 3}} + params := sysConfig.OperatorFee() + require.Equal(t, uint32(0x4000000), params.Scalar) + require.Equal(t, uint64(0x30000), params.Constant) +} + func FuzzEncodeScalar(f *testing.F) { f.Fuzz(func(t *testing.T, blobBaseFeeScalar uint32, baseFeeScalar uint32) { encoded := EncodeScalar(EcotoneScalars{BlobBaseFeeScalar: blobBaseFeeScalar, BaseFeeScalar: baseFeeScalar}) @@ -77,18 +84,28 @@ func FuzzEncodeScalar(f *testing.F) { }) } +func FuzzEncodeOperatorFeeParams(f *testing.F) { + f.Fuzz(func(t *testing.T, scalar uint32, constant uint64) { + encoded := EncodeOperatorFeeParams(OperatorFeeParams{Scalar: scalar, Constant: constant}) + scalars := DecodeOperatorFeeParams(encoded) + require.Equal(t, scalar, scalars.Scalar) + require.Equal(t, constant, scalars.Constant) + }) +} + func TestSystemConfigMarshaling(t *testing.T) { sysConfig := SystemConfig{ - BatcherAddr: common.Address{'A'}, - Overhead: Bytes32{0x4, 0x5, 0x6}, - Scalar: Bytes32{0x7, 0x8, 0x9}, - GasLimit: 1234, + BatcherAddr: common.Address{'A'}, + Overhead: Bytes32{0x4, 0x5, 0x6}, + Scalar: Bytes32{0x7, 0x8, 0x9}, + OperatorFeeParams: Bytes32{0x1, 0x2, 0x3}, + GasLimit: 1234, // Leave EIP1559 params empty to prove that the // zero value is sent. } j, err := json.Marshal(sysConfig) require.NoError(t, err) - require.Equal(t, `{"batcherAddr":"0x4100000000000000000000000000000000000000","overhead":"0x0405060000000000000000000000000000000000000000000000000000000000","scalar":"0x0708090000000000000000000000000000000000000000000000000000000000","gasLimit":1234,"eip1559Params":"0x0000000000000000"}`, string(j)) + require.Equal(t, `{"batcherAddr":"0x4100000000000000000000000000000000000000","overhead":"0x0405060000000000000000000000000000000000000000000000000000000000","scalar":"0x0708090000000000000000000000000000000000000000000000000000000000","gasLimit":1234,"eip1559Params":"0x0000000000000000","operatorFeeParams":"0x0102030000000000000000000000000000000000000000000000000000000000"}`, string(j)) sysConfig.MarshalPreHolocene = true j, err = json.Marshal(sysConfig) require.NoError(t, err) diff --git a/op-service/predeploys/addresses.go b/op-service/predeploys/addresses.go index 750c391e104..143abd04a86 100644 --- a/op-service/predeploys/addresses.go +++ b/op-service/predeploys/addresses.go @@ -23,6 +23,7 @@ const ( ProxyAdmin = "0x4200000000000000000000000000000000000018" BaseFeeVault = "0x4200000000000000000000000000000000000019" L1FeeVault = "0x420000000000000000000000000000000000001a" + OperatorFeeVault = "0x420000000000000000000000000000000000001b" SchemaRegistry = "0x4200000000000000000000000000000000000020" EAS = "0x4200000000000000000000000000000000000021" CrossL2Inbox = "0x4200000000000000000000000000000000000022" @@ -63,6 +64,7 @@ var ( ProxyAdminAddr = common.HexToAddress(ProxyAdmin) BaseFeeVaultAddr = common.HexToAddress(BaseFeeVault) L1FeeVaultAddr = common.HexToAddress(L1FeeVault) + OperatorFeeVaultAddr = common.HexToAddress(OperatorFeeVault) SchemaRegistryAddr = common.HexToAddress(SchemaRegistry) EASAddr = common.HexToAddress(EAS) CrossL2InboxAddr = common.HexToAddress(CrossL2Inbox) @@ -117,6 +119,7 @@ func init() { Predeploys["ProxyAdmin"] = &Predeploy{Address: ProxyAdminAddr} Predeploys["BaseFeeVault"] = &Predeploy{Address: BaseFeeVaultAddr} Predeploys["L1FeeVault"] = &Predeploy{Address: L1FeeVaultAddr} + Predeploys["OperatorFeeVault"] = &Predeploy{Address: OperatorFeeVaultAddr} Predeploys["SchemaRegistry"] = &Predeploy{Address: SchemaRegistryAddr} Predeploys["EAS"] = &Predeploy{Address: EASAddr} Predeploys["Create2Deployer"] = &Predeploy{ diff --git a/op-service/solabi/util.go b/op-service/solabi/util.go index 41c01106a32..f5d48a65769 100644 --- a/op-service/solabi/util.go +++ b/op-service/solabi/util.go @@ -16,6 +16,7 @@ import ( var ( addressEmptyPadding [12]byte = [12]byte{} uint64EmptyPadding [24]byte = [24]byte{} + uint8EmptyPadding [31]byte = [31]byte{} ) func ReadSignature(r io.Reader) ([]byte, error) { @@ -132,3 +133,13 @@ func WriteUint64(w io.Writer, n uint64) error { } return nil } + +func WriteUint8(w io.Writer, n uint8) error { + if _, err := w.Write(uint8EmptyPadding[:]); err != nil { + return err + } + if err := binary.Write(w, binary.BigEndian, n); err != nil { + return err + } + return nil +} diff --git a/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol b/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol index e687a17e048..e5677516fbf 100644 --- a/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol +++ b/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol @@ -9,7 +9,8 @@ interface ISystemConfig { FEE_SCALARS, GAS_LIMIT, UNSAFE_BLOCK_SIGNER, - EIP_1559_PARAMS + EIP_1559_PARAMS, + OPERATOR_FEE_PARAMS } struct Addresses { @@ -61,6 +62,8 @@ interface ISystemConfig { function l1StandardBridge() external view returns (address addr_); function maximumGasLimit() external pure returns (uint64); function minimumGasLimit() external view returns (uint64); + function operatorFeeConstant() external view returns (uint64); + function operatorFeeScalar() external view returns (uint32); function optimismMintableERC20Factory() external view returns (address addr_); function optimismPortal() external view returns (address addr_); function overhead() external view returns (uint256); @@ -72,6 +75,7 @@ interface ISystemConfig { function setGasConfig(uint256 _overhead, uint256 _scalar) external; function setGasConfigEcotone(uint32 _basefeeScalar, uint32 _blobbasefeeScalar) external; function setGasLimit(uint64 _gasLimit) external; + function setOperatorFeeScalars(uint32 _operatorFeeScalar, uint64 _operatorFeeConstant) external; function setUnsafeBlockSigner(address _unsafeBlockSigner) external; function setEIP1559Params(uint32 _denominator, uint32 _elasticity) external; function startBlock() external view returns (uint256 startBlock_); diff --git a/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol b/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol index c9b63db2b1d..19cac2b4154 100644 --- a/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol +++ b/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol @@ -32,6 +32,8 @@ interface ISystemConfigInterop { function l1StandardBridge() external view returns (address addr_); function maximumGasLimit() external pure returns (uint64); function minimumGasLimit() external view returns (uint64); + function operatorFeeConstant() external view returns (uint64); + function operatorFeeScalar() external view returns (uint32); function optimismMintableERC20Factory() external view returns (address addr_); function optimismPortal() external view returns (address addr_); function overhead() external view returns (uint256); @@ -45,6 +47,7 @@ interface ISystemConfigInterop { function setGasLimit(uint64 _gasLimit) external; function setUnsafeBlockSigner(address _unsafeBlockSigner) external; function setEIP1559Params(uint32 _denominator, uint32 _elasticity) external; + function setOperatorFeeScalars(uint32 _operatorFeeScalar, uint64 _operatorFeeConstant) external; function startBlock() external view returns (uint256 startBlock_); function transferOwnership(address newOwner) external; // nosemgrep function unsafeBlockSigner() external view returns (address addr_); diff --git a/packages/contracts-bedrock/interfaces/L2/IGasPriceOracle.sol b/packages/contracts-bedrock/interfaces/L2/IGasPriceOracle.sol index 8063725cb86..8e1b4255a07 100644 --- a/packages/contracts-bedrock/interfaces/L2/IGasPriceOracle.sol +++ b/packages/contracts-bedrock/interfaces/L2/IGasPriceOracle.sol @@ -12,13 +12,16 @@ interface IGasPriceOracle { function getL1Fee(bytes memory _data) external view returns (uint256); function getL1FeeUpperBound(uint256 _unsignedTxSize) external view returns (uint256); function getL1GasUsed(bytes memory _data) external view returns (uint256); + function getOperatorFee(uint256 _gasUsed) external view returns (uint256); function isEcotone() external view returns (bool); function isFjord() external view returns (bool); + function isIsthmus() external view returns (bool); function l1BaseFee() external view returns (uint256); function overhead() external view returns (uint256); function scalar() external view returns (uint256); function setEcotone() external; function setFjord() external; + function setIsthmus() external; function version() external view returns (string memory); function __constructor__() external; diff --git a/packages/contracts-bedrock/interfaces/L2/IL1Block.sol b/packages/contracts-bedrock/interfaces/L2/IL1Block.sol index ecac7980100..30c42275adf 100644 --- a/packages/contracts-bedrock/interfaces/L2/IL1Block.sol +++ b/packages/contracts-bedrock/interfaces/L2/IL1Block.sol @@ -16,6 +16,8 @@ interface IL1Block { function l1FeeOverhead() external view returns (uint256); function l1FeeScalar() external view returns (uint256); function number() external view returns (uint64); + function operatorFeeScalar() external view returns (uint32); + function operatorFeeConstant() external view returns (uint64); function sequenceNumber() external view returns (uint64); function setL1BlockValues( uint64 _number, @@ -29,6 +31,7 @@ interface IL1Block { ) external; function setL1BlockValuesEcotone() external; + function setL1BlockValuesIsthmus() external; function timestamp() external view returns (uint64); function version() external pure returns (string memory); diff --git a/packages/contracts-bedrock/interfaces/L2/IL1BlockInterop.sol b/packages/contracts-bedrock/interfaces/L2/IL1BlockInterop.sol index dea7ad721e0..53cfc890ca0 100644 --- a/packages/contracts-bedrock/interfaces/L2/IL1BlockInterop.sol +++ b/packages/contracts-bedrock/interfaces/L2/IL1BlockInterop.sol @@ -35,6 +35,8 @@ interface IL1BlockInterop { function l1FeeOverhead() external view returns (uint256); function l1FeeScalar() external view returns (uint256); function number() external view returns (uint64); + function operatorFeeScalar() external view returns (uint32); + function operatorFeeConstant() external view returns (uint64); function sequenceNumber() external view returns (uint64); function setConfig(ConfigType _type, bytes memory _value) external; function setL1BlockValues( @@ -50,6 +52,7 @@ interface IL1BlockInterop { external; function setL1BlockValuesEcotone() external; function setL1BlockValuesInterop() external; + function setL1BlockValuesIsthmus() external; function timestamp() external view returns (uint64); function version() external pure returns (string memory); diff --git a/packages/contracts-bedrock/interfaces/L2/IOperatorFeeVault.sol b/packages/contracts-bedrock/interfaces/L2/IOperatorFeeVault.sol new file mode 100644 index 00000000000..63978e203bc --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L2/IOperatorFeeVault.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { Types } from "src/libraries/Types.sol"; + +interface IOperatorFeeVault { + event Withdrawal(uint256 value, address to, address from); + event Withdrawal(uint256 value, address to, address from, Types.WithdrawalNetwork withdrawalNetwork); + + receive() external payable; + + function MIN_WITHDRAWAL_AMOUNT() external view returns (uint256); + function RECIPIENT() external view returns (address); + function WITHDRAWAL_NETWORK() external view returns (Types.WithdrawalNetwork); + function minWithdrawalAmount() external view returns (uint256 amount_); + function recipient() external view returns (address recipient_); + function totalProcessed() external view returns (uint256); + function withdraw() external; + function withdrawalNetwork() external view returns (Types.WithdrawalNetwork network_); + + function version() external view returns (string memory); + + function __constructor__() + external; +} diff --git a/packages/contracts-bedrock/scripts/Artifacts.s.sol b/packages/contracts-bedrock/scripts/Artifacts.s.sol index 1f42384bd1a..5858c8aa4c5 100644 --- a/packages/contracts-bedrock/scripts/Artifacts.s.sol +++ b/packages/contracts-bedrock/scripts/Artifacts.s.sol @@ -116,6 +116,8 @@ contract Artifacts { return payable(Predeploys.BASE_FEE_VAULT); } else if (digest == keccak256(bytes("L1FeeVault"))) { return payable(Predeploys.L1_FEE_VAULT); + } else if (digest == keccak256(bytes("OperatorFeeVault"))) { + return payable(Predeploys.OPERATOR_FEE_VAULT); } else if (digest == keccak256(bytes("GovernanceToken"))) { return payable(Predeploys.GOVERNANCE_TOKEN); } else if (digest == keccak256(bytes("SchemaRegistry"))) { diff --git a/packages/contracts-bedrock/scripts/L2Genesis.s.sol b/packages/contracts-bedrock/scripts/L2Genesis.s.sol index 9e04181c0f8..7ab4e9d6efb 100644 --- a/packages/contracts-bedrock/scripts/L2Genesis.s.sol +++ b/packages/contracts-bedrock/scripts/L2Genesis.s.sol @@ -21,6 +21,7 @@ import { Types } from "src/libraries/Types.sol"; import { ISequencerFeeVault } from "interfaces/L2/ISequencerFeeVault.sol"; import { IBaseFeeVault } from "interfaces/L2/IBaseFeeVault.sol"; import { IL1FeeVault } from "interfaces/L2/IL1FeeVault.sol"; +import { IOperatorFeeVault } from "interfaces/L2/IOperatorFeeVault.sol"; import { IOptimismMintableERC721Factory } from "interfaces/L2/IOptimismMintableERC721Factory.sol"; import { IGovernanceToken } from "interfaces/governance/IGovernanceToken.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; @@ -182,6 +183,9 @@ contract L2Genesis is Deployer { if (writeForkGenesisAllocs(_fork, Fork.HOLOCENE, _mode)) { return; } + + activateIsthmus(); + if (writeForkGenesisAllocs(_fork, Fork.ISTHMUS, _mode)) { return; } @@ -268,7 +272,8 @@ contract L2Genesis is Deployer { setProxyAdmin(); // 18 setBaseFeeVault(); // 19 setL1FeeVault(); // 1A - // 1B,1C,1D,1E,1F: not used. + setOperatorFeeVault(); // 1B + // 1C,1D,1E,1F: not used. setSchemaRegistry(); // 20 setEAS(); // 21 setGovernanceToken(); // 42: OP (not behind a proxy) @@ -484,6 +489,24 @@ contract L2Genesis is Deployer { vm.resetNonce(address(vault)); } + /// @notice This predeploy is following the safety invariant #2. + function setOperatorFeeVault() public { + IOperatorFeeVault vault = IOperatorFeeVault( + DeployUtils.create1({ + _name: "OperatorFeeVault", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IOperatorFeeVault.__constructor__, ())) + }) + ); + + address impl = Predeploys.predeployToCodeNamespace(Predeploys.OPERATOR_FEE_VAULT); + console.log("Setting %s implementation at: %s", "OperatorFeeVault", impl); + vm.etch(impl, address(vault).code); + + /// Reset so its not included state dump + vm.etch(address(vault), ""); + vm.resetNonce(address(vault)); + } + /// @notice This predeploy is following the safety invariant #2. function setGovernanceToken() public { if (!cfg.enableGovernance()) { @@ -609,6 +632,12 @@ contract L2Genesis is Deployer { IGasPriceOracle(Predeploys.GAS_PRICE_ORACLE).setFjord(); } + function activateIsthmus() public { + console.log("Activating isthmus in GasPriceOracle contract"); + vm.prank(IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).DEPOSITOR_ACCOUNT()); + IGasPriceOracle(Predeploys.GAS_PRICE_ORACLE).setIsthmus(); + } + /// @notice Sets the bytecode in state function _setImplementationCode(address _addr) internal returns (address) { string memory cname = Predeploys.getName(_addr); diff --git a/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol index 6a55abc6eb7..d6d09d020d4 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployConfig.s.sol @@ -59,6 +59,8 @@ contract DeployConfig is Script { uint256 public l2GenesisBlockGasLimit; uint32 public basefeeScalar; uint32 public blobbasefeeScalar; + uint32 public operatorFeeScalar; + uint64 public operatorFeeConstant; bool public enableGovernance; uint256 public eip1559Denominator; uint256 public eip1559Elasticity; @@ -137,6 +139,8 @@ contract DeployConfig is Script { l2GenesisBlockGasLimit = stdJson.readUint(_json, "$.l2GenesisBlockGasLimit"); basefeeScalar = uint32(_readOr(_json, "$.gasPriceOracleBaseFeeScalar", 1368)); blobbasefeeScalar = uint32(_readOr(_json, "$.gasPriceOracleBlobBaseFeeScalar", 810949)); + operatorFeeScalar = uint32(_readOr(_json, "$.gasPriceOracleOperatorFeeScalar", 0)); + operatorFeeConstant = uint64(_readOr(_json, "$.gasPriceOracleOperatorFeeConstant", 0)); enableGovernance = _readOr(_json, "$.enableGovernance", false); eip1559Denominator = stdJson.readUint(_json, "$.eip1559Denominator"); diff --git a/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol index 2112d614251..9541db0bbab 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol @@ -59,6 +59,9 @@ contract DeployOPChainInput is BaseDeployIO { Duration internal _disputeMaxClockDuration; bool internal _allowCustomDisputeParameters; + uint32 internal _operatorFeeScalar; + uint64 internal _operatorFeeConstant; + function set(bytes4 _sel, address _addr) public { require(_addr != address(0), "DeployOPChainInput: cannot set zero address"); if (_sel == this.opChainProxyAdminOwner.selector) _opChainProxyAdminOwner = _addr; @@ -91,6 +94,10 @@ contract DeployOPChainInput is BaseDeployIO { _disputeClockExtension = Duration.wrap(SafeCast.toUint64(_value)); } else if (_sel == this.disputeMaxClockDuration.selector) { _disputeMaxClockDuration = Duration.wrap(SafeCast.toUint64(_value)); + } else if (_sel == this.operatorFeeScalar.selector) { + _operatorFeeScalar = SafeCast.toUint32(_value); + } else if (_sel == this.operatorFeeConstant.selector) { + _operatorFeeConstant = SafeCast.toUint64(_value); } else { revert("DeployOPChainInput: unknown selector"); } @@ -214,6 +221,14 @@ contract DeployOPChainInput is BaseDeployIO { function allowCustomDisputeParameters() public view returns (bool) { return _allowCustomDisputeParameters; } + + function operatorFeeScalar() public view returns (uint32) { + return _operatorFeeScalar; + } + + function operatorFeeConstant() public view returns (uint64) { + return _operatorFeeConstant; + } } contract DeployOPChainOutput is BaseDeployIO { diff --git a/packages/contracts-bedrock/snapshots/abi/GasPriceOracle.json b/packages/contracts-bedrock/snapshots/abi/GasPriceOracle.json index cf350302359..36efa6c5768 100644 --- a/packages/contracts-bedrock/snapshots/abi/GasPriceOracle.json +++ b/packages/contracts-bedrock/snapshots/abi/GasPriceOracle.json @@ -147,6 +147,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_gasUsed", + "type": "uint256" + } + ], + "name": "getOperatorFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "isEcotone", @@ -173,6 +192,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "isIsthmus", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "l1BaseFee", @@ -226,6 +258,13 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "setIsthmus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "version", diff --git a/packages/contracts-bedrock/snapshots/abi/L1Block.json b/packages/contracts-bedrock/snapshots/abi/L1Block.json index 8ab5df00fcd..153d2676cf5 100644 --- a/packages/contracts-bedrock/snapshots/abi/L1Block.json +++ b/packages/contracts-bedrock/snapshots/abi/L1Block.json @@ -186,6 +186,32 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "operatorFeeConstant", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "operatorFeeScalar", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "sequenceNumber", @@ -254,6 +280,13 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "setL1BlockValuesIsthmus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "timestamp", diff --git a/packages/contracts-bedrock/snapshots/abi/L1BlockInterop.json b/packages/contracts-bedrock/snapshots/abi/L1BlockInterop.json index 3e838b5d807..b3b3d62cb48 100644 --- a/packages/contracts-bedrock/snapshots/abi/L1BlockInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/L1BlockInterop.json @@ -238,6 +238,32 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "operatorFeeConstant", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "operatorFeeScalar", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "sequenceNumber", @@ -331,6 +357,13 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "setL1BlockValuesIsthmus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "timestamp", diff --git a/packages/contracts-bedrock/snapshots/abi/OperatorFeeVault.json b/packages/contracts-bedrock/snapshots/abi/OperatorFeeVault.json new file mode 100644 index 00000000000..657c3d27808 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/OperatorFeeVault.json @@ -0,0 +1,178 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [], + "name": "MIN_WITHDRAWAL_AMOUNT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "RECIPIENT", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "WITHDRAWAL_NETWORK", + "outputs": [ + { + "internalType": "enum Types.WithdrawalNetwork", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minWithdrawalAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "recipient", + "outputs": [ + { + "internalType": "address", + "name": "recipient_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalProcessed", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawalNetwork", + "outputs": [ + { + "internalType": "enum Types.WithdrawalNetwork", + "name": "network_", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "from", + "type": "address" + } + ], + "name": "Withdrawal", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "enum Types.WithdrawalNetwork", + "name": "withdrawalNetwork", + "type": "uint8" + } + ], + "name": "Withdrawal", + "type": "event" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/SystemConfig.json b/packages/contracts-bedrock/snapshots/abi/SystemConfig.json index 7105f16bf44..5009ab31ca8 100644 --- a/packages/contracts-bedrock/snapshots/abi/SystemConfig.json +++ b/packages/contracts-bedrock/snapshots/abi/SystemConfig.json @@ -465,6 +465,32 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "operatorFeeConstant", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "operatorFeeScalar", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "optimismMintableERC20Factory", @@ -662,6 +688,24 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_operatorFeeScalar", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "_operatorFeeConstant", + "type": "uint64" + } + ], + "name": "setOperatorFeeScalars", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { diff --git a/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json b/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json index f87d9e9e546..8bcac1f13cc 100644 --- a/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json @@ -608,6 +608,32 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "operatorFeeConstant", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "operatorFeeScalar", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "optimismMintableERC20Factory", @@ -818,6 +844,24 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint32", + "name": "_operatorFeeScalar", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "_operatorFeeConstant", + "type": "uint64" + } + ], + "name": "setOperatorFeeScalars", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 68d3e79e9fb..7782289486d 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -36,12 +36,12 @@ "sourceCodeHash": "0xfd56e63e76b1f203cceeb9bbb14396ae803cbbbf7e80ca0ee11fb586321812af" }, "src/L1/SystemConfig.sol": { - "initCodeHash": "0x98c1049952199f55ae63e34ec61a839d43bde52b0892c482ae4246d0c088e826", - "sourceCodeHash": "0x9016b1979c2f1def83a849389543708d857cf0430756815737dadda8e63047c5" + "initCodeHash": "0xd5b8b8eb47763556d9953019d1f81b1d790f15433aa9696b159a3fc45ecee148", + "sourceCodeHash": "0x6bfbc78b0fef2f65beff11a81f924728a7bd439a56986997621099551805aff9" }, "src/L1/SystemConfigInterop.sol": { - "initCodeHash": "0x36fd6c64d81c83fd97e039ab77fb6dfd3a76f12faba923bf04491bb124b590e8", - "sourceCodeHash": "0x609a10f2f85a2b1cc60a5accd795f65c84edc09b0e98124011bd9e7caeb905d9" + "initCodeHash": "0xbef4696b2dcb6d43c3b3c438338bfb2224a1ea5002ed0612ec36a7821d7e3da2", + "sourceCodeHash": "0x1653aaa4d2b44d34ca1f9f2b4971eeb7594c8c2d27771b1f68b8d38cb79f2368" }, "src/L2/BaseFeeVault.sol": { "initCodeHash": "0xc403d4c555d8e69a2699e01d192ae7327136701fa02da10a6d75a584b3c364c9", @@ -56,16 +56,16 @@ "sourceCodeHash": "0xe5c08ce62327113e4bbaf29f47e5f1ddfad6fbd63c07132eedfba5af5325f331" }, "src/L2/GasPriceOracle.sol": { - "initCodeHash": "0x83d50e3b34cd1b4de32f1cced28796b07aefc526cc17ceb1903ad55f4abc90b7", - "sourceCodeHash": "0x305c72d7be9149fce7095bd4641a1a19acada3126fbc43599f674cadbf6e7d6c" + "initCodeHash": "0x38ef70b2783dd45ad807afcf57972c7df4abaaeb5d16d17cdb451b9e931a9cbb", + "sourceCodeHash": "0x4351fe2ac1106c8c220b8cfe7839bc107c24d8084deb21259ac954f5a362725d" }, "src/L2/L1Block.sol": { - "initCodeHash": "0xc4c2f3ceaa2738a326f7c3e449c45bc642c0810f4c21332b261bb3a3b3f4397e", - "sourceCodeHash": "0x0d72e0709675fdef8b22255000f49d895ee57a7c682cfbc60a5e272b03d38115" + "initCodeHash": "0xa1f984b8ea199574261c19122b5a9c8c7dbd3633980b1e7aaf6b7af24af60478", + "sourceCodeHash": "0xd04d64355dcf55247ac937748518e7f9620ae3f9eabe80fae9a82c0115ed77bc" }, "src/L2/L1BlockInterop.sol": { - "initCodeHash": "0xb2782f1ca0fa0899aa5e879471d9e9044e032e7f931fedcff7803c9a43f8a735", - "sourceCodeHash": "0x01017177d32f567df0273acb1561043d97cf0a36308a95917e0f402682ae5209" + "initCodeHash": "0x55d09f00ad284fd7ca4b55c45fb901ed021b83118012be217aec53876ab34c12", + "sourceCodeHash": "0x7dd627c198a583fbe2c7d257f06001e1a2e563c6c7d79ea6ba9ca0d47cd1599b" }, "src/L2/L1FeeVault.sol": { "initCodeHash": "0x6745b7be3895a5e8d373df0066d931bae29c47672ac46c2f5829bd0052cc6d9e", @@ -95,6 +95,10 @@ "initCodeHash": "0xc56db8cb569efa0467fd53ab3fa218af3051e54f5517d7fafb7b5831b4350618", "sourceCodeHash": "0x72062343a044e9c56f4143dcfc71706286eb205902006c2afcf6a4cd90c3e9f8" }, + "src/L2/OperatorFeeVault.sol": { + "initCodeHash": "0x3d8c0d7736e8767f2f797da1c20c5fe30bd7f48a4cf75f376290481ad7c0f91f", + "sourceCodeHash": "0x2022fdb4e32769eb9446dab4aed4b8abb5261fd866f381cccfa7869df1a2adff" + }, "src/L2/OptimismMintableERC721.sol": { "initCodeHash": "0xcfa6ad9997a422aef5a19a490a0a535bc870ee34b1f5258c2949eb3680f71e8a", "sourceCodeHash": "0xb67b91f28c8666fee26c40375f835c61629e0f14054bfaf78bc3c61175bbf136" diff --git a/packages/contracts-bedrock/snapshots/storageLayout/GasPriceOracle.json b/packages/contracts-bedrock/snapshots/storageLayout/GasPriceOracle.json index f5149cb06ce..8e4127173dc 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/GasPriceOracle.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/GasPriceOracle.json @@ -12,5 +12,12 @@ "offset": 1, "slot": "0", "type": "bool" + }, + { + "bytes": "1", + "label": "isIsthmus", + "offset": 2, + "slot": "0", + "type": "bool" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/L1Block.json b/packages/contracts-bedrock/snapshots/storageLayout/L1Block.json index 2928d2147b5..2c23f063678 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/L1Block.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/L1Block.json @@ -75,5 +75,19 @@ "offset": 0, "slot": "7", "type": "uint256" + }, + { + "bytes": "8", + "label": "operatorFeeConstant", + "offset": 0, + "slot": "8", + "type": "uint64" + }, + { + "bytes": "4", + "label": "operatorFeeScalar", + "offset": 8, + "slot": "8", + "type": "uint32" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/L1BlockInterop.json b/packages/contracts-bedrock/snapshots/storageLayout/L1BlockInterop.json index 14ee2ff9609..d7f312e0bf7 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/L1BlockInterop.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/L1BlockInterop.json @@ -76,11 +76,25 @@ "slot": "7", "type": "uint256" }, + { + "bytes": "8", + "label": "operatorFeeConstant", + "offset": 0, + "slot": "8", + "type": "uint64" + }, + { + "bytes": "4", + "label": "operatorFeeScalar", + "offset": 8, + "slot": "8", + "type": "uint32" + }, { "bytes": "64", "label": "dependencySet", "offset": 0, - "slot": "8", + "slot": "9", "type": "struct EnumerableSet.UintSet" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OperatorFeeVault.json b/packages/contracts-bedrock/snapshots/storageLayout/OperatorFeeVault.json new file mode 100644 index 00000000000..93c5a7ec8a6 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/OperatorFeeVault.json @@ -0,0 +1,16 @@ +[ + { + "bytes": "32", + "label": "totalProcessed", + "offset": 0, + "slot": "0", + "type": "uint256" + }, + { + "bytes": "1536", + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "uint256[48]" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json index a6184a1f10d..ea0d05feb90 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json @@ -96,5 +96,19 @@ "offset": 4, "slot": "106", "type": "uint32" + }, + { + "bytes": "4", + "label": "operatorFeeScalar", + "offset": 8, + "slot": "106", + "type": "uint32" + }, + { + "bytes": "8", + "label": "operatorFeeConstant", + "offset": 12, + "slot": "106", + "type": "uint64" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigInterop.json b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigInterop.json index a6184a1f10d..ea0d05feb90 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigInterop.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigInterop.json @@ -96,5 +96,19 @@ "offset": 4, "slot": "106", "type": "uint32" + }, + { + "bytes": "4", + "label": "operatorFeeScalar", + "offset": 8, + "slot": "106", + "type": "uint32" + }, + { + "bytes": "8", + "label": "operatorFeeConstant", + "offset": 12, + "slot": "106", + "type": "uint64" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L1/SystemConfig.sol b/packages/contracts-bedrock/src/L1/SystemConfig.sol index d5cc62db1a6..e767bc64a50 100644 --- a/packages/contracts-bedrock/src/L1/SystemConfig.sol +++ b/packages/contracts-bedrock/src/L1/SystemConfig.sol @@ -28,7 +28,8 @@ contract SystemConfig is OwnableUpgradeable, ISemver { FEE_SCALARS, GAS_LIMIT, UNSAFE_BLOCK_SIGNER, - EIP_1559_PARAMS + EIP_1559_PARAMS, + OPERATOR_FEE_PARAMS } /// @notice Struct representing the addresses of L1 system contracts. These should be the @@ -122,6 +123,12 @@ contract SystemConfig is OwnableUpgradeable, ISemver { /// @notice The EIP-1559 elasticity multiplier. uint32 public eip1559Elasticity; + /// @notice The operator fee scalar. + uint32 public operatorFeeScalar; + + /// @notice The operator fee constant. + uint64 public operatorFeeConstant; + /// @notice Emitted when configuration is updated. /// @param version SystemConfig version. /// @param updateType Type of update. @@ -129,9 +136,9 @@ contract SystemConfig is OwnableUpgradeable, ISemver { event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data); /// @notice Semantic version. - /// @custom:semver 2.4.0 + /// @custom:semver 2.5.0 function version() public pure virtual returns (string memory) { - return "2.4.0"; + return "2.5.0"; } /// @notice Constructs the SystemConfig contract. @@ -375,6 +382,22 @@ contract SystemConfig is OwnableUpgradeable, ISemver { emit ConfigUpdate(VERSION, UpdateType.EIP_1559_PARAMS, data); } + /// @notice Updates the operator fee parameters. Can only be called by the owner. + /// @param _operatorFeeScalar operator fee scalar. + /// @param _operatorFeeConstant operator fee constant. + function setOperatorFeeScalars(uint32 _operatorFeeScalar, uint64 _operatorFeeConstant) external onlyOwner { + _setOperatorFeeScalars(_operatorFeeScalar, _operatorFeeConstant); + } + + /// @notice Internal function for updating the operator fee parameters. + function _setOperatorFeeScalars(uint32 _operatorFeeScalar, uint64 _operatorFeeConstant) internal { + operatorFeeScalar = _operatorFeeScalar; + operatorFeeConstant = _operatorFeeConstant; + + bytes memory data = abi.encode(uint256(_operatorFeeScalar) << 64 | _operatorFeeConstant); + emit ConfigUpdate(VERSION, UpdateType.OPERATOR_FEE_PARAMS, data); + } + /// @notice Sets the start block in a backwards compatible way. Proxies /// that were initialized before the startBlock existed in storage /// can have their start block set by a user provided override. diff --git a/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol b/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol index e18ed7400f3..1d7bd879451 100644 --- a/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol +++ b/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol @@ -65,9 +65,9 @@ contract SystemConfigInterop is SystemConfig { Storage.setAddress(DEPENDENCY_MANAGER_SLOT, _dependencyManager); } - /// @custom:semver +interop + /// @custom:semver +interop.1 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop"); + return string.concat(super.version(), "+interop.1"); } /// @notice Adds a chain to the interop dependency set. Can only be called by the dependency manager. diff --git a/packages/contracts-bedrock/src/L2/GasPriceOracle.sol b/packages/contracts-bedrock/src/L2/GasPriceOracle.sol index 11b6c897db8..5af0b0e9de0 100644 --- a/packages/contracts-bedrock/src/L2/GasPriceOracle.sol +++ b/packages/contracts-bedrock/src/L2/GasPriceOracle.sol @@ -5,6 +5,7 @@ pragma solidity 0.8.15; import { LibZip } from "@solady/utils/LibZip.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; import { Constants } from "src/libraries/Constants.sol"; +import { Arithmetic } from "src/libraries/Arithmetic.sol"; // Interfaces import { ISemver } from "interfaces/universal/ISemver.sol"; @@ -29,8 +30,8 @@ contract GasPriceOracle is ISemver { uint256 public constant DECIMALS = 6; /// @notice Semantic version. - /// @custom:semver 1.3.1-beta.4 - string public constant version = "1.3.1-beta.4"; + /// @custom:semver 1.4.0 + string public constant version = "1.4.0"; /// @notice This is the intercept value for the linear regression used to estimate the final size of the /// compressed transaction. @@ -50,6 +51,9 @@ contract GasPriceOracle is ISemver { /// @notice Indicates whether the network has gone through the Fjord upgrade. bool public isFjord; + /// @notice Indicates whether the network has gone through the Isthmus upgrade. + bool public isIsthmus; + /// @notice Computes the L1 portion of the fee based on the size of the rlp encoded input /// transaction, the current L1 base fee, and the various dynamic parameters. /// @param _data Unsigned fully RLP-encoded transaction to get the L1 fee for. @@ -100,6 +104,17 @@ contract GasPriceOracle is ISemver { isFjord = true; } + /// @notice Set chain to be Isthmus chain (callable by depositor account) + function setIsthmus() external { + require( + msg.sender == Constants.DEPOSITOR_ACCOUNT, + "GasPriceOracle: only the depositor account can set isIsthmus flag" + ); + require(isFjord, "GasPriceOracle: Isthmus can only be activated after Fjord"); + require(isIsthmus == false, "GasPriceOracle: Isthmus already active"); + isIsthmus = true; + } + /// @notice Retrieves the current gas price (base fee). /// @return Current L2 gas price (base fee). function gasPrice() public view returns (uint256) { @@ -179,6 +194,17 @@ contract GasPriceOracle is ISemver { return l1GasUsed + IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).l1FeeOverhead(); } + function getOperatorFee(uint256 _gasUsed) public view returns (uint256) { + if (!isIsthmus) { + return 0; + } + + return Arithmetic.saturatingAdd( + Arithmetic.saturatingMul(_gasUsed, IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).operatorFeeScalar()) / 1e6, + IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES).operatorFeeConstant() + ); + } + /// @notice Computation of the L1 portion of the fee for Bedrock. /// @param _data Unsigned fully RLP-encoded transaction to get the L1 fee for. /// @return L1 fee that should be paid for the tx diff --git a/packages/contracts-bedrock/src/L2/L1Block.sol b/packages/contracts-bedrock/src/L2/L1Block.sol index 81b942446ce..63d508c478c 100644 --- a/packages/contracts-bedrock/src/L2/L1Block.sol +++ b/packages/contracts-bedrock/src/L2/L1Block.sol @@ -56,9 +56,15 @@ contract L1Block is ISemver { /// @notice The latest L1 blob base fee. uint256 public blobBaseFee; - /// @custom:semver 1.5.1-beta.6 + /// @notice The constant value applied to the operator fee. + uint64 public operatorFeeConstant; + + /// @notice The scalar value applied to the operator fee. + uint32 public operatorFeeScalar; + + /// @custom:semver 1.6.0 function version() public pure virtual returns (string memory) { - return "1.5.1-beta.6"; + return "1.6.0"; } /// @notice Returns the gas paying token, its decimals, name and symbol. @@ -167,4 +173,44 @@ contract L1Block is ISemver { sstore(batcherHash.slot, calldataload(132)) // bytes32 } } + + /// @notice Updates the L1 block values for an Isthmus upgraded chain. + /// Params are packed and passed in as raw msg.data instead of ABI to reduce calldata size. + /// Params are expected to be in the following order: + /// 1. _baseFeeScalar L1 base fee scalar + /// 2. _blobBaseFeeScalar L1 blob base fee scalar + /// 3. _sequenceNumber Number of L2 blocks since epoch start. + /// 4. _timestamp L1 timestamp. + /// 5. _number L1 blocknumber. + /// 6. _basefee L1 base fee. + /// 7. _blobBaseFee L1 blob base fee. + /// 8. _hash L1 blockhash. + /// 9. _batcherHash Versioned hash to authenticate batcher by. + /// 10. _operatorFeeScalar Operator fee scalar. + /// 11. _operatorFeeConstant Operator fee constant. + function setL1BlockValuesIsthmus() public { + _setL1BlockValuesIsthmus(); + } + + /// @notice Updates the L1 block values for an Isthmus upgraded chain. + /// Params are packed and passed in as raw msg.data instead of ABI to reduce calldata size. + /// Params are expected to be in the following order: + /// 1. _baseFeeScalar L1 base fee scalar + /// 2. _blobBaseFeeScalar L1 blob base fee scalar + /// 3. _sequenceNumber Number of L2 blocks since epoch start. + /// 4. _timestamp L1 timestamp. + /// 5. _number L1 blocknumber. + /// 6. _basefee L1 base fee. + /// 7. _blobBaseFee L1 blob base fee. + /// 8. _hash L1 blockhash. + /// 9. _batcherHash Versioned hash to authenticate batcher by. + /// 10. _operatorFeeScalar Operator fee scalar. + /// 11. _operatorFeeConstant Operator fee constant. + function _setL1BlockValuesIsthmus() internal { + _setL1BlockValuesEcotone(); + assembly { + // operatorFeeScalar (uint32), operatorFeeConstant (uint64) + sstore(operatorFeeConstant.slot, shr(160, calldataload(164))) + } + } } diff --git a/packages/contracts-bedrock/src/L2/L1BlockInterop.sol b/packages/contracts-bedrock/src/L2/L1BlockInterop.sol index 3cc03c1ef53..19c31306015 100644 --- a/packages/contracts-bedrock/src/L2/L1BlockInterop.sol +++ b/packages/contracts-bedrock/src/L2/L1BlockInterop.sol @@ -46,9 +46,9 @@ contract L1BlockInterop is L1Block { /// keccak256(abi.encode(uint256(keccak256("l1Block.identifier.isDeposit")) - 1)) & ~bytes32(uint256(0xff)) uint256 internal constant IS_DEPOSIT_SLOT = 0x921bd3a089295c6e5540e8fba8195448d253efd6f2e3e495b499b627dc36a300; - /// @custom:semver +interop-beta.4 + /// @custom:semver +interop.6 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop-beta.4"); + return string.concat(super.version(), "+interop.6"); } /// @notice Returns whether the call was triggered from a a deposit or not. diff --git a/packages/contracts-bedrock/src/L2/OperatorFeeVault.sol b/packages/contracts-bedrock/src/L2/OperatorFeeVault.sol new file mode 100644 index 00000000000..b7341824d6f --- /dev/null +++ b/packages/contracts-bedrock/src/L2/OperatorFeeVault.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Contracts +import { FeeVault } from "src/L2/FeeVault.sol"; + +// Libraries +import { Types } from "src/libraries/Types.sol"; +import { Predeploys } from "src/libraries/Predeploys.sol"; + +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; + +/// @custom:proxied true +/// @custom:predeploy 0x420000000000000000000000000000000000001B +/// @title OperatorFeeVault +/// @notice The OperatorFeeVault accumulates the operator portion of the transaction fees. +contract OperatorFeeVault is FeeVault, ISemver { + /// @notice Semantic version. + /// @custom:semver 1.0.0 + string public constant version = "1.0.0"; + + /// @notice Constructs the OperatorFeeVault contract. + /// Funds are withdrawn to the base fee vault on the L2 network. + constructor() FeeVault(Predeploys.BASE_FEE_VAULT, 0, Types.WithdrawalNetwork.L2) { } +} diff --git a/packages/contracts-bedrock/src/libraries/Arithmetic.sol b/packages/contracts-bedrock/src/libraries/Arithmetic.sol index 140affaa718..d4f44c6103f 100644 --- a/packages/contracts-bedrock/src/libraries/Arithmetic.sol +++ b/packages/contracts-bedrock/src/libraries/Arithmetic.sol @@ -26,4 +26,30 @@ library Arithmetic { function cdexp(int256 _coefficient, int256 _denominator, int256 _exponent) internal pure returns (int256) { return (_coefficient * (FixedPointMathLib.powWad(1e18 - (1e18 / _denominator), _exponent * 1e18))) / 1e18; } + + /// @notice Saturating addition. + /// @param _x The first value. + /// @param _y The second value. + /// @return z_ The sum of the two values, or the maximum value if the sum overflows. + /// @dev Returns `min(2 ** 256 - 1, x + y)`. + /// @dev Taken from Solady + /// https://github.com/Vectorized/solady/blob/63416d60c78aba70a12ca1b3c11125d1061caa12/src/utils/FixedPointMathLib.sol#L673 + function saturatingAdd(uint256 _x, uint256 _y) internal pure returns (uint256 z_) { + assembly ("memory-safe") { + z_ := or(sub(0, lt(add(_x, _y), _x)), add(_x, _y)) + } + } + + /// @notice Saturating multiplication. + /// @param _x The first value. + /// @param _y The second value. + /// @return z_ The product of the two values, or the maximum value if the product overflows. + /// @dev Returns `min(2 ** 256 - 1, x * y). + /// @dev Taken from Solady + /// https://github.com/Vectorized/solady/blob/63416d60c78aba70a12ca1b3c11125d1061caa12/src/utils/FixedPointMathLib.sol#L681 + function saturatingMul(uint256 _x, uint256 _y) internal pure returns (uint256 z_) { + assembly ("memory-safe") { + z_ := or(sub(or(iszero(_x), eq(div(mul(_x, _y), _x), _y)), 1), mul(_x, _y)) + } + } } diff --git a/packages/contracts-bedrock/src/libraries/Encoding.sol b/packages/contracts-bedrock/src/libraries/Encoding.sol index 5aa4ee7d3d8..00c20ea459b 100644 --- a/packages/contracts-bedrock/src/libraries/Encoding.sol +++ b/packages/contracts-bedrock/src/libraries/Encoding.sol @@ -177,6 +177,52 @@ library Encoding { ); } + /// @notice Returns an appropriately encoded call to L1Block.setL1BlockValuesIsthmus + /// @param _baseFeeScalar L1 base fee Scalar + /// @param _blobBaseFeeScalar L1 blob base fee Scalar + /// @param _sequenceNumber Number of L2 blocks since epoch start. + /// @param _timestamp L1 timestamp. + /// @param _number L1 blocknumber. + /// @param _baseFee L1 base fee. + /// @param _blobBaseFee L1 blob base fee. + /// @param _hash L1 blockhash. + /// @param _batcherHash Versioned hash to authenticate batcher by. + /// @param _operatorFeeScalar Operator fee scalar. + /// @param _operatorFeeConstant Operator fee constant. + function encodeSetL1BlockValuesIsthmus( + uint32 _baseFeeScalar, + uint32 _blobBaseFeeScalar, + uint64 _sequenceNumber, + uint64 _timestamp, + uint64 _number, + uint256 _baseFee, + uint256 _blobBaseFee, + bytes32 _hash, + bytes32 _batcherHash, + uint32 _operatorFeeScalar, + uint64 _operatorFeeConstant + ) + internal + pure + returns (bytes memory) + { + bytes4 functionSignature = bytes4(keccak256("setL1BlockValuesIsthmus()")); + return abi.encodePacked( + functionSignature, + _baseFeeScalar, + _blobBaseFeeScalar, + _sequenceNumber, + _timestamp, + _number, + _baseFee, + _blobBaseFee, + _hash, + _batcherHash, + _operatorFeeScalar, + _operatorFeeConstant + ); + } + /// @notice Returns an appropriately encoded call to L1Block.setL1BlockValuesInterop /// @param _baseFeeScalar L1 base fee Scalar /// @param _blobBaseFeeScalar L1 blob base fee Scalar diff --git a/packages/contracts-bedrock/src/libraries/Predeploys.sol b/packages/contracts-bedrock/src/libraries/Predeploys.sol index c700d74e9f0..1c7496e1ec4 100644 --- a/packages/contracts-bedrock/src/libraries/Predeploys.sol +++ b/packages/contracts-bedrock/src/libraries/Predeploys.sol @@ -68,6 +68,9 @@ library Predeploys { /// @notice Address of the L1FeeVault predeploy. address internal constant L1_FEE_VAULT = 0x420000000000000000000000000000000000001A; + /// @notice Address of the OperatorFeeVault predeploy. + address internal constant OPERATOR_FEE_VAULT = 0x420000000000000000000000000000000000001b; + /// @notice Address of the SchemaRegistry predeploy. address internal constant SCHEMA_REGISTRY = 0x4200000000000000000000000000000000000020; @@ -128,6 +131,7 @@ library Predeploys { if (_addr == PROXY_ADMIN) return "ProxyAdmin"; if (_addr == BASE_FEE_VAULT) return "BaseFeeVault"; if (_addr == L1_FEE_VAULT) return "L1FeeVault"; + if (_addr == OPERATOR_FEE_VAULT) return "OperatorFeeVault"; if (_addr == SCHEMA_REGISTRY) return "SchemaRegistry"; if (_addr == EAS) return "EAS"; if (_addr == GOVERNANCE_TOKEN) return "GovernanceToken"; @@ -154,10 +158,10 @@ library Predeploys { || _addr == SEQUENCER_FEE_WALLET || _addr == OPTIMISM_MINTABLE_ERC20_FACTORY || _addr == L1_BLOCK_NUMBER || _addr == L2_ERC721_BRIDGE || _addr == L1_BLOCK_ATTRIBUTES || _addr == L2_TO_L1_MESSAGE_PASSER || _addr == OPTIMISM_MINTABLE_ERC721_FACTORY || _addr == PROXY_ADMIN || _addr == BASE_FEE_VAULT - || _addr == L1_FEE_VAULT || _addr == SCHEMA_REGISTRY || _addr == EAS || _addr == GOVERNANCE_TOKEN - || (_useInterop && _addr == CROSS_L2_INBOX) || (_useInterop && _addr == L2_TO_L2_CROSS_DOMAIN_MESSENGER) - || (_useInterop && _addr == SUPERCHAIN_WETH) || (_useInterop && _addr == ETH_LIQUIDITY) - || (_useInterop && _addr == SUPERCHAIN_TOKEN_BRIDGE); + || _addr == L1_FEE_VAULT || _addr == OPERATOR_FEE_VAULT || _addr == SCHEMA_REGISTRY || _addr == EAS + || _addr == GOVERNANCE_TOKEN || (_useInterop && _addr == CROSS_L2_INBOX) + || (_useInterop && _addr == L2_TO_L2_CROSS_DOMAIN_MESSENGER) || (_useInterop && _addr == SUPERCHAIN_WETH) + || (_useInterop && _addr == ETH_LIQUIDITY) || (_useInterop && _addr == SUPERCHAIN_TOKEN_BRIDGE); } function isPredeployNamespace(address _addr) internal pure returns (bool) { diff --git a/packages/contracts-bedrock/test/L2/GasPriceOracle.t.sol b/packages/contracts-bedrock/test/L2/GasPriceOracle.t.sol index f013325ef7e..8b92a7dbd5e 100644 --- a/packages/contracts-bedrock/test/L2/GasPriceOracle.t.sol +++ b/packages/contracts-bedrock/test/L2/GasPriceOracle.t.sol @@ -27,6 +27,8 @@ contract GasPriceOracle_Test is CommonTest { uint256 constant l1FeeScalar = 10; uint32 constant blobBaseFeeScalar = 15; uint32 constant baseFeeScalar = 20; + uint32 constant operatorFeeScalar = 4_000_000; + uint64 constant operatorFeeConstant = 300; /// @dev Sets up the test suite. function setUp() public virtual override { @@ -351,4 +353,47 @@ contract GasPriceOracleFjordActive_Test is GasPriceOracle_Test { uint256 upperBound = gasPriceOracle.getL1FeeUpperBound(data.length); assertEq(upperBound, 111214); } + + /// @dev Tests that `operatorFee` is 0 is Isthmus is not activated. + function test_getOperatorFee_succeeds() external view { + assertEq(gasPriceOracle.isIsthmus(), false); + assertEq(gasPriceOracle.getOperatorFee(10), 0); + } +} + +contract GasPriceOracleIsthmus_Test is GasPriceOracle_Test { + /// @dev Sets up the test suite. + function setUp() public virtual override { + l2Fork = Fork.ISTHMUS; + super.setUp(); + + bytes memory calldataPacked = Encoding.encodeSetL1BlockValuesIsthmus( + baseFeeScalar, + blobBaseFeeScalar, + sequenceNumber, + timestamp, + number, + baseFee, + blobBaseFee, + hash, + batcherHash, + operatorFeeScalar, + operatorFeeConstant + ); + + vm.prank(depositor); + (bool success,) = address(l1Block).call(calldataPacked); + require(success, "GasPriceOracleIsthmus_Test: Function call failed"); + } + + /// @dev Tests that `operatorFee` is set correctly. + function test_getOperatorFee_succeeds() external view { + assertEq(gasPriceOracle.getOperatorFee(10), 10 * operatorFeeScalar / 1e6 + operatorFeeConstant); + } + + /// @dev Tests that `setIsthmus` is only callable by the depositor. + function test_setIsthmus_wrongCaller_reverts() external { + vm.expectRevert("GasPriceOracle: only the depositor account can set isIsthmus flag"); + gasPriceOracle.setIsthmus(); + } } diff --git a/packages/contracts-bedrock/test/L2/L1Block.t.sol b/packages/contracts-bedrock/test/L2/L1Block.t.sol index 4e9b0cb2f6f..c300e09223a 100644 --- a/packages/contracts-bedrock/test/L2/L1Block.t.sol +++ b/packages/contracts-bedrock/test/L2/L1Block.t.sol @@ -180,3 +180,108 @@ contract L1BlockEcotone_Test is L1BlockTest { assertEq(data, expReturn); } } + +contract L1BlockIsthmus_Test is L1BlockTest { + /// @dev Tests that setL1BlockValuesIsthmus updates the values appropriately. + function testFuzz_setL1BlockValuesIsthmus_succeeds( + uint32 baseFeeScalar, + uint32 blobBaseFeeScalar, + uint64 sequenceNumber, + uint64 timestamp, + uint64 number, + uint256 baseFee, + uint256 blobBaseFee, + bytes32 hash, + bytes32 batcherHash, + uint32 operatorFeeScalar, + uint64 operatorFeeConstant + ) + external + { + bytes memory functionCallDataPacked = Encoding.encodeSetL1BlockValuesIsthmus( + baseFeeScalar, + blobBaseFeeScalar, + sequenceNumber, + timestamp, + number, + baseFee, + blobBaseFee, + hash, + batcherHash, + operatorFeeScalar, + operatorFeeConstant + ); + + vm.prank(depositor); + (bool success,) = address(l1Block).call(functionCallDataPacked); + assertTrue(success, "Function call failed"); + + assertEq(l1Block.baseFeeScalar(), baseFeeScalar); + assertEq(l1Block.blobBaseFeeScalar(), blobBaseFeeScalar); + assertEq(l1Block.sequenceNumber(), sequenceNumber); + assertEq(l1Block.timestamp(), timestamp); + assertEq(l1Block.number(), number); + assertEq(l1Block.basefee(), baseFee); + assertEq(l1Block.blobBaseFee(), blobBaseFee); + assertEq(l1Block.hash(), hash); + assertEq(l1Block.batcherHash(), batcherHash); + assertEq(l1Block.operatorFeeScalar(), operatorFeeScalar); + assertEq(l1Block.operatorFeeConstant(), operatorFeeConstant); + + // ensure we didn't accidentally pollute the 128 bits of the sequencenum+scalars slot that + // should be empty + bytes32 scalarsSlot = vm.load(address(l1Block), bytes32(uint256(3))); + bytes32 mask128 = hex"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000000"; + + assertEq(0, scalarsSlot & mask128); + + // ensure we didn't accidentally pollute the 128 bits of the number & timestamp slot that + // should be empty + bytes32 numberTimestampSlot = vm.load(address(l1Block), bytes32(uint256(0))); + assertEq(0, numberTimestampSlot & mask128); + } + + /// @dev Tests that `setL1BlockValuesIsthmus` succeeds if sender address is the depositor + function test_setL1BlockValuesIsthmus_isDepositor_succeeds() external { + bytes memory functionCallDataPacked = Encoding.encodeSetL1BlockValuesIsthmus( + type(uint32).max, + type(uint32).max, + type(uint64).max, + type(uint64).max, + type(uint64).max, + type(uint256).max, + type(uint256).max, + bytes32(type(uint256).max), + bytes32(type(uint256).max), + type(uint32).max, + type(uint64).max + ); + + vm.prank(depositor); + (bool success,) = address(l1Block).call(functionCallDataPacked); + assertTrue(success, "function call failed"); + } + + /// @dev Tests that `setL1BlockValuesIsthmus` reverts if sender address is not the depositor + function test_setL1BlockValuesIsthmus_notDepositor_reverts() external { + bytes memory functionCallDataPacked = Encoding.encodeSetL1BlockValuesIsthmus( + type(uint32).max, + type(uint32).max, + type(uint64).max, + type(uint64).max, + type(uint64).max, + type(uint256).max, + type(uint256).max, + bytes32(type(uint256).max), + bytes32(type(uint256).max), + type(uint32).max, + type(uint64).max + ); + + (bool success, bytes memory data) = address(l1Block).call(functionCallDataPacked); + assertTrue(!success, "function call should have failed"); + // make sure return value is the expected function selector for "NotDepositor()" + bytes memory expReturn = hex"3cc50b45"; + assertEq(data, expReturn); + } +} diff --git a/packages/contracts-bedrock/test/L2/L2Genesis.t.sol b/packages/contracts-bedrock/test/L2/L2Genesis.t.sol index 68c67d7e379..f105a86632a 100644 --- a/packages/contracts-bedrock/test/L2/L2Genesis.t.sol +++ b/packages/contracts-bedrock/test/L2/L2Genesis.t.sol @@ -139,8 +139,8 @@ contract L2GenesisTest is Test { // 2 predeploys do not have proxies assertEq(getCodeCount(_path, "Proxy.sol:Proxy"), Predeploys.PREDEPLOY_COUNT - 2); - // 22 proxies have the implementation set if useInterop is true and 17 if useInterop is false - assertEq(getPredeployCountWithSlotSet(_path, Constants.PROXY_IMPLEMENTATION_ADDRESS), _useInterop ? 22 : 17); + // 23 proxies have the implementation set if useInterop is true and 18 if useInterop is false + assertEq(getPredeployCountWithSlotSet(_path, Constants.PROXY_IMPLEMENTATION_ADDRESS), _useInterop ? 23 : 18); // All proxies except 2 have the proxy 1967 admin slot set to the proxy admin assertEq( @@ -175,7 +175,7 @@ contract L2GenesisTest is Test { uint256 expected = 0; expected += 2048 - 2; // predeploy proxies - expected += 21; // predeploy implementations (excl. legacy erc20-style eth and legacy message sender) + expected += 22; // predeploy implementations (excl. legacy erc20-style eth and legacy message sender) expected += 256; // precompiles expected += 14; // preinstalls expected += 1; // 4788 deployer account diff --git a/packages/contracts-bedrock/test/L2/OperatorFeeVault.t.sol b/packages/contracts-bedrock/test/L2/OperatorFeeVault.t.sol new file mode 100644 index 00000000000..ab90d627014 --- /dev/null +++ b/packages/contracts-bedrock/test/L2/OperatorFeeVault.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Testing utilities +import { CommonTest } from "test/setup/CommonTest.sol"; + +// Libraries +import { Types } from "src/libraries/Types.sol"; +import { Predeploys } from "src/libraries/Predeploys.sol"; + +// Test the implementations of the FeeVault +contract FeeVault_Test is CommonTest { + /// @dev Tests that the constructor sets the correct values. + function test_constructor_operatorFeeVault_succeeds() external view { + assertEq(operatorFeeVault.RECIPIENT(), Predeploys.BASE_FEE_VAULT); + assertEq(operatorFeeVault.recipient(), Predeploys.BASE_FEE_VAULT); + assertEq(operatorFeeVault.MIN_WITHDRAWAL_AMOUNT(), 0); + assertEq(operatorFeeVault.minWithdrawalAmount(), 0); + assertEq(uint8(operatorFeeVault.WITHDRAWAL_NETWORK()), uint8(Types.WithdrawalNetwork.L2)); + assertEq(uint8(operatorFeeVault.withdrawalNetwork()), uint8(Types.WithdrawalNetwork.L2)); + } +} diff --git a/packages/contracts-bedrock/test/L2/Predeploys.t.sol b/packages/contracts-bedrock/test/L2/Predeploys.t.sol index a6a4e5b32b4..dbe97c7f616 100644 --- a/packages/contracts-bedrock/test/L2/Predeploys.t.sol +++ b/packages/contracts-bedrock/test/L2/Predeploys.t.sol @@ -34,8 +34,8 @@ contract PredeploysBaseTest is CommonTest { /// @dev Returns true if the predeploy uses immutables. function _usesImmutables(address _addr) internal pure returns (bool) { return _addr == Predeploys.OPTIMISM_MINTABLE_ERC721_FACTORY || _addr == Predeploys.SEQUENCER_FEE_WALLET - || _addr == Predeploys.BASE_FEE_VAULT || _addr == Predeploys.L1_FEE_VAULT || _addr == Predeploys.EAS - || _addr == Predeploys.GOVERNANCE_TOKEN; + || _addr == Predeploys.BASE_FEE_VAULT || _addr == Predeploys.L1_FEE_VAULT + || _addr == Predeploys.OPERATOR_FEE_VAULT || _addr == Predeploys.EAS || _addr == Predeploys.GOVERNANCE_TOKEN; } function test_predeployToCodeNamespace_works() external pure { diff --git a/packages/contracts-bedrock/test/setup/Setup.sol b/packages/contracts-bedrock/test/setup/Setup.sol index 8c6a3bd8a27..62c7118ac73 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -44,6 +44,7 @@ import { IOptimismSuperchainERC20Factory } from "interfaces/L2/IOptimismSupercha import { IBaseFeeVault } from "interfaces/L2/IBaseFeeVault.sol"; import { ISequencerFeeVault } from "interfaces/L2/ISequencerFeeVault.sol"; import { IL1FeeVault } from "interfaces/L2/IL1FeeVault.sol"; +import { IOperatorFeeVault } from "interfaces/L2/IOperatorFeeVault.sol"; import { IGasPriceOracle } from "interfaces/L2/IGasPriceOracle.sol"; import { IL1Block } from "interfaces/L2/IL1Block.sol"; import { ISuperchainWETH } from "interfaces/L2/ISuperchainWETH.sol"; @@ -120,6 +121,7 @@ contract Setup { IBaseFeeVault baseFeeVault = IBaseFeeVault(payable(Predeploys.BASE_FEE_VAULT)); ISequencerFeeVault sequencerFeeVault = ISequencerFeeVault(payable(Predeploys.SEQUENCER_FEE_WALLET)); IL1FeeVault l1FeeVault = IL1FeeVault(payable(Predeploys.L1_FEE_VAULT)); + IOperatorFeeVault operatorFeeVault = IOperatorFeeVault(payable(Predeploys.OPERATOR_FEE_VAULT)); IGasPriceOracle gasPriceOracle = IGasPriceOracle(Predeploys.GAS_PRICE_ORACLE); IL1Block l1Block = IL1Block(Predeploys.L1_BLOCK_ATTRIBUTES); IGovernanceToken governanceToken = IGovernanceToken(Predeploys.GOVERNANCE_TOKEN); @@ -286,6 +288,7 @@ contract Setup { labelPredeploy(Predeploys.OPTIMISM_MINTABLE_ERC721_FACTORY); labelPredeploy(Predeploys.BASE_FEE_VAULT); labelPredeploy(Predeploys.L1_FEE_VAULT); + labelPredeploy(Predeploys.OPERATOR_FEE_VAULT); labelPredeploy(Predeploys.L1_BLOCK_ATTRIBUTES); labelPredeploy(Predeploys.GAS_PRICE_ORACLE); labelPredeploy(Predeploys.LEGACY_MESSAGE_PASSER); diff --git a/packages/contracts-bedrock/test/universal/Specs.t.sol b/packages/contracts-bedrock/test/universal/Specs.t.sol index 90ac8ae5373..abd2ebfc8d2 100644 --- a/packages/contracts-bedrock/test/universal/Specs.t.sol +++ b/packages/contracts-bedrock/test/universal/Specs.t.sol @@ -350,6 +350,8 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "SystemConfig", _sel: _getSel("gasLimit()") }); _addSpec({ _name: "SystemConfig", _sel: _getSel("eip1559Denominator()") }); _addSpec({ _name: "SystemConfig", _sel: _getSel("eip1559Elasticity()") }); + _addSpec({ _name: "SystemConfig", _sel: _getSel("operatorFeeScalar()") }); + _addSpec({ _name: "SystemConfig", _sel: _getSel("operatorFeeConstant()") }); _addSpec({ _name: "SystemConfig", _sel: ISystemConfig.initialize.selector }); _addSpec({ _name: "SystemConfig", _sel: ISystemConfig.minimumGasLimit.selector }); _addSpec({ _name: "SystemConfig", _sel: _getSel("overhead()") }); @@ -361,6 +363,11 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "SystemConfig", _sel: ISystemConfig.setGasConfig.selector, _auth: Role.SYSTEMCONFIGOWNER }); _addSpec({ _name: "SystemConfig", _sel: ISystemConfig.setGasLimit.selector, _auth: Role.SYSTEMCONFIGOWNER }); _addSpec({ _name: "SystemConfig", _sel: ISystemConfig.setEIP1559Params.selector, _auth: Role.SYSTEMCONFIGOWNER }); + _addSpec({ + _name: "SystemConfig", + _sel: ISystemConfig.setOperatorFeeScalars.selector, + _auth: Role.SYSTEMCONFIGOWNER + }); _addSpec({ _name: "SystemConfig", _sel: ISystemConfig.setUnsafeBlockSigner.selector, @@ -402,6 +409,8 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("gasLimit()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("eip1559Denominator()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("eip1559Elasticity()") }); + _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("operatorFeeScalar()") }); + _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("operatorFeeConstant()") }); _addSpec({ _name: "SystemConfigInterop", _sel: ISystemConfigInterop.initialize.selector }); _addSpec({ _name: "SystemConfigInterop", _sel: ISystemConfig.initialize.selector }); _addSpec({ _name: "SystemConfigInterop", _sel: ISystemConfigInterop.minimumGasLimit.selector }); @@ -430,6 +439,11 @@ contract Specification_Test is CommonTest { _sel: ISystemConfigInterop.setEIP1559Params.selector, _auth: Role.SYSTEMCONFIGOWNER }); + _addSpec({ + _name: "SystemConfigInterop", + _sel: ISystemConfigInterop.setOperatorFeeScalars.selector, + _auth: Role.SYSTEMCONFIGOWNER + }); _addSpec({ _name: "SystemConfigInterop", _sel: ISystemConfigInterop.setUnsafeBlockSigner.selector, From b0eec050752e427e8000ef8fe2c9fb17ba855077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien?= <3535019+leruaa@users.noreply.github.com> Date: Wed, 5 Mar 2025 23:38:32 +0100 Subject: [PATCH 076/130] feat: handle operator fee in OPCM (#14662) * feat: handle operator fee in OPCM * fix: upgrade version * feat: check implementation address --- packages/contracts-bedrock/snapshots/semver-lock.json | 4 ++-- packages/contracts-bedrock/src/L1/OPContractsManager.sol | 9 +++++++-- .../contracts-bedrock/test/L1/OPContractsManager.t.sol | 4 ++++ 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 7782289486d..8a31cd06dc9 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -16,8 +16,8 @@ "sourceCodeHash": "0x44797707aea8c63dec049a02d69ea056662a06e5cf320028ab8b388634bf1c67" }, "src/L1/OPContractsManager.sol": { - "initCodeHash": "0xc50bce08abbfc66d7f902b5b1f5504c99cbdca17ca4ff98f67801e40c0751f6b", - "sourceCodeHash": "0xff40fd58a78fdfa90e8ac84bed278e6af8329fd826b478fb4fcd6641f62ae1b6" + "initCodeHash": "0x216fe3ceef5d3e839d18a00383793bd326f20944fc3b55ef099a26a22141de18", + "sourceCodeHash": "0xd1de3414a3db731447cb6172df23a00cb1664783de56a93fb29f87d6bc8b8bb0" }, "src/L1/OptimismPortal2.sol": { "initCodeHash": "0xd1651b8a6f4d25611a0105d5cc7c1da3921417bd44da870ec63bf5ccd1bc7c63", diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index eb858c76709..8aaabee8ef2 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -532,6 +532,11 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { assertValidOpChainConfig(_opChainConfigs[i]); ISystemConfig.Addresses memory opChainAddrs = _opChainConfigs[i].systemConfigProxy.getAddresses(); + // -------- Upgrade SystemConfig to Isthmus implementation -------- + upgradeTo( + _opChainConfigs[i].proxyAdmin, address(_opChainConfigs[i].systemConfigProxy), impls.systemConfigImpl + ); + // -------- Upgrade Contracts Stored in SystemConfig -------- // OptimismPortal and L1CrossDomainMessenger are being upgraded to include the fixes @@ -1230,9 +1235,9 @@ contract OPContractsManager is ISemver { // -------- Constants and Variables -------- - /// @custom:semver 1.8.0 + /// @custom:semver 1.9.0 function version() public pure virtual returns (string memory) { - return "1.8.0"; + return "1.9.0"; } OPContractsManagerGameTypeAdder public immutable opcmGameTypeAdder; diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index e33e00c0a95..6f742180c67 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -444,6 +444,10 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { assertEq(ISemver(address(pdg)).version(), "1.4.1"); assertEq(address(pdg.vm()), impls.mipsImpl); + // Check that the SystemConfig is upgraded to the expected version + assertEq(ISemver(address(systemConfig)).version(), "2.5.0"); + assertEq(impls.systemConfigImpl, EIP1967Helper.getImplementation(address(systemConfig))); + if (address(oldFDG) != address(0)) { // Check that the PermissionlessDisputeGame is upgraded to the expected version IFaultDisputeGame newFDG = IFaultDisputeGame(address(disputeGameFactory.gameImpls(GameTypes.CANNON))); From a33bba2fb32f0ddb350e9b672ac6a7207b607b5e Mon Sep 17 00:00:00 2001 From: zhiqiangxu <652732310@qq.com> Date: Thu, 6 Mar 2025 18:21:48 +0800 Subject: [PATCH 077/130] op-deployer: `l1-rpc-url` is not needed when `deployment-target` is `genesis` (#13515) * l1-rpc-url is only needed when deployment-strategy is live * address comment --- op-deployer/pkg/deployer/apply.go | 6 ++++++ op-deployer/pkg/deployer/flags.go | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/op-deployer/pkg/deployer/apply.go b/op-deployer/pkg/deployer/apply.go index 033ba843021..ef4c5ca0ff1 100644 --- a/op-deployer/pkg/deployer/apply.go +++ b/op-deployer/pkg/deployer/apply.go @@ -58,6 +58,12 @@ func (a *ApplyConfig) Check() error { return fmt.Errorf("logger must be specified") } + if a.DeploymentTarget == DeploymentTargetGenesis { + if a.L1RPCUrl != "" { + return fmt.Errorf("l1-rpc-url should not be specified when deployment-target is genesis") + } + } + if a.DeploymentTarget == DeploymentTargetLive { if a.L1RPCUrl == "" { return fmt.Errorf("l1 RPC URL must be specified for live deployment") diff --git a/op-deployer/pkg/deployer/flags.go b/op-deployer/pkg/deployer/flags.go index 8ef70b77b62..09e300981a2 100644 --- a/op-deployer/pkg/deployer/flags.go +++ b/op-deployer/pkg/deployer/flags.go @@ -62,7 +62,7 @@ var ( L1RPCURLFlag = &cli.StringFlag{ Name: L1RPCURLFlagName, Usage: "RPC URL for the L1 chain. Must be set for live chains. " + - "Can be blank for chains deploying to local allocs files.", + "Must be blank for chains deploying to local allocs files.", EnvVars: []string{ "L1_RPC_URL", }, From 1db5141fdcfdf9302c5fbdd1f23e8399195027f9 Mon Sep 17 00:00:00 2001 From: Park Changwan Date: Fri, 7 Mar 2025 01:58:32 +0900 Subject: [PATCH 078/130] Decouple node from chain (#14660) --- devnet-sdk/system/chain.go | 51 +++-------- devnet-sdk/system/interfaces.go | 3 + devnet-sdk/system/node.go | 61 +++++++++++++ devnet-sdk/system/txbuilder.go | 6 +- devnet-sdk/system/txbuilder_test.go | 85 +++++++++++++------ devnet-sdk/system/wallet_test.go | 10 ++- devnet-sdk/testing/systest/testing_test.go | 1 + .../testlib/validators/validators_test.go | 1 + kurtosis-devnet/tests/interop/mocks_test.go | 1 + 9 files changed, 145 insertions(+), 74 deletions(-) create mode 100644 devnet-sdk/system/node.go diff --git a/devnet-sdk/system/chain.go b/devnet-sdk/system/chain.go index 6a3e4700a55..f143c7ba7c7 100644 --- a/devnet-sdk/system/chain.go +++ b/devnet-sdk/system/chain.go @@ -10,8 +10,6 @@ import ( "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" "github.com/ethereum-optimism/optimism/devnet-sdk/types" - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common" coreTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" ) @@ -65,6 +63,12 @@ type chain struct { clients *clientManager registry interfaces.ContractsRegistry mu sync.Mutex + + node Node +} + +func (c *chain) Node() Node { + return c.node } func (c *chain) Client() (*ethclient.Client, error) { @@ -72,12 +76,15 @@ func (c *chain) Client() (*ethclient.Client, error) { } func newChain(chainID string, rpcUrl string, users map[string]Wallet) *chain { - return &chain{ + clients := newClientManager() + chain := &chain{ id: chainID, rpcUrl: rpcUrl, users: users, - clients: newClientManager(), + clients: clients, + node: newNode(rpcUrl, clients), } + return chain } func (c *chain) ContractsRegistry() interfaces.ContractsRegistry { @@ -126,42 +133,6 @@ func (c *chain) ID() types.ChainID { return types.ChainID(id) } -func (c *chain) GasPrice(ctx context.Context) (*big.Int, error) { - client, err := c.Client() - if err != nil { - return nil, fmt.Errorf("failed to get client: %w", err) - } - return client.SuggestGasPrice(ctx) -} - -func (c *chain) GasLimit(ctx context.Context, tx TransactionData) (uint64, error) { - client, err := c.Client() - if err != nil { - return 0, fmt.Errorf("failed to get client: %w", err) - } - - msg := ethereum.CallMsg{ - From: tx.From(), - To: tx.To(), - Value: tx.Value(), - Data: tx.Data(), - } - estimated, err := client.EstimateGas(ctx, msg) - if err != nil { - return 0, fmt.Errorf("failed to estimate gas: %w", err) - } - - return estimated, nil -} - -func (c *chain) PendingNonceAt(ctx context.Context, address common.Address) (uint64, error) { - client, err := c.Client() - if err != nil { - return 0, fmt.Errorf("failed to get client: %w", err) - } - return client.PendingNonceAt(ctx, address) -} - func checkHeader(ctx context.Context, client *ethclient.Client, check func(*coreTypes.Header) bool) bool { head, err := client.HeaderByNumber(ctx, nil) if err != nil { diff --git a/devnet-sdk/system/interfaces.go b/devnet-sdk/system/interfaces.go index 01ededc3c8e..26ddd99f13c 100644 --- a/devnet-sdk/system/interfaces.go +++ b/devnet-sdk/system/interfaces.go @@ -28,7 +28,10 @@ type Chain interface { Wallets(ctx context.Context) ([]Wallet, error) ContractsRegistry() interfaces.ContractsRegistry SupportsEIP(ctx context.Context, eip uint64) bool + Node() Node +} +type Node interface { GasPrice(ctx context.Context) (*big.Int, error) GasLimit(ctx context.Context, tx TransactionData) (uint64, error) PendingNonceAt(ctx context.Context, address common.Address) (uint64, error) diff --git a/devnet-sdk/system/node.go b/devnet-sdk/system/node.go new file mode 100644 index 00000000000..0e1589a423b --- /dev/null +++ b/devnet-sdk/system/node.go @@ -0,0 +1,61 @@ +package system + +import ( + "context" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" +) + +var ( + // This will make sure that we implement the Node interface + _ Node = (*node)(nil) +) + +type node struct { + rpcUrl string + + clients *clientManager +} + +func newNode(rpcUrl string, clients *clientManager) *node { + return &node{rpcUrl: rpcUrl, clients: clients} +} + +func (n *node) GasPrice(ctx context.Context) (*big.Int, error) { + client, err := n.clients.Client(n.rpcUrl) + if err != nil { + return nil, fmt.Errorf("failed to get client: %w", err) + } + return client.SuggestGasPrice(ctx) +} + +func (n *node) GasLimit(ctx context.Context, tx TransactionData) (uint64, error) { + client, err := n.clients.Client(n.rpcUrl) + if err != nil { + return 0, fmt.Errorf("failed to get client: %w", err) + } + + msg := ethereum.CallMsg{ + From: tx.From(), + To: tx.To(), + Value: tx.Value(), + Data: tx.Data(), + } + estimated, err := client.EstimateGas(ctx, msg) + if err != nil { + return 0, fmt.Errorf("failed to estimate gas: %w", err) + } + + return estimated, nil +} + +func (n *node) PendingNonceAt(ctx context.Context, address common.Address) (uint64, error) { + client, err := n.clients.Client(n.rpcUrl) + if err != nil { + return 0, fmt.Errorf("failed to get client: %w", err) + } + return client.PendingNonceAt(ctx, address) +} diff --git a/devnet-sdk/system/txbuilder.go b/devnet-sdk/system/txbuilder.go index c1e54aa37af..5287e84e2ee 100644 --- a/devnet-sdk/system/txbuilder.go +++ b/devnet-sdk/system/txbuilder.go @@ -180,7 +180,7 @@ func (b *TxBuilder) chooseTxType(hasAccessList bool, hasBlobs bool) uint8 { // getNonce gets the next nonce for the given address func (b *TxBuilder) getNonce(from common.Address) (uint64, error) { - nonce, err := b.chain.PendingNonceAt(b.ctx, from) + nonce, err := b.chain.Node().PendingNonceAt(b.ctx, from) if err != nil { return 0, fmt.Errorf("failed to get nonce: %w", err) } @@ -189,7 +189,7 @@ func (b *TxBuilder) getNonce(from common.Address) (uint64, error) { // getGasPrice gets the suggested gas price from the network func (b *TxBuilder) getGasPrice() (*big.Int, error) { - gasPrice, err := b.chain.GasPrice(b.ctx) + gasPrice, err := b.chain.Node().GasPrice(b.ctx) if err != nil { return nil, fmt.Errorf("failed to get gas price: %w", err) } @@ -202,7 +202,7 @@ func (b *TxBuilder) calculateGasLimit(opts *TxOpts) (uint64, error) { return opts.gasLimit, nil } - estimated, err := b.chain.GasLimit(b.ctx, opts) + estimated, err := b.chain.Node().GasLimit(b.ctx, opts) if err != nil { return 0, fmt.Errorf("failed to estimate gas: %w", err) } diff --git a/devnet-sdk/system/txbuilder_test.go b/devnet-sdk/system/txbuilder_test.go index 2443ea77572..de20322b85a 100644 --- a/devnet-sdk/system/txbuilder_test.go +++ b/devnet-sdk/system/txbuilder_test.go @@ -18,6 +18,7 @@ import ( var ( _ Chain = (*mockChain)(nil) + _ Node = (*mockNode)(nil) _ Wallet = (*mockWallet)(nil) ) @@ -75,24 +76,14 @@ func newMockChain() *mockChain { } } -func (m *mockChain) ID() types.ChainID { +func (m *mockChain) Node() Node { args := m.Called() - return args.Get(0).(types.ChainID) -} - -func (m *mockChain) GasPrice(ctx context.Context) (*big.Int, error) { - args := m.Called(ctx) - return args.Get(0).(*big.Int), args.Error(1) -} - -func (m *mockChain) GasLimit(ctx context.Context, tx TransactionData) (uint64, error) { - args := m.Called(ctx, tx) - return args.Get(0).(uint64), args.Error(1) + return args.Get(0).(Node) } -func (m *mockChain) PendingNonceAt(ctx context.Context, addr common.Address) (uint64, error) { - args := m.Called(ctx, addr) - return args.Get(0).(uint64), args.Error(1) +func (m *mockChain) ID() types.ChainID { + args := m.Called() + return args.Get(0).(types.ChainID) } func (m *mockChain) SupportsEIP(ctx context.Context, eip uint64) bool { @@ -119,6 +110,29 @@ func (m *mockChain) Wallets(ctx context.Context) ([]Wallet, error) { return nil, nil } +type mockNode struct { + mock.Mock +} + +func newMockNode() *mockNode { + return &mockNode{} +} + +func (m *mockNode) GasPrice(ctx context.Context) (*big.Int, error) { + args := m.Called(ctx) + return args.Get(0).(*big.Int), args.Error(1) +} + +func (m *mockNode) GasLimit(ctx context.Context, tx TransactionData) (uint64, error) { + args := m.Called(ctx, tx) + return args.Get(0).(uint64), args.Error(1) +} + +func (m *mockNode) PendingNonceAt(ctx context.Context, addr common.Address) (uint64, error) { + args := m.Called(ctx, addr) + return args.Get(0).(uint64), args.Error(1) +} + func TestNewTxBuilder(t *testing.T) { ctx := context.Background() chain := newMockChain() @@ -200,6 +214,7 @@ func TestNewTxBuilder(t *testing.T) { func TestBuildTx(t *testing.T) { ctx := context.Background() chain := newMockChain() + node := newMockNode() addr := common.HexToAddress("0x1234567890123456789012345678901234567890") to := common.HexToAddress("0x0987654321098765432109876543210987654321") chainID := big.NewInt(1) @@ -218,9 +233,12 @@ func TestBuildTx(t *testing.T) { setupMock: func() { chain.On("SupportsEIP", ctx, uint64(1559)).Return(false).Once() chain.On("SupportsEIP", ctx, uint64(4844)).Return(false).Once() - chain.On("PendingNonceAt", ctx, addr).Return(nonce, nil).Once() - chain.On("GasPrice", ctx).Return(gasPrice, nil).Once() - chain.On("GasLimit", ctx, mock.Anything).Return(uint64(21000), nil).Once() + chain.On("Node").Return(node).Once() + node.On("PendingNonceAt", ctx, addr).Return(nonce, nil).Once() + chain.On("Node").Return(node).Once() + node.On("GasPrice", ctx).Return(gasPrice, nil).Once() + chain.On("Node").Return(node).Once() + node.On("GasLimit", ctx, mock.Anything).Return(uint64(21000), nil).Once() }, opts: []TxOption{ WithFrom(addr), @@ -235,10 +253,13 @@ func TestBuildTx(t *testing.T) { setupMock: func() { chain.On("SupportsEIP", ctx, uint64(1559)).Return(true).Once() chain.On("SupportsEIP", ctx, uint64(4844)).Return(false).Once() - chain.On("PendingNonceAt", ctx, addr).Return(nonce, nil).Once() - chain.On("GasPrice", ctx).Return(gasPrice, nil).Once() + chain.On("Node").Return(node).Once() + node.On("PendingNonceAt", ctx, addr).Return(nonce, nil).Once() + chain.On("Node").Return(node).Once() + node.On("GasPrice", ctx).Return(gasPrice, nil).Once() chain.On("ID").Return(chainID).Once() - chain.On("GasLimit", ctx, mock.Anything).Return(uint64(21000), nil).Once() + chain.On("Node").Return(node).Once() + node.On("GasLimit", ctx, mock.Anything).Return(uint64(21000), nil).Once() }, opts: []TxOption{ WithFrom(addr), @@ -253,10 +274,13 @@ func TestBuildTx(t *testing.T) { setupMock: func() { chain.On("SupportsEIP", ctx, uint64(1559)).Return(true).Once() chain.On("SupportsEIP", ctx, uint64(4844)).Return(false).Once() - chain.On("PendingNonceAt", ctx, addr).Return(nonce, nil).Once() - chain.On("GasPrice", ctx).Return(gasPrice, nil).Once() + chain.On("Node").Return(node).Once() + node.On("PendingNonceAt", ctx, addr).Return(nonce, nil).Once() + chain.On("Node").Return(node).Once() + node.On("GasPrice", ctx).Return(gasPrice, nil).Once() chain.On("ID").Return(chainID).Once() - chain.On("GasLimit", ctx, mock.Anything).Return(uint64(21000), nil).Once() + chain.On("Node").Return(node).Once() + node.On("GasLimit", ctx, mock.Anything).Return(uint64(21000), nil).Once() }, opts: []TxOption{ WithFrom(addr), @@ -279,10 +303,13 @@ func TestBuildTx(t *testing.T) { setupMock: func() { chain.On("SupportsEIP", ctx, uint64(1559)).Return(true).Once() chain.On("SupportsEIP", ctx, uint64(4844)).Return(true).Once() - chain.On("PendingNonceAt", ctx, addr).Return(nonce, nil).Once() - chain.On("GasPrice", ctx).Return(gasPrice, nil).Once() + chain.On("Node").Return(node).Once() + node.On("PendingNonceAt", ctx, addr).Return(nonce, nil).Once() + chain.On("Node").Return(node).Once() + node.On("GasPrice", ctx).Return(gasPrice, nil).Once() chain.On("ID").Return(chainID).Once() - chain.On("GasLimit", ctx, mock.Anything).Return(uint64(21000), nil).Once() + chain.On("Node").Return(node).Once() + node.On("GasLimit", ctx, mock.Anything).Return(uint64(21000), nil).Once() }, opts: []TxOption{ WithFrom(addr), @@ -319,6 +346,7 @@ func TestBuildTx(t *testing.T) { func TestCalculateGasLimit(t *testing.T) { ctx := context.Background() chain := newMockChain() + node := newMockNode() addr := common.HexToAddress("0x1234567890123456789012345678901234567890") tests := []struct { @@ -366,7 +394,8 @@ func TestCalculateGasLimit(t *testing.T) { chain.On("SupportsEIP", ctx, uint64(4844)).Return(false).Once() if tt.expectEstimate { - chain.On("GasLimit", ctx, tt.opts).Return(tt.estimatedGas, nil).Once() + chain.On("Node").Return(node).Once() + node.On("GasLimit", ctx, tt.opts).Return(tt.estimatedGas, nil).Once() } builder := NewTxBuilder(ctx, chain, WithGasLimitMargin(tt.margin)) diff --git a/devnet-sdk/system/wallet_test.go b/devnet-sdk/system/wallet_test.go index 41065eadce1..80a8a27b69d 100644 --- a/devnet-sdk/system/wallet_test.go +++ b/devnet-sdk/system/wallet_test.go @@ -135,6 +135,7 @@ func TestWallet_Address(t *testing.T) { func TestWallet_SendETH(t *testing.T) { ctx := context.Background() mockChain := newMockChain() + mockNode := newMockNode() internalChain := &internalMockChain{mockChain} // Use a valid 256-bit private key (32 bytes) @@ -162,11 +163,14 @@ func TestWallet_SendETH(t *testing.T) { mockChain.On("SupportsEIP", ctx, uint64(4844)).Return(false) // Mock gas price and limit - mockChain.On("GasPrice", ctx).Return(big.NewInt(1000000000), nil) - mockChain.On("GasLimit", ctx, mock.Anything).Return(uint64(21000), nil) + mockChain.On("Node").Return(mockNode).Once() + mockNode.On("GasPrice", ctx).Return(big.NewInt(1000000000), nil) + mockChain.On("Node").Return(mockNode).Once() + mockNode.On("GasLimit", ctx, mock.Anything).Return(uint64(21000), nil) // Mock nonce retrieval - mockChain.On("PendingNonceAt", ctx, fromAddr).Return(uint64(0), nil) + mockChain.On("Node").Return(mockNode).Once() + mockNode.On("PendingNonceAt", ctx, fromAddr).Return(uint64(0), nil) // Mock client access client, err := ethclient.Dial("http://this.domain.definitely.does.not.exist:8545") diff --git a/devnet-sdk/testing/systest/testing_test.go b/devnet-sdk/testing/systest/testing_test.go index 64c407233ae..ebd182ff807 100644 --- a/devnet-sdk/testing/systest/testing_test.go +++ b/devnet-sdk/testing/systest/testing_test.go @@ -78,6 +78,7 @@ func (m *mockTBRecorder) Skipped() bool { return m.skipped } // mockChain implements a minimal system.Chain for testing type mockChain struct{} +func (m *mockChain) Node() system.Node { return nil } func (m *mockChain) RPCURL() string { return "http://localhost:8545" } func (m *mockChain) Client() (*ethclient.Client, error) { return ethclient.Dial(m.RPCURL()) } func (m *mockChain) ID() types.ChainID { return types.ChainID(big.NewInt(1)) } diff --git a/devnet-sdk/testing/testlib/validators/validators_test.go b/devnet-sdk/testing/testlib/validators/validators_test.go index 4d11d52ba90..425022a59cc 100644 --- a/devnet-sdk/testing/testlib/validators/validators_test.go +++ b/devnet-sdk/testing/testlib/validators/validators_test.go @@ -94,6 +94,7 @@ type mockChain struct { wallets []system.Wallet } +func (m *mockChain) Node() system.Node { return nil } func (m *mockChain) RPCURL() string { return "http://localhost:8545" } func (m *mockChain) Client() (*ethclient.Client, error) { return ethclient.Dial(m.RPCURL()) } func (m *mockChain) ID() types.ChainID { return types.ChainID(big.NewInt(1)) } diff --git a/kurtosis-devnet/tests/interop/mocks_test.go b/kurtosis-devnet/tests/interop/mocks_test.go index 3bcbea9370f..a27037ff3b4 100644 --- a/kurtosis-devnet/tests/interop/mocks_test.go +++ b/kurtosis-devnet/tests/interop/mocks_test.go @@ -134,6 +134,7 @@ func newMockFailingChain(id types.ChainID, wallets []system.Wallet) *mockFailing } } +func (m *mockFailingChain) Node() system.Node { return nil } func (m *mockFailingChain) RPCURL() string { return "mock://failing" } func (m *mockFailingChain) Client() (*ethclient.Client, error) { return ethclient.Dial(m.RPCURL()) } func (m *mockFailingChain) ID() types.ChainID { return m.id } From 7a7d6ecbb1f299f6158c823f8a734cb9df2fadf1 Mon Sep 17 00:00:00 2001 From: Dan Pulitano Date: Thu, 6 Mar 2025 12:15:39 -0500 Subject: [PATCH 079/130] op-conductor: Remove block time check for unsafe head progress (#14655) * remove block time check for unsafe head progress * fix test for this case * slightly more succinct * fix sequencer mock to stay healthy --- op-conductor/health/monitor.go | 33 +++-------------------- op-conductor/health/monitor_test.go | 42 +++++++++++++++-------------- 2 files changed, 25 insertions(+), 50 deletions(-) diff --git a/op-conductor/health/monitor.go b/op-conductor/health/monitor.go index d2b28ffab7d..fbd2073c14d 100644 --- a/op-conductor/health/monitor.go +++ b/op-conductor/health/monitor.go @@ -131,10 +131,9 @@ func (hm *SequencerHealthMonitor) loop(ctx context.Context) { } // healthCheck checks the health of the sequencer by 3 criteria: -// 1. unsafe head is progressing per block time -// 2. unsafe head is not too far behind now (measured by unsafeInterval) -// 3. safe head is progressing every configured batch submission interval -// 4. peer count is above the configured minimum +// 1. unsafe head is not too far behind now (measured by unsafeInterval) +// 2. safe head is progressing every configured batch submission interval +// 3. peer count is above the configured minimum func (hm *SequencerHealthMonitor) healthCheck(ctx context.Context) error { status, err := hm.node.SyncStatus(ctx) if err != nil { @@ -144,37 +143,11 @@ func (hm *SequencerHealthMonitor) healthCheck(ctx context.Context) error { now := hm.timeProviderFn() - var timeDiff, blockDiff, expectedBlocks uint64 - if hm.lastSeenUnsafeNum != 0 { - timeDiff = calculateTimeDiff(now, hm.lastSeenUnsafeTime) - blockDiff = status.UnsafeL2.Number - hm.lastSeenUnsafeNum - // how many blocks do we expect to see, minus 1 to account for edge case with respect to time. - // for example, if diff = 2.001s and block time = 2s, expecting to see 1 block could potentially cause sequencer to be considered unhealthy. - expectedBlocks = timeDiff / hm.rollupCfg.BlockTime - if expectedBlocks > 0 { - expectedBlocks-- - } - } if status.UnsafeL2.Number > hm.lastSeenUnsafeNum { hm.lastSeenUnsafeNum = status.UnsafeL2.Number hm.lastSeenUnsafeTime = now } - if timeDiff > hm.rollupCfg.BlockTime && expectedBlocks > blockDiff { - hm.log.Error( - "unsafe head is not progressing as expected", - "now", now, - "unsafe_head_num", status.UnsafeL2.Number, - "last_seen_unsafe_num", hm.lastSeenUnsafeNum, - "last_seen_unsafe_time", hm.lastSeenUnsafeTime, - "unsafe_interval", hm.unsafeInterval, - "time_diff", timeDiff, - "block_diff", blockDiff, - "expected_blocks", expectedBlocks, - ) - return ErrSequencerNotHealthy - } - curUnsafeTimeDiff := calculateTimeDiff(now, status.UnsafeL2.Time) if curUnsafeTimeDiff > hm.unsafeInterval { hm.log.Error( diff --git a/op-conductor/health/monitor_test.go b/op-conductor/health/monitor_test.go index 1a38f7f3a79..cd5d3bdd8c8 100644 --- a/op-conductor/health/monitor_test.go +++ b/op-conductor/health/monitor_test.go @@ -93,8 +93,8 @@ func (s *HealthMonitorTestSuite) TestUnhealthyLowPeerCount() { monitor := s.SetupMonitor(now, 60, 60, rc, pc) healthUpdateCh := monitor.Subscribe() - healthy := <-healthUpdateCh - s.NotNil(healthy) + healthFailure := <-healthUpdateCh + s.NotNil(healthFailure) s.NoError(monitor.Stop()) } @@ -105,21 +105,23 @@ func (s *HealthMonitorTestSuite) TestUnhealthyUnsafeHeadNotProgressing() { rc := &testutils.MockRollupClient{} ss1 := mockSyncStatus(now, 5, now-8, 1) - for i := 0; i < 5; i++ { + unsafeBlocksInterval := 10 + for i := 0; i < unsafeBlocksInterval+2; i++ { rc.ExpectSyncStatus(ss1, nil) } - monitor := s.SetupMonitor(now, 60, 60, rc, nil) + monitor := s.SetupMonitor(now, uint64(unsafeBlocksInterval), 60, rc, nil) healthUpdateCh := monitor.Subscribe() - for i := 0; i < 5; i++ { - healthy := <-healthUpdateCh - if i < 4 { - s.Nil(healthy) + // once the unsafe interval is surpassed, we should expect "unsafe head is falling behind the unsafe interval" + for i := 0; i < unsafeBlocksInterval+2; i++ { + healthFailure := <-healthUpdateCh + if i <= unsafeBlocksInterval { + s.Nil(healthFailure) s.Equal(now, monitor.lastSeenUnsafeTime) s.Equal(uint64(5), monitor.lastSeenUnsafeNum) } else { - s.NotNil(healthy) + s.NotNil(healthFailure) } } @@ -142,11 +144,11 @@ func (s *HealthMonitorTestSuite) TestUnhealthySafeHeadNotProgressing() { healthUpdateCh := monitor.Subscribe() for i := 0; i < 5; i++ { - healthy := <-healthUpdateCh + healthFailure := <-healthUpdateCh if i < 4 { - s.Nil(healthy) + s.Nil(healthFailure) } else { - s.NotNil(healthy) + s.NotNil(healthFailure) } } @@ -181,24 +183,24 @@ func (s *HealthMonitorTestSuite) TestHealthyWithUnsafeLag() { s.Zero(monitor.lastSeenUnsafeTime) // confirm state after first check - healthy := <-healthUpdateCh - s.Nil(healthy) + healthFailure := <-healthUpdateCh + s.Nil(healthFailure) lastSeenUnsafeTime := monitor.lastSeenUnsafeTime s.NotZero(monitor.lastSeenUnsafeTime) s.Equal(uint64(1), monitor.lastSeenUnsafeNum) - healthy = <-healthUpdateCh - s.Nil(healthy) + healthFailure = <-healthUpdateCh + s.Nil(healthFailure) s.Equal(lastSeenUnsafeTime, monitor.lastSeenUnsafeTime) s.Equal(uint64(1), monitor.lastSeenUnsafeNum) - healthy = <-healthUpdateCh - s.Nil(healthy) + healthFailure = <-healthUpdateCh + s.Nil(healthFailure) s.Equal(lastSeenUnsafeTime+2, monitor.lastSeenUnsafeTime) s.Equal(uint64(2), monitor.lastSeenUnsafeNum) - healthy = <-healthUpdateCh - s.Nil(healthy) + healthFailure = <-healthUpdateCh + s.Nil(healthFailure) s.Equal(lastSeenUnsafeTime+2, monitor.lastSeenUnsafeTime) s.Equal(uint64(2), monitor.lastSeenUnsafeNum) From 5be024ccd3b9fe33f39ac7015b6d1768973ccf2b Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Thu, 6 Mar 2025 10:24:08 -0800 Subject: [PATCH 080/130] fix: remove requests processing step (#14623) * fix: remove requests processing step * Keep processing steps, but disable for Isthmus * Update geth version --- go.mod | 4 ++-- go.sum | 10 ++-------- op-program/client/l2/engineapi/block_processor.go | 7 +++++-- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 6750dd3e95a..27ffa5d8807 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb github.com/google/go-cmp v0.6.0 github.com/google/gofuzz v1.2.1-0.20220503160820-4a35382e8fc8 + github.com/google/uuid v1.6.0 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/hashicorp/raft v1.7.2 @@ -118,7 +119,6 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/pprof v0.0.0-20241009165004-a3522334989c // indirect - github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/graph-gophers/graphql-go v1.3.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -256,7 +256,7 @@ require ( rsc.io/tmplfunc v0.0.3 // indirect ) -replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101500.2-rc.2 +replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101500.2-rc.3 //replace github.com/ethereum/go-ethereum => ../op-geth diff --git a/go.sum b/go.sum index e7d7380581f..43c36613eb9 100644 --- a/go.sum +++ b/go.sum @@ -192,14 +192,8 @@ github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/u github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs= -github.com/ethereum-optimism/op-geth v1.101500.2-rc.2 h1:sUPXJ07X4Ud0QCtVWFeuhWe0y9WP5QXEduo8ZRVOeXs= -github.com/ethereum-optimism/op-geth v1.101500.2-rc.2/go.mod h1:OMpyVMMy5zpAAHlR5s/aGbXRk+7cIKczUEIJj54APbY= -github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250205201532-8ff62ada16e1 h1:OqRYDcjiOx5QCLn5krpd3BK1CW+VfSZx7YIa6zY9ePE= -github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250205201532-8ff62ada16e1/go.mod h1:NZ816PzLU1TLv1RdAvYAb6KWOj4Zm5aInT0YpDVml2Y= -github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250226225001-eda352a0be39 h1:PKn3dL9W0FyGC/oynhP6T43P0Xm80W+woW53lRUp2nE= -github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250226225001-eda352a0be39/go.mod h1:NZ816PzLU1TLv1RdAvYAb6KWOj4Zm5aInT0YpDVml2Y= -github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250227173852-b4f7e4022c2e h1:CWdI4k9JxMvWLv6HjWuAFJuuEwMEEwKdW/mOVnrNBD8= -github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250227173852-b4f7e4022c2e/go.mod h1:NZ816PzLU1TLv1RdAvYAb6KWOj4Zm5aInT0YpDVml2Y= +github.com/ethereum-optimism/op-geth v1.101500.2-rc.3 h1:rA/pwy10ep9RDK08x5F1fjkDh8BFUg7OtCAICTo43Kg= +github.com/ethereum-optimism/op-geth v1.101500.2-rc.3/go.mod h1:OMpyVMMy5zpAAHlR5s/aGbXRk+7cIKczUEIJj54APbY= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250228185245-d4bb112dc979 h1:P37l7EFCz5KxE20+yPa3LWiH2Cg+xx6lQ4mOKYCZFPs= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250228185245-d4bb112dc979/go.mod h1:NZ816PzLU1TLv1RdAvYAb6KWOj4Zm5aInT0YpDVml2Y= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= diff --git a/op-program/client/l2/engineapi/block_processor.go b/op-program/client/l2/engineapi/block_processor.go index b88642abce5..260cf9885c3 100644 --- a/op-program/client/l2/engineapi/block_processor.go +++ b/op-program/client/l2/engineapi/block_processor.go @@ -152,15 +152,18 @@ func (b *BlockProcessor) Assemble() (*types.Block, types.Receipts, error) { body := types.Body{ Transactions: b.transactions, } - if b.dataProvider.Config().IsPrague(b.header.Number, b.header.Time) { + + // Processing for EIP-7685 requests would happen here, but is skipped on OP. + // Kept here to minimize diff. + if b.dataProvider.Config().IsPrague(b.header.Number, b.header.Time) && !b.dataProvider.Config().IsIsthmus(b.header.Time) { _requests := [][]byte{} // EIP-6110 - no-op because we just ignore all deposit requests, so no need to parse logs // EIP-7002 core.ProcessWithdrawalQueue(&_requests, b.evm) // EIP-7251 core.ProcessConsolidationQueue(&_requests, b.evm) - } + block, err := b.dataProvider.Engine().FinalizeAndAssemble(b.dataProvider, b.header, b.state, &body, b.receipts) if err != nil { return nil, nil, err From d31590f0cb5bb140263ebe2d6757243ed2315def Mon Sep 17 00:00:00 2001 From: Inphi Date: Thu, 6 Mar 2025 13:34:13 -0500 Subject: [PATCH 081/130] op-e2e: Configurable blocktime setup for interop (#14672) --- op-chain-ops/interopgen/recipe.go | 50 ++++++++++++++++++++------- op-e2e/actions/interop/dsl/dsl.go | 4 +-- op-e2e/actions/interop/dsl/interop.go | 25 +++++++++++--- op-e2e/actions/interop/proofs_test.go | 22 ++++++++++++ op-e2e/faultproofs/util_interop.go | 2 +- op-e2e/interop/interop_recipe_test.go | 2 +- op-e2e/interop/interop_test.go | 2 +- op-node/cmd/interop/interop.go | 7 +++- 8 files changed, 91 insertions(+), 23 deletions(-) diff --git a/op-chain-ops/interopgen/recipe.go b/op-chain-ops/interopgen/recipe.go index 5c1d4ffac94..6cf3265925c 100644 --- a/op-chain-ops/interopgen/recipe.go +++ b/op-chain-ops/interopgen/recipe.go @@ -14,11 +14,13 @@ import ( type InteropDevRecipe struct { L1ChainID uint64 - L2ChainIDs []uint64 + L2s []InteropDevL2Recipe GenesisTimestamp uint64 } -func (r *InteropDevRecipe) Build(addrs devkeys.Addresses) (*WorldConfig, error) { +func (recipe *InteropDevRecipe) Build(addrs devkeys.Addresses) (*WorldConfig, error) { + r := recipe.hydrated() + // L1 genesis l1Cfg := &L1Config{ ChainID: new(big.Int).SetUint64(r.L1ChainID), @@ -88,19 +90,41 @@ func (r *InteropDevRecipe) Build(addrs devkeys.Addresses) (*WorldConfig, error) Superchain: superchainCfg, L2s: make(map[string]*L2Config), } - for _, l2ChainID := range r.L2ChainIDs { - l2Cfg, err := InteropL2DevConfig(r.L1ChainID, l2ChainID, addrs) + for _, l2 := range r.L2s { + l2Cfg, err := l2.build(r.L1ChainID, addrs) if err != nil { - return nil, fmt.Errorf("failed to generate L2 config for chain %d: %w", l2ChainID, err) + return nil, fmt.Errorf("failed to generate L2 config for chain %d: %w", l2.ChainID, err) } if err := prefundL2Accounts(l1Cfg, l2Cfg, addrs); err != nil { - return nil, fmt.Errorf("failed to prefund addresses on L1 for L2 chain %d: %w", l2ChainID, err) + return nil, fmt.Errorf("failed to prefund addresses on L1 for L2 chain %d: %w", l2.ChainID, err) } - world.L2s[fmt.Sprintf("%d", l2ChainID)] = l2Cfg + world.L2s[fmt.Sprintf("%d", l2.ChainID)] = l2Cfg } return world, nil } +func (r *InteropDevRecipe) hydrated() InteropDevRecipe { + out := InteropDevRecipe{ + L1ChainID: r.L1ChainID, + L2s: make([]InteropDevL2Recipe, len(r.L2s)), + GenesisTimestamp: r.GenesisTimestamp, + } + for i, l := range r.L2s { + out.L2s[i] = l + if l.BlockTime == 0 { + out.L2s[i].BlockTime = defaultBlockTime + } + } + return out +} + +const defaultBlockTime = 2 + +type InteropDevL2Recipe struct { + ChainID uint64 + BlockTime uint64 +} + func prefundL2Accounts(l1Cfg *L1Config, l2Cfg *L2Config, addrs devkeys.Addresses) error { l1Cfg.Prefund[l2Cfg.BatchSenderAddress] = Ether(10_000_000) l1Cfg.Prefund[l2Cfg.Deployer] = Ether(10_000_000) @@ -125,10 +149,10 @@ func prefundL2Accounts(l1Cfg *L1Config, l2Cfg *L2Config, addrs devkeys.Addresses return nil } -func InteropL2DevConfig(l1ChainID, l2ChainID uint64, addrs devkeys.Addresses) (*L2Config, error) { +func (r *InteropDevL2Recipe) build(l1ChainID uint64, addrs devkeys.Addresses) (*L2Config, error) { // Padded chain ID, hex encoded, prefixed with 0xff like inboxes, then 0x02 to signify devnet. - batchInboxAddress := common.HexToAddress(fmt.Sprintf("0xff02%016x", l2ChainID)) - chainOps := devkeys.ChainOperatorKeys(new(big.Int).SetUint64(l2ChainID)) + batchInboxAddress := common.HexToAddress(fmt.Sprintf("0xff02%016x", r.ChainID)) + chainOps := devkeys.ChainOperatorKeys(new(big.Int).SetUint64(r.ChainID)) deployer, err := addrs.Address(chainOps(devkeys.DeployerRole)) if err != nil { @@ -238,8 +262,8 @@ func InteropL2DevConfig(l1ChainID, l2ChainID uint64, addrs devkeys.Addresses) (* }, L2CoreDeployConfig: genesis.L2CoreDeployConfig{ L1ChainID: l1ChainID, - L2ChainID: l2ChainID, - L2BlockTime: 2, + L2ChainID: r.ChainID, + L2BlockTime: r.BlockTime, FinalizationPeriodSeconds: 2, // instant output finalization MaxSequencerDrift: 300, SequencerWindowSize: 200, @@ -262,7 +286,7 @@ func InteropL2DevConfig(l1ChainID, l2ChainID uint64, addrs devkeys.Addresses) (* DisputeMaxClockDuration: 302400, // 3.5 days (input in seconds) } - l2Users := devkeys.ChainUserKeys(new(big.Int).SetUint64(l2ChainID)) + l2Users := devkeys.ChainUserKeys(new(big.Int).SetUint64(r.ChainID)) for i := uint64(0); i < 20; i++ { userAddr, err := addrs.Address(l2Users(i)) if err != nil { diff --git a/op-e2e/actions/interop/dsl/dsl.go b/op-e2e/actions/interop/dsl/dsl.go index 6c5ca36a3c4..cf123c13cde 100644 --- a/op-e2e/actions/interop/dsl/dsl.go +++ b/op-e2e/actions/interop/dsl/dsl.go @@ -62,8 +62,8 @@ type InteropDSL struct { createdUsers uint64 } -func NewInteropDSL(t helpers.Testing) *InteropDSL { - setup := SetupInterop(t) +func NewInteropDSL(t helpers.Testing, opts ...setupOption) *InteropDSL { + setup := SetupInterop(t, opts...) actors := setup.CreateActors() actors.PrepareChainState(t) diff --git a/op-e2e/actions/interop/dsl/interop.go b/op-e2e/actions/interop/dsl/interop.go index 48bdde8e682..dec5aff6300 100644 --- a/op-e2e/actions/interop/dsl/interop.go +++ b/op-e2e/actions/interop/dsl/interop.go @@ -103,15 +103,32 @@ func (actors *InteropActors) PrepareChainState(t helpers.Testing) { // At a 2 second block time, this should be small enough to cover all events buffered in the supervisor event queue. const messageExpiryTime = 120 // 2 minutes -// SetupInterop creates an InteropSetup to instantiate actors on, with 2 L2 chains. -func SetupInterop(t helpers.Testing) *InteropSetup { - logger := testlog.Logger(t, log.LevelDebug) +type setupOption func(*interopgen.InteropDevRecipe) + +func SetBlockTimeForChainA(blockTime uint64) setupOption { + return func(recipe *interopgen.InteropDevRecipe) { + recipe.L2s[0].BlockTime = blockTime + } +} + +func SetBlockTimeForChainB(blockTime uint64) setupOption { + return func(recipe *interopgen.InteropDevRecipe) { + recipe.L2s[1].BlockTime = blockTime + } +} +// SetupInterop creates an InteropSetup to instantiate actors on, with 2 L2 chains. +func SetupInterop(t helpers.Testing, opts ...setupOption) *InteropSetup { recipe := interopgen.InteropDevRecipe{ L1ChainID: 900100, - L2ChainIDs: []uint64{900200, 900201}, + L2s: []interopgen.InteropDevL2Recipe{{ChainID: 900200}, {ChainID: 900201}}, GenesisTimestamp: uint64(time.Now().Unix() + 3), } + for _, opt := range opts { + opt(&recipe) + } + + logger := testlog.Logger(t, log.LevelDebug) hdWallet, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) require.NoError(t, err) worldCfg, err := recipe.Build(hdWallet) diff --git a/op-e2e/actions/interop/proofs_test.go b/op-e2e/actions/interop/proofs_test.go index cc6b88881a8..dc700c8972c 100644 --- a/op-e2e/actions/interop/proofs_test.go +++ b/op-e2e/actions/interop/proofs_test.go @@ -769,6 +769,19 @@ func TestInteropFaultProofsInvalidBlock(gt *testing.T) { runFppAndChallengerTests(gt, system, tests) } +func TestInteropFaultProofs_VariedBlockTimes(gt *testing.T) { + t := helpers.NewDefaultTesting(gt) + system := dsl.NewInteropDSL(t, dsl.SetBlockTimeForChainA(1), dsl.SetBlockTimeForChainB(2)) + actors := system.Actors + + system.AddL2Block(system.Actors.ChainA) + system.AddL2Block(system.Actors.ChainB) + + assertTime(t, actors.ChainA, 1, 1, 0, 0) + assertTime(t, actors.ChainB, 2, 2, 0, 0) + // TODO(#14479): Complete test case +} + func runFppAndChallengerTests(gt *testing.T, system *dsl.InteropDSL, tests []*transitionTest) { for _, test := range tests { test := test @@ -873,6 +886,15 @@ func WithInteropEnabled(t helpers.StatefulTesting, actors *dsl.InteropActors, de } } +func assertTime(t helpers.Testing, chain *dsl.Chain, unsafe, crossUnsafe, localSafe, safe uint64) { + start := chain.L2Genesis.Timestamp + status := chain.Sequencer.SyncStatus() + require.Equal(t, start+unsafe, status.UnsafeL2.Time, "Unsafe") + require.Equal(t, start+crossUnsafe, status.CrossUnsafeL2.Time, "Cross Unsafe") + require.Equal(t, start+localSafe, status.LocalSafeL2.Time, "Local safe") + require.Equal(t, start+safe, status.SafeL2.Time, "Safe") +} + type transitionTest struct { name string agreedClaim []byte diff --git a/op-e2e/faultproofs/util_interop.go b/op-e2e/faultproofs/util_interop.go index 29afb3bc6d0..0f9aa3b7b1f 100644 --- a/op-e2e/faultproofs/util_interop.go +++ b/op-e2e/faultproofs/util_interop.go @@ -21,7 +21,7 @@ func StartInteropFaultDisputeSystem(t *testing.T, opts ...faultDisputeConfigOpts } recipe := interopgen.InteropDevRecipe{ L1ChainID: 900100, - L2ChainIDs: []uint64{900200, 900201}, + L2s: []interopgen.InteropDevL2Recipe{{ChainID: 900200}, {ChainID: 900201}}, GenesisTimestamp: uint64(time.Now().Unix() + 3), // start chain 3 seconds from now } worldResources := interop.WorldResourcePaths{ diff --git a/op-e2e/interop/interop_recipe_test.go b/op-e2e/interop/interop_recipe_test.go index 05a2e056c39..038460668c6 100644 --- a/op-e2e/interop/interop_recipe_test.go +++ b/op-e2e/interop/interop_recipe_test.go @@ -18,7 +18,7 @@ import ( func TestInteropDevRecipe(t *testing.T) { rec := interopgen.InteropDevRecipe{ L1ChainID: 900100, - L2ChainIDs: []uint64{900200, 900201}, + L2s: []interopgen.InteropDevL2Recipe{{ChainID: 900200}, {ChainID: 900201}}, GenesisTimestamp: uint64(1234567), } hd, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) diff --git a/op-e2e/interop/interop_test.go b/op-e2e/interop/interop_test.go index 67013947f27..6ae812f1609 100644 --- a/op-e2e/interop/interop_test.go +++ b/op-e2e/interop/interop_test.go @@ -34,7 +34,7 @@ import ( func setupAndRun(t *testing.T, config SuperSystemConfig, fn func(*testing.T, SuperSystem)) { recipe := interopgen.InteropDevRecipe{ L1ChainID: 900100, - L2ChainIDs: []uint64{900200, 900201}, + L2s: []interopgen.InteropDevL2Recipe{{ChainID: 900200}, {ChainID: 900201}}, GenesisTimestamp: uint64(time.Now().Unix() + 3), // start chain 3 seconds from now } worldResources := WorldResourcePaths{ diff --git a/op-node/cmd/interop/interop.go b/op-node/cmd/interop/interop.go index 3e6f75d530b..7b71940ff81 100644 --- a/op-node/cmd/interop/interop.go +++ b/op-node/cmd/interop/interop.go @@ -96,9 +96,14 @@ var InteropDevSetup = &cli.Command{ logCfg := oplog.ReadCLIConfig(cliCtx) logger := oplog.NewLogger(cliCtx.App.Writer, logCfg) + l2ChainIDs := cliCtx.Uint64Slice(l2ChainIDsFlag.Name) + l2Recipes := make([]interopgen.InteropDevL2Recipe, len(l2ChainIDs)) + for i, id := range l2ChainIDs { + l2Recipes[i] = interopgen.InteropDevL2Recipe{ChainID: id} + } recipe := &interopgen.InteropDevRecipe{ L1ChainID: cliCtx.Uint64(l1ChainIDFlag.Name), - L2ChainIDs: cliCtx.Uint64Slice(l2ChainIDsFlag.Name), + L2s: l2Recipes, GenesisTimestamp: cliCtx.Uint64(timestampFlag.Name), } if recipe.GenesisTimestamp == 0 { From 9092fb37e5284902f36151630647c5a8c390d862 Mon Sep 17 00:00:00 2001 From: George Knee Date: Thu, 6 Mar 2025 19:07:01 +0000 Subject: [PATCH 082/130] all: update op-geth to v1.101503.0 (#14560) * replace vm.AccountRef with common.Address * op-chain-ops: replace prankRef by common.Address * mise: update golangci-lint to 1.64.5 * op-chain-ops: fix prank handeCaller * update to canonical op-geth version --- cannon/mipsevm/testutil/evm.go | 2 +- cannon/mipsevm/testutil/mips.go | 6 +++--- go.mod | 4 ++-- go.sum | 4 ++-- mise.toml | 2 +- op-chain-ops/script/prank.go | 32 ++++-------------------------- op-chain-ops/script/script.go | 7 ++----- op-program/client/l2/fast_canon.go | 2 +- 8 files changed, 16 insertions(+), 43 deletions(-) diff --git a/cannon/mipsevm/testutil/evm.go b/cannon/mipsevm/testutil/evm.go index b986711c2fc..25f05f124ba 100644 --- a/cannon/mipsevm/testutil/evm.go +++ b/cannon/mipsevm/testutil/evm.go @@ -120,7 +120,7 @@ func NewEVMEnv(contracts *ContractMetadata) (*vm.EVM, *state.StateDB) { copy(mipsCtorArgs[12:], contracts.Addresses.Oracle[:]) mipsDeploy := append(bytes.Clone(contracts.Artifacts.MIPS.Bytecode.Object), mipsCtorArgs[:]...) startingGas := uint64(30_000_000) - _, deployedMipsAddr, leftOverGas, err := env.Create(vm.AccountRef(contracts.Addresses.Sender), mipsDeploy, startingGas, common.U2560) + _, deployedMipsAddr, leftOverGas, err := env.Create(contracts.Addresses.Sender, mipsDeploy, startingGas, common.U2560) if err != nil { panic(fmt.Errorf("failed to deploy MIPS contract: %w. took %d gas", err, startingGas-leftOverGas)) } diff --git a/cannon/mipsevm/testutil/mips.go b/cannon/mipsevm/testutil/mips.go index 43de91f0962..3bafc361317 100644 --- a/cannon/mipsevm/testutil/mips.go +++ b/cannon/mipsevm/testutil/mips.go @@ -27,7 +27,7 @@ import ( const maxStepGas = 20_000_000 type MIPSEVM struct { - sender vm.AccountRef + sender common.Address startingGas uint64 env *vm.EVM evmState *state.StateDB @@ -42,7 +42,7 @@ type MIPSEVM struct { func newMIPSEVM(contracts *ContractMetadata, opts ...evmOption) *MIPSEVM { env, evmState := NewEVMEnv(contracts) - sender := vm.AccountRef{0x13, 0x37} + sender := common.Address{0x13, 0x37} startingGas := uint64(maxStepGas) evm := &MIPSEVM{sender, startingGas, env, evmState, contracts.Addresses, nil, contracts.Artifacts, math.MaxUint64, nil, nil} for _, opt := range opts { @@ -264,7 +264,7 @@ func AssertEVMReverts(t *testing.T, state mipsevm.FPVMState, contracts *Contract env, evmState := NewEVMEnv(contracts) env.Config.Tracer = tracer sender := common.Address{0x13, 0x37} - ret, _, err := env.Call(vm.AccountRef(sender), contracts.Addresses.MIPS, input, startingGas, common.U2560) + ret, _, err := env.Call(sender, contracts.Addresses.MIPS, input, startingGas, common.U2560) require.EqualValues(t, err, vm.ErrExecutionReverted) matcher(t, ret) diff --git a/go.mod b/go.mod index 27ffa5d8807..993b5b6cb29 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250228185245-d4bb112dc979 - github.com/ethereum/go-ethereum v1.15.1 + github.com/ethereum/go-ethereum v1.15.3 github.com/fatih/color v1.18.0 github.com/fsnotify/fsnotify v1.8.0 github.com/go-task/slim-sprig/v3 v3.0.0 @@ -256,7 +256,7 @@ require ( rsc.io/tmplfunc v0.0.3 // indirect ) -replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101500.2-rc.3 +replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101503.0-rc.1 //replace github.com/ethereum/go-ethereum => ../op-geth diff --git a/go.sum b/go.sum index 43c36613eb9..7f9ad6f0c91 100644 --- a/go.sum +++ b/go.sum @@ -192,8 +192,8 @@ github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/u github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs= -github.com/ethereum-optimism/op-geth v1.101500.2-rc.3 h1:rA/pwy10ep9RDK08x5F1fjkDh8BFUg7OtCAICTo43Kg= -github.com/ethereum-optimism/op-geth v1.101500.2-rc.3/go.mod h1:OMpyVMMy5zpAAHlR5s/aGbXRk+7cIKczUEIJj54APbY= +github.com/ethereum-optimism/op-geth v1.101503.0-rc.1 h1:8qSXpCfLvLIPlkmwO4J+J9chi9NgZlhRckVJEjxS5Lg= +github.com/ethereum-optimism/op-geth v1.101503.0-rc.1/go.mod h1:QUo3fn+45vWqJWzJW+rIzRHUV7NmhhHLPdI87mAn1M8= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250228185245-d4bb112dc979 h1:P37l7EFCz5KxE20+yPa3LWiH2Cg+xx6lQ4mOKYCZFPs= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250228185245-d4bb112dc979/go.mod h1:NZ816PzLU1TLv1RdAvYAb6KWOj4Zm5aInT0YpDVml2Y= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= diff --git a/mise.toml b/mise.toml index 11901cab175..ca63c38d44c 100644 --- a/mise.toml +++ b/mise.toml @@ -18,7 +18,7 @@ just = "1.37.0" "go:github.com/ethereum/go-ethereum/cmd/abigen" = "1.10.25" "go:gotest.tools/gotestsum" = "1.12.0" "go:github.com/vektra/mockery/v2" = "2.46.0" -"go:github.com/golangci/golangci-lint/cmd/golangci-lint" = "1.61.0" +"go:github.com/golangci/golangci-lint/cmd/golangci-lint" = "1.64.5" # Python dependencies "pipx:slither-analyzer" = "0.10.2" diff --git a/op-chain-ops/script/prank.go b/op-chain-ops/script/prank.go index cdea026063a..2d4fda75b7d 100644 --- a/op-chain-ops/script/prank.go +++ b/op-chain-ops/script/prank.go @@ -33,41 +33,17 @@ type Prank struct { Broadcast bool } -// prankRef implements the vm.ContractRef interface, to mock a caller. -type prankRef struct { - prank common.Address - ref vm.ContractRef -} - -var _ vm.ContractRef = (*prankRef)(nil) - -func (p *prankRef) Address() common.Address { - return p.prank -} - -// Value returns the value send into this contract context. -// The delegate call tracer implicitly relies on this being implemented on ContractRef -func (p *prankRef) Value() *uint256.Int { - return p.ref.(interface{ Value() *uint256.Int }).Value() -} - -func (h *Host) handleCaller(caller vm.ContractRef) vm.ContractRef { +func (h *Host) handleCaller(caller common.Address) common.Address { // apply prank, if top call-frame had set up a prank if len(h.callStack) > 0 { parentCallFrame := h.callStack[len(h.callStack)-1] - if parentCallFrame.Prank != nil && caller.Address() != addresses.VMAddr { // pranks do not apply to the cheatcode precompile + if parentCallFrame.Prank != nil && caller != addresses.VMAddr { // pranks do not apply to the cheatcode precompile if parentCallFrame.Prank.Broadcast && parentCallFrame.LastOp == vm.CREATE2 && h.useCreate2Deployer { - return &prankRef{ - prank: DeterministicDeployerAddress, - ref: caller, - } + return DeterministicDeployerAddress } if parentCallFrame.Prank.Sender != nil { - return &prankRef{ - prank: *parentCallFrame.Prank.Sender, - ref: caller, - } + return *parentCallFrame.Prank.Sender } if parentCallFrame.Prank.Origin != nil { h.env.TxContext.Origin = *parentCallFrame.Prank.Origin diff --git a/op-chain-ops/script/script.go b/op-chain-ops/script/script.go index 65523ccbd27..e9553ff4172 100644 --- a/op-chain-ops/script/script.go +++ b/op-chain-ops/script/script.go @@ -346,7 +346,7 @@ func (h *Host) prelude(from common.Address, to *common.Address) { // Call calls a contract in the EVM. The state changes persist. func (h *Host) Call(from common.Address, to common.Address, input []byte, gas uint64, value *uint256.Int) (returnData []byte, leftOverGas uint64, err error) { h.prelude(from, &to) - return h.env.Call(vm.AccountRef(from), to, input, gas, value) + return h.env.Call(from, to, input, gas, value) } // LoadContract loads the bytecode of a contract, and deploys it with regular CREATE. @@ -386,7 +386,7 @@ func (h *Host) RememberArtifact(addr common.Address, artifact *foundry.Artifact, // This create function helps deploy contracts quickly for scripting etc. func (h *Host) Create(from common.Address, initCode []byte) (common.Address, error) { h.prelude(from, nil) - ret, addr, _, err := h.env.Create(vm.AccountRef(from), + ret, addr, _, err := h.env.Create(from, initCode, DefaultFoundryGasLimit, uint256.NewInt(0)) if err != nil { retStr := fmt.Sprintf("%x", ret) @@ -790,9 +790,6 @@ func (h *Host) LogCallStack() { for _, cf := range h.callStack { callsite := "" srcMap, ok := h.srcMaps[cf.Ctx.Address()] - if !ok && cf.Ctx.Contract.CodeAddr != nil { // if delegate-call, we might know the implementation code. - srcMap, ok = h.srcMaps[*cf.Ctx.Contract.CodeAddr] - } if ok { callsite = srcMap.FormattedInfo(cf.LastPC) if callsite == "unknown:0:0" && len(cf.LastJumps) > 0 { diff --git a/op-program/client/l2/fast_canon.go b/op-program/client/l2/fast_canon.go index 4b40786dd77..52fe997d277 100644 --- a/op-program/client/l2/fast_canon.go +++ b/op-program/client/l2/fast_canon.go @@ -118,7 +118,7 @@ func (o *FastCanonicalBlockHeaderOracle) getHistoricalBlockHash(head *types.Head context := core.NewEVMBlockContext(head, o.ctx, nil, o.config, statedb) vmenv := vm.NewEVM(context, statedb, o.config, vm.Config{}) - var caller vm.AccountRef // can be anything as long as it's not the system contract + var caller common.Address // can be anything as long as it's not the system contract gas := uint64(1000000) var input [32]byte binary.BigEndian.PutUint64(input[24:], n) From 61272aa94a932508fdda9f1c02d6d6d130330320 Mon Sep 17 00:00:00 2001 From: Maurelian Date: Thu, 6 Mar 2025 14:51:06 -0500 Subject: [PATCH 083/130] ct docs: update release process without suffixes in contract semvers (#14682) * ct docs: update release process without suffixes in contract semvers * ct docs: discourage code freeze on trunk * ct docs: update the additional release candidate process * ct docs: precribe a method for avoiding semver collisions --- .../book/src/policies/versioning.md | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/packages/contracts-bedrock/book/src/policies/versioning.md b/packages/contracts-bedrock/book/src/policies/versioning.md index c1272aaa7fe..9665f35d0e1 100644 --- a/packages/contracts-bedrock/book/src/policies/versioning.md +++ b/packages/contracts-bedrock/book/src/policies/versioning.md @@ -92,29 +92,38 @@ For example, the [Fault Proofs governance proposal](https://gov.optimism.io/t/up To accommodate this, once contract changes are ready for governance approval, the release flow is: -- On the `develop` branch, bump the version of all contracts to be included in this release to their respective `X.Y.Z-rc.n`. The `X.Y.Z` here refers to the contract-specific versions, so it differs per-contract. The `-rc.n` begins as `-rc.1` for all contracts. - - Any `-beta.n` and `+feature-name` identifiers are removed at this point. - - Contracts that are not included as part of this release are left untouched. -- Branch off of `develop` and create a branch named `proposal/op-contracts/vX.Y.Z`. Here, `X.Y.Z` is the new version of the monorepo release. - - Using the `proposal/` prefix signals that this branch is for a governance proposal, and intentionally does not convey whether or not the proposal has passed. -- Open a PR into the `proposal/op-contracts/vX.Y.Z` branch that removes the `-rc.1` suffixes from all contracts, and merge it into the `proposal/op-contracts/vX.Y.Z` branch. - - After merge, the new commit on the `proposal/op-contracts/vX.Y.Z` branch is the commit hash that will be tagged as `op-contracts/vX.Y.Z-rc.1`, used to deploy the contracts, and proposed to governance. - - Sometimes additional release candidates are needed before proposal—see [Additional Release Candidates](#additional-release-candidates) for more information on this flow. -- Once the governance approval is posted, any lock on contracts on `develop` is released. -- Once governance approves the proposal: - - Create the official `op-contracts/vX.Y.Z` off of this `proposal/op-contracts/vX.Y.Z` branch. It should be at the same commit as the most recent release candidate. - - Merge the proposal branch into `develop` and set the version of all contracts to the appropriate `X.Y.Z` after considering any changes made to `develop` since the release candidate was created. - - See [Merging Back to Develop After Governance Approval](#merging-back-to-develop-after-governance-approval) for more information on how to choose the resulting contract versions when merging back into `develop`. +1. Go to https://github.com/ethereum-optimism/optimism/releases/new +2. Enter the release title as `op-contracts/vX.Y.Z-rc.1` +3. In the "choose a tag" dropdown, enter the same `op-contracts/vX.Y.Z-rc.1` and click the "Create new tag" option that shows up +4. Populate the release notes. +5. Check "set as pre-release" since it's not yet governance approved +6. Uncheck "Set as the latest release" and "Create a discussion for this release". +7. Click publish release. +8. After governance vote passes, edit the relase to uncheck "set as pre-release", and remove the `-rc.1` tag. + +Although the tools exist to apply a [code freeze](./code-freezes.md) to specific contracts, this is +discouraged. If a change is required to a release candidate after it has been tagged, the +[Additional Release Candidates](#additional-release-candidates) for more information on this flow. ### Additional Release Candidates -Sometimes additional release candidate versions are needed. -The process for this is: +Sometimes additional release candidate versions are needed, in that case, the follow process should be used. +This process is designed to (1) ensures fixes are made on both the release and the trunk branch + and (2) avoids the need to stop development efforts on the trunk branch. -- Make the fixes on `develop`. Increment the `-rc.n` qualifier for the changed contracts only. -- Open a PR into the `proposal/op-contracts/vX.Y.Z` branch that incorporates the changes from `develop`. -- Open another PR to remove the new `-rc.2` version identifiers from the changed contracts. Tag the resulting commit on the proposal branch as `op-contracts/vX.Y.Z-rc.2`. -- This flow (1) ensures develop stays up to date during the release process, (2) mitigates the risk of forgetting to merge the release back into the develop branch, and (3) mitigates the risk of the merge into develop removing the required `-rc.n` version that is needed until the release is approved. + +1. Make the fixes on `develop`. *For whatever the normal semver level increment should be, bump that value by 5.* +2. Create a new release branch, named `proposal/op-contracts/X.Y.Z-rc.n+1` off of the rc tag. +3. Cherry pick the fixes from `develop` into that branch. *Bump the semvers as normal, ensuring that the resulting version is less than the one on `develop`. +4. After merging the changes into the new release branch, tag the resulting commit on the proposal branch as `op-contracts/vX.Y.Z-rc.2`. + Create a new release for this tag per the instructions above. + +Note: The reason for the larger semver increment on `develop` is to prevent a collision, wherein a +contract could have the same semver, but different source/bytecode on the two branches. + +For example: if the current version of a contract is `1.1.1` and a minor bump is required (most common for a bug fix), + then the fixed version should become `1.8.0` on `develop`. Then on the release branch is should become + `1.2.0`. ### Merging Back to Develop After Governance Approval From 17a51ed19001ed4308fd8f2eab21d818cd097079 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Thu, 6 Mar 2025 15:29:22 -0500 Subject: [PATCH 084/130] Fix broken link in versioning.md (#14688) * Fix broken link * Fix again :) * Fix another broken link --- packages/contracts-bedrock/book/src/policies/versioning.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contracts-bedrock/book/src/policies/versioning.md b/packages/contracts-bedrock/book/src/policies/versioning.md index 9665f35d0e1..599996e5ea9 100644 --- a/packages/contracts-bedrock/book/src/policies/versioning.md +++ b/packages/contracts-bedrock/book/src/policies/versioning.md @@ -5,7 +5,7 @@ However, there are some changes to accommodate the unique nature of smart contra There are five parts to the versioning and release process: -- [Semver Rules](#semver-rules): Follows the rules defined in the [style guide](contributing/STYLE_GUIDE.mdLE_GUIDE.md#versioning) for when to bump major, minor, and patch versions in individual contracts. +- [Semver Rules](#semver-rules): Follows the rules defined in the [style guide](../contributing/style-guide.md#versioning) for when to bump major, minor, and patch versions in individual contracts. - [Individual Contract Versioning](#individual-contract-versioning): The versioning scheme for individual contracts and includes beta, release candidate, and feature tags. - [Monorepo Contracts Release Versioning](#monorepo-contracts-release-versioning): The versioning scheme for monorepo smart contract releases. - [Release Process](#release-process): The process for deploying contracts, creating a governance proposal, and the required associated releases. @@ -18,7 +18,7 @@ There are five parts to the versioning and release process: ## Semver Rules -Version increments follow the [style guide rules](contributing/STYLE_GUIDE.mdLE_GUIDE.md#versioning) for when to bump major, minor, and patch versions in individual contracts: +Version increments follow the [style guide rules](../contributing/style-guide.md#versioning) for when to bump major, minor, and patch versions in individual contracts: > - `patch` releases are to be used only for changes that do NOT modify contract bytecode (such as updating comments). > - `minor` releases are to be used for changes that modify bytecode OR changes that expand the contract ABI provided that these changes do NOT break the existing interface. From 13862fe3afa20cde7b76edfa1ab1f6b6d936b53d Mon Sep 17 00:00:00 2001 From: zhiqiangxu <652732310@qq.com> Date: Fri, 7 Mar 2025 06:16:15 +0800 Subject: [PATCH 085/130] op-node: continue sequencing when `L1TemporaryErrorEvent` happens (#14062) * continue sequencing when L1TemporaryErrorEvent happens * add TestSequencerL1TemporaryErrorEvent * address comment * fix test --- op-node/rollup/sequencing/sequencer.go | 2 + op-node/rollup/sequencing/sequencer_test.go | 54 +++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/op-node/rollup/sequencing/sequencer.go b/op-node/rollup/sequencing/sequencer.go index 9e6a7e96c37..d66bcbad022 100644 --- a/op-node/rollup/sequencing/sequencer.go +++ b/op-node/rollup/sequencing/sequencer.go @@ -491,6 +491,8 @@ func (d *Sequencer) startBuildingBlock() { // Figure out which L1 origin block we're going to be building on top of. l1Origin, err := d.l1OriginSelector.FindL1Origin(ctx, l2Head) if err != nil { + d.nextAction = d.timeNow().Add(time.Second) + d.nextActionOK = d.active.Load() d.log.Error("Error finding next L1 Origin", "err", err) d.emitter.Emit(rollup.L1TemporaryErrorEvent{Err: err}) return diff --git a/op-node/rollup/sequencing/sequencer_test.go b/op-node/rollup/sequencing/sequencer_test.go index 9c4e77ee725..094ed0b09c0 100644 --- a/op-node/rollup/sequencing/sequencer_test.go +++ b/op-node/rollup/sequencing/sequencer_test.go @@ -3,6 +3,7 @@ package sequencing import ( "context" "encoding/binary" + "fmt" "math/rand" // nosemgrep "testing" "time" @@ -613,6 +614,59 @@ func TestSequencerBuild(t *testing.T) { require.Equal(t, testClock.Now(), nextTime, "start asap on the next block") } +func TestSequencerL1TemporaryErrorEvent(t *testing.T) { + logger := testlog.Logger(t, log.LevelError) + seq, deps := createSequencer(logger) + testClock := clock.NewSimpleClock() + seq.timeNow = testClock.Now + testClock.SetTime(30000) + emitter := &testutils.MockEmitter{} + seq.AttachEmitter(emitter) + + // Init will request a forkchoice update + emitter.ExpectOnce(engine.ForkchoiceRequestEvent{}) + require.NoError(t, seq.Init(context.Background(), true)) + emitter.AssertExpectations(t) + require.True(t, seq.Active(), "started in active mode") + + // It will request a forkchoice update, it needs the head before being able to build on top of it + emitter.ExpectOnce(engine.ForkchoiceRequestEvent{}) + seq.OnEvent(SequencerActionEvent{}) + emitter.AssertExpectations(t) + + // Now send the forkchoice data, for the sequencer to learn what to build on top of. + head := eth.L2BlockRef{ + Hash: common.Hash{0x22}, + Number: 100, + L1Origin: eth.BlockID{ + Hash: common.Hash{0x11, 0xa}, + Number: 1000, + }, + Time: uint64(testClock.Now().Unix()), + } + seq.OnEvent(engine.ForkchoiceUpdateEvent{UnsafeL2Head: head}) + emitter.AssertExpectations(t) + + // force FindL1Origin to return an error + deps.l1OriginSelector.l1OriginFn = func(l2Head eth.L2BlockRef) (eth.L1BlockRef, error) { + return eth.L1BlockRef{}, fmt.Errorf("l1OriginFn error") + } + + emitter.ExpectOnceRun(func(ev event.Event) { + _, ok := ev.(rollup.L1TemporaryErrorEvent) + require.True(t, ok) + }) + + sealTargetTime1, ok1 := seq.NextAction() + seq.OnEvent(SequencerActionEvent{}) + emitter.AssertExpectations(t) + + // FindL1Origin error will updating d.nextAction + sealTargetTime2, ok2 := seq.NextAction() + + require.True(t, ok1 == ok2 && sealTargetTime2.After(sealTargetTime1)) +} + type sequencerTestDeps struct { cfg *rollup.Config attribBuilder *FakeAttributesBuilder From 050cb1a17e2d7b56d623dd20dfd0a60f19f8b802 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Fri, 7 Mar 2025 08:37:38 +1000 Subject: [PATCH 086/130] op-challenger: Add config option to set dependency set config (#14666) * op-challenger: Add config option to set dependency set config * Fix expected error message --- op-challenger/cmd/main_test.go | 279 +++++++++++++----- op-challenger/config/config.go | 5 + op-challenger/config/config_test.go | 39 ++- op-challenger/flags/flags.go | 29 +- op-challenger/game/fault/trace/vm/executor.go | 1 + 5 files changed, 271 insertions(+), 82 deletions(-) diff --git a/op-challenger/cmd/main_test.go b/op-challenger/cmd/main_test.go index a7d20781fe7..c2886778d30 100644 --- a/op-challenger/cmd/main_test.go +++ b/op-challenger/cmd/main_test.go @@ -620,6 +620,196 @@ func TestAlphabetRequiredArgs(t *testing.T) { }) } +func TestCannonCustomConfigArgs(t *testing.T) { + for _, traceType := range []types.TraceType{types.TraceTypeCannon, types.TraceTypePermissioned} { + traceType := traceType + + t.Run(fmt.Sprintf("TestRequireEitherCannonNetworkOrRollupAndGenesis-%v", traceType), func(t *testing.T) { + verifyArgsInvalid( + t, + "flag network or rollup-config/cannon-rollup-config and l2-genesis/cannon-l2-genesis is required", + addRequiredArgsExcept(traceType, "--network")) + verifyArgsInvalid( + t, + "flag network or rollup-config/cannon-rollup-config and l2-genesis/cannon-l2-genesis is required", + addRequiredArgsExcept(traceType, "--network", "--cannon-rollup-config=rollup.json")) + verifyArgsInvalid( + t, + "flag network or rollup-config/cannon-rollup-config and l2-genesis/cannon-l2-genesis is required", + addRequiredArgsExcept(traceType, "--network", "--cannon-l2-genesis=gensis.json")) + }) + + t.Run(fmt.Sprintf("TestMustNotSpecifyNetworkAndRollup-%v", traceType), func(t *testing.T) { + verifyArgsInvalid( + t, + "flag network can not be used with cannon-rollup-config, l2-genesis or cannon-l2-custom", + addRequiredArgs(traceType, "--cannon-rollup-config=rollup.json")) + }) + + t.Run(fmt.Sprintf("TestMustNotSpecifyNetworkAndRollup-%v", traceType), func(t *testing.T) { + args := requiredArgs(traceType) + delete(args, "--network") + delete(args, "--game-factory-address") + args["--network"] = network + args["--cannon-rollup-config"] = "rollup.json" + args["--cannon-l2-genesis"] = "gensis.json" + args["--cannon-l2-custom"] = "true" + verifyArgsInvalid( + t, + "flag network can not be used with cannon-rollup-config, cannon-l2-genesis or cannon-l2-custom", + toArgList(args)) + }) + + t.Run(fmt.Sprintf("TestNetwork-%v", traceType), func(t *testing.T) { + t.Run("NotRequiredWhenRollupAndGenesIsSpecified", func(t *testing.T) { + configForArgs(t, addRequiredArgsExcept(traceType, "--network", + "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json")) + }) + + t.Run("Valid", func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--network", testNetwork)) + require.Equal(t, []string{testNetwork}, cfg.Cannon.Networks) + }) + }) + + t.Run(fmt.Sprintf("TestSetCannonL2ChainId-%v", traceType), func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", + "--cannon-rollup-config=rollup.json", + "--cannon-l2-genesis=genesis.json", + "--cannon-l2-custom")) + require.True(t, cfg.Cannon.L2Custom) + }) + + t.Run(fmt.Sprintf("TestCannonRollupConfig-%v", traceType), func(t *testing.T) { + t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { + configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-rollup-config")) + }) + + t.Run("Valid", func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json")) + require.Equal(t, []string{"rollup.json"}, cfg.Cannon.RollupConfigPaths) + }) + }) + + t.Run(fmt.Sprintf("TestCannonL2Genesis-%v", traceType), func(t *testing.T) { + t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { + configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-l2-genesis")) + }) + + t.Run("Valid", func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json")) + require.Equal(t, []string{"genesis.json"}, cfg.Cannon.L2GenesisPaths) + }) + }) + } +} + +func TestSuperCannonCustomConfigArgs(t *testing.T) { + for _, traceType := range []types.TraceType{types.TraceTypeSuperCannon, types.TraceTypeSuperPermissioned} { + traceType := traceType + + t.Run(fmt.Sprintf("TestRequireEitherCannonNetworkOrRollupAndGenesisAndDepset-%v", traceType), func(t *testing.T) { + expectedErrorMessage := "flag network or rollup-config/cannon-rollup-config, l2-genesis/cannon-l2-genesis and depset-config/cannon-depset-config is required" + // Missing all + verifyArgsInvalid( + t, + expectedErrorMessage, + addRequiredArgsExcept(traceType, "--network")) + // Missing l2-genesis + verifyArgsInvalid( + t, + expectedErrorMessage, + addRequiredArgsExcept(traceType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-depset-config=depset.json")) + // Missing rollup-config + verifyArgsInvalid( + t, + expectedErrorMessage, + addRequiredArgsExcept(traceType, "--network", "--cannon-l2-genesis=gensis.json", "--cannon-depset-config=depset.json")) + // Missing depset-config + verifyArgsInvalid( + t, + expectedErrorMessage, + addRequiredArgsExcept(traceType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=gensis.json")) + }) + + t.Run(fmt.Sprintf("TestMustNotSpecifyNetworkAndRollup-%v", traceType), func(t *testing.T) { + verifyArgsInvalid( + t, + "flag network can not be used with cannon-rollup-config, l2-genesis or cannon-l2-custom", + addRequiredArgs(traceType, "--cannon-rollup-config=rollup.json")) + }) + + t.Run(fmt.Sprintf("TestMustNotSpecifyNetworkAndRollup-%v", traceType), func(t *testing.T) { + args := requiredArgs(traceType) + delete(args, "--network") + delete(args, "--game-factory-address") + args["--network"] = network + args["--cannon-rollup-config"] = "rollup.json" + args["--cannon-l2-genesis"] = "gensis.json" + args["--cannon-l2-custom"] = "true" + verifyArgsInvalid( + t, + "flag network can not be used with cannon-rollup-config, cannon-l2-genesis or cannon-l2-custom", + toArgList(args)) + }) + + t.Run(fmt.Sprintf("TestNetwork-%v", traceType), func(t *testing.T) { + t.Run("NotRequiredWhenRollupGenesisAndDepsetIsSpecified", func(t *testing.T) { + configForArgs(t, addRequiredArgsExcept(traceType, "--network", + "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json", "--cannon-depset-config=depset.json")) + }) + + t.Run("Valid", func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--network", testNetwork)) + require.Equal(t, []string{testNetwork}, cfg.Cannon.Networks) + }) + }) + + t.Run(fmt.Sprintf("TestSetCannonL2ChainId-%v", traceType), func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", + "--cannon-rollup-config=rollup.json", + "--cannon-l2-genesis=genesis.json", + "--cannon-depset-config=depset.json", + "--cannon-l2-custom")) + require.True(t, cfg.Cannon.L2Custom) + }) + + t.Run(fmt.Sprintf("TestCannonRollupConfig-%v", traceType), func(t *testing.T) { + t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { + configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-rollup-config")) + }) + + t.Run("Valid", func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", + "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json", "--cannon-depset-config=depset.json")) + require.Equal(t, []string{"rollup.json"}, cfg.Cannon.RollupConfigPaths) + }) + }) + + t.Run(fmt.Sprintf("TestCannonL2Genesis-%v", traceType), func(t *testing.T) { + t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { + configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-l2-genesis")) + }) + + t.Run("Valid", func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json", "--cannon-depset-config=depset.json")) + require.Equal(t, []string{"genesis.json"}, cfg.Cannon.L2GenesisPaths) + }) + }) + + t.Run(fmt.Sprintf("TestCannonDepsetConfig-%v", traceType), func(t *testing.T) { + t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { + configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-depset-config")) + }) + + t.Run("Valid", func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json", "--cannon-depset-config=depset.json")) + require.Equal(t, "depset.json", cfg.Cannon.DepsetConfigPath) + }) + }) + } +} + func TestCannonRequiredArgs(t *testing.T) { for _, traceType := range []types.TraceType{types.TraceTypeCannon, types.TraceTypePermissioned, types.TraceTypeSuperCannon, types.TraceTypeSuperPermissioned} { traceType := traceType @@ -756,84 +946,23 @@ func TestCannonRequiredArgs(t *testing.T) { addRequiredArgs(traceType, "--cannon-info-freq=abc")) }) }) + } +} - t.Run(fmt.Sprintf("TestRequireEitherCannonNetworkOrRollupAndGenesis-%v", traceType), func(t *testing.T) { - verifyArgsInvalid( - t, - "flag network or rollup-config/cannon-rollup-config and l2-genesis/cannon-l2-genesis is required", - addRequiredArgsExcept(traceType, "--network")) - verifyArgsInvalid( - t, - "flag network or rollup-config/cannon-rollup-config and l2-genesis/cannon-l2-genesis is required", - addRequiredArgsExcept(traceType, "--network", "--cannon-rollup-config=rollup.json")) - verifyArgsInvalid( - t, - "flag network or rollup-config/cannon-rollup-config and l2-genesis/cannon-l2-genesis is required", - addRequiredArgsExcept(traceType, "--network", "--cannon-l2-genesis=gensis.json")) - }) - - t.Run(fmt.Sprintf("TestMustNotSpecifyNetworkAndRollup-%v", traceType), func(t *testing.T) { - verifyArgsInvalid( - t, - "flag network can not be used with cannon-rollup-config, l2-genesis or cannon-l2-custom", - addRequiredArgs(traceType, "--cannon-rollup-config=rollup.json")) - }) - - t.Run(fmt.Sprintf("TestMustNotSpecifyNetworkAndRollup-%v", traceType), func(t *testing.T) { - args := requiredArgs(traceType) - delete(args, "--network") - delete(args, "--game-factory-address") - args["--network"] = network - args["--cannon-rollup-config"] = "rollup.json" - args["--cannon-l2-genesis"] = "gensis.json" - args["--cannon-l2-custom"] = "true" - verifyArgsInvalid( - t, - "flag network can not be used with cannon-rollup-config, cannon-l2-genesis or cannon-l2-custom", - toArgList(args)) - }) - - t.Run(fmt.Sprintf("TestNetwork-%v", traceType), func(t *testing.T) { - t.Run("NotRequiredWhenRollupAndGenesIsSpecified", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(traceType, "--network", - "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json")) - }) - - t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--network", testNetwork)) - require.Equal(t, []string{testNetwork}, cfg.Cannon.Networks) - }) - }) - - t.Run(fmt.Sprintf("TestSetCannonL2ChainId-%v", traceType), func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", - "--cannon-rollup-config=rollup.json", - "--cannon-l2-genesis=genesis.json", - "--cannon-l2-custom")) - require.True(t, cfg.Cannon.L2Custom) - }) - - t.Run(fmt.Sprintf("TestCannonRollupConfig-%v", traceType), func(t *testing.T) { - t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-rollup-config")) - }) - - t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json")) - require.Equal(t, []string{"rollup.json"}, cfg.Cannon.RollupConfigPaths) - }) - }) - - t.Run(fmt.Sprintf("TestCannonL2Genesis-%v", traceType), func(t *testing.T) { - t.Run("NotRequiredForAlphabetTrace", func(t *testing.T) { - configForArgs(t, addRequiredArgsExcept(types.TraceTypeAlphabet, "--cannon-l2-genesis")) +func TestDepsetConfig(t *testing.T) { + for _, traceType := range types.TraceTypes { + if traceType == types.TraceTypeSuperCannon || traceType == types.TraceTypeSuperPermissioned { + t.Run("Required-"+traceType.String(), func(t *testing.T) { + verifyArgsInvalid(t, + "flag network or rollup-config/cannon-rollup-config, l2-genesis/cannon-l2-genesis and depset-config/cannon-depset-config is required", + addRequiredArgsExcept(traceType, "--network", "--rollup-config=rollup.json", "--l2-genesis=genesis.json")) }) - - t.Run("Valid", func(t *testing.T) { - cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--cannon-rollup-config=rollup.json", "--cannon-l2-genesis=genesis.json")) - require.Equal(t, []string{"genesis.json"}, cfg.Cannon.L2GenesisPaths) + } else { + t.Run("NotRequired-"+traceType.String(), func(t *testing.T) { + cfg := configForArgs(t, addRequiredArgsExcept(traceType, "--network", "--rollup-config=rollup.json", "--l2-genesis=genesis.json")) + require.Equal(t, "", cfg.Cannon.DepsetConfigPath) }) - }) + } } } diff --git a/op-challenger/config/config.go b/op-challenger/config/config.go index 55695f4d519..ab10aefc99c 100644 --- a/op-challenger/config/config.go +++ b/op-challenger/config/config.go @@ -27,6 +27,7 @@ var ( ErrMissingGameFactoryAddress = errors.New("missing game factory address") ErrMissingCannonSnapshotFreq = errors.New("missing cannon snapshot freq") ErrMissingCannonInfoFreq = errors.New("missing cannon info freq") + ErrMissingDepsetConfig = errors.New("missing network or depset config path") ErrMissingRollupRpc = errors.New("missing rollup rpc url") ErrMissingSupervisorRpc = errors.New("missing supervisor rpc url") @@ -189,6 +190,10 @@ func (c Config) Check() error { if c.SupervisorRPC == "" { return ErrMissingSupervisorRpc } + + if len(c.Cannon.Networks) == 0 && c.Cannon.DepsetConfigPath == "" { + return ErrMissingDepsetConfig + } if err := c.validateBaseCannonOptions(); err != nil { return err } diff --git a/op-challenger/config/config_test.go b/op-challenger/config/config_test.go index d8dd00ec60a..7fea06a129f 100644 --- a/op-challenger/config/config_test.go +++ b/op-challenger/config/config_test.go @@ -44,10 +44,17 @@ var ( validAsteriscKonaAbsolutePreStateBaseURL, _ = url.Parse("http://localhost/bar/") ) -var cannonTraceTypes = []types.TraceType{types.TraceTypeCannon, types.TraceTypePermissioned, types.TraceTypeSuperCannon, types.TraceTypeSuperPermissioned} +var singleCannonTraceTypes = []types.TraceType{types.TraceTypeCannon, types.TraceTypePermissioned} +var superCannonTraceTypes = []types.TraceType{types.TraceTypeSuperCannon, types.TraceTypeSuperPermissioned} +var allCannonTraceTypes []types.TraceType var asteriscTraceTypes = []types.TraceType{types.TraceTypeAsterisc} var asteriscKonaTraceTypes = []types.TraceType{types.TraceTypeAsteriscKona} +func init() { + allCannonTraceTypes = append(allCannonTraceTypes, singleCannonTraceTypes...) + allCannonTraceTypes = append(allCannonTraceTypes, superCannonTraceTypes...) +} + func ensureExists(path string) error { _, err := os.Stat(path) if err == nil { @@ -178,7 +185,7 @@ func TestGameAllowlistNotRequired(t *testing.T) { } func TestCannonRequiredArgs(t *testing.T) { - for _, traceType := range cannonTraceTypes { + for _, traceType := range allCannonTraceTypes { traceType := traceType t.Run(fmt.Sprintf("TestCannonBinRequired-%v", traceType), func(t *testing.T) { @@ -249,6 +256,7 @@ func TestCannonRequiredArgs(t *testing.T) { cfg.Cannon.Networks = nil cfg.Cannon.RollupConfigPaths = nil cfg.Cannon.L2GenesisPaths = []string{"genesis.json"} + cfg.Cannon.DepsetConfigPath = "foo.json" require.ErrorIs(t, cfg.Check(), vm.ErrMissingRollupConfig) }) @@ -257,6 +265,7 @@ func TestCannonRequiredArgs(t *testing.T) { cfg.Cannon.Networks = nil cfg.Cannon.RollupConfigPaths = []string{"foo.json"} cfg.Cannon.L2GenesisPaths = nil + cfg.Cannon.DepsetConfigPath = "foo.json" require.ErrorIs(t, cfg.Check(), vm.ErrMissingL2Genesis) }) @@ -305,6 +314,32 @@ func TestCannonRequiredArgs(t *testing.T) { } } +func TestDepsetConfig(t *testing.T) { + for _, traceType := range superCannonTraceTypes { + traceType := traceType + t.Run(fmt.Sprintf("TestCannonNetworkOrDepsetConfigRequired-%v", traceType), func(t *testing.T) { + cfg := validConfig(t, traceType) + cfg.Cannon.Networks = nil + cfg.Cannon.RollupConfigPaths = []string{"foo.json"} + cfg.Cannon.L2GenesisPaths = []string{"genesis.json"} + cfg.Cannon.DepsetConfigPath = "" + require.ErrorIs(t, cfg.Check(), ErrMissingDepsetConfig) + }) + } + + for _, traceType := range singleCannonTraceTypes { + traceType := traceType + t.Run(fmt.Sprintf("TestDepsetConfigNotRequired-%v", traceType), func(t *testing.T) { + cfg := validConfig(t, traceType) + cfg.Cannon.Networks = nil + cfg.Cannon.RollupConfigPaths = []string{"foo.json"} + cfg.Cannon.L2GenesisPaths = []string{"genesis.json"} + cfg.Cannon.DepsetConfigPath = "" + require.NoError(t, cfg.Check()) + }) + } +} + func TestAsteriscRequiredArgs(t *testing.T) { for _, traceType := range asteriscTraceTypes { traceType := traceType diff --git a/op-challenger/flags/flags.go b/op-challenger/flags/flags.go index 24ff396c199..6a2e7804cbc 100644 --- a/op-challenger/flags/flags.go +++ b/op-challenger/flags/flags.go @@ -138,6 +138,13 @@ var ( EnvVars: envVars, } }) + DepsetConfigFlag = NewVMFlag("depset-config", EnvVarPrefix, faultDisputeVMs, func(name string, envVars []string, traceTypeInfo string) cli.Flag { + return &cli.StringFlag{ + Name: name, + Usage: "Interop dependency set config file " + traceTypeInfo, + EnvVars: envVars, + } + }) CannonL2CustomFlag = &cli.BoolFlag{ Name: "cannon-l2-custom", Usage: "Notify the op-program host that the L2 chain uses custom config to be loaded via the preimage oracle. " + @@ -274,6 +281,7 @@ func init() { optionalFlags = append(optionalFlags, PreStatesURLFlag.Flags()...) optionalFlags = append(optionalFlags, RollupConfigFlag.Flags()...) optionalFlags = append(optionalFlags, L2GenesisFlag.Flags()...) + optionalFlags = append(optionalFlags, DepsetConfigFlag.Flags()...) optionalFlags = append(optionalFlags, txmgr.CLIFlagsWithDefaults(EnvVarPrefix, txmgr.DefaultChallengerFlagValues)...) optionalFlags = append(optionalFlags, opmetrics.CLIFlags(EnvVarPrefix)...) optionalFlags = append(optionalFlags, oppprof.CLIFlags(EnvVarPrefix)...) @@ -292,11 +300,6 @@ func checkOutputProviderFlags(ctx *cli.Context) error { } func CheckCannonBaseFlags(ctx *cli.Context) error { - if !ctx.IsSet(flags.NetworkFlagName) && - !(RollupConfigFlag.IsSet(ctx, types.TraceTypeCannon) && L2GenesisFlag.IsSet(ctx, types.TraceTypeCannon)) { - return fmt.Errorf("flag %v or %v and %v is required", - flags.NetworkFlagName, RollupConfigFlag.EitherFlagName(types.TraceTypeCannon), L2GenesisFlag.EitherFlagName(types.TraceTypeCannon)) - } if ctx.IsSet(flags.NetworkFlagName) && (RollupConfigFlag.IsSet(ctx, types.TraceTypeCannon) || L2GenesisFlag.IsSet(ctx, types.TraceTypeCannon) || ctx.Bool(CannonL2CustomFlag.Name)) { return fmt.Errorf("flag %v can not be used with %v, %v or %v", @@ -322,6 +325,14 @@ func CheckSuperCannonFlags(ctx *cli.Context) error { if !ctx.IsSet(SupervisorRpcFlag.Name) { return fmt.Errorf("flag %v is required", SupervisorRpcFlag.Name) } + if !ctx.IsSet(flags.NetworkFlagName) && + !(RollupConfigFlag.IsSet(ctx, types.TraceTypeCannon) && L2GenesisFlag.IsSet(ctx, types.TraceTypeCannon) && DepsetConfigFlag.IsSet(ctx, types.TraceTypeCannon)) { + return fmt.Errorf("flag %v or %v, %v and %v is required", + flags.NetworkFlagName, + RollupConfigFlag.EitherFlagName(types.TraceTypeCannon), + L2GenesisFlag.EitherFlagName(types.TraceTypeCannon), + DepsetConfigFlag.EitherFlagName(types.TraceTypeCannon)) + } if err := CheckCannonBaseFlags(ctx); err != nil { return err } @@ -332,6 +343,11 @@ func CheckCannonFlags(ctx *cli.Context) error { if err := checkOutputProviderFlags(ctx); err != nil { return err } + if !ctx.IsSet(flags.NetworkFlagName) && + !(RollupConfigFlag.IsSet(ctx, types.TraceTypeCannon) && L2GenesisFlag.IsSet(ctx, types.TraceTypeCannon)) { + return fmt.Errorf("flag %v or %v and %v is required", + flags.NetworkFlagName, RollupConfigFlag.EitherFlagName(types.TraceTypeCannon), L2GenesisFlag.EitherFlagName(types.TraceTypeCannon)) + } if err := CheckCannonBaseFlags(ctx); err != nil { return err } @@ -567,6 +583,7 @@ func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, erro L2Custom: ctx.Bool(CannonL2CustomFlag.Name), RollupConfigPaths: RollupConfigFlag.StringSlice(ctx, types.TraceTypeCannon), L2GenesisPaths: L2GenesisFlag.StringSlice(ctx, types.TraceTypeCannon), + DepsetConfigPath: DepsetConfigFlag.String(ctx, types.TraceTypeCannon), SnapshotFreq: ctx.Uint(CannonSnapshotFreqFlag.Name), InfoFreq: ctx.Uint(CannonInfoFreqFlag.Name), DebugInfo: true, @@ -586,6 +603,7 @@ func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, erro Networks: networks, RollupConfigPaths: RollupConfigFlag.StringSlice(ctx, types.TraceTypeAsterisc), L2GenesisPaths: L2GenesisFlag.StringSlice(ctx, types.TraceTypeAsterisc), + DepsetConfigPath: DepsetConfigFlag.String(ctx, types.TraceTypeAsterisc), SnapshotFreq: ctx.Uint(AsteriscSnapshotFreqFlag.Name), InfoFreq: ctx.Uint(AsteriscInfoFreqFlag.Name), BinarySnapshots: true, @@ -603,6 +621,7 @@ func NewConfigFromCLI(ctx *cli.Context, logger log.Logger) (*config.Config, erro Networks: networks, RollupConfigPaths: RollupConfigFlag.StringSlice(ctx, types.TraceTypeAsteriscKona), L2GenesisPaths: L2GenesisFlag.StringSlice(ctx, types.TraceTypeAsteriscKona), + DepsetConfigPath: DepsetConfigFlag.String(ctx, types.TraceTypeAsteriscKona), SnapshotFreq: ctx.Uint(AsteriscSnapshotFreqFlag.Name), InfoFreq: ctx.Uint(AsteriscInfoFreqFlag.Name), BinarySnapshots: true, diff --git a/op-challenger/game/fault/trace/vm/executor.go b/op-challenger/game/fault/trace/vm/executor.go index cef8bbc429c..c6339a6a723 100644 --- a/op-challenger/game/fault/trace/vm/executor.go +++ b/op-challenger/game/fault/trace/vm/executor.go @@ -58,6 +58,7 @@ type Config struct { L2Custom bool RollupConfigPaths []string L2GenesisPaths []string + DepsetConfigPath string } func (c *Config) Check() error { From 7d435a7cbd543be537f1ad3634f64a1bc665f819 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Fri, 7 Mar 2025 09:00:59 +1000 Subject: [PATCH 087/130] Add 1.5.0-rc.3 to releases.json (#14695) --- op-program/prestates/releases.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/op-program/prestates/releases.json b/op-program/prestates/releases.json index 1c0aa1d22c2..f66063232b7 100644 --- a/op-program/prestates/releases.json +++ b/op-program/prestates/releases.json @@ -1,4 +1,13 @@ [ + { + "version": "1.5.0-rc.3", + "hash": "0x039970872142f48b189d18dcbc03a3737338d098b0101713dc2d6710f9deb5ef", + "type": "cannon64" + }, + { + "version": "1.5.0-rc.3", + "hash": "0x039facea52b20c605c05efb0a33560a92de7074218998f75bcdf61e8989cb5d9" + }, { "version": "1.5.0-rc.2", "hash": "0x03a7d967025dc434a9ca65154acdb88a7b658147b9b049f0b2f5ecfb9179b0fe", From 596d0897bcc0e2409919e7d302ebed2ee193c222 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Fri, 7 Mar 2025 09:59:20 +1000 Subject: [PATCH 088/130] ci: Enforce correct formatting for TODOs. (#14639) Fix the few TODOs that didn't correctly reference issues. No requests to github are made when not checking closed issues. Ensures this runs fast and reliably in CI pre-merge. Closed issues are then checked with a scheduled job. --- .circleci/config.yml | 2 +- ops/scripts/todo-checker.sh | 15 ++++++++++----- .../test/safe/DeputyGuardianModule.t.sol | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f2c78ad5dea..3f21cf2fe24 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -956,7 +956,7 @@ jobs: command: sudo apt-get install -y ripgrep - run: name: Check TODO issues - command: ./ops/scripts/todo-checker.sh --verbose <<#parameters.check_closed>> --check-closed <> + command: ./ops/scripts/todo-checker.sh --verbose --strict <<#parameters.check_closed>> --check-closed <> - notify-failures-on-develop fuzz-golang: diff --git a/ops/scripts/todo-checker.sh b/ops/scripts/todo-checker.sh index a437a7b83f4..27d15ac8ba5 100755 --- a/ops/scripts/todo-checker.sh +++ b/ops/scripts/todo-checker.sh @@ -44,6 +44,7 @@ for arg in "$@"; do case $arg in --strict) FAIL_INVALID_FMT=true + VERBOSE=true shift ;; --verbose) @@ -58,7 +59,7 @@ for arg in "$@"; do done # Use ripgrep to search for the pattern in all files within the repo -todos=$(rg -o --with-filename -i -n -g '!ops/scripts/todo-checker.sh' 'TODO\(([^)]+)\): [^,;]*') +todos=$(rg -o --with-filename -i -n -g '!ops/scripts/todo-checker.sh' -g '!packages/contracts-bedrock/lib' 'TODO\(([^)]+)\): [^,;]*') # Check each TODO comment in the repo IFS=$'\n' # Set Internal Field Separator to newline for iteration @@ -88,14 +89,15 @@ for todo in $todos; do else if $FAIL_INVALID_FMT || $VERBOSE; then echo -e "${YELLOW}[Warning]${NC} Invalid TODO format: $todo" - if $FAIL_INVALID_FMT; then - exit 1 - fi fi ((MISMATCH_COUNT++)) continue fi + # Don't fetch issue status if we aren't checking for closed issues. + if ! $CHECK_CLOSED; then + continue + fi # Use GitHub API to fetch issue details GH_URL_PATH="$REPO_FULL/issues/$ISSUE_NUM" # Grab the status code and response as a two item array [response, status] @@ -164,7 +166,10 @@ if [[ $NOT_FOUND_COUNT -gt 0 ]]; then echo -e "${YELLOW}[Warning]${NC} ${CYAN}$NOT_FOUND_COUNT${NC} TODOs referred to issues that were not found." fi if [[ $MISMATCH_COUNT -gt 0 ]]; then - echo -e "${YELLOW}[Warning]${NC} ${CYAN}$MISMATCH_COUNT${NC} TODOs did not match the expected pattern. Run with ${RED}\`--verbose\`${NC} to show details." + echo -e "${RED}[Error]${NC} ${CYAN}$MISMATCH_COUNT${NC} TODOs did not match the expected pattern. Run with ${RED}\`--verbose\`${NC} to show details." + if $FAIL_INVALID_FMT; then + exit 1 + fi fi if [[ $OPEN_COUNT -gt 0 ]]; then echo -e "${GREEN}[Info]${NC} ${CYAN}$OPEN_COUNT${NC} TODOs refer to issues that are still open." diff --git a/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol b/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol index 593173acffe..f17ec2204f7 100644 --- a/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol +++ b/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol @@ -241,7 +241,7 @@ contract DeputyGuardianModule_setRespectedGameType_Test is DeputyGuardianModule_ /// guardian. function testFuzz_setRespectedGameType_succeeds(GameType _gameType) external { // Game type(uint32).max is reserved for setting the respectedGameTypeUpdatedAt timestamp. - // TODO(kelvin): Remove this once we've removed the hack. + // TODO(#14638): Remove this once we've removed the hack. uint32 boundedGameType = uint32(bound(_gameType.raw(), 0, type(uint32).max - 1)); _gameType = GameType.wrap(boundedGameType); From 2c037d05ac579242c096df472dca3170af5759ef Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Fri, 7 Mar 2025 10:38:25 +1000 Subject: [PATCH 089/130] op-challenger: Update op-program executor to handle interop properly (#14667) * op-challenger: Add config option to set dependency set config * Fix expected error message * op-challenger: Update op-program executor to handle interop inputs. --- .../trace/vm/op_program_server_executor.go | 15 ++- .../vm/op_program_server_executor_test.go | 93 ++++++++++++++++--- 2 files changed, 92 insertions(+), 16 deletions(-) diff --git a/op-challenger/game/fault/trace/vm/op_program_server_executor.go b/op-challenger/game/fault/trace/vm/op_program_server_executor.go index e08eb12207f..c79a1bfcaf9 100644 --- a/op-challenger/game/fault/trace/vm/op_program_server_executor.go +++ b/op-challenger/game/fault/trace/vm/op_program_server_executor.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) @@ -26,11 +27,21 @@ func (s *OpProgramServerExecutor) OracleCommand(cfg Config, dataDir string, inpu "--l2", strings.Join(cfg.L2s, ","), "--datadir", dataDir, "--l1.head", inputs.L1Head.Hex(), - "--l2.head", inputs.L2Head.Hex(), - "--l2.outputroot", inputs.L2OutputRoot.Hex(), "--l2.claim", inputs.L2Claim.Hex(), "--l2.blocknumber", inputs.L2BlockNumber.Text(10), } + if inputs.L2Head != (common.Hash{}) { + args = append(args, "--l2.head", inputs.L2Head.Hex()) + } + if inputs.L2OutputRoot != (common.Hash{}) { + args = append(args, "--l2.outputroot", inputs.L2OutputRoot.Hex()) + } + if len(inputs.AgreedPreState) > 0 { + args = append(args, "--l2.agreed-prestate", common.Bytes2Hex(inputs.AgreedPreState)) + } + if cfg.DepsetConfigPath != "" { + args = append(args, "--depset.config", cfg.DepsetConfigPath) + } if len(cfg.Networks) != 0 { args = append(args, "--network", strings.Join(cfg.Networks, ",")) } diff --git a/op-challenger/game/fault/trace/vm/op_program_server_executor_test.go b/op-challenger/game/fault/trace/vm/op_program_server_executor_test.go index eb5327a1224..c990f03a8bb 100644 --- a/op-challenger/game/fault/trace/vm/op_program_server_executor_test.go +++ b/op-challenger/game/fault/trace/vm/op_program_server_executor_test.go @@ -31,7 +31,7 @@ func TestOpProgramFillHostCommand(t *testing.T) { return pairs } - oracleCommand := func(t *testing.T, lvl slog.Level, configModifier func(c *Config)) map[string]string { + oracleCommand := func(t *testing.T, lvl slog.Level, configModifier func(c *Config, inputs *utils.LocalGameInputs)) map[string]string { cfg := Config{ L1: "http://localhost:8888", L1Beacon: "http://localhost:9000", @@ -45,7 +45,7 @@ func TestOpProgramFillHostCommand(t *testing.T) { L2Claim: common.Hash{0x44}, L2BlockNumber: big.NewInt(3333), } - configModifier(&cfg) + configModifier(&cfg, &inputs) executor := NewOpProgramServerExecutor(testlog.Logger(t, lvl)) args, err := executor.OracleCommand(cfg, dir, inputs) @@ -58,71 +58,69 @@ func TestOpProgramFillHostCommand(t *testing.T) { require.Equal(t, strings.Join(cfg.L2s, ","), pairs["--l2"]) require.Equal(t, dir, pairs["--datadir"]) require.Equal(t, inputs.L1Head.Hex(), pairs["--l1.head"]) - require.Equal(t, inputs.L2Head.Hex(), pairs["--l2.head"]) - require.Equal(t, inputs.L2OutputRoot.Hex(), pairs["--l2.outputroot"]) require.Equal(t, inputs.L2Claim.Hex(), pairs["--l2.claim"]) require.Equal(t, inputs.L2BlockNumber.String(), pairs["--l2.blocknumber"]) return pairs } t.Run("NoExtras", func(t *testing.T) { - pairs := oracleCommand(t, log.LvlInfo, func(c *Config) {}) + pairs := oracleCommand(t, log.LvlInfo, func(c *Config, _ *utils.LocalGameInputs) {}) require.NotContains(t, pairs, "--network") require.NotContains(t, pairs, "--rollup.config") require.NotContains(t, pairs, "--l2.genesis") }) t.Run("WithNetwork", func(t *testing.T) { - pairs := oracleCommand(t, log.LvlInfo, func(c *Config) { + pairs := oracleCommand(t, log.LvlInfo, func(c *Config, _ *utils.LocalGameInputs) { c.Networks = []string{"op-test"} }) require.Equal(t, "op-test", pairs["--network"]) }) t.Run("WithMultipleNetworks", func(t *testing.T) { - pairs := oracleCommand(t, log.LvlInfo, func(c *Config) { + pairs := oracleCommand(t, log.LvlInfo, func(c *Config, _ *utils.LocalGameInputs) { c.Networks = []string{"op-test", "op-other"} }) require.Equal(t, "op-test,op-other", pairs["--network"]) }) t.Run("WithL2Custom", func(t *testing.T) { - pairs := oracleCommand(t, log.LvlInfo, func(c *Config) { + pairs := oracleCommand(t, log.LvlInfo, func(c *Config, _ *utils.LocalGameInputs) { c.L2Custom = true }) require.Equal(t, "true", pairs["--l2.custom"]) }) t.Run("WithRollupConfigPath", func(t *testing.T) { - pairs := oracleCommand(t, log.LvlInfo, func(c *Config) { + pairs := oracleCommand(t, log.LvlInfo, func(c *Config, _ *utils.LocalGameInputs) { c.RollupConfigPaths = []string{"rollup.config.json"} }) require.Equal(t, "rollup.config.json", pairs["--rollup.config"]) }) t.Run("WithMultipleRollupConfigPaths", func(t *testing.T) { - pairs := oracleCommand(t, log.LvlInfo, func(c *Config) { + pairs := oracleCommand(t, log.LvlInfo, func(c *Config, _ *utils.LocalGameInputs) { c.RollupConfigPaths = []string{"rollup.config.json", "rollup2.json"} }) require.Equal(t, "rollup.config.json,rollup2.json", pairs["--rollup.config"]) }) t.Run("WithL2GenesisPath", func(t *testing.T) { - pairs := oracleCommand(t, log.LvlInfo, func(c *Config) { + pairs := oracleCommand(t, log.LvlInfo, func(c *Config, _ *utils.LocalGameInputs) { c.L2GenesisPaths = []string{"genesis.json"} }) require.Equal(t, "genesis.json", pairs["--l2.genesis"]) }) t.Run("WithMultipleL2GenesisPaths", func(t *testing.T) { - pairs := oracleCommand(t, log.LvlInfo, func(c *Config) { + pairs := oracleCommand(t, log.LvlInfo, func(c *Config, _ *utils.LocalGameInputs) { c.L2GenesisPaths = []string{"genesis.json", "genesis2.json"} }) require.Equal(t, "genesis.json,genesis2.json", pairs["--l2.genesis"]) }) t.Run("WithAllExtras", func(t *testing.T) { - pairs := oracleCommand(t, log.LvlInfo, func(c *Config) { + pairs := oracleCommand(t, log.LvlInfo, func(c *Config, _ *utils.LocalGameInputs) { c.Networks = []string{"op-test"} c.RollupConfigPaths = []string{"rollup.config.json"} c.L2GenesisPaths = []string{"genesis.json"} @@ -132,6 +130,73 @@ func TestOpProgramFillHostCommand(t *testing.T) { require.Equal(t, "genesis.json", pairs["--l2.genesis"]) }) + t.Run("WithoutL2Head", func(t *testing.T) { + pairs := oracleCommand(t, log.LvlInfo, func(_ *Config, inputs *utils.LocalGameInputs) { + inputs.L2Head = common.Hash{} + }) + require.NotContains(t, pairs, "--l2.head") + }) + + t.Run("WithL2Head", func(t *testing.T) { + val := common.Hash{0xab} + pairs := oracleCommand(t, log.LvlInfo, func(_ *Config, inputs *utils.LocalGameInputs) { + inputs.L2Head = val + }) + require.Equal(t, val.Hex(), pairs["--l2.head"]) + }) + + t.Run("WithoutL2OutputRoot", func(t *testing.T) { + pairs := oracleCommand(t, log.LvlInfo, func(_ *Config, inputs *utils.LocalGameInputs) { + inputs.L2OutputRoot = common.Hash{} + }) + require.NotContains(t, pairs, "--l2.outputroot") + }) + + t.Run("WithL2OutputRoot", func(t *testing.T) { + val := common.Hash{0xab} + pairs := oracleCommand(t, log.LvlInfo, func(_ *Config, inputs *utils.LocalGameInputs) { + inputs.L2OutputRoot = val + }) + require.Equal(t, val.Hex(), pairs["--l2.outputroot"]) + }) + + t.Run("NilAgreedPrestate", func(t *testing.T) { + pairs := oracleCommand(t, log.LvlInfo, func(_ *Config, inputs *utils.LocalGameInputs) { + inputs.AgreedPreState = nil + }) + require.NotContains(t, pairs, "--l2.agreed-prestate") + }) + + t.Run("EmptyAgreedPrestate", func(t *testing.T) { + pairs := oracleCommand(t, log.LvlInfo, func(_ *Config, inputs *utils.LocalGameInputs) { + inputs.AgreedPreState = []byte{} + }) + require.NotContains(t, pairs, "--l2.agreed-prestate") + }) + + t.Run("WithAgreedPrestate", func(t *testing.T) { + val := []byte{1, 6, 53, 42} + pairs := oracleCommand(t, log.LvlInfo, func(_ *Config, inputs *utils.LocalGameInputs) { + inputs.AgreedPreState = val + }) + require.Equal(t, common.Bytes2Hex(val), pairs["--l2.agreed-prestate"]) + }) + + t.Run("WithouDepsetConfig", func(t *testing.T) { + pairs := oracleCommand(t, log.LvlInfo, func(c *Config, _ *utils.LocalGameInputs) { + c.DepsetConfigPath = "" + }) + require.NotContains(t, pairs, "--depset.config") + }) + + t.Run("WithL2OutputRoot", func(t *testing.T) { + val := "depset.json" + pairs := oracleCommand(t, log.LvlInfo, func(c *Config, _ *utils.LocalGameInputs) { + c.DepsetConfigPath = val + }) + require.Equal(t, val, pairs["--depset.config"]) + }) + logTests := []struct { level slog.Level arg string @@ -146,7 +211,7 @@ func TestOpProgramFillHostCommand(t *testing.T) { for _, logTest := range logTests { logTest := logTest t.Run(fmt.Sprintf("LogLevel-%v", logTest.arg), func(t *testing.T) { - pairs := oracleCommand(t, logTest.level, func(c *Config) {}) + pairs := oracleCommand(t, logTest.level, func(c *Config, _ *utils.LocalGameInputs) {}) require.Equal(t, pairs["--log.level"], logTest.arg) }) } From 7cab3e79be13cde941f863819c0f26517c4cefff Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Fri, 7 Mar 2025 13:49:50 +1000 Subject: [PATCH 090/130] Add ToSuper() method to convert super reponse to Super instance. (#14670) * Add ToSuper() method to convert super reponse to Super instance. Fix op-supervisor so it includes the Version in responses. * op-proposer: Only enforce supported version of output roots on rollup source. * op-challenger: Fix tests --- .../game/fault/trace/super/convert.go | 12 --- .../game/fault/trace/super/convert_test.go | 60 -------------- .../game/fault/trace/super/prestate.go | 2 +- .../game/fault/trace/super/prestate_test.go | 4 +- .../game/fault/trace/super/provider.go | 13 +++- .../game/fault/trace/super/provider_test.go | 15 +++- .../fault/trace/super/split_adapter_test.go | 22 ++++-- op-proposer/proposer/abi_test.go | 2 - op-proposer/proposer/driver.go | 6 +- op-proposer/proposer/driver_test.go | 2 +- op-proposer/proposer/source/source.go | 1 - op-proposer/proposer/source/source_rollup.go | 8 +- .../proposer/source/source_supervisor.go | 1 - .../proposer/source/source_supervisor_test.go | 1 - op-service/eth/super_root.go | 13 ++++ op-service/eth/super_root_test.go | 78 +++++++++++++++++++ op-supervisor/supervisor/backend/backend.go | 6 +- 17 files changed, 146 insertions(+), 100 deletions(-) delete mode 100644 op-challenger/game/fault/trace/super/convert.go delete mode 100644 op-challenger/game/fault/trace/super/convert_test.go diff --git a/op-challenger/game/fault/trace/super/convert.go b/op-challenger/game/fault/trace/super/convert.go deleted file mode 100644 index c0e553b27ce..00000000000 --- a/op-challenger/game/fault/trace/super/convert.go +++ /dev/null @@ -1,12 +0,0 @@ -package super - -import "github.com/ethereum-optimism/optimism/op-service/eth" - -func responseToSuper(prevRoot eth.SuperRootResponse) *eth.SuperV1 { - prevChainOutputs := make([]eth.ChainIDAndOutput, 0, len(prevRoot.Chains)) - for _, chain := range prevRoot.Chains { - prevChainOutputs = append(prevChainOutputs, eth.ChainIDAndOutput{ChainID: chain.ChainID, Output: chain.Canonical}) - } - superV1 := eth.NewSuperV1(prevRoot.Timestamp, prevChainOutputs...) - return superV1 -} diff --git a/op-challenger/game/fault/trace/super/convert_test.go b/op-challenger/game/fault/trace/super/convert_test.go deleted file mode 100644 index 2142c7f98df..00000000000 --- a/op-challenger/game/fault/trace/super/convert_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package super - -import ( - "testing" - - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/stretchr/testify/require" -) - -func TestResponseToSuper(t *testing.T) { - t.Run("SingleChain", func(t *testing.T) { - input := eth.SuperRootResponse{ - Timestamp: 4978924, - SuperRoot: eth.Bytes32{0x65}, - Chains: []eth.ChainRootInfo{ - { - ChainID: eth.ChainID{2987}, - Canonical: eth.Bytes32{0x88}, - Pending: []byte{1, 2, 3, 4, 5}, - }, - }, - } - expected := ð.SuperV1{ - Timestamp: 4978924, - Chains: []eth.ChainIDAndOutput{ - {ChainID: eth.ChainIDFromUInt64(2987), Output: eth.Bytes32{0x88}}, - }, - } - actual := responseToSuper(input) - require.Equal(t, expected, actual) - }) - - t.Run("SortChainsByChainID", func(t *testing.T) { - input := eth.SuperRootResponse{ - Timestamp: 4978924, - SuperRoot: eth.Bytes32{0x65}, - Chains: []eth.ChainRootInfo{ - { - ChainID: eth.ChainID{2987}, - Canonical: eth.Bytes32{0x88}, - Pending: []byte{1, 2, 3, 4, 5}, - }, - { - ChainID: eth.ChainID{100}, - Canonical: eth.Bytes32{0x10}, - Pending: []byte{1, 2, 3, 4, 5}, - }, - }, - } - expected := ð.SuperV1{ - Timestamp: 4978924, - Chains: []eth.ChainIDAndOutput{ - {ChainID: eth.ChainIDFromUInt64(100), Output: eth.Bytes32{0x10}}, - {ChainID: eth.ChainIDFromUInt64(2987), Output: eth.Bytes32{0x88}}, - }, - } - actual := responseToSuper(input) - require.Equal(t, expected, actual) - }) -} diff --git a/op-challenger/game/fault/trace/super/prestate.go b/op-challenger/game/fault/trace/super/prestate.go index 727cdc149a5..5a1f2c4f834 100644 --- a/op-challenger/game/fault/trace/super/prestate.go +++ b/op-challenger/game/fault/trace/super/prestate.go @@ -39,5 +39,5 @@ func (s *SuperRootPrestateProvider) AbsolutePreState(ctx context.Context) (eth.S } else if err != nil { return nil, err } - return responseToSuper(response), nil + return response.ToSuper() } diff --git a/op-challenger/game/fault/trace/super/prestate_test.go b/op-challenger/game/fault/trace/super/prestate_test.go index ba404f332a6..7d918efd692 100644 --- a/op-challenger/game/fault/trace/super/prestate_test.go +++ b/op-challenger/game/fault/trace/super/prestate_test.go @@ -26,6 +26,7 @@ func TestAbsolutePreState(t *testing.T) { response := eth.SuperRootResponse{ Timestamp: 100, SuperRoot: eth.Bytes32{0x11}, + Version: eth.SuperRootVersionV1, Chains: []eth.ChainRootInfo{ { ChainID: eth.ChainID{2987}, @@ -39,7 +40,8 @@ func TestAbsolutePreState(t *testing.T) { }, }, } - expectedPreimage := responseToSuper(response) + expectedPreimage, err := response.ToSuper() + require.NoError(t, err) rootProvider := &stubRootProvider{ rootsByTimestamp: map[uint64]eth.SuperRootResponse{ 100: response, diff --git a/op-challenger/game/fault/trace/super/provider.go b/op-challenger/game/fault/trace/super/provider.go index e38ea6f1cad..cfca0176aaf 100644 --- a/op-challenger/game/fault/trace/super/provider.go +++ b/op-challenger/game/fault/trace/super/provider.go @@ -86,7 +86,11 @@ func (s *SuperTraceProvider) GetPreimageBytes(ctx context.Context, pos types.Pos if root.CrossSafeDerivedFrom.Number > s.l1Head.Number { return InvalidTransition, nil } - return responseToSuper(root).Marshal(), nil + super, err := root.ToSuper() + if err != nil { + return nil, fmt.Errorf("failed to create super root at timestamp %v: %w", timestamp, err) + } + return super.Marshal(), nil } // Fetch the super root at the next timestamp since we are part way through the transition to it prevRoot, err := s.rootProvider.SuperRootAtTimestamp(ctx, hexutil.Uint64(timestamp)) @@ -118,9 +122,12 @@ func (s *SuperTraceProvider) GetPreimageBytes(ctx context.Context, pos types.Pos return nil, fmt.Errorf("failed to retrieve safe derived blocks at L1 head %v: %w", s.l1Head, err) } } - superV1 := responseToSuper(prevRoot) + prevSuper, err := prevRoot.ToSuper() + if err != nil { + return nil, fmt.Errorf("failed to create super root at timestamp %v: %w", timestamp, err) + } expectedState := interopTypes.TransitionState{ - SuperRoot: superV1.Marshal(), + SuperRoot: prevSuper.Marshal(), PendingProgress: make([]interopTypes.OptimisticBlock, 0, step), Step: step, } diff --git a/op-challenger/game/fault/trace/super/provider_test.go b/op-challenger/game/fault/trace/super/provider_test.go index 87ee3b115df..4fd59c9716a 100644 --- a/op-challenger/game/fault/trace/super/provider_test.go +++ b/op-challenger/game/fault/trace/super/provider_test.go @@ -34,6 +34,7 @@ func TestGet(t *testing.T) { CrossSafeDerivedFrom: l1Head, Timestamp: poststateTimestamp, SuperRoot: eth.Bytes32{0xaa}, + Version: eth.SuperRootVersionV1, Chains: []eth.ChainRootInfo{ { ChainID: eth.ChainIDFromUInt64(1), @@ -45,7 +46,8 @@ func TestGet(t *testing.T) { stubSupervisor.Add(response) claim, err := provider.Get(context.Background(), types.RootPosition) require.NoError(t, err) - expected := responseToSuper(response) + expected, err := response.ToSuper() + require.NoError(t, err) require.Equal(t, common.Hash(eth.SuperRoot(expected)), claim) }) @@ -55,6 +57,7 @@ func TestGet(t *testing.T) { CrossSafeDerivedFrom: l1Head, Timestamp: prestateTimestamp + 1, SuperRoot: eth.Bytes32{0xaa}, + Version: eth.SuperRootVersionV1, Chains: []eth.ChainRootInfo{ { ChainID: eth.ChainIDFromUInt64(1), @@ -66,7 +69,8 @@ func TestGet(t *testing.T) { stubSupervisor.Add(response) claim, err := provider.Get(context.Background(), types.NewPosition(gameDepth, big.NewInt(StepsPerTimestamp-1))) require.NoError(t, err) - expected := responseToSuper(response) + expected, err := response.ToSuper() + require.NoError(t, err) require.Equal(t, common.Hash(eth.SuperRoot(expected)), claim) }) @@ -85,6 +89,7 @@ func TestGet(t *testing.T) { CrossSafeDerivedFrom: eth.BlockID{Number: l1Head.Number - 10, Hash: common.Hash{0xcc}}, Timestamp: poststateTimestamp, SuperRoot: eth.Bytes32{0xaa}, + Version: eth.SuperRootVersionV1, Chains: []eth.ChainRootInfo{ { ChainID: eth.ChainIDFromUInt64(1), @@ -96,7 +101,8 @@ func TestGet(t *testing.T) { stubSupervisor.Add(response) claim, err := provider.Get(context.Background(), types.RootPosition) require.NoError(t, err) - expected := responseToSuper(response) + expected, err := response.ToSuper() + require.NoError(t, err) require.Equal(t, common.Hash(eth.SuperRoot(expected)), claim) }) @@ -106,6 +112,7 @@ func TestGet(t *testing.T) { CrossSafeDerivedFrom: eth.BlockID{Number: l1Head.Number + 1, Hash: common.Hash{0xaa}}, Timestamp: poststateTimestamp, SuperRoot: eth.Bytes32{0xaa}, + Version: eth.SuperRootVersionV1, Chains: []eth.ChainRootInfo{ { ChainID: eth.ChainIDFromUInt64(1), @@ -369,6 +376,7 @@ func createValidSuperRoots(l1Head eth.BlockID) (superRootData, superRootData) { CrossSafeDerivedFrom: l1Head, Timestamp: prestateTimestamp, SuperRoot: eth.SuperRoot(prevSuper), + Version: eth.SuperRootVersionV1, Chains: []eth.ChainRootInfo{ { ChainID: eth.ChainIDFromUInt64(1), @@ -386,6 +394,7 @@ func createValidSuperRoots(l1Head eth.BlockID) (superRootData, superRootData) { CrossSafeDerivedFrom: l1Head, Timestamp: prestateTimestamp + 1, SuperRoot: eth.SuperRoot(nextSuper), + Version: eth.SuperRootVersionV1, Chains: []eth.ChainRootInfo{ { ChainID: eth.ChainIDFromUInt64(1), diff --git a/op-challenger/game/fault/trace/super/split_adapter_test.go b/op-challenger/game/fault/trace/super/split_adapter_test.go index d4c4e653434..7d1f4ffa35f 100644 --- a/op-challenger/game/fault/trace/super/split_adapter_test.go +++ b/op-challenger/game/fault/trace/super/split_adapter_test.go @@ -26,6 +26,7 @@ func TestSplitAdapter(t *testing.T) { t.Run("FromAbsolutePrestate", func(t *testing.T) { creator, rootProvider, adapter := setupSplitAdapterTest(t, depth, prestateTimestamp, poststateTimestamp) prestateResponse := eth.SuperRootResponse{ + Version: eth.SuperRootVersionV1, Timestamp: prestateTimestamp, } rootProvider.Add(prestateResponse) @@ -35,11 +36,13 @@ func TestSplitAdapter(t *testing.T) { Position: types.NewPosition(depth, big.NewInt(0)), }, } + super, err := prestateResponse.ToSuper() + require.NoError(t, err) expectedClaimInfo := ClaimInfo{ - AgreedPrestate: responseToSuper(prestateResponse).Marshal(), + AgreedPrestate: super.Marshal(), Claim: postClaim.Value, } - _, err := adapter(context.Background(), depth, types.Claim{}, postClaim) + _, err = adapter(context.Background(), depth, types.Claim{}, postClaim) require.ErrorIs(t, err, creatorError) require.Equal(t, split.CreateLocalContext(types.Claim{}, postClaim), creator.localContext) require.Equal(t, expectedClaimInfo, creator.claimInfo) @@ -48,6 +51,7 @@ func TestSplitAdapter(t *testing.T) { t.Run("AfterClaimedBlock", func(t *testing.T) { creator, rootProvider, adapter := setupSplitAdapterTest(t, depth, prestateTimestamp, poststateTimestamp) prestateResponse := eth.SuperRootResponse{ + Version: eth.SuperRootVersionV1, Timestamp: poststateTimestamp, } rootProvider.Add(prestateResponse) @@ -63,11 +67,13 @@ func TestSplitAdapter(t *testing.T) { Position: types.NewPosition(depth, big.NewInt(1_000_000)), }, } + super, err := prestateResponse.ToSuper() + require.NoError(t, err) expectedClaimInfo := ClaimInfo{ - AgreedPrestate: responseToSuper(prestateResponse).Marshal(), + AgreedPrestate: super.Marshal(), Claim: postClaim.Value, } - _, err := adapter(context.Background(), depth, preClaim, postClaim) + _, err = adapter(context.Background(), depth, preClaim, postClaim) require.ErrorIs(t, err, creatorError) require.Equal(t, split.CreateLocalContext(preClaim, postClaim), creator.localContext) require.Equal(t, expectedClaimInfo, creator.claimInfo) @@ -76,10 +82,12 @@ func TestSplitAdapter(t *testing.T) { t.Run("MiddleOfTimestampTransition", func(t *testing.T) { creator, rootProvider, adapter := setupSplitAdapterTest(t, depth, prestateTimestamp, poststateTimestamp) prestateResponse := eth.SuperRootResponse{ + Version: eth.SuperRootVersionV1, Timestamp: prestateTimestamp, } rootProvider.Add(prestateResponse) rootProvider.Add(eth.SuperRootResponse{ + Version: eth.SuperRootVersionV1, Timestamp: prestateTimestamp + 1, }) preClaim := types.Claim{ @@ -94,15 +102,17 @@ func TestSplitAdapter(t *testing.T) { Position: types.NewPosition(depth, big.NewInt(3)), }, } + super, err := prestateResponse.ToSuper() + require.NoError(t, err) expectedPrestate := interopTypes.TransitionState{ - SuperRoot: responseToSuper(prestateResponse).Marshal(), + SuperRoot: super.Marshal(), Step: 3, } expectedClaimInfo := ClaimInfo{ AgreedPrestate: expectedPrestate.Marshal(), Claim: postClaim.Value, } - _, err := adapter(context.Background(), depth, preClaim, postClaim) + _, err = adapter(context.Background(), depth, preClaim, postClaim) require.ErrorIs(t, err, creatorError) require.Equal(t, split.CreateLocalContext(preClaim, postClaim), creator.localContext) require.Equal(t, expectedClaimInfo, creator.claimInfo) diff --git a/op-proposer/proposer/abi_test.go b/op-proposer/proposer/abi_test.go index 91e883def3b..f973e0cfe20 100644 --- a/op-proposer/proposer/abi_test.go +++ b/op-proposer/proposer/abi_test.go @@ -8,7 +8,6 @@ import ( "github.com/ethereum-optimism/optimism/op-proposer/bindings" "github.com/ethereum-optimism/optimism/op-proposer/proposer/source" - "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/testutils" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" @@ -60,7 +59,6 @@ func TestManualABIPacking(t *testing.T) { require.NoError(t, err) proposal := source.Proposal{ - Version: eth.Bytes32{}, Root: testutils.RandomHash(rng), CurrentL1: testutils.RandomBlockID(rng), Legacy: source.LegacyProposalData{ diff --git a/op-proposer/proposer/driver.go b/op-proposer/proposer/driver.go index e22767565a1..1e5e96bf39c 100644 --- a/op-proposer/proposer/driver.go +++ b/op-proposer/proposer/driver.go @@ -26,8 +26,7 @@ import ( ) var ( - supportedL2OutputVersion = eth.Bytes32{} - ErrProposerNotRunning = errors.New("proposer is not running") + ErrProposerNotRunning = errors.New("proposer is not running") ) type L1Client interface { @@ -318,9 +317,6 @@ func (l *L2OutputSubmitter) FetchOutput(ctx context.Context, block uint64) (sour if err != nil { return source.Proposal{}, fmt.Errorf("fetching output at block %d: %w", block, err) } - if output.Version != supportedL2OutputVersion { - return source.Proposal{}, fmt.Errorf("unsupported l2 output version: %v, supported: %v", output.Version, supportedL2OutputVersion) - } if onum := output.SequenceNum; onum != block { // sanity check, e.g. in case of bad RPC caching return source.Proposal{}, fmt.Errorf("output block number %d mismatches requested %d", output.SequenceNum, block) } diff --git a/op-proposer/proposer/driver_test.go b/op-proposer/proposer/driver_test.go index dfc44a6ecf4..1e34910d0af 100644 --- a/op-proposer/proposer/driver_test.go +++ b/op-proposer/proposer/driver_test.go @@ -149,7 +149,7 @@ func TestL2OutputSubmitter_OutputRetry(t *testing.T) { ep.rollupClient.ExpectOutputAtBlock( 42, ð.OutputResponse{ - Version: supportedL2OutputVersion, + Version: eth.OutputVersionV0, BlockRef: eth.L2BlockRef{Number: 42}, Status: ð.SyncStatus{ CurrentL1: eth.L1BlockRef{Hash: common.Hash{}}, diff --git a/op-proposer/proposer/source/source.go b/op-proposer/proposer/source/source.go index a312cdb7aa0..2c4e5e4d810 100644 --- a/op-proposer/proposer/source/source.go +++ b/op-proposer/proposer/source/source.go @@ -8,7 +8,6 @@ import ( ) type Proposal struct { - Version eth.Bytes32 // Root is the proposal hash Root common.Hash // SequenceNum identifies the position in the overall state transition. diff --git a/op-proposer/proposer/source/source_rollup.go b/op-proposer/proposer/source/source_rollup.go index 7fb09f89ea1..0154ee8d4ae 100644 --- a/op-proposer/proposer/source/source_rollup.go +++ b/op-proposer/proposer/source/source_rollup.go @@ -5,9 +5,12 @@ import ( "fmt" "github.com/ethereum-optimism/optimism/op-service/dial" + "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum/go-ethereum/common" ) +var supportedL2OutputVersion = eth.Bytes32{} + type RollupProposalSource struct { provider dial.RollupProvider } @@ -47,8 +50,11 @@ func (r *RollupProposalSource) ProposalAtSequenceNum(ctx context.Context, blockN if err != nil { return Proposal{}, err } + + if output.Version != supportedL2OutputVersion { + return Proposal{}, fmt.Errorf("unsupported l2 output version: %v, supported: %v", output.Version, supportedL2OutputVersion) + } return Proposal{ - Version: output.Version, Root: common.Hash(output.OutputRoot), SequenceNum: output.BlockRef.Number, CurrentL1: output.Status.CurrentL1.ID(), diff --git a/op-proposer/proposer/source/source_supervisor.go b/op-proposer/proposer/source/source_supervisor.go index 27d0ef5c6a1..98053cc9542 100644 --- a/op-proposer/proposer/source/source_supervisor.go +++ b/op-proposer/proposer/source/source_supervisor.go @@ -90,7 +90,6 @@ func (s *SupervisorProposalSource) ProposalAtSequenceNum(ctx context.Context, ti continue } return Proposal{ - Version: eth.Bytes32{output.Version}, Root: common.Hash(output.SuperRoot), SequenceNum: output.Timestamp, CurrentL1: output.CrossSafeDerivedFrom, diff --git a/op-proposer/proposer/source/source_supervisor_test.go b/op-proposer/proposer/source/source_supervisor_test.go index 88648223f31..9e6af590197 100644 --- a/op-proposer/proposer/source/source_supervisor_test.go +++ b/op-proposer/proposer/source/source_supervisor_test.go @@ -157,7 +157,6 @@ func TestSupervisorSource_ProposalAtSequenceNum(t *testing.T) { Chains: nil, } expected := Proposal{ - Version: eth.Bytes32{3}, Root: common.Hash(response.SuperRoot), SequenceNum: response.Timestamp, CurrentL1: response.CrossSafeDerivedFrom, diff --git a/op-service/eth/super_root.go b/op-service/eth/super_root.go index fb80507eda9..dbe80991a85 100644 --- a/op-service/eth/super_root.go +++ b/op-service/eth/super_root.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "encoding/json" "errors" + "fmt" "slices" "github.com/ethereum/go-ethereum/common" @@ -157,6 +158,18 @@ type SuperRootResponse struct { Chains []ChainRootInfo `json:"chains"` } +func (s SuperRootResponse) ToSuper() (Super, error) { + if s.Version != SuperRootVersionV1 { + return nil, fmt.Errorf("%w: %v", ErrInvalidSuperRootVersion, s.Version) + } + prevChainOutputs := make([]ChainIDAndOutput, 0, len(s.Chains)) + for _, chain := range s.Chains { + prevChainOutputs = append(prevChainOutputs, ChainIDAndOutput{ChainID: chain.ChainID, Output: chain.Canonical}) + } + superV1 := NewSuperV1(s.Timestamp, prevChainOutputs...) + return superV1, nil +} + type superRootResponseMarshalling struct { CrossSafeDerivedFrom BlockID `json:"crossSafeDerivedFrom"` Timestamp hexutil.Uint64 `json:"timestamp"` diff --git a/op-service/eth/super_root_test.go b/op-service/eth/super_root_test.go index 16c3726246e..ba873985ba4 100644 --- a/op-service/eth/super_root_test.go +++ b/op-service/eth/super_root_test.go @@ -50,3 +50,81 @@ func TestSuperRootV1Codec(t *testing.T) { require.ErrorIs(t, err, ErrInvalidSuperRoot) }) } + +func TestResponseToSuper(t *testing.T) { + t.Run("SingleChain", func(t *testing.T) { + input := SuperRootResponse{ + Timestamp: 4978924, + SuperRoot: Bytes32{0x65}, + Version: SuperRootVersionV1, + Chains: []ChainRootInfo{ + { + ChainID: ChainID{2987}, + Canonical: Bytes32{0x88}, + Pending: []byte{1, 2, 3, 4, 5}, + }, + }, + } + expected := &SuperV1{ + Timestamp: 4978924, + Chains: []ChainIDAndOutput{ + {ChainID: ChainIDFromUInt64(2987), Output: Bytes32{0x88}}, + }, + } + actual, err := input.ToSuper() + require.NoError(t, err) + require.Equal(t, expected, actual) + }) + + t.Run("SortChainsByChainID", func(t *testing.T) { + input := SuperRootResponse{ + Timestamp: 4978924, + SuperRoot: Bytes32{0x65}, + Version: SuperRootVersionV1, + Chains: []ChainRootInfo{ + { + ChainID: ChainID{2987}, + Canonical: Bytes32{0x88}, + Pending: []byte{1, 2, 3, 4, 5}, + }, + { + ChainID: ChainID{100}, + Canonical: Bytes32{0x10}, + Pending: []byte{1, 2, 3, 4, 5}, + }, + }, + } + expected := &SuperV1{ + Timestamp: 4978924, + Chains: []ChainIDAndOutput{ + {ChainID: ChainIDFromUInt64(100), Output: Bytes32{0x10}}, + {ChainID: ChainIDFromUInt64(2987), Output: Bytes32{0x88}}, + }, + } + actual, err := input.ToSuper() + require.NoError(t, err) + require.Equal(t, expected, actual) + }) + + t.Run("InvalidVersion", func(t *testing.T) { + input := SuperRootResponse{ + Timestamp: 4978924, + SuperRoot: Bytes32{0x65}, + Version: 0, + Chains: []ChainRootInfo{ + { + ChainID: ChainID{2987}, + Canonical: Bytes32{0x88}, + Pending: []byte{1, 2, 3, 4, 5}, + }, + { + ChainID: ChainID{100}, + Canonical: Bytes32{0x10}, + Pending: []byte{1, 2, 3, 4, 5}, + }, + }, + } + _, err := input.ToSuper() + require.ErrorIs(t, err, ErrInvalidSuperRootVersion) + }) +} diff --git a/op-supervisor/supervisor/backend/backend.go b/op-supervisor/supervisor/backend/backend.go index e13ccd4a7d2..33dd3ae0924 100644 --- a/op-supervisor/supervisor/backend/backend.go +++ b/op-supervisor/supervisor/backend/backend.go @@ -662,14 +662,16 @@ func (su *SupervisorBackend) SuperRootAtTimestamp(ctx context.Context, timestamp crossSafeSource = source.ID() } } - superRoot := eth.SuperRoot(ð.SuperV1{ + super := eth.SuperV1{ Timestamp: uint64(timestamp), Chains: superRootChains, - }) + } + superRoot := eth.SuperRoot(&super) return eth.SuperRootResponse{ CrossSafeDerivedFrom: crossSafeSource, Timestamp: uint64(timestamp), SuperRoot: superRoot, + Version: super.Version(), Chains: chainInfos, }, nil } From ffec66814d416f0da14a2052fd33a8848e49096e Mon Sep 17 00:00:00 2001 From: Teddy Knox Date: Fri, 7 Mar 2025 07:21:49 -0500 Subject: [PATCH 091/130] Add devnet-sdk validators for fork configuration (#14668) --- devnet-sdk/descriptors/deployment.go | 20 +- devnet-sdk/kt/fs/fs.go | 61 ++++-- devnet-sdk/kt/fs/fs_test.go | 98 +++++++++ devnet-sdk/system/chain.go | 40 ++-- devnet-sdk/system/chain_test.go | 10 +- devnet-sdk/system/interfaces.go | 3 + devnet-sdk/system/node.go | 9 + devnet-sdk/system/system.go | 10 +- devnet-sdk/system/system_test.go | 4 +- devnet-sdk/system/txbuilder_test.go | 11 + devnet-sdk/system/txprocessor_test.go | 2 +- devnet-sdk/testing/systest/testing_test.go | 8 + .../testing/testlib/validators/forks.go | 120 +++++++++++ .../testlib/validators/validators_test.go | 197 +++++++++++++++++- kurtosis-devnet/pkg/kurtosis/kurtosis.go | 9 +- .../pkg/kurtosis/sources/deployer/deployer.go | 94 +++++++-- .../tests/interop/interop_smoke_test.go | 3 + kurtosis-devnet/tests/interop/mocks_test.go | 10 + 18 files changed, 629 insertions(+), 80 deletions(-) create mode 100644 devnet-sdk/testing/testlib/validators/forks.go diff --git a/devnet-sdk/descriptors/deployment.go b/devnet-sdk/descriptors/deployment.go index 70e38d1bc50..4b27e967ad9 100644 --- a/devnet-sdk/descriptors/deployment.go +++ b/devnet-sdk/descriptors/deployment.go @@ -1,6 +1,9 @@ package descriptors -import "github.com/ethereum-optimism/optimism/devnet-sdk/types" +import ( + "github.com/ethereum-optimism/optimism/devnet-sdk/types" + "github.com/ethereum/go-ethereum/params" +) type PortInfo struct { Host string `json:"host"` @@ -30,13 +33,14 @@ type AddressMap map[string]types.Address // Chain represents a chain (L1 or L2) in a devnet. type Chain struct { - Name string `json:"name"` - ID string `json:"id,omitempty"` - Services ServiceMap `json:"services,omitempty"` - Nodes []Node `json:"nodes"` - Addresses AddressMap `json:"addresses,omitempty"` - Wallets WalletMap `json:"wallets,omitempty"` - JWT string `json:"jwt,omitempty"` + Name string `json:"name"` + ID string `json:"id,omitempty"` + Services ServiceMap `json:"services,omitempty"` + Nodes []Node `json:"nodes"` + Addresses AddressMap `json:"addresses,omitempty"` + Wallets WalletMap `json:"wallets,omitempty"` + JWT string `json:"jwt,omitempty"` + Config *params.ChainConfig `json:"config,omitempty"` } // Wallet represents a wallet with an address and optional private key. diff --git a/devnet-sdk/kt/fs/fs.go b/devnet-sdk/kt/fs/fs.go index e1e4d9d3f5d..d05551ac933 100644 --- a/devnet-sdk/kt/fs/fs.go +++ b/devnet-sdk/kt/fs/fs.go @@ -46,7 +46,8 @@ func NewEnclaveFSWithContext(ctx EnclaveContextIface) *EnclaveFS { } type Artifact struct { - reader *tar.Reader + rawData []byte + reader *tar.Reader } func (fs *EnclaveFS) GetAllArtifactNames(ctx context.Context) ([]string, error) { @@ -69,30 +70,37 @@ func (fs *EnclaveFS) GetArtifact(ctx context.Context, name string) (*Artifact, e return nil, err } + // Store the raw data buffer := bytes.NewBuffer(artifact) zipReader, err := gzip.NewReader(buffer) if err != nil { return nil, err } tarReader := tar.NewReader(zipReader) - return &Artifact{reader: tarReader}, nil + return &Artifact{ + rawData: artifact, + reader: tarReader, + }, nil } -type ArtifactFileWriter struct { - path string - writer io.Writer -} - -func NewArtifactFileWriter(path string, writer io.Writer) *ArtifactFileWriter { - return &ArtifactFileWriter{ - path: path, - writer: writer, +func (a *Artifact) newReader() (*tar.Reader, error) { + buffer := bytes.NewBuffer(a.rawData) + zipReader, err := gzip.NewReader(buffer) + if err != nil { + return nil, err } + return tar.NewReader(zipReader), nil } func (a *Artifact) Download(path string) error { + // Create a new reader for this operation + reader, err := a.newReader() + if err != nil { + return fmt.Errorf("failed to create reader: %w", err) + } + for { - header, err := a.reader.Next() + header, err := reader.Next() if err == io.EOF { return nil } @@ -120,7 +128,7 @@ func (a *Artifact) Download(path string) error { } // Copy contents from tar reader to file - if _, err := io.Copy(f, a.reader); err != nil { + if _, err := io.Copy(f, reader); err != nil { f.Close() return fmt.Errorf("failed to write contents to %s: %w", fpath, err) } @@ -132,6 +140,12 @@ func (a *Artifact) Download(path string) error { } func (a *Artifact) ExtractFiles(writers ...*ArtifactFileWriter) error { + // Create a new reader for this operation + reader, err := a.newReader() + if err != nil { + return fmt.Errorf("failed to create reader: %w", err) + } + paths := make(map[string]io.Writer) for _, writer := range writers { canonicalPath := filepath.Clean(writer.path) @@ -139,10 +153,13 @@ func (a *Artifact) ExtractFiles(writers ...*ArtifactFileWriter) error { } for { - header, err := a.reader.Next() + header, err := reader.Next() if err == io.EOF { break } + if err != nil { + return fmt.Errorf("failed to read tar header: %w", err) + } headerPath := filepath.Clean(header.Name) if _, ok := paths[headerPath]; !ok { @@ -150,9 +167,9 @@ func (a *Artifact) ExtractFiles(writers ...*ArtifactFileWriter) error { } writer := paths[headerPath] - _, err = io.Copy(writer, a.reader) + _, err = io.Copy(writer, reader) if err != nil { - return err + return fmt.Errorf("failed to copy content: %w", err) } } @@ -207,3 +224,15 @@ func NewArtifactFileReader(path string, reader io.Reader) *ArtifactFileReader { reader: reader, } } + +type ArtifactFileWriter struct { + path string + writer io.Writer +} + +func NewArtifactFileWriter(path string, writer io.Writer) *ArtifactFileWriter { + return &ArtifactFileWriter{ + path: path, + writer: writer, + } +} diff --git a/devnet-sdk/kt/fs/fs_test.go b/devnet-sdk/kt/fs/fs_test.go index d202e544b6f..3e801183731 100644 --- a/devnet-sdk/kt/fs/fs_test.go +++ b/devnet-sdk/kt/fs/fs_test.go @@ -220,3 +220,101 @@ func TestPutArtifact(t *testing.T) { }) } } + +func TestMultipleExtractCalls(t *testing.T) { + // Create a test artifact with multiple files + files := map[string]string{ + "file1.txt": "content1", + "file2.txt": "content2", + "dir/file3.txt": "content3", + "dir/file4.txt": "content4", + } + + // Create mock context with artifact + mockCtx := &mockEnclaveContext{ + artifacts: map[string][]byte{ + "test-artifact": createTarGzArtifact(t, files), + }, + } + + fs := NewEnclaveFSWithContext(mockCtx) + artifact, err := fs.GetArtifact(context.Background(), "test-artifact") + require.NoError(t, err) + + // First extraction - get file1.txt and file3.txt + firstExtractFiles := map[string]string{ + "file1.txt": "content1", + "dir/file3.txt": "content3", + } + + firstWriters := make([]*ArtifactFileWriter, 0, len(firstExtractFiles)) + firstBuffers := make(map[string]*bytes.Buffer, len(firstExtractFiles)) + + for reqPath := range firstExtractFiles { + buf := &bytes.Buffer{} + firstBuffers[reqPath] = buf + firstWriters = append(firstWriters, NewArtifactFileWriter(reqPath, buf)) + } + + // First extraction + err = artifact.ExtractFiles(firstWriters...) + require.NoError(t, err) + + // Verify first extraction + for reqPath, wantContent := range firstExtractFiles { + require.Equal(t, wantContent, firstBuffers[reqPath].String(), + "first extraction: content mismatch for %s", reqPath) + } + + // Second extraction - get file2.txt and file4.txt + secondExtractFiles := map[string]string{ + "file2.txt": "content2", + "dir/file4.txt": "content4", + } + + secondWriters := make([]*ArtifactFileWriter, 0, len(secondExtractFiles)) + secondBuffers := make(map[string]*bytes.Buffer, len(secondExtractFiles)) + + for reqPath := range secondExtractFiles { + buf := &bytes.Buffer{} + secondBuffers[reqPath] = buf + secondWriters = append(secondWriters, NewArtifactFileWriter(reqPath, buf)) + } + + // Second extraction using the same artifact + err = artifact.ExtractFiles(secondWriters...) + require.NoError(t, err) + + // Verify second extraction + for reqPath, wantContent := range secondExtractFiles { + require.Equal(t, wantContent, secondBuffers[reqPath].String(), + "second extraction: content mismatch for %s", reqPath) + } + + // Third extraction - extract all files again to prove we can keep extracting + allFiles := map[string]string{ + "file1.txt": "content1", + "file2.txt": "content2", + "dir/file3.txt": "content3", + "dir/file4.txt": "content4", + } + + allWriters := make([]*ArtifactFileWriter, 0, len(allFiles)) + allBuffers := make(map[string]*bytes.Buffer, len(allFiles)) + + for reqPath := range allFiles { + buf := &bytes.Buffer{} + allBuffers[reqPath] = buf + allWriters = append(allWriters, NewArtifactFileWriter(reqPath, buf)) + } + + // Third extraction + err = artifact.ExtractFiles(allWriters...) + require.NoError(t, err) + + // Verify third extraction + for reqPath, wantContent := range allFiles { + require.Equal(t, wantContent, allBuffers[reqPath].String(), + "third extraction: content mismatch for %s", reqPath) + } +} diff --git a/devnet-sdk/system/chain.go b/devnet-sdk/system/chain.go index f143c7ba7c7..c8ba9ef8ab7 100644 --- a/devnet-sdk/system/chain.go +++ b/devnet-sdk/system/chain.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum-optimism/optimism/devnet-sdk/types" coreTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/params" ) var ( @@ -56,15 +57,14 @@ func (m *clientManager) Client(rpcURL string) (*ethclient.Client, error) { } type chain struct { - id string - rpcUrl string - - users map[string]Wallet - clients *clientManager - registry interfaces.ContractsRegistry - mu sync.Mutex - - node Node + id string + rpcUrl string + users map[string]Wallet + clients *clientManager + registry interfaces.ContractsRegistry + mu sync.Mutex + node Node + chainConfig *params.ChainConfig } func (c *chain) Node() Node { @@ -75,14 +75,15 @@ func (c *chain) Client() (*ethclient.Client, error) { return c.clients.Client(c.rpcUrl) } -func newChain(chainID string, rpcUrl string, users map[string]Wallet) *chain { +func newChain(chainID string, rpcUrl string, users map[string]Wallet, chainConfig *params.ChainConfig) *chain { clients := newClientManager() chain := &chain{ - id: chainID, - rpcUrl: rpcUrl, - users: users, - clients: clients, - node: newNode(rpcUrl, clients), + id: chainID, + rpcUrl: rpcUrl, + users: users, + clients: clients, + node: newNode(rpcUrl, clients), + chainConfig: chainConfig, } return chain } @@ -160,12 +161,19 @@ func (c *chain) SupportsEIP(ctx context.Context, eip uint64) bool { return false } +func (c *chain) Config() (*params.ChainConfig, error) { + if c.chainConfig == nil { + return nil, fmt.Errorf("chain config not configured on L1 chains yet") + } + return c.chainConfig, nil +} + func chainFromDescriptor(d *descriptors.Chain) (Chain, error) { // TODO: handle incorrect descriptors better. We could panic here. firstNodeRPC := d.Nodes[0].Services["el"].Endpoints["rpc"] rpcURL := fmt.Sprintf("http://%s:%d", firstNodeRPC.Host, firstNodeRPC.Port) - c := newChain(d.ID, rpcURL, nil) // Create chain first + c := newChain(d.ID, rpcURL, nil, d.Config) // Create chain first users := make(map[string]Wallet) for key, w := range d.Wallets { diff --git a/devnet-sdk/system/chain_test.go b/devnet-sdk/system/chain_test.go index 9730c8384cb..1457036f603 100644 --- a/devnet-sdk/system/chain_test.go +++ b/devnet-sdk/system/chain_test.go @@ -89,9 +89,7 @@ func TestChainWallet(t *testing.T) { wallet, err := newWallet("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", testAddr, nil) assert.Nil(t, err) - chain := newChain("1", "http://localhost:8545", map[string]Wallet{ - "user1": wallet, - }) + chain := newChain("1", "http://localhost:8545", map[string]Wallet{"user1": wallet}, nil) t.Run("finds wallet meeting constraints", func(t *testing.T) { constraint := &addressConstraint{addr: testAddr} @@ -156,7 +154,7 @@ func TestChainID(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - chain := newChain(tt.idString, "", nil) + chain := newChain(tt.idString, "", nil, nil) got := chain.ID() // Compare the underlying big.Int values assert.Equal(t, 0, tt.want.Cmp(got)) @@ -166,7 +164,7 @@ func TestChainID(t *testing.T) { func TestSupportsEIP(t *testing.T) { ctx := context.Background() - chain := newChain("1", "http://localhost:8545", nil) + chain := newChain("1", "http://localhost:8545", nil, nil) // Since we can't reliably test against a live node, we're just testing the error case t.Run("returns false for connection error", func(t *testing.T) { @@ -176,7 +174,7 @@ func TestSupportsEIP(t *testing.T) { } func TestContractsRegistry(t *testing.T) { - chain := newChain("1", "http://localhost:8545", nil) + chain := newChain("1", "http://localhost:8545", nil, nil) t.Run("returns empty registry on error", func(t *testing.T) { registry := chain.ContractsRegistry() diff --git a/devnet-sdk/system/interfaces.go b/devnet-sdk/system/interfaces.go index 26ddd99f13c..bf6d64a46bb 100644 --- a/devnet-sdk/system/interfaces.go +++ b/devnet-sdk/system/interfaces.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common" coreTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/params" ) type genSystem[T Chain] interface { @@ -29,12 +30,14 @@ type Chain interface { ContractsRegistry() interfaces.ContractsRegistry SupportsEIP(ctx context.Context, eip uint64) bool Node() Node + Config() (*params.ChainConfig, error) } type Node interface { GasPrice(ctx context.Context) (*big.Int, error) GasLimit(ctx context.Context, tx TransactionData) (uint64, error) PendingNonceAt(ctx context.Context, address common.Address) (uint64, error) + BlockByNumber(ctx context.Context, number *big.Int) (*coreTypes.Block, error) } // LowLevelChain is a Chain that gives direct access to the low level RPC client. diff --git a/devnet-sdk/system/node.go b/devnet-sdk/system/node.go index 0e1589a423b..5c9c12b7b2d 100644 --- a/devnet-sdk/system/node.go +++ b/devnet-sdk/system/node.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" + coreTypes "github.com/ethereum/go-ethereum/core/types" ) var ( @@ -59,3 +60,11 @@ func (n *node) PendingNonceAt(ctx context.Context, address common.Address) (uint } return client.PendingNonceAt(ctx, address) } + +func (n *node) BlockByNumber(ctx context.Context, number *big.Int) (*coreTypes.Block, error) { + client, err := n.clients.Client(n.rpcUrl) + if err != nil { + return nil, fmt.Errorf("failed to get client: %w", err) + } + return client.BlockByNumber(ctx, number) +} diff --git a/devnet-sdk/system/system.go b/devnet-sdk/system/system.go index 51a1c0252b9..76f562c5ed1 100644 --- a/devnet-sdk/system/system.go +++ b/devnet-sdk/system/system.go @@ -45,11 +45,13 @@ func (s *system) Identifier() string { func (s *system) addChains(chains ...*descriptors.Chain) error { for _, chainDesc := range chains { if chainDesc.ID == "" { - l1, err := chainFromDescriptor(chainDesc) - if err != nil { - return fmt.Errorf("failed to add L1 chain: %w", err) + if chainDesc.Name == "Ethereum" { + l1, err := chainFromDescriptor(chainDesc) + if err != nil { + return fmt.Errorf("failed to add L1 chain: %w", err) + } + s.l1 = l1 } - s.l1 = l1 } else { l2, err := chainFromDescriptor(chainDesc) if err != nil { diff --git a/devnet-sdk/system/system_test.go b/devnet-sdk/system/system_test.go index b05d7e10bc5..9a66dc15dcf 100644 --- a/devnet-sdk/system/system_test.go +++ b/devnet-sdk/system/system_test.go @@ -164,7 +164,7 @@ func TestSystemFromDevnet(t *testing.T) { } func TestWallet(t *testing.T) { - chain := newChain("1", "http://localhost:8545", nil) + chain := newChain("1", "http://localhost:8545", nil, nil) tests := []struct { name string @@ -204,7 +204,7 @@ func TestWallet(t *testing.T) { } func TestChainUser(t *testing.T) { - chain := newChain("1", "http://localhost:8545", nil) + chain := newChain("1", "http://localhost:8545", nil, nil) testWallet, err := newWallet("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", common.HexToAddress("0x123"), chain) assert.Nil(t, err) diff --git a/devnet-sdk/system/txbuilder_test.go b/devnet-sdk/system/txbuilder_test.go index de20322b85a..21e220795ed 100644 --- a/devnet-sdk/system/txbuilder_test.go +++ b/devnet-sdk/system/txbuilder_test.go @@ -2,6 +2,7 @@ package system import ( "context" + "fmt" "math/big" "testing" @@ -12,6 +13,7 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/params" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) @@ -110,6 +112,10 @@ func (m *mockChain) Wallets(ctx context.Context) ([]Wallet, error) { return nil, nil } +func (m *mockChain) Config() (*params.ChainConfig, error) { + return nil, fmt.Errorf("not implemented for mock chain") +} + type mockNode struct { mock.Mock } @@ -133,6 +139,11 @@ func (m *mockNode) PendingNonceAt(ctx context.Context, addr common.Address) (uin return args.Get(0).(uint64), args.Error(1) } +func (m *mockNode) BlockByNumber(ctx context.Context, number *big.Int) (*ethtypes.Block, error) { + args := m.Called(ctx, number) + return args.Get(0).(*ethtypes.Block), args.Error(1) +} + func TestNewTxBuilder(t *testing.T) { ctx := context.Background() chain := newMockChain() diff --git a/devnet-sdk/system/txprocessor_test.go b/devnet-sdk/system/txprocessor_test.go index e6824b08a99..aed1228e79c 100644 --- a/devnet-sdk/system/txprocessor_test.go +++ b/devnet-sdk/system/txprocessor_test.go @@ -26,7 +26,7 @@ func TestTransactionProcessor_Sign(t *testing.T) { client := new(mockEthClient) // Create a wallet with the test key - chain := newChain(chainID.String(), "http://localhost:8545", nil) + chain := newChain(chainID.String(), "http://localhost:8545", nil, nil) wallet, err := newWallet(testKey, testAddr, chain) assert.NoError(t, err) diff --git a/devnet-sdk/testing/systest/testing_test.go b/devnet-sdk/testing/systest/testing_test.go index ebd182ff807..9146ad39d16 100644 --- a/devnet-sdk/testing/systest/testing_test.go +++ b/devnet-sdk/testing/systest/testing_test.go @@ -12,7 +12,9 @@ import ( "github.com/ethereum-optimism/optimism/devnet-sdk/system" "github.com/ethereum-optimism/optimism/devnet-sdk/types" "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/params" "github.com/stretchr/testify/require" ) @@ -98,6 +100,12 @@ func (m *mockChain) PendingNonceAt(ctx context.Context, address common.Address) func (m *mockChain) SupportsEIP(ctx context.Context, eip uint64) bool { return true } +func (m *mockChain) Config() (*params.ChainConfig, error) { + return nil, fmt.Errorf("not implemented on mockChain") +} +func (m *mockChain) BlockByNumber(ctx context.Context, number *big.Int) (*ethtypes.Block, error) { + return nil, fmt.Errorf("not implemented on mockChain") +} // mockSystem implements a minimal system.System for testing type mockSystem struct{} diff --git a/devnet-sdk/testing/testlib/validators/forks.go b/devnet-sdk/testing/testlib/validators/forks.go new file mode 100644 index 00000000000..7434c8392f5 --- /dev/null +++ b/devnet-sdk/testing/testlib/validators/forks.go @@ -0,0 +1,120 @@ +package validators + +import ( + "context" + "fmt" + + "github.com/ethereum-optimism/optimism/devnet-sdk/system" + "github.com/ethereum-optimism/optimism/devnet-sdk/testing/systest" + "github.com/ethereum-optimism/optimism/op-node/rollup" + "github.com/ethereum/go-ethereum/params" +) + +// getChainConfig is a helper function that retrieves the ForkConfig for a specific L2 chain. +func getChainConfig(t systest.T, sys system.System, chainIdx uint64) (*params.ChainConfig, *uint64, error) { + if len(sys.L2s()) <= int(chainIdx) { + return nil, nil, fmt.Errorf("chain index %d out of range, only %d L2 chains available", chainIdx, len(sys.L2s())) + } + + chain := sys.L2s()[chainIdx] + + chainConfig, err := chain.Config() + if err != nil || chainConfig == nil { + return nil, nil, fmt.Errorf("failed to get chain config for L2 chain %d: %w", chainIdx, err) + } + + block, err := chain.Node().BlockByNumber(t.Context(), nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to get latest block for L2 chain %d: %w", chainIdx, err) + } + + timestamp := block.Time() + + return chainConfig, ×tamp, nil +} + +// IsForkActivated checks if a specific fork is activated at the given timestamp +// based on the chain configuration. +func IsForkActivated(c *params.ChainConfig, forkName rollup.ForkName, timestamp uint64) (bool, error) { + if c == nil { + return false, fmt.Errorf("provided chain config is nil") + } + + switch forkName { + case rollup.Bedrock: + // Bedrock is activated based on block number, not timestamp + return true, nil // Assuming bedrock is always active in the context of this validator + case rollup.Regolith: + return c.IsOptimismRegolith(timestamp), nil + case rollup.Canyon: + return c.IsOptimismCanyon(timestamp), nil + case rollup.Ecotone: + return c.IsOptimismEcotone(timestamp), nil + case rollup.Fjord: + return c.IsOptimismFjord(timestamp), nil + case rollup.Granite: + return c.IsOptimismGranite(timestamp), nil + case rollup.Holocene: + return c.IsOptimismHolocene(timestamp), nil + case rollup.Isthmus: + return c.IsOptimismIsthmus(timestamp), nil + case rollup.Interop: + return c.IsInterop(timestamp), nil + default: + return false, fmt.Errorf("unknown fork name: %s", forkName) + } +} + +// forkConfigValidator is a helper function that checks if a specific L2 chain meets a fork condition. +func forkConfigValidator(chainIdx uint64, forkName rollup.ForkName, shouldBeActive bool, forkConfigMarker interface{}) systest.PreconditionValidator { + return func(t systest.T, sys system.System) (context.Context, error) { + chainConfig, timestamp, err := getChainConfig(t, sys, chainIdx) + if err != nil { + return nil, err + } + if chainConfig == nil { + return nil, fmt.Errorf("chain config is nil") + } + + isActive, err := IsForkActivated(chainConfig, forkName, *timestamp) + if err != nil { + return nil, err + } + + if isActive != shouldBeActive { + if shouldBeActive { + return nil, fmt.Errorf("L2 chain %d does not have fork %s activated, which it should be for this validator to pass", chainIdx, forkName) + } else { + return nil, fmt.Errorf("L2 chain %d has fork %s activated, but it should not be for this validator to pass", chainIdx, forkName) + } + } + + return context.WithValue(t.Context(), forkConfigMarker, chainConfig), nil + } +} + +// ChainConfigGetter is a function type that retrieves a ForkConfig from a context. +type ChainConfigGetter = func(context.Context) *params.ChainConfig + +// AcquireForkConfig returns a ForkConfigGetter and a PreconditionValidator +// that ensures a ForkConfig is available for the specified L2 chain. +// The ForkConfig can be used to check if various forks are activated. +func acquireForkConfig(chainIdx uint64, forkName rollup.ForkName, shouldBeActive bool) (ChainConfigGetter, systest.PreconditionValidator) { + chainConfigMarker := new(byte) + validator := forkConfigValidator(chainIdx, forkName, shouldBeActive, chainConfigMarker) + return func(ctx context.Context) *params.ChainConfig { + return ctx.Value(chainConfigMarker).(*params.ChainConfig) + }, validator +} + +// RequiresFork returns a validator that ensures a specific L2 chain has a specific fork activated. +func AcquireL2WithFork(chainIdx uint64, forkName rollup.ForkName) (ChainConfigGetter, systest.PreconditionValidator) { + return acquireForkConfig(chainIdx, forkName, true) +} + +// RequiresNotFork returns a validator that ensures a specific L2 chain does not +// have a specific fork activated. Will not work with the interop fork +// specifically since interop is not an ordered release fork. +func AcquireL2WithoutFork(chainIdx uint64, forkName rollup.ForkName) (ChainConfigGetter, systest.PreconditionValidator) { + return acquireForkConfig(chainIdx, forkName, false) +} diff --git a/devnet-sdk/testing/testlib/validators/validators_test.go b/devnet-sdk/testing/testlib/validators/validators_test.go index 425022a59cc..af81628e22d 100644 --- a/devnet-sdk/testing/testlib/validators/validators_test.go +++ b/devnet-sdk/testing/testlib/validators/validators_test.go @@ -2,6 +2,7 @@ package validators import ( "context" + "fmt" "math/big" "testing" @@ -9,19 +10,27 @@ import ( "github.com/ethereum-optimism/optimism/devnet-sdk/system" "github.com/ethereum-optimism/optimism/devnet-sdk/testing/systest" "github.com/ethereum-optimism/optimism/devnet-sdk/types" + "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/params" "github.com/stretchr/testify/require" ) +func Uint64Ptr(x uint64) *uint64 { + return &x +} + // TestSystemTestHelper tests the basic implementation of systemTestHelper func TestValidators(t *testing.T) { t.Run("multiple validators", func(t *testing.T) { walletGetter1, validator1 := AcquireL2WalletWithFunds(0, types.NewBalance(big.NewInt(1))) walletGetter2, validator2 := AcquireL2WalletWithFunds(0, types.NewBalance(big.NewInt(10))) lowLevelSystemGetter, validator3 := AcquireLowLevelSystem() + chainConfigGetter, l2ForkValidator := AcquireL2WithFork(0, rollup.Isthmus) // We create a system that has a low-level L1 chain and at least one wallet systestSystem := &mockSystem{ @@ -36,6 +45,10 @@ func TestValidators(t *testing.T) { balance: types.NewBalance(big.NewInt(11)), }, }, + config: ¶ms.ChainConfig{ + Optimism: ¶ms.OptimismConfig{}, + IsthmusTime: Uint64Ptr(0), + }, }, }, } @@ -56,12 +69,17 @@ func TestValidators(t *testing.T) { systestT = systestT.WithContext(ctx3) require.NoError(t, err) + ctx4, err := l2ForkValidator(systestT, systestSystem) + systestT = systestT.WithContext(ctx4) + require.NoError(t, err) + ctx := systestT.Context() // Now we call all the getters to make sure they work wallet1 := walletGetter1(ctx) wallet2 := walletGetter2(ctx) lowLevelSystem := lowLevelSystemGetter(ctx) + chainConfig := chainConfigGetter(ctx) // And we ensure that the values are not mismatched require.NotEqual(t, wallet1, wallet2) @@ -70,6 +88,129 @@ func TestValidators(t *testing.T) { // And that we got a lowlevelSystem require.NotNil(t, lowLevelSystem) + + // And that we got a chain config + require.NotNil(t, chainConfig) + }) + + t.Run("test AcquireL2WithFork - fork active", func(t *testing.T) { + // Create a system with the Isthmus fork active + systestSystem := &mockSystem{ + l2s: []system.Chain{ + &mockChain{ + config: ¶ms.ChainConfig{ + Optimism: ¶ms.OptimismConfig{}, + IsthmusTime: Uint64Ptr(50), + }, + }, + }, + } + + // Get the validator for requiring Isthmus fork to be active + chainConfigGetter, validator := AcquireL2WithFork(0, rollup.Isthmus) + systestT := systest.NewT(t) + + // Apply the validator + ctx, err := validator(systestT, systestSystem) + require.NoError(t, err, "Validator should pass when fork is active") + + // Verify the chain config getter works + chainConfig := chainConfigGetter(ctx) + require.NotNil(t, chainConfig) + isActive, err := IsForkActivated(chainConfig, rollup.Isthmus, 100) + require.NoError(t, err) + require.True(t, isActive) + }) + + t.Run("test AcquireL2WithFork - fork not active", func(t *testing.T) { + // Create a system where the Isthmus fork is not yet active + systestSystem := &mockSystem{ + l2s: []system.Chain{ + &mockChain{ + config: ¶ms.ChainConfig{ + Optimism: ¶ms.OptimismConfig{}, + IsthmusTime: Uint64Ptr(150), + }, + }, + }, + } + + // Get the validator for requiring Isthmus fork to be active + _, validator := AcquireL2WithFork(0, rollup.Isthmus) + systestT := systest.NewT(t) + + // Apply the validator - should fail since fork is not active + _, err := validator(systestT, systestSystem) + require.Error(t, err, "Validator should fail when fork is not active") + require.Contains(t, err.Error(), "does not have fork", "Error message should indicate fork is not active") + }) + + t.Run("test AcquireRequiresNotL2Fork - fork not active", func(t *testing.T) { + // Create a system where the Isthmus fork is not yet active + systestSystem := &mockSystem{ + l2s: []system.Chain{ + &mockChain{ + config: ¶ms.ChainConfig{ + Optimism: ¶ms.OptimismConfig{}, + IsthmusTime: Uint64Ptr(150), // Activates after current timestamp + }, + }, + }, + } + + // Get the validator for requiring Isthmus fork to not be active + chainConfigGetter, validator := AcquireL2WithoutFork(0, rollup.Isthmus) + systestT := systest.NewT(t) + + // Apply the validator + ctx, err := validator(systestT, systestSystem) + require.NoError(t, err, "Validator should pass when fork is not active") + + // Verify the chain config getter works + chainConfig := chainConfigGetter(ctx) + require.NotNil(t, chainConfig) + isActive, err := IsForkActivated(chainConfig, rollup.Isthmus, 100) + require.NoError(t, err) + require.False(t, isActive) + }) + + t.Run("test AcquireRequiresNotL2Fork - fork active", func(t *testing.T) { + // Create a system with the Isthmus fork active + systestSystem := &mockSystem{ + l2s: []system.Chain{ + &mockChain{ + config: ¶ms.ChainConfig{ + Optimism: ¶ms.OptimismConfig{}, + IsthmusTime: Uint64Ptr(50), + }, + }, + }, + } + + // Get the validator for requiring Isthmus fork to not be active + _, validator := AcquireL2WithoutFork(0, rollup.Isthmus) + systestT := systest.NewT(t) + + // Apply the validator - should fail since fork is active + _, err := validator(systestT, systestSystem) + require.Error(t, err, "Validator should fail when fork is active") + require.Contains(t, err.Error(), "has fork", "Error message should indicate fork is active") + }) + + t.Run("chain index out of range", func(t *testing.T) { + // Create a system with no L2 chains + systestSystem := &mockSystem{ + l2s: []system.Chain{}, + } + + // Try to get chain config for an invalid chain index + _, validator := AcquireL2WithFork(0, rollup.Isthmus) + systestT := systest.NewT(t) + + // Apply the validator - should fail since chain index is out of range + _, err := validator(systestT, systestSystem) + require.Error(t, err, "Validator should fail when chain index is out of range") + require.Contains(t, err.Error(), "chain index 0 out of range", "Error message should indicate chain index out of range") }) } @@ -92,9 +233,9 @@ func (sys *mockSystem) L2s() []system.Chain { type mockChain struct { wallets []system.Wallet + config *params.ChainConfig } -func (m *mockChain) Node() system.Node { return nil } func (m *mockChain) RPCURL() string { return "http://localhost:8545" } func (m *mockChain) Client() (*ethclient.Client, error) { return ethclient.Dial(m.RPCURL()) } func (m *mockChain) ID() types.ChainID { return types.ChainID(big.NewInt(1)) } @@ -114,6 +255,54 @@ func (m *mockChain) PendingNonceAt(ctx context.Context, address common.Address) func (m *mockChain) SupportsEIP(ctx context.Context, eip uint64) bool { return true } +func (m *mockChain) Config() (*params.ChainConfig, error) { + if m.config == nil { + return nil, fmt.Errorf("chain config not implemented") + } + return m.config, nil +} +func (m *mockChain) Node() system.Node { + return newMockNode(m.config) +} + +type mockNode struct { + chainConfig *params.ChainConfig +} + +func newMockNode(chainConfig *params.ChainConfig) *mockNode { + return &mockNode{ + chainConfig: chainConfig, + } +} + +func (m *mockNode) GasPrice(ctx context.Context) (*big.Int, error) { + return nil, fmt.Errorf("not implemented") +} + +func (m *mockNode) GasLimit(ctx context.Context, tx system.TransactionData) (uint64, error) { + return 0, fmt.Errorf("not implemented") +} + +func (m *mockNode) PendingNonceAt(ctx context.Context, addr common.Address) (uint64, error) { + return 0, fmt.Errorf("not implemented") +} + +func (m *mockNode) BlockByNumber(ctx context.Context, number *big.Int) (*ethtypes.Block, error) { + isIsthmusActivated, err := IsForkActivated(m.chainConfig, rollup.Isthmus, 100) + if err != nil { + return nil, err + } + var blockConfig *ethtypes.BlockConfig + if isIsthmusActivated { + blockConfig = ethtypes.DefaultBlockConfig + } else { + blockConfig = ethtypes.IsthmusBlockConfig + } + + return ethtypes.NewBlock(ðtypes.Header{ + Time: 100, + }, ðtypes.Body{Withdrawals: []*ethtypes.Withdrawal{}}, nil, nil, blockConfig), nil +} type mockWallet struct { balance types.Balance @@ -156,8 +345,6 @@ func (m mockWallet) Transactor() *bind.TransactOpts { var ( _ system.Chain = (*mockChain)(nil) _ system.LowLevelChain = (*mockChain)(nil) - - _ system.System = (*mockSystem)(nil) - - _ system.Wallet = (*mockWallet)(nil) + _ system.System = (*mockSystem)(nil) + _ system.Wallet = (*mockWallet)(nil) ) diff --git a/kurtosis-devnet/pkg/kurtosis/kurtosis.go b/kurtosis-devnet/pkg/kurtosis/kurtosis.go index 9cd3869d6f1..f0938a73ffb 100644 --- a/kurtosis-devnet/pkg/kurtosis/kurtosis.go +++ b/kurtosis-devnet/pkg/kurtosis/kurtosis.go @@ -199,11 +199,10 @@ func (d *KurtosisDeployer) GetEnvironmentInfo(ctx context.Context, spec *spec.En // Add contract addresses if available if deployerState.State != nil && deployerState.State.Deployments != nil { - if addresses, ok := deployerState.State.Deployments[chainSpec.NetworkID]; ok { - chain.Addresses = descriptors.AddressMap(addresses.Addresses) - } - if wallets, ok := deployerState.State.Deployments[chainSpec.NetworkID]; ok { - chain.Wallets = d.getWallets(wallets.Wallets) + if deployment, ok := deployerState.State.Deployments[chainSpec.NetworkID]; ok { + chain.Addresses = descriptors.AddressMap(deployment.Addresses) + chain.Config = deployment.Config + chain.Wallets = d.getWallets(deployment.Wallets) } } diff --git a/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go b/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go index 3a99039093f..3b9cf87d829 100644 --- a/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go +++ b/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go @@ -8,10 +8,13 @@ import ( "io" "math/big" "strings" + "text/template" ktfs "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" "github.com/ethereum-optimism/optimism/devnet-sdk/types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/params" ) const ( @@ -20,6 +23,8 @@ const ( defaultStateName = "state.json" defaultGenesisArtifactName = "el_cl_genesis_data" defaultMnemonicsName = "mnemonics.yaml" + defaultGenesisNameTemplate = "genesis-{{.ChainID}}.json" + defaultL1GenesisName = "genesis.json" ) // DeploymentAddresses maps contract names to their addresses @@ -31,6 +36,7 @@ type DeploymentStateAddresses map[string]DeploymentAddresses type DeploymentState struct { Addresses DeploymentAddresses `json:"addresses"` Wallets WalletList `json:"wallets"` + Config *params.ChainConfig `json:"chain_config"` } type DeployerState struct { @@ -61,12 +67,14 @@ type DeployerData struct { } type Deployer struct { - enclave string - deployerArtifactName string - walletsName string - stateName string - genesisArtifactName string - mnemonicsName string + enclave string + deployerArtifactName string + walletsName string + stateName string + genesisArtifactName string + mnemonicsName string + l2GenesisNameTemplate string + l1GenesisName string } type DeployerOption func(*Deployer) @@ -101,14 +109,22 @@ func WithMnemonicsName(name string) DeployerOption { } } +func WithGenesisNameTemplate(name string) DeployerOption { + return func(d *Deployer) { + d.l2GenesisNameTemplate = name + } +} + func NewDeployer(enclave string, opts ...DeployerOption) *Deployer { d := &Deployer{ - enclave: enclave, - deployerArtifactName: defaultDeployerArtifactName, - walletsName: defaultWalletsName, - stateName: defaultStateName, - genesisArtifactName: defaultGenesisArtifactName, - mnemonicsName: defaultMnemonicsName, + enclave: enclave, + deployerArtifactName: defaultDeployerArtifactName, + walletsName: defaultWalletsName, + stateName: defaultStateName, + genesisArtifactName: defaultGenesisArtifactName, + mnemonicsName: defaultMnemonicsName, + l2GenesisNameTemplate: defaultGenesisNameTemplate, + l1GenesisName: defaultL1GenesisName, } for _, opt := range opts { @@ -276,25 +292,69 @@ func (d *Deployer) ExtractData(ctx context.Context) (*DeployerData, error) { return nil, err } - wallets, err := parseWalletsFile(walletsBuffer) + l1WalletsForL2Admin, err := parseWalletsFile(walletsBuffer) if err != nil { return nil, err } - for id, wallets := range wallets { + for id, l1Wallets := range l1WalletsForL2Admin { if deployment, exists := state.Deployments[id]; exists { - deployment.Wallets = wallets + deployment.Wallets = l1Wallets state.Deployments[id] = deployment } } - knownWallets, err := d.getKnownWallets(ctx, fs) + // retrieve L2 genesis files + for id, deployment := range state.Deployments { + genesisBuffer := bytes.NewBuffer(nil) + genesisName, err := d.renderGenesisNameTemplate(id) + if err != nil { + return nil, err + } + + a, err = fs.GetArtifact(ctx, d.deployerArtifactName) + if err != nil { + return nil, err + } + if err := a.ExtractFiles( + ktfs.NewArtifactFileWriter(genesisName, genesisBuffer), + ); err != nil { + return nil, err + } + + // Parse the genesis file JSON into a core.Genesis struct + var genesis core.Genesis + if err := json.NewDecoder(genesisBuffer).Decode(&genesis); err != nil { + return nil, fmt.Errorf("failed to parse genesis file %s in artifact %s for chain ID %s: %w", genesisName, d.deployerArtifactName, id, err) + } + + // Store the genesis data in the deployment state + deployment.Config = genesis.Config + state.Deployments[id] = deployment + } + + l1ValidatorWallets, err := d.getKnownWallets(ctx, fs) if err != nil { return nil, err } return &DeployerData{ State: state, - Wallets: knownWallets, + Wallets: l1ValidatorWallets, }, nil } + +func (d *Deployer) renderGenesisNameTemplate(chainID string) (string, error) { + tmpl, err := template.New("genesis").Parse(d.l2GenesisNameTemplate) + if err != nil { + return "", fmt.Errorf("failed to compile genesis name template %s: %w", d.l2GenesisNameTemplate, err) + } + + var buf bytes.Buffer + err = tmpl.Execute(&buf, map[string]string{"ChainID": chainID}) + if err != nil { + return "", fmt.Errorf("failed to execute name template %s: %w", d.l2GenesisNameTemplate, err) + } + + return buf.String(), nil +} diff --git a/kurtosis-devnet/tests/interop/interop_smoke_test.go b/kurtosis-devnet/tests/interop/interop_smoke_test.go index 4046c849dcf..96874b44430 100644 --- a/kurtosis-devnet/tests/interop/interop_smoke_test.go +++ b/kurtosis-devnet/tests/interop/interop_smoke_test.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum-optimism/optimism/devnet-sdk/testing/systest" "github.com/ethereum-optimism/optimism/devnet-sdk/testing/testlib/validators" sdktypes "github.com/ethereum-optimism/optimism/devnet-sdk/types" + "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -55,10 +56,12 @@ func TestSystemWrapETH(t *testing.T) { chainIdx := uint64(0) // We'll use the first L2 chain for this test walletGetter, fundsValidator := validators.AcquireL2WalletWithFunds(chainIdx, sdktypes.NewBalance(big.NewInt(1.0*constants.ETH))) + _, interopValidator := validators.AcquireL2WithFork(chainIdx, rollup.Interop) systest.SystemTest(t, smokeTestScenario(chainIdx, walletGetter), fundsValidator, + interopValidator, ) } diff --git a/kurtosis-devnet/tests/interop/mocks_test.go b/kurtosis-devnet/tests/interop/mocks_test.go index a27037ff3b4..c02f5c8b0e1 100644 --- a/kurtosis-devnet/tests/interop/mocks_test.go +++ b/kurtosis-devnet/tests/interop/mocks_test.go @@ -16,7 +16,9 @@ import ( "github.com/ethereum-optimism/optimism/devnet-sdk/types" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/params" ) var ( @@ -126,6 +128,8 @@ type mockFailingChain struct { wallets []system.Wallet } +var _ system.Chain = (*mockFailingChain)(nil) + func newMockFailingChain(id types.ChainID, wallets []system.Wallet) *mockFailingChain { return &mockFailingChain{ id: id, @@ -154,6 +158,12 @@ func (m *mockFailingChain) PendingNonceAt(ctx context.Context, address common.Ad func (m *mockFailingChain) SupportsEIP(ctx context.Context, eip uint64) bool { return true } +func (m *mockFailingChain) Config() (*params.ChainConfig, error) { + return nil, fmt.Errorf("not implemented") +} +func (m *mockFailingChain) BlockByNumber(ctx context.Context, number *big.Int) (*ethtypes.Block, error) { + return nil, fmt.Errorf("not implemented") +} // mockFailingSystem implements system.System type mockFailingSystem struct { From 61e8f4ebfe0b32bf242619206fe571a1d864d4f3 Mon Sep 17 00:00:00 2001 From: Teddy Knox Date: Fri, 7 Mar 2025 07:39:30 -0500 Subject: [PATCH 092/130] Improve address management in kurtosis-devnet and devnet-sdk (#14703) --- devnet-sdk/system/chain.go | 13 +++- devnet-sdk/system/chain_test.go | 10 ++- devnet-sdk/system/interfaces.go | 15 ++++ devnet-sdk/system/system.go | 12 ++- devnet-sdk/system/system_test.go | 4 +- devnet-sdk/system/txbuilder_test.go | 6 ++ devnet-sdk/system/txprocessor_test.go | 3 +- devnet-sdk/testing/systest/testing_test.go | 6 +- .../testlib/validators/validators_test.go | 5 ++ .../testing/testlib/validators/wallet.go | 29 ++++++-- kurtosis-devnet/pkg/kurtosis/kurtosis.go | 4 +- kurtosis-devnet/pkg/kurtosis/kurtosis_test.go | 4 +- .../pkg/kurtosis/sources/deployer/deployer.go | 74 ++++++++++++++----- .../pkg/kurtosis/sources/deployer/wallets.go | 10 +-- kurtosis-devnet/tests/interop/mocks_test.go | 6 +- 15 files changed, 140 insertions(+), 61 deletions(-) diff --git a/devnet-sdk/system/chain.go b/devnet-sdk/system/chain.go index c8ba9ef8ab7..a0bbf810bc7 100644 --- a/devnet-sdk/system/chain.go +++ b/devnet-sdk/system/chain.go @@ -65,6 +65,7 @@ type chain struct { mu sync.Mutex node Node chainConfig *params.ChainConfig + addresses descriptors.AddressMap } func (c *chain) Node() Node { @@ -75,7 +76,7 @@ func (c *chain) Client() (*ethclient.Client, error) { return c.clients.Client(c.rpcUrl) } -func newChain(chainID string, rpcUrl string, users map[string]Wallet, chainConfig *params.ChainConfig) *chain { +func newChain(chainID string, rpcUrl string, users map[string]Wallet, chainConfig *params.ChainConfig, addresses descriptors.AddressMap) *chain { clients := newClientManager() chain := &chain{ id: chainID, @@ -84,6 +85,7 @@ func newChain(chainID string, rpcUrl string, users map[string]Wallet, chainConfi clients: clients, node: newNode(rpcUrl, clients), chainConfig: chainConfig, + addresses: addresses, } return chain } @@ -168,15 +170,22 @@ func (c *chain) Config() (*params.ChainConfig, error) { return c.chainConfig, nil } +func (c *chain) Addresses() descriptors.AddressMap { + return c.addresses +} + func chainFromDescriptor(d *descriptors.Chain) (Chain, error) { // TODO: handle incorrect descriptors better. We could panic here. firstNodeRPC := d.Nodes[0].Services["el"].Endpoints["rpc"] rpcURL := fmt.Sprintf("http://%s:%d", firstNodeRPC.Host, firstNodeRPC.Port) - c := newChain(d.ID, rpcURL, nil, d.Config) // Create chain first + c := newChain(d.ID, rpcURL, nil, d.Config, d.Addresses) // Create chain first users := make(map[string]Wallet) for key, w := range d.Wallets { + // TODO: The assumption that the wallet will necessarily be used on chain `d` may + // be problematic if the L2 admin wallets are to be used to sign L1 transactions. + // TBD on whether they belong somewhere other than `d.Wallets`. k, err := newWallet(w.PrivateKey, w.Address, c) if err != nil { return nil, fmt.Errorf("failed to create wallet: %w", err) diff --git a/devnet-sdk/system/chain_test.go b/devnet-sdk/system/chain_test.go index 1457036f603..775b331ba1a 100644 --- a/devnet-sdk/system/chain_test.go +++ b/devnet-sdk/system/chain_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" + "github.com/ethereum-optimism/optimism/devnet-sdk/types" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -89,7 +90,8 @@ func TestChainWallet(t *testing.T) { wallet, err := newWallet("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", testAddr, nil) assert.Nil(t, err) - chain := newChain("1", "http://localhost:8545", map[string]Wallet{"user1": wallet}, nil) + chain := newChain("1", "http://localhost:8545", map[string]Wallet{ + "user1": wallet}, nil, map[string]common.Address{}) t.Run("finds wallet meeting constraints", func(t *testing.T) { constraint := &addressConstraint{addr: testAddr} @@ -154,7 +156,7 @@ func TestChainID(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - chain := newChain(tt.idString, "", nil, nil) + chain := newChain(tt.idString, "", nil, nil, map[string]types.Address{}) got := chain.ID() // Compare the underlying big.Int values assert.Equal(t, 0, tt.want.Cmp(got)) @@ -164,7 +166,7 @@ func TestChainID(t *testing.T) { func TestSupportsEIP(t *testing.T) { ctx := context.Background() - chain := newChain("1", "http://localhost:8545", nil, nil) + chain := newChain("1", "http://localhost:8545", nil, nil, map[string]types.Address{}) // Since we can't reliably test against a live node, we're just testing the error case t.Run("returns false for connection error", func(t *testing.T) { @@ -174,7 +176,7 @@ func TestSupportsEIP(t *testing.T) { } func TestContractsRegistry(t *testing.T) { - chain := newChain("1", "http://localhost:8545", nil, nil) + chain := newChain("1", "http://localhost:8545", nil, nil, map[string]types.Address{}) t.Run("returns empty registry on error", func(t *testing.T) { registry := chain.ContractsRegistry() diff --git a/devnet-sdk/system/interfaces.go b/devnet-sdk/system/interfaces.go index bf6d64a46bb..33628c5bfc9 100644 --- a/devnet-sdk/system/interfaces.go +++ b/devnet-sdk/system/interfaces.go @@ -4,6 +4,7 @@ import ( "context" "math/big" + "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" "github.com/ethereum-optimism/optimism/devnet-sdk/types" "github.com/ethereum/go-ethereum/common" @@ -26,11 +27,25 @@ type LowLevelSystem = genSystem[LowLevelChain] // Chain represents an Ethereum chain (L1 or L2) type Chain interface { ID() types.ChainID + // If an instance of an implementation this interface represents an L1 chain, + // then then the wallets returned should be either validator wallets or test wallets, + // both useful in the context of sending transactions on the L1. + // + // If an instance of an implementation of this interface represents an L2 chain, + // then the wallets returned should be a combination of: + // 1. L2 admin wallets: wallets with admin priviledges for administrating an + // L2's bridge contracts, etc on L1. Despite inclusion on the L2 wallet list, these wallets + // are not useful for sending transactions on the L2 and do not control any L2 balance. + // 2. L2 test wallets: wallets controlling balance on the L2 for purposes of + // testing. The balance on these wallets will originate unbacked L2 ETH from + // the L2 genesis definition which cannot be withdrawn without maybe "stealing" + // the backing from other deposits. Wallets(ctx context.Context) ([]Wallet, error) ContractsRegistry() interfaces.ContractsRegistry SupportsEIP(ctx context.Context, eip uint64) bool Node() Node Config() (*params.ChainConfig, error) + Addresses() descriptors.AddressMap } type Node interface { diff --git a/devnet-sdk/system/system.go b/devnet-sdk/system/system.go index 76f562c5ed1..8290a903269 100644 --- a/devnet-sdk/system/system.go +++ b/devnet-sdk/system/system.go @@ -44,14 +44,12 @@ func (s *system) Identifier() string { func (s *system) addChains(chains ...*descriptors.Chain) error { for _, chainDesc := range chains { - if chainDesc.ID == "" { - if chainDesc.Name == "Ethereum" { - l1, err := chainFromDescriptor(chainDesc) - if err != nil { - return fmt.Errorf("failed to add L1 chain: %w", err) - } - s.l1 = l1 + if chainDesc.Name == "Ethereum" { + l1, err := chainFromDescriptor(chainDesc) + if err != nil { + return fmt.Errorf("failed to add L1 chain: %w", err) } + s.l1 = l1 } else { l2, err := chainFromDescriptor(chainDesc) if err != nil { diff --git a/devnet-sdk/system/system_test.go b/devnet-sdk/system/system_test.go index 9a66dc15dcf..29fe3931b55 100644 --- a/devnet-sdk/system/system_test.go +++ b/devnet-sdk/system/system_test.go @@ -164,7 +164,7 @@ func TestSystemFromDevnet(t *testing.T) { } func TestWallet(t *testing.T) { - chain := newChain("1", "http://localhost:8545", nil, nil) + chain := newChain("1", "http://localhost:8545", nil, nil, map[string]types.Address{}) tests := []struct { name string @@ -204,7 +204,7 @@ func TestWallet(t *testing.T) { } func TestChainUser(t *testing.T) { - chain := newChain("1", "http://localhost:8545", nil, nil) + chain := newChain("1", "http://localhost:8545", nil, nil, map[string]types.Address{}) testWallet, err := newWallet("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", common.HexToAddress("0x123"), chain) assert.Nil(t, err) diff --git a/devnet-sdk/system/txbuilder_test.go b/devnet-sdk/system/txbuilder_test.go index 21e220795ed..64fcf90a62a 100644 --- a/devnet-sdk/system/txbuilder_test.go +++ b/devnet-sdk/system/txbuilder_test.go @@ -6,6 +6,7 @@ import ( "math/big" "testing" + "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" "github.com/ethereum-optimism/optimism/devnet-sdk/types" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -116,6 +117,11 @@ func (m *mockChain) Config() (*params.ChainConfig, error) { return nil, fmt.Errorf("not implemented for mock chain") } +func (m *mockChain) Addresses() descriptors.AddressMap { + args := m.Called() + return args.Get(0).(descriptors.AddressMap) +} + type mockNode struct { mock.Mock } diff --git a/devnet-sdk/system/txprocessor_test.go b/devnet-sdk/system/txprocessor_test.go index aed1228e79c..e0b74441e3f 100644 --- a/devnet-sdk/system/txprocessor_test.go +++ b/devnet-sdk/system/txprocessor_test.go @@ -6,6 +6,7 @@ import ( "math/big" "testing" + devnetsdktypes "github.com/ethereum-optimism/optimism/devnet-sdk/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/stretchr/testify/assert" @@ -26,7 +27,7 @@ func TestTransactionProcessor_Sign(t *testing.T) { client := new(mockEthClient) // Create a wallet with the test key - chain := newChain(chainID.String(), "http://localhost:8545", nil, nil) + chain := newChain(chainID.String(), "http://localhost:8545", nil, nil, map[string]devnetsdktypes.Address{}) wallet, err := newWallet(testKey, testAddr, chain) assert.NoError(t, err) diff --git a/devnet-sdk/testing/systest/testing_test.go b/devnet-sdk/testing/systest/testing_test.go index 9146ad39d16..aeb31d94ba8 100644 --- a/devnet-sdk/testing/systest/testing_test.go +++ b/devnet-sdk/testing/systest/testing_test.go @@ -7,12 +7,12 @@ import ( "os" "testing" + "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" "github.com/ethereum-optimism/optimism/devnet-sdk/shell/env" "github.com/ethereum-optimism/optimism/devnet-sdk/system" "github.com/ethereum-optimism/optimism/devnet-sdk/types" "github.com/ethereum/go-ethereum/common" - ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/params" "github.com/stretchr/testify/require" @@ -103,8 +103,8 @@ func (m *mockChain) SupportsEIP(ctx context.Context, eip uint64) bool { func (m *mockChain) Config() (*params.ChainConfig, error) { return nil, fmt.Errorf("not implemented on mockChain") } -func (m *mockChain) BlockByNumber(ctx context.Context, number *big.Int) (*ethtypes.Block, error) { - return nil, fmt.Errorf("not implemented on mockChain") +func (m *mockChain) Addresses() descriptors.AddressMap { + return descriptors.AddressMap{} } // mockSystem implements a minimal system.System for testing diff --git a/devnet-sdk/testing/testlib/validators/validators_test.go b/devnet-sdk/testing/testlib/validators/validators_test.go index af81628e22d..8313a649324 100644 --- a/devnet-sdk/testing/testlib/validators/validators_test.go +++ b/devnet-sdk/testing/testlib/validators/validators_test.go @@ -6,6 +6,7 @@ import ( "math/big" "testing" + "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" "github.com/ethereum-optimism/optimism/devnet-sdk/system" "github.com/ethereum-optimism/optimism/devnet-sdk/testing/systest" @@ -265,6 +266,10 @@ func (m *mockChain) Node() system.Node { return newMockNode(m.config) } +func (m *mockChain) Addresses() descriptors.AddressMap { + return descriptors.AddressMap{} +} + type mockNode struct { chainConfig *params.ChainConfig } diff --git a/devnet-sdk/testing/testlib/validators/wallet.go b/devnet-sdk/testing/testlib/validators/wallet.go index 4757ec03046..b8065831126 100644 --- a/devnet-sdk/testing/testlib/validators/wallet.go +++ b/devnet-sdk/testing/testlib/validators/wallet.go @@ -12,10 +12,9 @@ import ( type WalletGetter = func(context.Context) system.Wallet -func walletFundsValidator(chainIdx uint64, minFunds types.Balance, userMarker interface{}) systest.PreconditionValidator { +func walletFundsValidator(chain system.Chain, minFunds types.Balance, userMarker interface{}) systest.PreconditionValidator { constraint := constraints.WithBalance(minFunds) return func(t systest.T, sys system.System) (context.Context, error) { - chain := sys.L2s()[chainIdx] wallets, err := chain.Wallets(t.Context()) if err != nil { return nil, err @@ -28,14 +27,28 @@ func walletFundsValidator(chainIdx uint64, minFunds types.Balance, userMarker in } return nil, fmt.Errorf("no available wallet with balance of at least of %s", minFunds) - } } -func AcquireL2WalletWithFunds(chainIdx uint64, minFunds types.Balance) (WalletGetter, systest.PreconditionValidator) { - userMarker := new(byte) - validator := walletFundsValidator(chainIdx, minFunds, userMarker) +func AcquireL2WalletWithFunds(chainIndex uint64, minFunds types.Balance) (WalletGetter, systest.PreconditionValidator) { + walletMarker := new(byte) return func(ctx context.Context) system.Wallet { - return ctx.Value(userMarker).(system.Wallet) - }, validator + return ctx.Value(walletMarker).(system.Wallet) + }, func(t systest.T, sys system.System) (context.Context, error) { + if len(sys.L2s()) <= int(chainIndex) { + return nil, fmt.Errorf("chain index %d out of range, only %d L2 chains available", chainIndex, len(sys.L2s())) + } + chain := sys.L2s()[chainIndex] + return walletFundsValidator(chain, minFunds, walletMarker)(t, sys) + } +} + +func AcquireL1WalletWithFunds(minFunds types.Balance) (WalletGetter, systest.PreconditionValidator) { + walletMarker := new(byte) + return func(ctx context.Context) system.Wallet { + return ctx.Value(walletMarker).(system.Wallet) + }, func(t systest.T, sys system.System) (context.Context, error) { + chain := sys.L1() + return walletFundsValidator(chain, minFunds, walletMarker)(t, sys) + } } diff --git a/kurtosis-devnet/pkg/kurtosis/kurtosis.go b/kurtosis-devnet/pkg/kurtosis/kurtosis.go index f0938a73ffb..b79d0d165c0 100644 --- a/kurtosis-devnet/pkg/kurtosis/kurtosis.go +++ b/kurtosis-devnet/pkg/kurtosis/kurtosis.go @@ -180,7 +180,7 @@ func (d *KurtosisDeployer) GetEnvironmentInfo(ctx context.Context, spec *spec.En } if deployerState.State != nil { chain.Addresses = descriptors.AddressMap(deployerState.State.Addresses) - chain.Wallets = d.getWallets(deployerState.Wallets) + chain.Wallets = d.getWallets(deployerState.L1ValidatorWallets) } env.L1 = chain } @@ -202,7 +202,7 @@ func (d *KurtosisDeployer) GetEnvironmentInfo(ctx context.Context, spec *spec.En if deployment, ok := deployerState.State.Deployments[chainSpec.NetworkID]; ok { chain.Addresses = descriptors.AddressMap(deployment.Addresses) chain.Config = deployment.Config - chain.Wallets = d.getWallets(deployment.Wallets) + chain.Wallets = d.getWallets(append(deployment.L2Wallets, deployment.L1Wallets...)) } } diff --git a/kurtosis-devnet/pkg/kurtosis/kurtosis_test.go b/kurtosis-devnet/pkg/kurtosis/kurtosis_test.go index 74821607a9f..8ecbcf063f7 100644 --- a/kurtosis-devnet/pkg/kurtosis/kurtosis_test.go +++ b/kurtosis-devnet/pkg/kurtosis/kurtosis_test.go @@ -201,7 +201,7 @@ func TestDeploy(t *testing.T) { }), WithKurtosisEnclaveObserver(&fakeEnclaveObserver{ state: &deployer.DeployerData{ - Wallets: testWallets, + L1ValidatorWallets: testWallets, }, err: tt.deployerErr, }), @@ -272,7 +272,7 @@ func TestGetEnvironmentInfo(t *testing.T) { name: "successful environment info with JWT", spec: testSpec, inspect: &inspect.InspectData{UserServices: testServices}, - deploy: &deployer.DeployerData{Wallets: testWallets}, + deploy: &deployer.DeployerData{L1ValidatorWallets: testWallets}, jwt: testJWTs, want: &KurtosisEnvironment{ DevnetEnvironment: descriptors.DevnetEnvironment{ diff --git a/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go b/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go index 3b9cf87d829..d4ad22d37f7 100644 --- a/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go +++ b/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go @@ -12,8 +12,11 @@ import ( ktfs "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" "github.com/ethereum-optimism/optimism/devnet-sdk/types" + "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" ) @@ -35,7 +38,8 @@ type DeploymentStateAddresses map[string]DeploymentAddresses type DeploymentState struct { Addresses DeploymentAddresses `json:"addresses"` - Wallets WalletList `json:"wallets"` + L1Wallets WalletList `json:"l1_wallets"` + L2Wallets WalletList `json:"l2_wallets"` Config *params.ChainConfig `json:"chain_config"` } @@ -62,8 +66,8 @@ type Wallet struct { type WalletList []*Wallet type DeployerData struct { - Wallets WalletList `json:"wallets"` - State *DeployerState `json:"state"` + L1ValidatorWallets WalletList `json:"wallets"` + State *DeployerState `json:"state"` } type Deployer struct { @@ -273,14 +277,14 @@ func (d *Deployer) ExtractData(ctx context.Context) (*DeployerData, error) { return nil, err } - a, err := fs.GetArtifact(ctx, d.deployerArtifactName) + deployerArtifact, err := fs.GetArtifact(ctx, d.deployerArtifactName) if err != nil { return nil, err } stateBuffer := bytes.NewBuffer(nil) walletsBuffer := bytes.NewBuffer(nil) - if err := a.ExtractFiles( + if err := deployerArtifact.ExtractFiles( ktfs.NewArtifactFileWriter(d.stateName, stateBuffer), ktfs.NewArtifactFileWriter(d.walletsName, walletsBuffer), ); err != nil { @@ -297,26 +301,26 @@ func (d *Deployer) ExtractData(ctx context.Context) (*DeployerData, error) { return nil, err } - for id, l1Wallets := range l1WalletsForL2Admin { - if deployment, exists := state.Deployments[id]; exists { - deployment.Wallets = l1Wallets - state.Deployments[id] = deployment - } + // Generate test wallets from the standard "test test test..." mnemonic + // These are the same wallets funded in L2Genesis.s.sol's devAccounts array + devWallets, err := d.getDevWallets() + if err != nil { + return nil, err } - // retrieve L2 genesis files for id, deployment := range state.Deployments { + if l1Wallets, exists := l1WalletsForL2Admin[id]; exists { + deployment.L1Wallets = l1Wallets + } + deployment.L2Wallets = devWallets + genesisBuffer := bytes.NewBuffer(nil) genesisName, err := d.renderGenesisNameTemplate(id) if err != nil { return nil, err } - a, err = fs.GetArtifact(ctx, d.deployerArtifactName) - if err != nil { - return nil, err - } - if err := a.ExtractFiles( + if err := deployerArtifact.ExtractFiles( ktfs.NewArtifactFileWriter(genesisName, genesisBuffer), ); err != nil { return nil, err @@ -333,14 +337,14 @@ func (d *Deployer) ExtractData(ctx context.Context) (*DeployerData, error) { state.Deployments[id] = deployment } - l1ValidatorWallets, err := d.getKnownWallets(ctx, fs) + l1ValidatorWallets, err := d.getL1ValidatorWallets(deployerArtifact) if err != nil { return nil, err } return &DeployerData{ - State: state, - Wallets: l1ValidatorWallets, + State: state, + L1ValidatorWallets: l1ValidatorWallets, }, nil } @@ -358,3 +362,35 @@ func (d *Deployer) renderGenesisNameTemplate(chainID string) (string, error) { return buf.String(), nil } + +// getDevWallets generates the set of test wallets used in L2Genesis.s.sol +// These wallets are derived from the standard test mnemonic +func (d *Deployer) getDevWallets() ([]*Wallet, error) { + m, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) + if err != nil { + return nil, fmt.Errorf("failed to create mnemonic dev keys: %w", err) + } + + // Generate 30 wallets to match L2Genesis.s.sol's devAccounts array + testWallets := make([]*Wallet, 0, 30) + for i := 0; i < 30; i++ { + key := devkeys.UserKey(uint64(i)) + addr, err := m.Address(key) + if err != nil { + return nil, fmt.Errorf("failed to get address for test wallet %d: %w", i, err) + } + + sec, err := m.Secret(key) + if err != nil { + return nil, fmt.Errorf("failed to get secret key for test wallet %d: %w", i, err) + } + + testWallets = append(testWallets, &Wallet{ + Name: fmt.Sprintf("dev-account-%d", i), + Address: addr, + PrivateKey: hexutil.Bytes(crypto.FromECDSA(sec)).String(), + }) + } + + return testWallets, nil +} diff --git a/kurtosis-devnet/pkg/kurtosis/sources/deployer/wallets.go b/kurtosis-devnet/pkg/kurtosis/sources/deployer/wallets.go index 3dcb5cab6f1..7247fe3b913 100644 --- a/kurtosis-devnet/pkg/kurtosis/sources/deployer/wallets.go +++ b/kurtosis-devnet/pkg/kurtosis/sources/deployer/wallets.go @@ -2,7 +2,6 @@ package deployer import ( "bytes" - "context" "fmt" "io" @@ -34,14 +33,9 @@ func getMnemonics(r io.Reader) (string, error) { return config[0].Mnemonic, nil } -func (d *Deployer) getKnownWallets(ctx context.Context, fs *ktfs.EnclaveFS) ([]*Wallet, error) { - a, err := fs.GetArtifact(ctx, d.genesisArtifactName) - if err != nil { - return nil, err - } - +func (d *Deployer) getL1ValidatorWallets(deployerArtifact *ktfs.Artifact) ([]*Wallet, error) { mnemonicsBuffer := bytes.NewBuffer(nil) - if err := a.ExtractFiles( + if err := deployerArtifact.ExtractFiles( ktfs.NewArtifactFileWriter(d.mnemonicsName, mnemonicsBuffer), ); err != nil { return nil, err diff --git a/kurtosis-devnet/tests/interop/mocks_test.go b/kurtosis-devnet/tests/interop/mocks_test.go index c02f5c8b0e1..2bc2305d598 100644 --- a/kurtosis-devnet/tests/interop/mocks_test.go +++ b/kurtosis-devnet/tests/interop/mocks_test.go @@ -10,13 +10,13 @@ import ( "time" "github.com/ethereum-optimism/optimism/devnet-sdk/contracts/registry/empty" + "github.com/ethereum-optimism/optimism/devnet-sdk/descriptors" "github.com/ethereum-optimism/optimism/devnet-sdk/interfaces" "github.com/ethereum-optimism/optimism/devnet-sdk/system" "github.com/ethereum-optimism/optimism/devnet-sdk/testing/systest" "github.com/ethereum-optimism/optimism/devnet-sdk/types" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/params" ) @@ -161,8 +161,8 @@ func (m *mockFailingChain) SupportsEIP(ctx context.Context, eip uint64) bool { func (m *mockFailingChain) Config() (*params.ChainConfig, error) { return nil, fmt.Errorf("not implemented") } -func (m *mockFailingChain) BlockByNumber(ctx context.Context, number *big.Int) (*ethtypes.Block, error) { - return nil, fmt.Errorf("not implemented") +func (m *mockFailingChain) Addresses() descriptors.AddressMap { + return map[string]common.Address{} } // mockFailingSystem implements system.System From 6f68dc35e103278e366d2b8ba178ca87bbaacb0c Mon Sep 17 00:00:00 2001 From: Park Changwan Date: Fri, 7 Mar 2025 22:22:03 +0900 Subject: [PATCH 093/130] kt-devnet: add missing network params for supervisor (#14708) --- kurtosis-devnet/interop.yaml | 1 + kurtosis-devnet/templates/devnet.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/kurtosis-devnet/interop.yaml b/kurtosis-devnet/interop.yaml index 0a0b4d08b59..97899b40841 100644 --- a/kurtosis-devnet/interop.yaml +++ b/kurtosis-devnet/interop.yaml @@ -37,6 +37,7 @@ optimism_package: } extra_params: - {{ $flags.log_level }} + network: "kurtosis" chains: - participants: - el_type: op-geth diff --git a/kurtosis-devnet/templates/devnet.yaml b/kurtosis-devnet/templates/devnet.yaml index 233e90c4f91..d38eb437932 100644 --- a/kurtosis-devnet/templates/devnet.yaml +++ b/kurtosis-devnet/templates/devnet.yaml @@ -15,6 +15,7 @@ optimism_package: image: {{ dig "overrides" "images" "op_supervisor" (localDockerImage "op-supervisor") $context }} extra_params: - {{ dig "overrides" "flags" "log_level" "!!str" $context }} + network: "kurtosis" {{ end }} chains: {{ range $l2_id, $l2 := $l2s }} From 9a81b5b94a31de85fc8308f7694e5bfd352fa536 Mon Sep 17 00:00:00 2001 From: zhiqiangxu <652732310@qq.com> Date: Sat, 8 Mar 2025 00:30:11 +0800 Subject: [PATCH 094/130] show more error info when EngineAPIError is returned (#14673) --- op-node/rollup/engine/engine_controller.go | 6 +++--- op-node/rollup/engine/engine_update.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/op-node/rollup/engine/engine_controller.go b/op-node/rollup/engine/engine_controller.go index 67ea651c994..9a37ff7073e 100644 --- a/op-node/rollup/engine/engine_controller.go +++ b/op-node/rollup/engine/engine_controller.go @@ -342,7 +342,7 @@ func (e *EngineController) TryUpdateEngine(ctx context.Context) error { if errors.As(err, &rpcErr) { switch eth.ErrorCode(rpcErr.ErrorCode()) { case eth.InvalidForkchoiceState: - return derive.NewResetError(fmt.Errorf("forkchoice update was inconsistent with engine, need reset to resolve: %w", rpcErr)) + return derive.NewResetError(fmt.Errorf("forkchoice update was inconsistent with engine, need reset to resolve: %w", err)) default: return derive.NewTemporaryError(fmt.Errorf("unexpected error code in forkchoice-updated response: %w", err)) } @@ -423,7 +423,7 @@ func (e *EngineController) InsertUnsafePayload(ctx context.Context, envelope *et if errors.As(err, &rpcErr) { switch eth.ErrorCode(rpcErr.ErrorCode()) { case eth.InvalidForkchoiceState: - return derive.NewResetError(fmt.Errorf("pre-unsafe-block forkchoice update was inconsistent with engine, need reset to resolve: %w", rpcErr)) + return derive.NewResetError(fmt.Errorf("pre-unsafe-block forkchoice update was inconsistent with engine, need reset to resolve: %w", err)) default: return derive.NewTemporaryError(fmt.Errorf("unexpected error code in forkchoice-updated response: %w", err)) } @@ -513,7 +513,7 @@ func (e *EngineController) TryBackupUnsafeReorg(ctx context.Context) (bool, erro switch eth.ErrorCode(rpcErr.ErrorCode()) { case eth.InvalidForkchoiceState: e.SetBackupUnsafeL2Head(eth.L2BlockRef{}, false) - return true, derive.NewResetError(fmt.Errorf("forkchoice update was inconsistent with engine, need reset to resolve: %w", rpcErr)) + return true, derive.NewResetError(fmt.Errorf("forkchoice update was inconsistent with engine, need reset to resolve: %w", err)) default: // Retry when forkChoiceUpdate returns non-input error. // Do not reset backupUnsafeHead because it will be used again. diff --git a/op-node/rollup/engine/engine_update.go b/op-node/rollup/engine/engine_update.go index dbf578d3037..7ea984388cb 100644 --- a/op-node/rollup/engine/engine_update.go +++ b/op-node/rollup/engine/engine_update.go @@ -89,9 +89,9 @@ func startPayload(ctx context.Context, eng ExecEngine, fc eth.ForkchoiceState, a if errors.As(err, &rpcErr) { switch code := eth.ErrorCode(rpcErr.ErrorCode()); code { case eth.InvalidForkchoiceState: - return eth.PayloadID{}, BlockInsertPrestateErr, fmt.Errorf("pre-block-creation forkchoice update was inconsistent with engine, need reset to resolve: %w", rpcErr) + return eth.PayloadID{}, BlockInsertPrestateErr, fmt.Errorf("pre-block-creation forkchoice update was inconsistent with engine, need reset to resolve: %w", err) case eth.InvalidPayloadAttributes: - return eth.PayloadID{}, BlockInsertPayloadErr, fmt.Errorf("payload attributes are not valid, cannot build block: %w", rpcErr) + return eth.PayloadID{}, BlockInsertPayloadErr, fmt.Errorf("payload attributes are not valid, cannot build block: %w", err) default: if code.IsEngineError() { return eth.PayloadID{}, BlockInsertPrestateErr, fmt.Errorf("unexpected engine error code in forkchoice-updated response: %w", err) From 46736afca8ab77d124d25598d0b4878ba2b1a8bd Mon Sep 17 00:00:00 2001 From: Teddy Knox Date: Fri, 7 Mar 2025 11:47:21 -0500 Subject: [PATCH 095/130] Misc renames for clarity to fields in devnet-sdk and kurtosis-devnet (#14705) * Improve address management in kurtosis-devnet and devnet-sdk * Perform various renames for clarity in kurtosis-devnet and devnet-sdk --- devnet-sdk/book/src/shell.md | 3 +- .../pkg/kurtosis/sources/deployer/deployer.go | 36 +++++++++---------- .../pkg/kurtosis/sources/deployer/wallets.go | 6 ++-- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/devnet-sdk/book/src/shell.md b/devnet-sdk/book/src/shell.md index e5a4e91e314..f0ad9cad1dc 100644 --- a/devnet-sdk/book/src/shell.md +++ b/devnet-sdk/book/src/shell.md @@ -53,7 +53,7 @@ export ETH_JWT_SECRET=... ```bash # Enter devnet shell -go run devnet-sdk/shll/cmd/enter/main.go --descriptor devnet.json --chain ... +go run devnet-sdk/shell/cmd/enter/main.go --descriptor devnet.json --chain ... # Now you can use tools directly cast block latest @@ -72,6 +72,7 @@ exit ## Implementation Details The shell integration: + 1. Reads the descriptor file 2. Sets up environment variables based on the descriptor content 3. Creates a new shell session with the configured environment diff --git a/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go b/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go index d4ad22d37f7..2a523774b34 100644 --- a/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go +++ b/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go @@ -25,7 +25,7 @@ const ( defaultWalletsName = "wallets.json" defaultStateName = "state.json" defaultGenesisArtifactName = "el_cl_genesis_data" - defaultMnemonicsName = "mnemonics.yaml" + defaultMnemonicName = "mnemonics.yaml" defaultGenesisNameTemplate = "genesis-{{.ChainID}}.json" defaultL1GenesisName = "genesis.json" ) @@ -71,14 +71,14 @@ type DeployerData struct { } type Deployer struct { - enclave string - deployerArtifactName string - walletsName string - stateName string - genesisArtifactName string - mnemonicsName string - l2GenesisNameTemplate string - l1GenesisName string + enclave string + deployerArtifactName string + walletsName string + stateName string + genesisArtifactName string + l1ValidatorMnemonicName string + l2GenesisNameTemplate string + l1GenesisName string } type DeployerOption func(*Deployer) @@ -109,7 +109,7 @@ func WithGenesisArtifactName(name string) DeployerOption { func WithMnemonicsName(name string) DeployerOption { return func(d *Deployer) { - d.mnemonicsName = name + d.l1ValidatorMnemonicName = name } } @@ -121,14 +121,14 @@ func WithGenesisNameTemplate(name string) DeployerOption { func NewDeployer(enclave string, opts ...DeployerOption) *Deployer { d := &Deployer{ - enclave: enclave, - deployerArtifactName: defaultDeployerArtifactName, - walletsName: defaultWalletsName, - stateName: defaultStateName, - genesisArtifactName: defaultGenesisArtifactName, - mnemonicsName: defaultMnemonicsName, - l2GenesisNameTemplate: defaultGenesisNameTemplate, - l1GenesisName: defaultL1GenesisName, + enclave: enclave, + deployerArtifactName: defaultDeployerArtifactName, + walletsName: defaultWalletsName, + stateName: defaultStateName, + genesisArtifactName: defaultGenesisArtifactName, + l1ValidatorMnemonicName: defaultMnemonicName, + l2GenesisNameTemplate: defaultGenesisNameTemplate, + l1GenesisName: defaultL1GenesisName, } for _, opt := range opts { diff --git a/kurtosis-devnet/pkg/kurtosis/sources/deployer/wallets.go b/kurtosis-devnet/pkg/kurtosis/sources/deployer/wallets.go index 7247fe3b913..55744bea86b 100644 --- a/kurtosis-devnet/pkg/kurtosis/sources/deployer/wallets.go +++ b/kurtosis-devnet/pkg/kurtosis/sources/deployer/wallets.go @@ -36,17 +36,17 @@ func getMnemonics(r io.Reader) (string, error) { func (d *Deployer) getL1ValidatorWallets(deployerArtifact *ktfs.Artifact) ([]*Wallet, error) { mnemonicsBuffer := bytes.NewBuffer(nil) if err := deployerArtifact.ExtractFiles( - ktfs.NewArtifactFileWriter(d.mnemonicsName, mnemonicsBuffer), + ktfs.NewArtifactFileWriter(d.l1ValidatorMnemonicName, mnemonicsBuffer), ); err != nil { return nil, err } - mnemonics, err := getMnemonics(mnemonicsBuffer) + mnemonic, err := getMnemonics(mnemonicsBuffer) if err != nil { return nil, err } - m, _ := devkeys.NewMnemonicDevKeys(mnemonics) + m, _ := devkeys.NewMnemonicDevKeys(mnemonic) knownWallets := make([]*Wallet, 0) var keys []devkeys.Key From 8a5ee6bb8cba7efbe69c8d40accf286caddd01a9 Mon Sep 17 00:00:00 2001 From: Yashvardhan Kukreja Date: Fri, 7 Mar 2025 12:29:13 -0500 Subject: [PATCH 096/130] fix(op-deployer): goreleaser linker flags for rendering version (#14344) Signed-off-by: Yashvardhan Kukreja --- op-deployer/.goreleaser.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/op-deployer/.goreleaser.yaml b/op-deployer/.goreleaser.yaml index 5040ed4bc4d..eaf05a78dc9 100644 --- a/op-deployer/.goreleaser.yaml +++ b/op-deployer/.goreleaser.yaml @@ -32,8 +32,8 @@ builds: ldflags: - -X main.GitCommit={{ .FullCommit }} - -X main.GitDate={{ .CommitDate }} - - -X github.com/ethereum-optimism/optimism/op-chain-ops/deployer/version.Version={{ .Version }} - - -X github.com/ethereum-optimism/optimism/op-chain-ops/deployer/version.Meta="unstable" + - -X github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/version.Version={{ .Version }} + - -X github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/version.Meta= archives: - format: tar.gz From aaedad608d4bda3fda8d784497966208ed00a28f Mon Sep 17 00:00:00 2001 From: Delweng Date: Sat, 8 Mar 2025 02:40:08 +0800 Subject: [PATCH 097/130] feat: rm unnecessary generic type arguments (#12663) * op-chain-ops: rm generic unnecessary type arguments Signed-off-by: jsvisa * op-conductor: rm generic unnecessary type arguments Signed-off-by: jsvisa * op-service: rm generic unnecessary type arguments Signed-off-by: jsvisa * op-chain-ops: rm +1 Signed-off-by: jsvisa --------- Signed-off-by: jsvisa --- op-chain-ops/script/cheatcodes_external.go | 56 ++++++++++----------- op-conductor/conductor/service_test.go | 4 +- op-service/sources/l1_beacon_client_test.go | 4 +- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/op-chain-ops/script/cheatcodes_external.go b/op-chain-ops/script/cheatcodes_external.go index afb42de58f6..5b1b125d4e3 100644 --- a/op-chain-ops/script/cheatcodes_external.go +++ b/op-chain-ops/script/cheatcodes_external.go @@ -137,51 +137,51 @@ func envOrList[E any](key string, } func (c *CheatCodesPrecompile) EnvOr_4777f3cf(key string, defaultValue bool) (bool, error) { - return envOrSingular[bool](key, c.h.GetEnvVar, c.ParseBool, defaultValue) + return envOrSingular(key, c.h.GetEnvVar, c.ParseBool, defaultValue) } func (c *CheatCodesPrecompile) EnvOr_5e97348f(key string, defaultValue *big.Int) (*big.Int, error) { - return envOrSingular[*big.Int](key, c.h.GetEnvVar, c.ParseUint, defaultValue) + return envOrSingular(key, c.h.GetEnvVar, c.ParseUint, defaultValue) } func (c *CheatCodesPrecompile) EnvOr_bbcb713e(key string, defaultValue *ABIInt256) (*ABIInt256, error) { - return envOrSingular[*ABIInt256](key, c.h.GetEnvVar, c.ParseInt, defaultValue) + return envOrSingular(key, c.h.GetEnvVar, c.ParseInt, defaultValue) } func (c *CheatCodesPrecompile) EnvOr_561fe540(key string, defaultValue common.Address) (common.Address, error) { - return envOrSingular[common.Address](key, c.h.GetEnvVar, c.ParseAddress, defaultValue) + return envOrSingular(key, c.h.GetEnvVar, c.ParseAddress, defaultValue) } func (c *CheatCodesPrecompile) EnvOr_b4a85892(key string, defaultValue [32]byte) ([32]byte, error) { - return envOrSingular[[32]byte](key, c.h.GetEnvVar, c.ParseBytes32, defaultValue) + return envOrSingular(key, c.h.GetEnvVar, c.ParseBytes32, defaultValue) } func (c *CheatCodesPrecompile) EnvOr_d145736c(key string, defaultValue string) (string, error) { - return envOrSingular[string](key, c.h.GetEnvVar, func(v string) (string, error) { + return envOrSingular(key, c.h.GetEnvVar, func(v string) (string, error) { return v, nil }, defaultValue) } func (c *CheatCodesPrecompile) EnvOr_b3e47705(key string, defaultValue []byte) ([]byte, error) { - return envOrSingular[[]byte](key, c.h.GetEnvVar, c.ParseBytes, defaultValue) + return envOrSingular(key, c.h.GetEnvVar, c.ParseBytes, defaultValue) } func (c *CheatCodesPrecompile) EnvOr_eb85e83b(key string, delimiter string, defaultValue []bool) ([]bool, error) { - return envOrList[bool](key, c.h.GetEnvVar, delimiter, c.ParseBool, defaultValue) + return envOrList(key, c.h.GetEnvVar, delimiter, c.ParseBool, defaultValue) } func (c *CheatCodesPrecompile) EnvOr_74318528(key string, delimiter string, defaultValue []*big.Int) ([]*big.Int, error) { - return envOrList[*big.Int](key, c.h.GetEnvVar, delimiter, c.ParseUint, defaultValue) + return envOrList(key, c.h.GetEnvVar, delimiter, c.ParseUint, defaultValue) } func (c *CheatCodesPrecompile) EnvOr_4700d74b(key string, delimiter string, defaultValue []*ABIInt256) ([]*ABIInt256, error) { - return envOrList[*ABIInt256](key, c.h.GetEnvVar, delimiter, c.ParseInt, defaultValue) + return envOrList(key, c.h.GetEnvVar, delimiter, c.ParseInt, defaultValue) } func (c *CheatCodesPrecompile) EnvOr_c74e9deb(key string, delimiter string, defaultValue []common.Address) ([]common.Address, error) { - return envOrList[common.Address](key, c.h.GetEnvVar, delimiter, c.ParseAddress, defaultValue) + return envOrList(key, c.h.GetEnvVar, delimiter, c.ParseAddress, defaultValue) } func (c *CheatCodesPrecompile) EnvOr_2281f367(key string, delimiter string, defaultValue [][32]byte) ([][32]byte, error) { - return envOrList[[32]byte](key, c.h.GetEnvVar, delimiter, c.ParseBytes32, defaultValue) + return envOrList(key, c.h.GetEnvVar, delimiter, c.ParseBytes32, defaultValue) } func (c *CheatCodesPrecompile) EnvOr_859216bc(key string, delimiter string, defaultValue []string) ([]string, error) { - return envOrList[string](key, c.h.GetEnvVar, delimiter, func(v string) (string, error) { + return envOrList(key, c.h.GetEnvVar, delimiter, func(v string) (string, error) { return v, nil }, defaultValue) } func (c *CheatCodesPrecompile) EnvOr_64bc3e64(key string, delimiter string, defaultValue [][]byte) ([][]byte, error) { - return envOrList[[]byte](key, c.h.GetEnvVar, delimiter, c.ParseBytes, defaultValue) + return envOrList(key, c.h.GetEnvVar, delimiter, c.ParseBytes, defaultValue) } func envSingular[E any](key string, @@ -221,69 +221,69 @@ func envList[E any](key string, // EnvBool implements https://book.getfoundry.sh/cheatcodes/env-bool func (c *CheatCodesPrecompile) EnvBool_7ed1ec7d(key string) (bool, error) { - return envSingular[bool](key, c.h.GetEnvVar, c.ParseBool) + return envSingular(key, c.h.GetEnvVar, c.ParseBool) } func (c *CheatCodesPrecompile) EnvBool_aaaddeaf(key string, delimiter string) ([]bool, error) { - return envList[bool](key, c.h.GetEnvVar, delimiter, c.ParseBool) + return envList(key, c.h.GetEnvVar, delimiter, c.ParseBool) } // EnvUint implements https://book.getfoundry.sh/cheatcodes/env-uint func (c *CheatCodesPrecompile) EnvUint_c1978d1f(key string) (*big.Int, error) { - return envSingular[*big.Int](key, c.h.GetEnvVar, c.ParseUint) + return envSingular(key, c.h.GetEnvVar, c.ParseUint) } func (c *CheatCodesPrecompile) EnvUint_f3dec099(key string, delimiter string) ([]*big.Int, error) { - return envList[*big.Int](key, c.h.GetEnvVar, delimiter, c.ParseUint) + return envList(key, c.h.GetEnvVar, delimiter, c.ParseUint) } // EnvInt implements https://book.getfoundry.sh/cheatcodes/env-int func (c *CheatCodesPrecompile) EnvInt_892a0c61(key string) (*ABIInt256, error) { - return envSingular[*ABIInt256](key, c.h.GetEnvVar, c.ParseInt) + return envSingular(key, c.h.GetEnvVar, c.ParseInt) } func (c *CheatCodesPrecompile) EnvInt_42181150(key string, delimiter string) ([]*ABIInt256, error) { - return envList[*ABIInt256](key, c.h.GetEnvVar, delimiter, c.ParseInt) + return envList(key, c.h.GetEnvVar, delimiter, c.ParseInt) } // EnvAddress implements https://book.getfoundry.sh/cheatcodes/env-address func (c *CheatCodesPrecompile) EnvAddress_350d56bf(key string) (common.Address, error) { - return envSingular[common.Address](key, c.h.GetEnvVar, c.ParseAddress) + return envSingular(key, c.h.GetEnvVar, c.ParseAddress) } func (c *CheatCodesPrecompile) EnvAddress_ad31b9fa(key string, delimiter string) ([]common.Address, error) { - return envList[common.Address](key, c.h.GetEnvVar, delimiter, c.ParseAddress) + return envList(key, c.h.GetEnvVar, delimiter, c.ParseAddress) } // EnvBytes32 implements https://book.getfoundry.sh/cheatcodes/env-bytes32 func (c *CheatCodesPrecompile) EnvBytes32_97949042(key string) ([32]byte, error) { - return envSingular[[32]byte](key, c.h.GetEnvVar, c.ParseBytes32) + return envSingular(key, c.h.GetEnvVar, c.ParseBytes32) } func (c *CheatCodesPrecompile) EnvBytes32_5af231c1(key string, delimiter string) ([][32]byte, error) { - return envList[[32]byte](key, c.h.GetEnvVar, delimiter, c.ParseBytes32) + return envList(key, c.h.GetEnvVar, delimiter, c.ParseBytes32) } // EnvString implements https://book.getfoundry.sh/cheatcodes/env-string func (c *CheatCodesPrecompile) EnvString_f877cb19(key string) (string, error) { - return envSingular[string](key, c.h.GetEnvVar, func(v string) (string, error) { + return envSingular(key, c.h.GetEnvVar, func(v string) (string, error) { return v, nil }) } func (c *CheatCodesPrecompile) EnvString_14b02bc9(key string, delimiter string) ([]string, error) { - return envList[string](key, c.h.GetEnvVar, delimiter, func(v string) (string, error) { + return envList(key, c.h.GetEnvVar, delimiter, func(v string) (string, error) { return v, nil }) } // EnvBytes implements https://book.getfoundry.sh/cheatcodes/env-bytes func (c *CheatCodesPrecompile) EnvBytes_4d7baf06(key string) ([]byte, error) { - return envSingular[[]byte](key, c.h.GetEnvVar, c.ParseBytes) + return envSingular(key, c.h.GetEnvVar, c.ParseBytes) } func (c *CheatCodesPrecompile) EnvBytes_ddc2651b(key string, delimiter string) ([][]byte, error) { - return envList[[]byte](key, c.h.GetEnvVar, delimiter, c.ParseBytes) + return envList(key, c.h.GetEnvVar, delimiter, c.ParseBytes) } // KeyExists implements https://book.getfoundry.sh/cheatcodes/key-exists diff --git a/op-conductor/conductor/service_test.go b/op-conductor/conductor/service_test.go index ae7332af2b9..704b52cc24d 100644 --- a/op-conductor/conductor/service_test.go +++ b/op-conductor/conductor/service_test.go @@ -187,11 +187,11 @@ func updateStatusAndExecuteAction[T any](s *OpConductorTestSuite, ch chan T, sta } func (s *OpConductorTestSuite) updateLeaderStatusAndExecuteAction(status bool) { - updateStatusAndExecuteAction[bool](s, s.leaderUpdateCh, status) + updateStatusAndExecuteAction(s, s.leaderUpdateCh, status) } func (s *OpConductorTestSuite) updateHealthStatusAndExecuteAction(status error) { - updateStatusAndExecuteAction[error](s, s.healthUpdateCh, status) + updateStatusAndExecuteAction(s, s.healthUpdateCh, status) } func (s *OpConductorTestSuite) executeAction() { diff --git a/op-service/sources/l1_beacon_client_test.go b/op-service/sources/l1_beacon_client_test.go index 086cb0f9dae..bf54db7b781 100644 --- a/op-service/sources/l1_beacon_client_test.go +++ b/op-service/sources/l1_beacon_client_test.go @@ -215,14 +215,14 @@ func TestBeaconHTTPClient(t *testing.T) { } func TestClientPoolSingle(t *testing.T) { - p := NewClientPool[int](1) + p := NewClientPool(1) for i := 0; i < 10; i++ { require.Equal(t, 1, p.Get()) p.MoveToNext() } } func TestClientPoolSeveral(t *testing.T) { - p := NewClientPool[int](0, 1, 2, 3) + p := NewClientPool(0, 1, 2, 3) for i := 0; i < 25; i++ { require.Equal(t, i%4, p.Get()) p.MoveToNext() From d1900114668dc40058ce4fe8573ed5c04d8ef040 Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 7 Mar 2025 19:48:30 +0100 Subject: [PATCH 098/130] ops: remove outdated legacy env rc example (#14724) --- .envrc.example | 68 -------------------------------------------------- 1 file changed, 68 deletions(-) delete mode 100644 .envrc.example diff --git a/.envrc.example b/.envrc.example deleted file mode 100644 index 43ccf74842b..00000000000 --- a/.envrc.example +++ /dev/null @@ -1,68 +0,0 @@ -################################################## -# Getting Started # -################################################## - -# Admin account -export GS_ADMIN_ADDRESS= -export GS_ADMIN_PRIVATE_KEY= - -# Batcher account -export GS_BATCHER_ADDRESS= -export GS_BATCHER_PRIVATE_KEY= - -# Proposer account -export GS_PROPOSER_ADDRESS= -export GS_PROPOSER_PRIVATE_KEY= - -# Sequencer account -export GS_SEQUENCER_ADDRESS= -export GS_SEQUENCER_PRIVATE_KEY= - - -################################################## -# Chain Information # -################################################## - -# L1 chain information -export L1_CHAIN_ID=11155111 -export L1_BLOCK_TIME=12 - -# L2 chain information -export L2_CHAIN_ID=42069 -export L2_BLOCK_TIME=2 - -################################################## -# op-node Configuration # -################################################## - -# The kind of RPC provider, used to inform optimal transactions receipts -# fetching. Valid options: alchemy, quicknode, infura, parity, nethermind, -# debug_geth, erigon, basic, any. -export L1_RPC_KIND= - -################################################## -# Contract Deployment # -################################################## - -# RPC URL for the L1 network to interact with -export L1_RPC_URL= - -# Salt used via CREATE2 to determine implementation addresses -# NOTE: If you want to deploy contracts from scratch you MUST reload this -# variable to ensure the salt is regenerated and the contracts are -# deployed to new addresses (otherwise deployment will fail) -export IMPL_SALT=$(openssl rand -hex 32) - -# Name for the deployed network -export DEPLOYMENT_CONTEXT=getting-started - -# Optional Tenderly details for simulation link during deployment -export TENDERLY_PROJECT= -export TENDERLY_USERNAME= - -# Optional Etherscan API key for contract verification -export ETHERSCAN_API_KEY= - -# Private key to use for contract deployments, you don't need to worry about -# this for the Getting Started guide. -export PRIVATE_KEY= From 21d120fca57509ebea696b62d56d187abbef7af4 Mon Sep 17 00:00:00 2001 From: Teddy Knox Date: Fri, 7 Mar 2025 14:37:14 -0500 Subject: [PATCH 099/130] Ingest L1 Chain ID into devnet-sdk (#14704) * Improve address management in kurtosis-devnet and devnet-sdk * Import L1 chain ID into devnet-sdk context --- kurtosis-devnet/pkg/kurtosis/kurtosis.go | 1 + .../pkg/kurtosis/sources/deployer/deployer.go | 7 +++++++ .../pkg/kurtosis/sources/deployer/wallets.go | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/kurtosis-devnet/pkg/kurtosis/kurtosis.go b/kurtosis-devnet/pkg/kurtosis/kurtosis.go index b79d0d165c0..ef394c230fd 100644 --- a/kurtosis-devnet/pkg/kurtosis/kurtosis.go +++ b/kurtosis-devnet/pkg/kurtosis/kurtosis.go @@ -173,6 +173,7 @@ func (d *KurtosisDeployer) GetEnvironmentInfo(ctx context.Context, spec *spec.En finder := NewServiceFinder(inspectResult.UserServices) if nodes, services := finder.FindL1Services(); len(nodes) > 0 { chain := &descriptors.Chain{ + ID: deployerState.L1ChainID, Name: "Ethereum", Services: services, Nodes: nodes, diff --git a/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go b/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go index 2a523774b34..3cbae8a8ca5 100644 --- a/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go +++ b/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go @@ -68,6 +68,7 @@ type WalletList []*Wallet type DeployerData struct { L1ValidatorWallets WalletList `json:"wallets"` State *DeployerState `json:"state"` + L1ChainID string `json:"l1_chain_id"` } type Deployer struct { @@ -342,7 +343,13 @@ func (d *Deployer) ExtractData(ctx context.Context) (*DeployerData, error) { return nil, err } + l1ChainID, err := d.getL1ChainID(deployerArtifact) + if err != nil { + return nil, err + } + return &DeployerData{ + L1ChainID: l1ChainID, State: state, L1ValidatorWallets: l1ValidatorWallets, }, nil diff --git a/kurtosis-devnet/pkg/kurtosis/sources/deployer/wallets.go b/kurtosis-devnet/pkg/kurtosis/sources/deployer/wallets.go index 55744bea86b..76373eb4c19 100644 --- a/kurtosis-devnet/pkg/kurtosis/sources/deployer/wallets.go +++ b/kurtosis-devnet/pkg/kurtosis/sources/deployer/wallets.go @@ -2,12 +2,14 @@ package deployer import ( "bytes" + "encoding/json" "fmt" "io" ktfs "github.com/ethereum-optimism/optimism/devnet-sdk/kt/fs" "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/crypto" "gopkg.in/yaml.v3" ) @@ -67,3 +69,20 @@ func (d *Deployer) getL1ValidatorWallets(deployerArtifact *ktfs.Artifact) ([]*Wa return knownWallets, nil } + +func (d *Deployer) getL1ChainID(genesisArtifact *ktfs.Artifact) (string, error) { + genesisBuffer := bytes.NewBuffer(nil) + if err := genesisArtifact.ExtractFiles( + ktfs.NewArtifactFileWriter(d.l1GenesisName, genesisBuffer), + ); err != nil { + return "", err + } + + // Parse the genesis file JSON into a core.Genesis struct + var genesis core.Genesis + if err := json.NewDecoder(genesisBuffer).Decode(&genesis); err != nil { + return "", fmt.Errorf("failed to parse genesis file %s in artifact %s: %w", d.l1GenesisName, d.genesisArtifactName, err) + } + + return genesis.Config.ChainID.String(), nil +} From a7af9121644c058cc2e31a5899c1abf82fdb67e3 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Fri, 7 Mar 2025 14:58:25 -0500 Subject: [PATCH 100/130] Unfreeze MIPS contracts (#14726) --- .../contracts-bedrock/scripts/checks/check-frozen-files.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh b/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh index 77eec5660b8..95d7f8482f4 100755 --- a/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh +++ b/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh @@ -84,9 +84,9 @@ ALLOWED_FILES=( "src/L2/SuperchainTokenBridge.sol" "src/L2/SuperchainWETH.sol" "src/L2/WETH.sol" - # "src/cannon/MIPS.sol" - # "src/cannon/MIPS2.sol" - # "src/cannon/MIPS64.sol" + "src/cannon/MIPS.sol" + "src/cannon/MIPS2.sol" + "src/cannon/MIPS64.sol" "src/cannon/PreimageOracle.sol" "src/dispute/AnchorStateRegistry.sol" "src/dispute/DelayedWETH.sol" From c7ae618a0eedd2d4898150b57a1f01a9871a0cf2 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Fri, 7 Mar 2025 15:32:45 -0500 Subject: [PATCH 101/130] cannon: Consolidate state version logic (#14725) * Consolidate state version logic, add helpers to simplify updates * Move version-related tests * Use consistent casing --- cannon/cmd/load_elf.go | 14 +--- cannon/mipsevm/versions/detect.go | 6 +- cannon/mipsevm/versions/detect_test.go | 6 +- cannon/mipsevm/versions/state.go | 76 ++--------------- cannon/mipsevm/versions/state64_test.go | 4 +- cannon/mipsevm/versions/state_test.go | 18 +--- cannon/mipsevm/versions/version.go | 105 ++++++++++++++++++++++++ cannon/mipsevm/versions/version_test.go | 17 ++++ cannon/multicannon/exec.go | 3 +- 9 files changed, 143 insertions(+), 106 deletions(-) create mode 100644 cannon/mipsevm/versions/version.go create mode 100644 cannon/mipsevm/versions/version_test.go diff --git a/cannon/cmd/load_elf.go b/cannon/cmd/load_elf.go index d28455479c4..a5ee43d1398 100644 --- a/cannon/cmd/load_elf.go +++ b/cannon/cmd/load_elf.go @@ -20,7 +20,7 @@ import ( var ( LoadELFVMTypeFlag = &cli.StringFlag{ Name: "type", - Usage: "VM type to create state for. Valid options: " + openum.EnumString(stateVersions()), + Usage: "VM type to create state for. Valid options: " + openum.EnumString(versions.GetStateVersionStrings()), Required: true, } LoadELFPathFlag = &cli.PathFlag{ @@ -43,14 +43,6 @@ var ( } ) -func stateVersions() []string { - vers := make([]string, len(versions.StateVersionTypes)) - for i, v := range versions.StateVersionTypes { - vers[i] = v.String() - } - return vers -} - func LoadELF(ctx *cli.Context) error { elfPath := ctx.Path(LoadELFPathFlag.Name) elfProgram, err := elf.Open(elfPath) @@ -69,7 +61,7 @@ func LoadELF(ctx *cli.Context) error { return err } switch ver { - case versions.VersionSingleThreaded2: + case versions.GetCurrentSingleThreaded(): createInitialState = func(f *elf.File) (mipsevm.FPVMState, error) { return program.LoadELF(f, singlethreaded.CreateInitialState) } @@ -80,7 +72,7 @@ func LoadELF(ctx *cli.Context) error { } return program.PatchStack(state) } - case versions.VersionMultiThreaded_v2, versions.VersionMultiThreaded64_v3: + case versions.GetCurrentMultiThreaded(), versions.GetCurrentMultiThreaded64(): createInitialState = func(f *elf.File) (mipsevm.FPVMState, error) { return program.LoadELF(f, multithreaded.CreateInitialState) } diff --git a/cannon/mipsevm/versions/detect.go b/cannon/mipsevm/versions/detect.go index c667ade2824..3e8d4e317df 100644 --- a/cannon/mipsevm/versions/detect.go +++ b/cannon/mipsevm/versions/detect.go @@ -26,10 +26,8 @@ func DetectVersion(path string) (StateVersion, error) { return 0, err } - switch ver { - case VersionSingleThreaded, VersionMultiThreaded, VersionSingleThreaded2, VersionMultiThreaded64, VersionMultiThreaded64_v2, VersionMultiThreaded_v2, VersionMultiThreaded64_v3: - return ver, nil - default: + if !IsValidStateVersion(ver) { return 0, fmt.Errorf("%w: %d", ErrUnknownVersion, ver) } + return ver, nil } diff --git a/cannon/mipsevm/versions/detect_test.go b/cannon/mipsevm/versions/detect_test.go index f0d9e19e97a..efdc650e9f0 100644 --- a/cannon/mipsevm/versions/detect_test.go +++ b/cannon/mipsevm/versions/detect_test.go @@ -50,7 +50,7 @@ func TestDetectVersion_fromFile(t *testing.T) { // Check that the latest supported versions write new states in a way that is detected correctly func TestDetectVersion_singleThreadedBinary(t *testing.T) { - targetVersion := VersionSingleThreaded2 + targetVersion := GetCurrentSingleThreaded() if !arch.IsMips32 { t.Skip("Single-threaded states are not supported for 64-bit VMs") } @@ -64,9 +64,9 @@ func TestDetectVersion_singleThreadedBinary(t *testing.T) { } func TestDetectVersion_multiThreadedBinary(t *testing.T) { - targetVersion := VersionMultiThreaded_v2 + targetVersion := GetCurrentMultiThreaded() if !arch.IsMips32 { - targetVersion = VersionMultiThreaded64_v3 + targetVersion = GetCurrentMultiThreaded64() } state, err := NewFromState(multithreaded.CreateEmptyState()) diff --git a/cannon/mipsevm/versions/state.go b/cannon/mipsevm/versions/state.go index 1e6c17ed142..bba831bfc35 100644 --- a/cannon/mipsevm/versions/state.go +++ b/cannon/mipsevm/versions/state.go @@ -14,34 +14,12 @@ import ( "github.com/ethereum-optimism/optimism/op-service/serialize" ) -type StateVersion uint8 - -const ( - // VersionSingleThreaded is the version of the Cannon STF found in op-contracts/v1.6.0 - https://github.com/ethereum-optimism/optimism/blob/op-contracts/v1.6.0/packages/contracts-bedrock/src/cannon/MIPS.sol - VersionSingleThreaded StateVersion = iota - // VersionMultiThreaded is the original implementation of 32-bit multithreaded cannon, tagged at cannon/v1.3.0 - VersionMultiThreaded - // VersionSingleThreaded2 is based on VersionSingleThreaded with the addition of support for fcntl(F_GETFD) syscall - // This is the latest 32-bit single-threaded vm - VersionSingleThreaded2 - // VersionMultiThreaded64 is the original 64-bit MTCannon implementation (pre-audit), tagged at cannon/v1.2.0 - VersionMultiThreaded64 - // VersionMultiThreaded64_v2 includes an audit fix to ensure futex values are always 32-bit, tagged at cannon/v1.3.0 - VersionMultiThreaded64_v2 - // VersionMultiThreaded_v2 is the latest 32-bit multithreaded vm - VersionMultiThreaded_v2 - // VersionMultiThreaded64_v3 is the latest 64-bit multithreaded vm - VersionMultiThreaded64_v3 -) - var ( ErrUnknownVersion = errors.New("unknown version") ErrJsonNotSupported = errors.New("json not supported") ErrUnsupportedMipsArch = errors.New("mips architecture is not supported") ) -var StateVersionTypes = []StateVersion{VersionSingleThreaded, VersionMultiThreaded, VersionSingleThreaded2, VersionMultiThreaded64, VersionMultiThreaded64_v2, VersionMultiThreaded_v2, VersionMultiThreaded64_v3} - func LoadStateFromFile(path string) (*VersionedState, error) { if !serialize.IsBinaryFile(path) { // Always use singlethreaded for JSON states @@ -61,18 +39,18 @@ func NewFromState(state mipsevm.FPVMState) (*VersionedState, error) { return nil, ErrUnsupportedMipsArch } return &VersionedState{ - Version: VersionSingleThreaded2, + Version: GetCurrentSingleThreaded(), FPVMState: state, }, nil case *multithreaded.State: if arch.IsMips32 { return &VersionedState{ - Version: VersionMultiThreaded_v2, + Version: GetCurrentMultiThreaded(), FPVMState: state, }, nil } else { return &VersionedState{ - Version: VersionMultiThreaded64_v3, + Version: GetCurrentMultiThreaded64(), FPVMState: state, }, nil } @@ -103,7 +81,7 @@ func (s *VersionedState) Deserialize(in io.Reader) error { } switch s.Version { - case VersionSingleThreaded2: + case GetCurrentSingleThreaded(): if !arch.IsMips32 { return ErrUnsupportedMipsArch } @@ -113,7 +91,7 @@ func (s *VersionedState) Deserialize(in io.Reader) error { } s.FPVMState = state return nil - case VersionMultiThreaded_v2: + case GetCurrentMultiThreaded(): if !arch.IsMips32 { return ErrUnsupportedMipsArch } @@ -123,7 +101,7 @@ func (s *VersionedState) Deserialize(in io.Reader) error { } s.FPVMState = state return nil - case VersionMultiThreaded64_v3: + case GetCurrentMultiThreaded64(): if arch.IsMips32 { return ErrUnsupportedMipsArch } @@ -149,45 +127,3 @@ func (s *VersionedState) MarshalJSON() ([]byte, error) { } return json.Marshal(s.FPVMState) } - -func (s StateVersion) String() string { - switch s { - case VersionSingleThreaded: - return "singlethreaded" - case VersionMultiThreaded: - return "multithreaded" - case VersionSingleThreaded2: - return "singlethreaded-2" - case VersionMultiThreaded64: - return "multithreaded64" - case VersionMultiThreaded64_v2: - return "multithreaded64-2" - case VersionMultiThreaded_v2: - return "multithreaded-2" - case VersionMultiThreaded64_v3: - return "multithreaded64-3" - default: - return "unknown" - } -} - -func ParseStateVersion(ver string) (StateVersion, error) { - switch ver { - case "singlethreaded": - return VersionSingleThreaded, nil - case "multithreaded": - return VersionMultiThreaded, nil - case "singlethreaded-2": - return VersionSingleThreaded2, nil - case "multithreaded64": - return VersionMultiThreaded64, nil - case "multithreaded64-2": - return VersionMultiThreaded64_v2, nil - case "multithreaded-2": - return VersionMultiThreaded_v2, nil - case "multithreaded64-3": - return VersionMultiThreaded64_v3, nil - default: - return StateVersion(0), errors.New("unknown state version") - } -} diff --git a/cannon/mipsevm/versions/state64_test.go b/cannon/mipsevm/versions/state64_test.go index d86bee3e81d..b8f74d1db03 100644 --- a/cannon/mipsevm/versions/state64_test.go +++ b/cannon/mipsevm/versions/state64_test.go @@ -19,7 +19,7 @@ func TestNewFromState(t *testing.T) { actual, err := NewFromState(multithreaded.CreateEmptyState()) require.NoError(t, err) require.IsType(t, &multithreaded.State{}, actual.FPVMState) - require.Equal(t, VersionMultiThreaded64_v3, actual.Version) + require.Equal(t, GetCurrentMultiThreaded64(), actual.Version) }) } @@ -40,7 +40,7 @@ func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) { version StateVersion createState func() mipsevm.FPVMState }{ - {VersionMultiThreaded64_v3, func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }}, + {GetCurrentMultiThreaded64(), func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }}, } for _, test := range tests { test := test diff --git a/cannon/mipsevm/versions/state_test.go b/cannon/mipsevm/versions/state_test.go index 1768ecaea03..930906b6fbb 100644 --- a/cannon/mipsevm/versions/state_test.go +++ b/cannon/mipsevm/versions/state_test.go @@ -20,14 +20,14 @@ func TestNewFromState(t *testing.T) { actual, err := NewFromState(singlethreaded.CreateEmptyState()) require.NoError(t, err) require.IsType(t, &singlethreaded.State{}, actual.FPVMState) - require.Equal(t, VersionSingleThreaded2, actual.Version) + require.Equal(t, GetCurrentSingleThreaded(), actual.Version) }) t.Run("multithreaded-latestVersion", func(t *testing.T) { actual, err := NewFromState(multithreaded.CreateEmptyState()) require.NoError(t, err) require.IsType(t, &multithreaded.State{}, actual.FPVMState) - require.Equal(t, VersionMultiThreaded_v2, actual.Version) + require.Equal(t, GetCurrentMultiThreaded(), actual.Version) }) } @@ -58,8 +58,8 @@ func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) { version StateVersion createState func() mipsevm.FPVMState }{ - {VersionSingleThreaded2, func() mipsevm.FPVMState { return singlethreaded.CreateEmptyState() }}, - {VersionMultiThreaded_v2, func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }}, + {GetCurrentSingleThreaded(), func() mipsevm.FPVMState { return singlethreaded.CreateEmptyState() }}, + {GetCurrentMultiThreaded(), func() mipsevm.FPVMState { return multithreaded.CreateEmptyState() }}, } for _, test := range tests { test := test @@ -75,16 +75,6 @@ func TestVersionsOtherThanZeroDoNotSupportJSON(t *testing.T) { } } -func TestParseStateVersion(t *testing.T) { - for _, version := range StateVersionTypes { - t.Run(version.String(), func(t *testing.T) { - result, err := ParseStateVersion(version.String()) - require.NoError(t, err) - require.Equal(t, version, result) - }) - } -} - func writeToFile(t *testing.T, filename string, data serialize.Serializable) string { dir := t.TempDir() path := filepath.Join(dir, filename) diff --git a/cannon/mipsevm/versions/version.go b/cannon/mipsevm/versions/version.go new file mode 100644 index 00000000000..0c33144e130 --- /dev/null +++ b/cannon/mipsevm/versions/version.go @@ -0,0 +1,105 @@ +package versions + +import ( + "errors" + "slices" +) + +type StateVersion uint8 + +const ( + // VersionSingleThreaded is the version of the Cannon STF found in op-contracts/v1.6.0 - https://github.com/ethereum-optimism/optimism/blob/op-contracts/v1.6.0/packages/contracts-bedrock/src/cannon/MIPS.sol + VersionSingleThreaded StateVersion = iota + // VersionMultiThreaded is the original implementation of 32-bit multithreaded cannon, tagged at cannon/v1.3.0 + VersionMultiThreaded + // VersionSingleThreaded2 is based on VersionSingleThreaded with the addition of support for fcntl(F_GETFD) syscall + // This is the latest 32-bit single-threaded vm + VersionSingleThreaded2 + // VersionMultiThreaded64 is the original 64-bit MTCannon implementation (pre-audit), tagged at cannon/v1.2.0 + VersionMultiThreaded64 + // VersionMultiThreaded64_v2 includes an audit fix to ensure futex values are always 32-bit, tagged at cannon/v1.3.0 + VersionMultiThreaded64_v2 + // VersionMultiThreaded_v2 is the latest 32-bit multithreaded vm + VersionMultiThreaded_v2 + // VersionMultiThreaded64_v3 is the latest 64-bit multithreaded vm + VersionMultiThreaded64_v3 +) + +var StateVersionTypes = []StateVersion{ + VersionSingleThreaded, + VersionMultiThreaded, + VersionSingleThreaded2, + VersionMultiThreaded64, + VersionMultiThreaded64_v2, + VersionMultiThreaded_v2, + VersionMultiThreaded64_v3, +} + +func (s StateVersion) String() string { + switch s { + case VersionSingleThreaded: + return "singlethreaded" + case VersionMultiThreaded: + return "multithreaded" + case VersionSingleThreaded2: + return "singlethreaded-2" + case VersionMultiThreaded64: + return "multithreaded64" + case VersionMultiThreaded64_v2: + return "multithreaded64-2" + case VersionMultiThreaded_v2: + return "multithreaded-2" + case VersionMultiThreaded64_v3: + return "multithreaded64-3" + default: + return "unknown" + } +} + +func ParseStateVersion(ver string) (StateVersion, error) { + switch ver { + case "singlethreaded": + return VersionSingleThreaded, nil + case "multithreaded": + return VersionMultiThreaded, nil + case "singlethreaded-2": + return VersionSingleThreaded2, nil + case "multithreaded64": + return VersionMultiThreaded64, nil + case "multithreaded64-2": + return VersionMultiThreaded64_v2, nil + case "multithreaded-2": + return VersionMultiThreaded_v2, nil + case "multithreaded64-3": + return VersionMultiThreaded64_v3, nil + default: + return StateVersion(0), errors.New("unknown state version") + } +} + +func IsValidStateVersion(ver StateVersion) bool { + return slices.Contains(StateVersionTypes, ver) +} + +func GetStateVersionStrings() []string { + vers := make([]string, len(StateVersionTypes)) + for i, v := range StateVersionTypes { + vers[i] = v.String() + } + return vers +} + +// GetCurrentMultiThreaded64 returns the 64-bit multithreaded VM version that is currently supported +func GetCurrentMultiThreaded64() StateVersion { + return VersionMultiThreaded64_v3 +} + +// GetCurrentMultiThreaded returns the 32-bit multithreaded VM version that is currently supported +func GetCurrentMultiThreaded() StateVersion { + return VersionMultiThreaded_v2 +} + +// GetCurrentSingleThreaded returns the 32-bit single-threaded VM version that is currently supported +func GetCurrentSingleThreaded() StateVersion { + return VersionSingleThreaded2 +} diff --git a/cannon/mipsevm/versions/version_test.go b/cannon/mipsevm/versions/version_test.go new file mode 100644 index 00000000000..2bfcf335edd --- /dev/null +++ b/cannon/mipsevm/versions/version_test.go @@ -0,0 +1,17 @@ +package versions + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestParseStateVersion(t *testing.T) { + for _, version := range StateVersionTypes { + t.Run(version.String(), func(t *testing.T) { + result, err := ParseStateVersion(version.String()) + require.NoError(t, err) + require.Equal(t, version, result) + }) + } +} diff --git a/cannon/multicannon/exec.go b/cannon/multicannon/exec.go index 982b83c5569..f8709a2fe1f 100644 --- a/cannon/multicannon/exec.go +++ b/cannon/multicannon/exec.go @@ -8,7 +8,6 @@ import ( "os" "os/exec" "path/filepath" - "slices" "github.com/ethereum-optimism/optimism/cannon/mipsevm/versions" ) @@ -21,7 +20,7 @@ var vmFS embed.FS const baseDir = "embeds" func ExecuteCannon(ctx context.Context, args []string, ver versions.StateVersion) error { - if !slices.Contains(versions.StateVersionTypes, ver) { + if !versions.IsValidStateVersion(ver) { return errors.New("unsupported version") } From 95c3642398c895e59a2aaab2f4bced1cafdc03e4 Mon Sep 17 00:00:00 2001 From: Sam Stokes <35908605+bitwiseguy@users.noreply.github.com> Date: Fri, 7 Mar 2025 15:51:41 -0500 Subject: [PATCH 102/130] contracts: use full compiler target for semver key (#14722) * contracts: use full compiler target for semver key * foundry: use_literal_content = true * update check-semver-diff.sh filename parsing * update semver-lock.json * update check-frozen-files allow list to match compiler target names * skip run-contracts-check ci job * use negation to avoid deep nesting --- .circleci/config.yml | 4 +- op-chain-ops/solc/types.go | 11 +- packages/contracts-bedrock/foundry.toml | 1 + .../autogen/generate-semver-lock/main.go | 67 ++++++++-- .../scripts/checks/check-frozen-files.sh | 120 +++++++++--------- .../scripts/checks/check-semver-diff.sh | 2 +- .../snapshots/semver-lock.json | 118 ++++++++--------- 7 files changed, 184 insertions(+), 139 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3f21cf2fe24..e1c4854fc49 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -923,8 +923,8 @@ jobs: command: semgrep - run-contracts-check: command: semver-lock-no-build - - run-contracts-check: - command: semver-diff-check-no-build + #- run-contracts-check: + #command: semver-diff-check-no-build - run-contracts-check: command: validate-deploy-configs - run-contracts-check: diff --git a/op-chain-ops/solc/types.go b/op-chain-ops/solc/types.go index d109231efff..ab6568183ac 100644 --- a/op-chain-ops/solc/types.go +++ b/op-chain-ops/solc/types.go @@ -26,11 +26,12 @@ type CompilerInput struct { } type CompilerSettings struct { - Optimizer OptimizerSettings `json:"optimizer"` - Metadata CompilerInputMetadata `json:"metadata"` - OutputSelection map[string]map[string][]string `json:"outputSelection"` - EvmVersion string `json:"evmVersion,omitempty"` - Libraries map[string]map[string]string `json:"libraries,omitempty"` + Optimizer OptimizerSettings `json:"optimizer"` + Metadata CompilerInputMetadata `json:"metadata"` + CompilationTarget map[string]string `json:"compilationTarget"` + OutputSelection map[string]map[string][]string `json:"outputSelection"` + EvmVersion string `json:"evmVersion,omitempty"` + Libraries map[string]map[string]string `json:"libraries,omitempty"` } type OptimizerSettings struct { diff --git a/packages/contracts-bedrock/foundry.toml b/packages/contracts-bedrock/foundry.toml index aafcc6c22b3..7f94d2bc885 100644 --- a/packages/contracts-bedrock/foundry.toml +++ b/packages/contracts-bedrock/foundry.toml @@ -12,6 +12,7 @@ snapshots = 'notarealpath' # workaround for foundry#9477 optimizer = true optimizer_runs = 999999 +use_literal_content = true # IMPORTANT: # When adding any new compiler profiles or compilation restrictions, you must diff --git a/packages/contracts-bedrock/scripts/autogen/generate-semver-lock/main.go b/packages/contracts-bedrock/scripts/autogen/generate-semver-lock/main.go index 4e826c81459..4a9e03b15a0 100644 --- a/packages/contracts-bedrock/scripts/autogen/generate-semver-lock/main.go +++ b/packages/contracts-bedrock/scripts/autogen/generate-semver-lock/main.go @@ -5,10 +5,10 @@ import ( "encoding/json" "fmt" "os" - "regexp" "sort" "strings" + "github.com/ethereum-optimism/optimism/op-chain-ops/solc" "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/scripts/checks/common" "github.com/ethereum/go-ethereum/crypto" ) @@ -21,8 +21,8 @@ type SemverLockOutput struct { } type SemverLockResult struct { - SemverLockOutput - SourceFilePath string + ContractKey string + SemverLockOutput SemverLockOutput } func main() { @@ -32,7 +32,7 @@ func main() { processFile, ) if err != nil { - fmt.Printf("error: %v\n", err) + fmt.Printf("Failed to generate semver lock: %v\n", err) os.Exit(1) } @@ -42,7 +42,7 @@ func main() { if result == nil { continue } - output[result.SourceFilePath] = result.SemverLockOutput + output[result.ContractKey] = result.SemverLockOutput } // Get and sort the keys @@ -76,16 +76,60 @@ func processFile(file string) (*SemverLockResult, []error) { return nil, []error{fmt.Errorf("failed to read artifact: %w", err)} } + var sourceFilePath, contractName, contractKey string + for path, name := range artifact.Metadata.Settings.CompilationTarget { + sourceFilePath = path + contractName = name + contractKey = sourceFilePath + ":" + name + break + } + // Only apply to files in the src directory. - sourceFilePath := artifact.Ast.AbsolutePath if !strings.HasPrefix(sourceFilePath, "src/") { return nil, nil } - // Check if the contract uses semver. - semverRegex := regexp.MustCompile(`custom:semver`) - semver := semverRegex.FindStringSubmatch(artifact.RawMetadata) - if len(semver) == 0 { + // Check if the contract has a version function or variable with @custom:semver tag + hasSemverTag := false + for _, node := range artifact.Ast.Nodes { + if node.NodeType != "ContractDefinition" || node.Name != contractName { + continue + } + // Check each node inside the contract + for _, subNode := range node.Nodes { + // Skip nodes that aren't version functions or variables + if (subNode.NodeType != "FunctionDefinition" && + subNode.NodeType != "VariableDeclaration") || + subNode.Name != "version" { + continue + } + if subNode.Documentation == nil { + continue + } + // Handle documentation based on its actual type + var docText string + switch doc := subNode.Documentation.(type) { + case string: + docText = doc + case map[string]interface{}: + if text, ok := doc["text"].(string); ok { + docText = text + } + case solc.AstDocumentation: + docText = doc.Text + case *solc.AstDocumentation: + docText = doc.Text + } + if strings.Contains(docText, "@custom:semver") { + hasSemverTag = true + break + } + } + if hasSemverTag { + break + } + } + if !hasSemverTag { return nil, nil } @@ -101,13 +145,12 @@ func processFile(file string) (*SemverLockResult, []error) { return nil, []error{fmt.Errorf("failed to read source file: %w", err)} } - // Calculate hashes using Keccak256 trimmedSourceCode := []byte(strings.TrimSuffix(string(sourceCode), "\n")) initCodeHash := fmt.Sprintf("0x%x", crypto.Keccak256Hash(initCodeBytes)) sourceCodeHash := fmt.Sprintf("0x%x", crypto.Keccak256Hash(trimmedSourceCode)) return &SemverLockResult{ - SourceFilePath: sourceFilePath, + ContractKey: contractKey, SemverLockOutput: SemverLockOutput{ InitCodeHash: initCodeHash, SourceCodeHash: sourceCodeHash, diff --git a/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh b/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh index 95d7f8482f4..28458bed22a 100755 --- a/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh +++ b/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh @@ -48,66 +48,66 @@ changed_contracts=$(jq -r ' # In order to prevent a file from being modified, comment it out. Do not delete it. # All files in semver-lock.json should be in this list. ALLOWED_FILES=( - "src/L1/DataAvailabilityChallenge.sol" - "src/L1/L1CrossDomainMessenger.sol" - "src/L1/L1ERC721Bridge.sol" - "src/L1/L1StandardBridge.sol" - "src/L1/OPContractsManager.sol" - "src/L1/OPContractsManagerInterop.sol" - "src/L1/OPPrestateUpdater.sol" - "src/L1/OptimismPortal2.sol" - "src/L1/OptimismPortalInterop.sol" - "src/L1/ProtocolVersions.sol" - "src/L1/SuperchainConfig.sol" - "src/L1/SystemConfig.sol" - "src/L1/SystemConfigInterop.sol" - "src/L2/BaseFeeVault.sol" - "src/L2/CrossL2Inbox.sol" - "src/L2/ETHLiquidity.sol" - "src/L2/GasPriceOracle.sol" - "src/L2/L1Block.sol" - "src/L2/L1BlockInterop.sol" - "src/L2/L1FeeVault.sol" - "src/L2/L2CrossDomainMessenger.sol" - "src/L2/L2ERC721Bridge.sol" - "src/L2/L2StandardBridge.sol" - "src/L2/L2StandardBridgeInterop.sol" - "src/L2/L2ToL1MessagePasser.sol" - "src/L2/L2ToL2CrossDomainMessenger.sol" - "src/L2/OptimismMintableERC721.sol" - "src/L2/OptimismMintableERC721Factory.sol" - "src/L2/OptimismSuperchainERC20.sol" - "src/L2/OptimismSuperchainERC20Beacon.sol" - "src/L2/OptimismSuperchainERC20Factory.sol" - "src/L2/SequencerFeeVault.sol" - "src/L2/SuperchainERC20.sol" - "src/L2/SuperchainTokenBridge.sol" - "src/L2/SuperchainWETH.sol" - "src/L2/WETH.sol" - "src/cannon/MIPS.sol" - "src/cannon/MIPS2.sol" - "src/cannon/MIPS64.sol" - "src/cannon/PreimageOracle.sol" - "src/dispute/AnchorStateRegistry.sol" - "src/dispute/DelayedWETH.sol" - "src/dispute/DisputeGameFactory.sol" - "src/dispute/FaultDisputeGame.sol" - "src/dispute/PermissionedDisputeGame.sol" - "src/dispute/SuperFaultDisputeGame.sol" - "src/dispute/SuperPermissionedDisputeGame.sol" - "src/legacy/DeployerWhitelist.sol" - "src/legacy/L1BlockNumber.sol" - "src/legacy/LegacyMessagePasser.sol" - "src/safe/DeputyGuardianModule.sol" - "src/safe/DeputyPauseModule.sol" - "src/safe/LivenessGuard.sol" - "src/safe/LivenessModule.sol" - "src/universal/OptimismMintableERC20.sol" - "src/universal/OptimismMintableERC20Factory.sol" - "src/universal/StorageSetter.sol" - "src/vendor/asterisc/RISCV.sol" - "src/vendor/eas/EAS.sol" - "src/vendor/eas/SchemaRegistry.sol" + "src/L1/DataAvailabilityChallenge.sol:DataAvailabilityChallenge" + "src/L1/L1CrossDomainMessenger.sol:L1CrossDomainMessenger" + "src/L1/L1ERC721Bridge.sol:L1ERC721Bridge" + "src/L1/L1StandardBridge.sol:L1StandardBridge" + "src/L1/OPContractsManager.sol:OPContractsManager" + "src/L1/OPContractsManagerInterop.sol:OPContractsManagerInterop" + "src/L1/OPPrestateUpdater.sol:OPPrestateUpdater" + "src/L1/OptimismPortal2.sol:OptimismPortal2" + "src/L1/OptimismPortalInterop.sol:OptimismPortalInterop" + "src/L1/ProtocolVersions.sol:ProtocolVersions" + "src/L1/SuperchainConfig.sol:SuperchainConfig" + "src/L1/SystemConfig.sol:SystemConfig" + "src/L1/SystemConfigInterop.sol:SystemConfigInterop" + "src/L2/BaseFeeVault.sol:BaseFeeVault" + "src/L2/CrossL2Inbox.sol:CrossL2Inbox" + "src/L2/ETHLiquidity.sol:ETHLiquidity" + "src/L2/GasPriceOracle.sol:GasPriceOracle" + "src/L2/L1Block.sol:L1Block" + "src/L2/L1BlockInterop.sol:L1BlockInterop" + "src/L2/L1FeeVault.sol:L1FeeVault" + "src/L2/L2CrossDomainMessenger.sol:L2CrossDomainMessenger" + "src/L2/L2ERC721Bridge.sol:L2ERC721Bridge" + "src/L2/L2StandardBridge.sol:L2StandardBridge" + "src/L2/L2StandardBridgeInterop.sol:L2StandardBridgeInterop" + "src/L2/L2ToL1MessagePasser.sol:L2ToL1MessagePasser" + "src/L2/L2ToL2CrossDomainMessenger.sol:L2ToL2CrossDomainMessenger" + "src/L2/OptimismMintableERC721.sol:OptimismMintableERC721" + "src/L2/OptimismMintableERC721Factory.sol:OptimismMintableERC721Factory" + "src/L2/OptimismSuperchainERC20.sol:OptimismSuperchainERC20" + "src/L2/OptimismSuperchainERC20Beacon.sol:OptimismSuperchainERC20Beacon" + "src/L2/OptimismSuperchainERC20Factory.sol:OptimismSuperchainERC20Factory" + "src/L2/SequencerFeeVault.sol:SequencerFeeVault" + "src/L2/SuperchainERC20.sol:SuperchainERC20" + "src/L2/SuperchainTokenBridge.sol:SuperchainTokenBridge" + "src/L2/SuperchainWETH.sol:SuperchainWETH" + "src/L2/WETH.sol:WETH" + "src/cannon/MIPS.sol:MIPS" + "src/cannon/MIPS2.sol:MIPS2" + "src/cannon/MIPS64.sol:MIPS64" + "src/cannon/PreimageOracle.sol:PreimageOracle" + "src/dispute/AnchorStateRegistry.sol:AnchorStateRegistry" + "src/dispute/DelayedWETH.sol:DelayedWETH" + "src/dispute/DisputeGameFactory.sol:DisputeGameFactory" + "src/dispute/FaultDisputeGame.sol:FaultDisputeGame" + "src/dispute/PermissionedDisputeGame.sol:PermissionedDisputeGame" + "src/dispute/SuperFaultDisputeGame.sol:SuperFaultDisputeGame" + "src/dispute/SuperPermissionedDisputeGame.sol:SuperPermissionedDisputeGame" + "src/legacy/DeployerWhitelist.sol:DeployerWhitelist" + "src/legacy/L1BlockNumber.sol:L1BlockNumber" + "src/legacy/LegacyMessagePasser.sol:LegacyMessagePasser" + "src/safe/DeputyGuardianModule.sol:DeputyGuardianModule" + "src/safe/DeputyPauseModule.sol:DeputyPauseModule" + "src/safe/LivenessGuard.sol:LivenessGuard" + "src/safe/LivenessModule.sol:LivenessModule" + "src/universal/OptimismMintableERC20.sol:OptimismMintableERC20" + "src/universal/OptimismMintableERC20Factory.sol:OptimismMintableERC20Factory" + "src/universal/StorageSetter.sol:StorageSetter" + "src/vendor/asterisc/RISCV.sol:RISCV" + "src/vendor/eas/EAS.sol:EAS" + "src/vendor/eas/SchemaRegistry.sol:SchemaRegistry" ) MATCHED_FILES=() diff --git a/packages/contracts-bedrock/scripts/checks/check-semver-diff.sh b/packages/contracts-bedrock/scripts/checks/check-semver-diff.sh index 08540879a96..18f698f9111 100755 --- a/packages/contracts-bedrock/scripts/checks/check-semver-diff.sh +++ b/packages/contracts-bedrock/scripts/checks/check-semver-diff.sh @@ -56,7 +56,7 @@ changed_contracts=$(jq -r ' .key as $key | .value != $upstream[$key] ) - ) | map(.key); + ) | map(.key | split(":")[0]); changes[] ' "$temp_dir/local_semver_lock.json" "$temp_dir/upstream_semver_lock.json") diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 8a31cd06dc9..5667bfa1580 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -1,237 +1,237 @@ { - "src/L1/DataAvailabilityChallenge.sol": { + "src/L1/DataAvailabilityChallenge.sol:DataAvailabilityChallenge": { "initCodeHash": "0xacbae98cc7c0f7ecbf36dc44bbf7cb0a011e6e6b781e28b9dbf947e31482b30d", "sourceCodeHash": "0xe772f7db8033e4a738850cb28ac4849d3a454c93732135a8a10d4f7cb498088e" }, - "src/L1/L1CrossDomainMessenger.sol": { + "src/L1/L1CrossDomainMessenger.sol:L1CrossDomainMessenger": { "initCodeHash": "0x5a6272f6bd4346da460b7ff2ebc426d158d7ffc65dc0f2c0651a9e6d545bfd03", "sourceCodeHash": "0xf11fa72481dbe43dad4e7a48645fcf92d0feeefa0b98b282042bdda568508372" }, - "src/L1/L1ERC721Bridge.sol": { + "src/L1/L1ERC721Bridge.sol:L1ERC721Bridge": { "initCodeHash": "0x819fcccb74bef53241d7d651f73949115e5706b61c7cb8cdb9b6d9d34ef39e89", "sourceCodeHash": "0x5036f59ae3414d1106f148765bba9d1759844c9c2d3e18ab5c81bb49cf59eab8" }, - "src/L1/L1StandardBridge.sol": { + "src/L1/L1StandardBridge.sol:L1StandardBridge": { "initCodeHash": "0xbd4a03a3611a0de9669c4c8556f90c5aaef666b899d9ded1c07abc60263da8d6", "sourceCodeHash": "0x44797707aea8c63dec049a02d69ea056662a06e5cf320028ab8b388634bf1c67" }, - "src/L1/OPContractsManager.sol": { + "src/L1/OPContractsManager.sol:OPContractsManager": { "initCodeHash": "0x216fe3ceef5d3e839d18a00383793bd326f20944fc3b55ef099a26a22141de18", "sourceCodeHash": "0xd1de3414a3db731447cb6172df23a00cb1664783de56a93fb29f87d6bc8b8bb0" }, - "src/L1/OptimismPortal2.sol": { + "src/L1/OptimismPortal2.sol:OptimismPortal2": { "initCodeHash": "0xd1651b8a6f4d25611a0105d5cc7c1da3921417bd44da870ec63bf5ccd1bc7c63", "sourceCodeHash": "0xce7373d8c7df47caa8b090f3afb3d2539677f12cb3eff7fc0ab1fd85638f05c1" }, - "src/L1/OptimismPortalInterop.sol": { + "src/L1/OptimismPortalInterop.sol:OptimismPortalInterop": { "initCodeHash": "0xd59854648bf205dfbea96b483b2937441c32e9ef66b002468c2c14c0d6661728", "sourceCodeHash": "0xd00b267dcf125e77c10b28c088be4378ec779927e3bcfeb6aa9a7f3d51370490" }, - "src/L1/ProtocolVersions.sol": { + "src/L1/ProtocolVersions.sol:ProtocolVersions": { "initCodeHash": "0x5a76c8530cb24cf23d3baacc6eefaac226382af13f1e2a35535d2ec2b0573b29", "sourceCodeHash": "0xb3e32b18c95d4940980333e1e99b4dcf42d8a8bfce78139db4dc3fb06e9349d0" }, - "src/L1/SuperchainConfig.sol": { + "src/L1/SuperchainConfig.sol:SuperchainConfig": { "initCodeHash": "0x23c54871316523111f3385b5acd7fcbda3c91096d0d44a21d8bae61736c380d7", "sourceCodeHash": "0xfd56e63e76b1f203cceeb9bbb14396ae803cbbbf7e80ca0ee11fb586321812af" }, - "src/L1/SystemConfig.sol": { + "src/L1/SystemConfig.sol:SystemConfig": { "initCodeHash": "0xd5b8b8eb47763556d9953019d1f81b1d790f15433aa9696b159a3fc45ecee148", "sourceCodeHash": "0x6bfbc78b0fef2f65beff11a81f924728a7bd439a56986997621099551805aff9" }, - "src/L1/SystemConfigInterop.sol": { + "src/L1/SystemConfigInterop.sol:SystemConfigInterop": { "initCodeHash": "0xbef4696b2dcb6d43c3b3c438338bfb2224a1ea5002ed0612ec36a7821d7e3da2", "sourceCodeHash": "0x1653aaa4d2b44d34ca1f9f2b4971eeb7594c8c2d27771b1f68b8d38cb79f2368" }, - "src/L2/BaseFeeVault.sol": { + "src/L2/BaseFeeVault.sol:BaseFeeVault": { "initCodeHash": "0xc403d4c555d8e69a2699e01d192ae7327136701fa02da10a6d75a584b3c364c9", "sourceCodeHash": "0xfa56426153227e798150f6becc30a33fd20a3c6e0d73c797a3922dd631acbb57" }, - "src/L2/CrossL2Inbox.sol": { + "src/L2/CrossL2Inbox.sol:CrossL2Inbox": { "initCodeHash": "0x2bc4a3765004f9a9e6e5278753bce3c3d53cc95da62efcc0cb10c50d8c806cd4", "sourceCodeHash": "0x661d7659f09b7f909e8bd5e6c41e8c98f2091036ed2123b7e18a1a74120bd849" }, - "src/L2/ETHLiquidity.sol": { + "src/L2/ETHLiquidity.sol:ETHLiquidity": { "initCodeHash": "0x776ece4a1bb24d97287806769470327641da240b083898a90943e2844957cc46", "sourceCodeHash": "0xe5c08ce62327113e4bbaf29f47e5f1ddfad6fbd63c07132eedfba5af5325f331" }, - "src/L2/GasPriceOracle.sol": { + "src/L2/GasPriceOracle.sol:GasPriceOracle": { "initCodeHash": "0x38ef70b2783dd45ad807afcf57972c7df4abaaeb5d16d17cdb451b9e931a9cbb", "sourceCodeHash": "0x4351fe2ac1106c8c220b8cfe7839bc107c24d8084deb21259ac954f5a362725d" }, - "src/L2/L1Block.sol": { + "src/L2/L1Block.sol:L1Block": { "initCodeHash": "0xa1f984b8ea199574261c19122b5a9c8c7dbd3633980b1e7aaf6b7af24af60478", "sourceCodeHash": "0xd04d64355dcf55247ac937748518e7f9620ae3f9eabe80fae9a82c0115ed77bc" }, - "src/L2/L1BlockInterop.sol": { + "src/L2/L1BlockInterop.sol:L1BlockInterop": { "initCodeHash": "0x55d09f00ad284fd7ca4b55c45fb901ed021b83118012be217aec53876ab34c12", "sourceCodeHash": "0x7dd627c198a583fbe2c7d257f06001e1a2e563c6c7d79ea6ba9ca0d47cd1599b" }, - "src/L2/L1FeeVault.sol": { + "src/L2/L1FeeVault.sol:L1FeeVault": { "initCodeHash": "0x6745b7be3895a5e8d373df0066d931bae29c47672ac46c2f5829bd0052cc6d9e", "sourceCodeHash": "0xd0471c328c1d17c5863261322bf8d5aff2e7e9e3a1135631a993aa75667621df" }, - "src/L2/L2CrossDomainMessenger.sol": { + "src/L2/L2CrossDomainMessenger.sol:L2CrossDomainMessenger": { "initCodeHash": "0xe160be403df12709c371c33195d1b9c3b5e9499e902e86bdabc8eed749c3fd61", "sourceCodeHash": "0x12ea125038b87e259a0d203e119faa6e9726ab2bdbc30430f820ccd48fe87e14" }, - "src/L2/L2ERC721Bridge.sol": { + "src/L2/L2ERC721Bridge.sol:L2ERC721Bridge": { "initCodeHash": "0x863f0f5b410983f3e51cd97c60a3a42915141b7452864d0e176571d640002b81", "sourceCodeHash": "0xc05bfcfadfd09a56cfea68e7c1853faa36d114d9a54cd307348be143e442c35a" }, - "src/L2/L2StandardBridge.sol": { + "src/L2/L2StandardBridge.sol:L2StandardBridge": { "initCodeHash": "0xba5b288a396b34488ba7be68473305529c7da7c43e5f1cfc48d6a4aecd014103", "sourceCodeHash": "0x9dd26676cd1276c807ffd4747236783c5170d0919c70693e70b7e4c4c2675429" }, - "src/L2/L2StandardBridgeInterop.sol": { + "src/L2/L2StandardBridgeInterop.sol:L2StandardBridgeInterop": { "initCodeHash": "0xa7a2e7efe8116ebb21f47ee06c1e62d3b2f5a046478094611a2ab4b714154030", "sourceCodeHash": "0xde724da82ecf3c96b330c2876a7285b6e2b933ac599241eaa3174c443ebbe33a" }, - "src/L2/L2ToL1MessagePasser.sol": { + "src/L2/L2ToL1MessagePasser.sol:L2ToL1MessagePasser": { "initCodeHash": "0xf9d82084dcef31a3737a76d8ee4e5842ea190d0f77ed4678adb3bbb95217050f", "sourceCodeHash": "0xaef8ea36c5b78cd12e0e62811d51db627ccf0dfd2cc5479fb707a10ef0d42048" }, - "src/L2/L2ToL2CrossDomainMessenger.sol": { + "src/L2/L2ToL2CrossDomainMessenger.sol:L2ToL2CrossDomainMessenger": { "initCodeHash": "0xc56db8cb569efa0467fd53ab3fa218af3051e54f5517d7fafb7b5831b4350618", "sourceCodeHash": "0x72062343a044e9c56f4143dcfc71706286eb205902006c2afcf6a4cd90c3e9f8" }, - "src/L2/OperatorFeeVault.sol": { + "src/L2/OperatorFeeVault.sol:OperatorFeeVault": { "initCodeHash": "0x3d8c0d7736e8767f2f797da1c20c5fe30bd7f48a4cf75f376290481ad7c0f91f", "sourceCodeHash": "0x2022fdb4e32769eb9446dab4aed4b8abb5261fd866f381cccfa7869df1a2adff" }, - "src/L2/OptimismMintableERC721.sol": { + "src/L2/OptimismMintableERC721.sol:OptimismMintableERC721": { "initCodeHash": "0xcfa6ad9997a422aef5a19a490a0a535bc870ee34b1f5258c2949eb3680f71e8a", "sourceCodeHash": "0xb67b91f28c8666fee26c40375f835c61629e0f14054bfaf78bc3c61175bbf136" }, - "src/L2/OptimismMintableERC721Factory.sol": { + "src/L2/OptimismMintableERC721Factory.sol:OptimismMintableERC721Factory": { "initCodeHash": "0x9ccba9a5db77356c361fe4aea0e93498a56bda9fdac8d5e654d6f7abc4553028", "sourceCodeHash": "0x2e4b8535b1f7749a0479b2c1de86b3ff79ee4ff6122c6f87c52d66cd301f3f97" }, - "src/L2/OptimismSuperchainERC20.sol": { + "src/L2/OptimismSuperchainERC20.sol:OptimismSuperchainERC20": { "initCodeHash": "0xdac32a1057a6bc8a8d2ffdce1db8f34950cd0ffd1454d2133865736d21869192", "sourceCodeHash": "0x4a7924f2195074145ac8e6221d77b24cd22d97423db2053937897e9d788990e2" }, - "src/L2/OptimismSuperchainERC20Beacon.sol": { + "src/L2/OptimismSuperchainERC20Beacon.sol:OptimismSuperchainERC20Beacon": { "initCodeHash": "0x8a4d7cac6dd8ce583c996837893b93560297be1269f97f785a502748b25ba310", "sourceCodeHash": "0xb57024e16b528bade5fee7c236e03ffbb3f22e6376e6852e2109298af850b43c" }, - "src/L2/OptimismSuperchainERC20Factory.sol": { + "src/L2/OptimismSuperchainERC20Factory.sol:OptimismSuperchainERC20Factory": { "initCodeHash": "0x44659ea207ed173db4f1b519944c09c671d49f118e9d9ab85a010b8ebaf899e7", "sourceCodeHash": "0xa1c0346cfe6932dde05dc6c1d9505cac38434d8a8f9e1e437253b1f4115f2506" }, - "src/L2/SequencerFeeVault.sol": { + "src/L2/SequencerFeeVault.sol:SequencerFeeVault": { "initCodeHash": "0x02ca6cb6eebd2d6b91cf1eab483ee00b3233a7e8ad31f0e9cafc1f645ab3c24a", "sourceCodeHash": "0x85c740c0888368ee95607635818ee698c27582e8917f40bc590d240447376da9" }, - "src/L2/SuperchainERC20.sol": { + "src/L2/SuperchainERC20.sol:SuperchainERC20": { "initCodeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "sourceCodeHash": "0x981dca5b09da9038a9dff071b40a880e1b52b20268c6780ef54be3bc98a4f629" }, - "src/L2/SuperchainTokenBridge.sol": { + "src/L2/SuperchainTokenBridge.sol:SuperchainTokenBridge": { "initCodeHash": "0x6b568ed564aede82a3a4cbcdb51282cad0e588a3fe6d91cf76616d3113df3901", "sourceCodeHash": "0xcd2b49cb7cf6d18616ee8bec9183fe5b5b460941875bc0b4158c4d5390ec3b0c" }, - "src/L2/SuperchainWETH.sol": { + "src/L2/SuperchainWETH.sol:SuperchainWETH": { "initCodeHash": "0x545686820e440d72529c815b7406844272d5ec33b741b2be6ebbe3a3db1ca8ad", "sourceCodeHash": "0x6145e61cc0a0c95db882a76ecffea15c358c2b574d5157e53b85a69908701613" }, - "src/L2/WETH.sol": { + "src/L2/WETH.sol:WETH": { "initCodeHash": "0x38b396fc35d72e8013bad2fe8d7dea5285499406d4c4b62e27c54252e1e0f00a", "sourceCodeHash": "0xf4f83ca89d2519045a2916c670bda66f39b431a13921e639a5342bfc6157b178" }, - "src/cannon/MIPS.sol": { + "src/cannon/MIPS.sol:MIPS": { "initCodeHash": "0x6072be7e25ad30d16ef86e2f95343b5378bc29d79541b3711f3463e712baebf6", "sourceCodeHash": "0x51d93a684bd9def207a47f6c1dbe481aba5def3f77533d4a6e490784204d113b" }, - "src/cannon/MIPS2.sol": { + "src/cannon/MIPS2.sol:MIPS2": { "initCodeHash": "0x1f4e7cfdbcf7a8ca0ebac69bc7fe74143286a0e51a06ee9cbd699d68efd26dba", "sourceCodeHash": "0x20256a2196daca39b56bfae1c90b8871349916dc47461b5ca078c2013c067571" }, - "src/cannon/MIPS64.sol": { + "src/cannon/MIPS64.sol:MIPS64": { "initCodeHash": "0x0a274f73b9fae62524a5773e480b398846e1140aed373be211a07cb586e4758e", "sourceCodeHash": "0xb710bd6d4844f9ee45f301bb815786619b5e2d6b2f85ae17f39bee4f414f1957" }, - "src/cannon/PreimageOracle.sol": { + "src/cannon/PreimageOracle.sol:PreimageOracle": { "initCodeHash": "0x6af5b0e83b455aab8d0946c160a4dc049a4e03be69f8a2a9e87b574f27b25a66", "sourceCodeHash": "0x03c160168986ffc8d26a90c37366e7ad6da03f49d83449e1f8b3de0f4b590f6f" }, - "src/dispute/AnchorStateRegistry.sol": { + "src/dispute/AnchorStateRegistry.sol:AnchorStateRegistry": { "initCodeHash": "0x08cc5a5e41eadb6c411fa6387ddc0cf12be360855599dd622cce84c0ba081e77", "sourceCodeHash": "0xe0aaa79f7184724ff0fba2e92e85f652f936fecd099288edb0a0f6b0e0240f34" }, - "src/dispute/DelayedWETH.sol": { + "src/dispute/DelayedWETH.sol:DelayedWETH": { "initCodeHash": "0xdd0b5e523f3b53563fe0b6e6165fb73605b14910ffa32a7cbed855cdebab47c6", "sourceCodeHash": "0x592fe720a86be12fd83511f641d5b3dee80834a7ed83a6ab81d3e7e4712c15d8" }, - "src/dispute/DisputeGameFactory.sol": { + "src/dispute/DisputeGameFactory.sol:DisputeGameFactory": { "initCodeHash": "0xfd3ead515b80db01722e56203bd3ef85c41fc2cb201cc6afa9fb42d8a9731bca", "sourceCodeHash": "0x08efbce44394f555a4e656816897029e2e80e455e4200a1b887fc18993294660" }, - "src/dispute/FaultDisputeGame.sol": { + "src/dispute/FaultDisputeGame.sol:FaultDisputeGame": { "initCodeHash": "0xd86a649deaa0d5ca6c4b60afb941c52fd2b5d10857657defda3629faaff6d77e", "sourceCodeHash": "0xf5d3760949af227ea161f552a1d0c5d605a26bfd24dc428f674c011cb7f0a416" }, - "src/dispute/PermissionedDisputeGame.sol": { + "src/dispute/PermissionedDisputeGame.sol:PermissionedDisputeGame": { "initCodeHash": "0x63ab07ca14b77da770de1814b112e1a21ea984c5132b8ff3beaab2cf88018c19", "sourceCodeHash": "0xba32e6f35777426839a60e5556c09844e805eaabc14b9d3732cc64f2a99ce7fc" }, - "src/dispute/SuperFaultDisputeGame.sol": { + "src/dispute/SuperFaultDisputeGame.sol:SuperFaultDisputeGame": { "initCodeHash": "0x397d9eda98459859f30f170f0d3c6f4320d474ea36510ae7ece020800b80ba22", "sourceCodeHash": "0xb08789200c909d24c8ebfbde9b9c55c50b9ebae2d21e761464c1b5abbbe59317" }, - "src/dispute/SuperPermissionedDisputeGame.sol": { + "src/dispute/SuperPermissionedDisputeGame.sol:SuperPermissionedDisputeGame": { "initCodeHash": "0x9613232c1c11abee8782e2e9a5bb963923c73643d8c228e558f06082e8886f67", "sourceCodeHash": "0xaefa8af211cee1b34063252f62020cd468175a2ab9cfc46b4e4cb03190e2d659" }, - "src/legacy/DeployerWhitelist.sol": { + "src/legacy/DeployerWhitelist.sol:DeployerWhitelist": { "initCodeHash": "0x53099379ed48b87f027d55712dbdd1da7d7099925426eb0531da9c0012e02c29", "sourceCodeHash": "0xf22c94ed20c32a8ed2705a22d12c6969c3c3bad409c4efe2f95b0db74f210e10" }, - "src/legacy/L1BlockNumber.sol": { + "src/legacy/L1BlockNumber.sol:L1BlockNumber": { "initCodeHash": "0x60dded11d35e42fe15ef5dd94d28aae6b8ff3e67c6fbbc667a6729fcb3ca7a9a", "sourceCodeHash": "0x53ef11021a52e9c87024a870566ec5dba1d1a12752396e654904384efdd8203e" }, - "src/legacy/LegacyMessagePasser.sol": { + "src/legacy/LegacyMessagePasser.sol:LegacyMessagePasser": { "initCodeHash": "0x3ca911b0578be7f8c91e7d01442a5609f04e5866768f99c8e31627c9ba79c9f0", "sourceCodeHash": "0x62c9a6182d82692fb9c173ddb0d7978bcff2d1d4dc8cd2f10625e1e65bda6888" }, - "src/safe/DeputyGuardianModule.sol": { + "src/safe/DeputyGuardianModule.sol:DeputyGuardianModule": { "initCodeHash": "0x5eaf823d81995ce1f703f26e31049c54c1d4902dd9873a0b4645d470f2f459a2", "sourceCodeHash": "0x17236a91c4171ae9525eae0e59fa65bb2dc320d62677cfc7d7eb942f182619fb" }, - "src/safe/DeputyPauseModule.sol": { + "src/safe/DeputyPauseModule.sol:DeputyPauseModule": { "initCodeHash": "0xa3b7bf0c93b41f39ebc18a81322b90127a633d684ae9f86c2f2a1c48fe7f1372", "sourceCodeHash": "0xfbe7f5733aa57de7557605e40e03e9708fbdec872bc8739c551905c4b1e1b61b" }, - "src/safe/LivenessGuard.sol": { + "src/safe/LivenessGuard.sol:LivenessGuard": { "initCodeHash": "0xc8e29e8b12f423c8cd229a38bc731240dd815d96f1b0ab96c71494dde63f6a81", "sourceCodeHash": "0x72b8d8d855e7af8beee29330f6cb9b9069acb32e23ce940002ec9a41aa012a16" }, - "src/safe/LivenessModule.sol": { + "src/safe/LivenessModule.sol:LivenessModule": { "initCodeHash": "0xde3b3273aa37604048b5fa228b90f3b05997db613dfcda45061545a669b2476a", "sourceCodeHash": "0x918965e52bbd358ac827ebe35998f5d8fa5ca77d8eb9ab8986b44181b9aaa48a" }, - "src/universal/OptimismMintableERC20.sol": { + "src/universal/OptimismMintableERC20.sol:OptimismMintableERC20": { "initCodeHash": "0xc3289416829b252c830ad7d389a430986a7404df4fe0be37cb19e1c40907f047", "sourceCodeHash": "0xf5e29dd5c750ea935c7281ec916ba5277f5610a0a9e984e53ae5d5245b3cf2f4" }, - "src/universal/OptimismMintableERC20Factory.sol": { + "src/universal/OptimismMintableERC20Factory.sol:OptimismMintableERC20Factory": { "initCodeHash": "0xdb4d93a65cf9d3e3af77d3d62249f06580e80a0431542350f953f0a4041566b4", "sourceCodeHash": "0xd1bad4408c26eb9c7b0ddcb088f0d4e3be73a43d899263ec8610f4d41a178ec7" }, - "src/universal/StorageSetter.sol": { + "src/universal/StorageSetter.sol:StorageSetter": { "initCodeHash": "0x8831c079f7b7a52679e8a15e0ea14e30ea7bb4f93feed0fcd369942fe8c1f1ec", "sourceCodeHash": "0x42151e2547ec5270353977fd66e78fa1fde18f362d7021cf7ddce16d5201b3ec" }, - "src/vendor/asterisc/RISCV.sol": { + "src/vendor/asterisc/RISCV.sol:RISCV": { "initCodeHash": "0x7329cca924e189eeaa2d883234f6cb5fd787c8bf3339d8298e721778c2947ce5", "sourceCodeHash": "0x02025b303a8f37b4e541f8c7936a8651402a60ea0147a53176e06b51b15a1f84" }, - "src/vendor/eas/EAS.sol": { + "src/vendor/eas/EAS.sol:EAS": { "initCodeHash": "0xbd79d6fff128b3da3e09ead84b805b7540740190488f2791a6b4e5b7aabf9cff", "sourceCodeHash": "0x3512c3a1b5871341346f6646a04c0895dd563e9824f2ab7ab965b6a81a41ad2e" }, - "src/vendor/eas/SchemaRegistry.sol": { + "src/vendor/eas/SchemaRegistry.sol:SchemaRegistry": { "initCodeHash": "0x2bfce526f82622288333d53ca3f43a0a94306ba1bab99241daa845f8f4b18bd4", "sourceCodeHash": "0xf49d7b0187912a6bb67926a3222ae51121e9239495213c975b3b4b217ee57a1b" } From b0e8e3147d1869dbb851fa8ce31ba7aa74dbe93d Mon Sep 17 00:00:00 2001 From: Sam Stokes <35908605+bitwiseguy@users.noreply.github.com> Date: Fri, 7 Mar 2025 16:44:27 -0500 Subject: [PATCH 103/130] contracts: reenable semver-diff-check in ci (#14727) --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e1c4854fc49..3f21cf2fe24 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -923,8 +923,8 @@ jobs: command: semgrep - run-contracts-check: command: semver-lock-no-build - #- run-contracts-check: - #command: semver-diff-check-no-build + - run-contracts-check: + command: semver-diff-check-no-build - run-contracts-check: command: validate-deploy-configs - run-contracts-check: From b11a819b7067ed7181fe6da62973fd162c337610 Mon Sep 17 00:00:00 2001 From: mbaxter Date: Fri, 7 Mar 2025 17:44:39 -0500 Subject: [PATCH 104/130] Refreeze MIPS contracts (#14732) --- .../contracts-bedrock/scripts/checks/check-frozen-files.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh b/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh index 28458bed22a..c2a9e6688af 100755 --- a/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh +++ b/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh @@ -84,9 +84,9 @@ ALLOWED_FILES=( "src/L2/SuperchainTokenBridge.sol:SuperchainTokenBridge" "src/L2/SuperchainWETH.sol:SuperchainWETH" "src/L2/WETH.sol:WETH" - "src/cannon/MIPS.sol:MIPS" - "src/cannon/MIPS2.sol:MIPS2" - "src/cannon/MIPS64.sol:MIPS64" +# "src/cannon/MIPS.sol:MIPS" +# "src/cannon/MIPS2.sol:MIPS2" +# "src/cannon/MIPS64.sol:MIPS64" "src/cannon/PreimageOracle.sol:PreimageOracle" "src/dispute/AnchorStateRegistry.sol:AnchorStateRegistry" "src/dispute/DelayedWETH.sol:DelayedWETH" From b84e4c4a840197d756e9571f4c6fb21d97b5fdec Mon Sep 17 00:00:00 2001 From: mbaxter Date: Fri, 7 Mar 2025 18:13:24 -0500 Subject: [PATCH 105/130] cannon: Add more differential tests (#14684) * Add a few more beq, bne test cases * Tweak shift operation tests * Add delay slot tests for jr/jalr * Rework mov tests, add some test cases * Add more slt(u) test cases * Add slti tests * Add sltiu tests * Add test for sync instruction * Rework binary operation tests * Migrate nor test to use binary * Fix comments --- cannon/mipsevm/tests/evm_common64_test.go | 15 +- cannon/mipsevm/tests/evm_common_test.go | 202 ++++++++++++++-------- 2 files changed, 134 insertions(+), 83 deletions(-) diff --git a/cannon/mipsevm/tests/evm_common64_test.go b/cannon/mipsevm/tests/evm_common64_test.go index 5c8d9b449ee..de994c6ff00 100644 --- a/cannon/mipsevm/tests/evm_common64_test.go +++ b/cannon/mipsevm/tests/evm_common64_test.go @@ -86,18 +86,13 @@ func TestEVM_SingleStep_Operators64(t *testing.T) { testOperators(t, cases, false) } +// Additional 64-bit tests func TestEVM_SingleStep_Bitwise64(t *testing.T) { cases := []operatorTestCase{ - {name: "and", funct: 0x24, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(160)}, // and t0, s1, s2 - {name: "andi", opcode: 0xc, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(0)}, // andi t0, s1, 40 - {name: "or", funct: 0x25, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(1530)}, // or t0, s1, s2 - {name: "ori", opcode: 0xd, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(44)}, // ori t0, s1, 40 - {name: "xor", funct: 0x26, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(1370)}, // xor t0, s1, s2 - {name: "xori", opcode: 0xe, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(44)}, // xori t0, s1, 40 - {name: "nor", funct: 0x27, isImm: false, rs: Word(0x4b0), rt: Word(0x1ea), expectRes: Word(0xFF_FF_FF_FF_FF_FF_FA_05)}, // nor t0, s1, s2 - {name: "slt", funct: 0x2a, isImm: false, rs: 0xFF_FF_FF_FE, rt: Word(5), expectRes: Word(0)}, // slt t0, s1, s2 - {name: "slt", funct: 0x2a, isImm: false, rs: 0xFF_FF_FF_FF_FF_FF_FF_FE, rt: Word(5), expectRes: Word(1)}, // slt t0, s1, s2 - {name: "sltu", funct: 0x2b, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(0)}, // sltu t0, s1, s2 + {name: "slt", funct: 0x2a, isImm: false, rs: 0xFF_FF_FF_FE, rt: Word(5), expectRes: Word(0)}, // slt t0, s1, s2 + {name: "slt", funct: 0x2a, isImm: false, rs: 0xFF_FF_FF_FF_FF_FF_FF_FE, rt: Word(5), expectRes: Word(1)}, // slt t0, s1, s2 + {name: "slti", opcode: 0xa, isImm: true, rs: 0xFF_FF_FF_FE, imm: 5, expectRes: Word(0)}, // slt t0, s1, s2 + {name: "slti", opcode: 0xa, isImm: true, rs: 0xFF_FF_FF_FF_FF_FF_FF_FE, imm: 5, expectRes: Word(1)}, // slt t0, s1, s2 } testOperators(t, cases, false) } diff --git a/cannon/mipsevm/tests/evm_common_test.go b/cannon/mipsevm/tests/evm_common_test.go index c43fb19e237..a8a06f9acc7 100644 --- a/cannon/mipsevm/tests/evm_common_test.go +++ b/cannon/mipsevm/tests/evm_common_test.go @@ -186,19 +186,36 @@ func TestEVM_SingleStep_Operators(t *testing.T) { testOperators(t, cases, true) } -func TestEVM_SingleStep_Bitwise32(t *testing.T) { - testutil.Cannon32OnlyTest(t, "These tests are fully covered for 64-bits in TestEVM_SingleStep_Bitwise64") +func TestEVM_SingleStep_Bitwise(t *testing.T) { // bitwise operations that use the full word size cases := []operatorTestCase{ - {name: "and", funct: 0x24, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(160)}, // and t0, s1, s2 - {name: "andi", opcode: 0xc, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(0)}, // andi t0, s1, 40 - {name: "or", funct: 0x25, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(1530)}, // or t0, s1, s2 - {name: "ori", opcode: 0xd, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(44)}, // ori t0, s1, 40 - {name: "xor", funct: 0x26, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(1370)}, // xor t0, s1, s2 - {name: "xori", opcode: 0xe, isImm: true, rs: Word(4), rt: Word(1), imm: uint16(40), expectRes: Word(44)}, // xori t0, s1, 40 - {name: "nor", funct: 0x27, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(4294965765)}, // nor t0, s1, s2 - {name: "slt", funct: 0x2a, isImm: false, rs: 0xFF_FF_FF_FE, rt: Word(5), expectRes: Word(1)}, // slt t0, s1, s2 - {name: "sltu", funct: 0x2b, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(0)}, // sltu t0, s1, s2 + {name: "and", funct: 0x24, isImm: false, rs: Word(0b1010_1100), rt: Word(0b1100_0101), expectRes: Word(0b1000_0100)}, // and t0, s1, s2 + {name: "andi", opcode: 0xc, isImm: true, rs: Word(0b1010_1100), rt: Word(1), imm: uint16(0b1100_0101), expectRes: Word(0b1000_0100)}, // andi t0, s1, imm + {name: "or", funct: 0x25, isImm: false, rs: Word(0b1010_1100), rt: Word(0b1100_0101), expectRes: Word(0b1110_1101)}, // or t0, s1, s2 + {name: "ori", opcode: 0xd, isImm: true, rs: Word(0b1010_1100), rt: Word(0xFFFF_FFFF), imm: uint16(0b1100_0101), expectRes: Word(0b1110_1101)}, // ori t0, s1, imm + {name: "xor", funct: 0x26, isImm: false, rs: Word(0b1010_1100), rt: Word(0b1100_0101), expectRes: Word(0b0110_1001)}, // xor t0, s1, s2 + {name: "xori", opcode: 0xe, isImm: true, rs: Word(0b1010_1100), rt: Word(1), imm: uint16(0b1100_0101), expectRes: Word(0b0110_1001)}, // xori t0, s1, imm + {name: "nor", funct: 0x27, isImm: false, rs: Word(0b1010_1100), rt: Word(0b1100_0101), expectRes: signExtend64(0b0001_0010 | 0xFFFF_FF00)}, // nor t0, s1, s2 + {name: "slt, success, positive vals", funct: 0x2a, isImm: false, rs: 1, rt: Word(5), expectRes: Word(1)}, // slt t0, s1, s2 + {name: "slt, success, mixed vals", funct: 0x2a, isImm: false, rs: signExtend64(0xFF_FF_FF_FE), rt: Word(5), expectRes: Word(1)}, // slt t0, s1, s2 + {name: "slt, success, negative vals", funct: 0x2a, isImm: false, rs: signExtend64(0xFF_FF_FF_FD), rt: signExtend64(0xFF_FF_FF_FE), expectRes: Word(1)}, // slt t0, s1, s2 + {name: "slt, fail, negative values", funct: 0x2a, isImm: false, rs: signExtend64(0xFF_FF_FF_FE), rt: signExtend64(0xFF_FF_FF_FD), expectRes: Word(0)}, // slt t0, s1, s2 + {name: "slt, fail, positive values", funct: 0x2a, isImm: false, rs: 555, rt: 123, expectRes: Word(0)}, // slt t0, s1, s2 + {name: "slt, fail, mixed values", funct: 0x2a, isImm: false, rs: 555, rt: signExtend64(0xFF_FF_FF_FD), expectRes: Word(0)}, // slt t0, s1, s2 + {name: "slti, success, positive vals", opcode: 0xa, isImm: true, rs: 1, imm: 5, expectRes: Word(1)}, + {name: "slti, success, mixed vals", opcode: 0xa, isImm: true, rs: signExtend64(0xFF_FF_FF_FE), imm: 5, expectRes: Word(1)}, + {name: "slti, success, negative vals", opcode: 0xa, isImm: true, rs: signExtend64(0xFF_FF_FF_FD), imm: 0xFFFE, expectRes: Word(1)}, + {name: "slti, fail, negative values", opcode: 0xa, isImm: true, rs: signExtend64(0xFF_FF_FF_FE), imm: 0xFFFD, expectRes: Word(0)}, + {name: "slti, fail, positive values", opcode: 0xa, isImm: true, rs: 555, imm: 123, expectRes: Word(0)}, + {name: "slti, fail, mixed values", opcode: 0xa, isImm: true, rs: 555, imm: 0xFFFD, expectRes: Word(0)}, + {name: "sltu, success", funct: 0x2b, isImm: false, rs: Word(490), rt: Word(1200), expectRes: Word(1)}, // sltu t0, s1, s2 + {name: "sltu, success, large values", funct: 0x2b, isImm: false, rs: signExtend64(0xFF_FF_FF_FD), rt: signExtend64(0xFF_FF_FF_FE), expectRes: Word(1)}, // sltu t0, s1, s2 + {name: "sltu, fail", funct: 0x2b, isImm: false, rs: Word(1200), rt: Word(490), expectRes: Word(0)}, // sltu t0, s1, s2 + {name: "sltu, fail, large values", funct: 0x2b, isImm: false, rs: signExtend64(0xFF_FF_FF_FE), rt: signExtend64(0xFF_FF_FF_FD), expectRes: Word(0)}, // sltu t0, s1, s2 + {name: "sltiu, success", opcode: 0xb, isImm: true, rs: Word(490), imm: 1200, expectRes: Word(1)}, + {name: "sltiu, success, large values", opcode: 0xb, isImm: true, rs: signExtend64(0xFF_FF_FF_FD), imm: 0xFFFE, expectRes: Word(1)}, + {name: "sltiu, fail", opcode: 0xb, isImm: true, rs: Word(1200), imm: 490, expectRes: Word(0)}, + {name: "sltiu, fail, large values", opcode: 0xb, isImm: true, rs: signExtend64(0xFF_FF_FF_FE), imm: 0xFFFD, expectRes: Word(0)}, } testOperators(t, cases, false) } @@ -338,11 +355,17 @@ func TestEVM_SingleStep_CloClz(t *testing.T) { func TestEVM_SingleStep_MovzMovn(t *testing.T) { versions := GetMipsVersionTestCases(t) cases := []struct { - name string - funct uint32 + name string + funct uint32 + testValue Word + shouldSucceed bool }{ - {name: "movz", funct: uint32(0xa)}, - {name: "movn", funct: uint32(0xb)}, + {name: "movz, success", funct: uint32(0xa), testValue: 0, shouldSucceed: true}, + {name: "movz, failure, testVal=1", funct: uint32(0xa), testValue: 1, shouldSucceed: false}, + {name: "movz, failure, testVal=2", funct: uint32(0xa), testValue: 2, shouldSucceed: false}, + {name: "movn, success, testVal=1", funct: uint32(0xb), testValue: 1, shouldSucceed: true}, + {name: "movn, success, testVal=2", funct: uint32(0xb), testValue: 2, shouldSucceed: true}, + {name: "movn, failure", funct: uint32(0xb), testValue: 0, shouldSucceed: false}, } for _, v := range versions { for i, tt := range cases { @@ -354,39 +377,21 @@ func TestEVM_SingleStep_MovzMovn(t *testing.T) { rtReg := uint32(10) rdReg := uint32(8) insn := rsReg<<21 | rtReg<<16 | rdReg<<11 | tt.funct - var t2 Word - if tt.funct == 0xa { - t2 = 0x0 - } else { - t2 = 0x1 - } - state.GetRegistersRef()[rtReg] = t2 + + state.GetRegistersRef()[rtReg] = tt.testValue state.GetRegistersRef()[rsReg] = Word(0xb) state.GetRegistersRef()[rdReg] = Word(0xa) testutil.StoreInstruction(state.GetMemory(), 0, insn) step := state.GetStep() + // Setup expectations expected := testutil.NewExpectedState(state) expected.ExpectStep() - expected.Registers[rdReg] = state.GetRegistersRef()[rsReg] - - stepWitness, err := goVm.Step(true) - require.NoError(t, err) - // Check expectations - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) - - if tt.funct == 0xa { - t2 = 0x1 - } else { - t2 = 0x0 + if tt.shouldSucceed { + expected.Registers[rdReg] = state.GetRegistersRef()[rsReg] } - state.GetRegistersRef()[rtReg] = t2 - expected.ExpectStep() - expected.Registers[rtReg] = t2 - expected.Registers[rdReg] = state.GetRegistersRef()[rdReg] - stepWitness, err = goVm.Step(true) + stepWitness, err := goVm.Step(true) require.NoError(t, err) // Check expectations expected.Validate(t, state) @@ -510,35 +515,47 @@ func TestEVM_SingleStep_MthiMtlo(t *testing.T) { } func TestEVM_SingleStep_BeqBne(t *testing.T) { + initialPC := Word(800) + negative := func(value Word) uint16 { + flipped := testutil.FlipSign(value) + return uint16(flipped) + } versions := GetMipsVersionTestCases(t) cases := []struct { - name string - imm uint32 - opcode uint32 - rs Word - rt Word + name string + imm uint16 + opcode uint32 + rs Word + rt Word + expectedNextPC Word }{ - {name: "bne", opcode: uint32(0x5), imm: uint32(0x10), rs: Word(0xaa), rt: Word(0xdeadbeef)}, // bne $t0, $t1, 16 - {name: "beq", opcode: uint32(0x4), imm: uint32(0x10), rs: Word(0xdeadbeef), rt: Word(0xdeadbeef)}, // beq $t0, $t1, 16 + // on success, expectedNextPC should be: (imm * 4) + pc + 4 + {name: "bne, success", opcode: uint32(0x5), imm: 10, rs: Word(0x123), rt: Word(0x456), expectedNextPC: 844}, // bne $t0, $t1, 16 + {name: "bne, success, signed-extended offset", opcode: uint32(0x5), imm: negative(3), rs: Word(0x123), rt: Word(0x456), expectedNextPC: 792}, // bne $t0, $t1, 16 + {name: "bne, fail", opcode: uint32(0x5), imm: 10, rs: Word(0x123), rt: Word(0x123), expectedNextPC: 808}, // bne $t0, $t1, 16 + {name: "beq, success", opcode: uint32(0x4), imm: 10, rs: Word(0x123), rt: Word(0x123), expectedNextPC: 844}, // beq $t0, $t1, 16 + {name: "beq, success, sign-extended offset", opcode: uint32(0x4), imm: negative(25), rs: Word(0x123), rt: Word(0x123), expectedNextPC: 704}, // beq $t0, $t1, 16 + {name: "beq, fail", opcode: uint32(0x4), imm: 10, rs: Word(0x123), rt: Word(0x456), expectedNextPC: 808}, // beq $t0, $t1, 16 } for _, v := range versions { for i, tt := range cases { testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) t.Run(testName, func(t *testing.T) { - goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(0), testutil.WithNextPC(4)) + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPCAndNextPC(initialPC)) state := goVm.GetState() rsReg := uint32(9) rtReg := uint32(8) - insn := tt.opcode<<26 | rsReg<<21 | rtReg<<16 | tt.imm + insn := tt.opcode<<26 | rsReg<<21 | rtReg<<16 | uint32(tt.imm) state.GetRegistersRef()[rtReg] = tt.rt state.GetRegistersRef()[rsReg] = tt.rs - testutil.StoreInstruction(state.GetMemory(), 0, insn) + testutil.StoreInstruction(state.GetMemory(), initialPC, insn) step := state.GetStep() + // Setup expectations expected := testutil.NewExpectedState(state) expected.Step = state.GetStep() + 1 expected.PC = state.GetCpu().NextPC - expected.NextPC = state.GetCpu().NextPC + Word(tt.imm<<2) + expected.NextPC = tt.expectedNextPC stepWitness, err := goVm.Step(true) require.NoError(t, err) @@ -561,22 +578,23 @@ func TestEVM_SingleStep_SlSr(t *testing.T) { funct uint16 expectVal Word }{ - {name: "sll", funct: uint16(4) << 6, rt: Word(0x20), rsReg: uint32(0x0), expectVal: Word(0x20) << uint8(4)}, // sll t0, t1, 3 + {name: "sll", funct: uint16(4) << 6, rt: Word(0x20), rsReg: uint32(0x0), expectVal: Word(0x200)}, // sll t0, t1, 3 {name: "sll with overflow", funct: uint16(1) << 6, rt: Word(0x8000_0000), rsReg: uint32(0x0), expectVal: 0x0}, {name: "sll with sign extension", funct: uint16(4) << 6, rt: Word(0x0800_0000), rsReg: uint32(0x0), expectVal: signExtend64(0x8000_0000)}, {name: "sll with max shift, sign extension", funct: uint16(31) << 6, rt: Word(0x01), rsReg: uint32(0x0), expectVal: signExtend64(0x8000_0000)}, {name: "sll with max shift, overflow", funct: uint16(31) << 6, rt: Word(0x02), rsReg: uint32(0x0), expectVal: 0x0}, - {name: "srl", funct: uint16(4)<<6 | 2, rt: Word(0x20), rsReg: uint32(0x0), expectVal: Word(0x20) >> uint8(4)}, // srl t0, t1, 3 + {name: "srl", funct: uint16(4)<<6 | 2, rt: Word(0x20), rsReg: uint32(0x0), expectVal: Word(0x2)}, // srl t0, t1, 3 {name: "srl with sign extension", funct: uint16(0)<<6 | 2, rt: Word(0x8000_0000), rsReg: uint32(0x0), expectVal: signExtend64(0x8000_0000)}, // srl t0, t1, 3 {name: "sra", funct: uint16(4)<<6 | 3, rt: Word(0x70_00_00_20), rsReg: uint32(0x0), expectVal: signExtend64(0x07_00_00_02)}, // sra t0, t1, 3 {name: "sra with sign extension", funct: uint16(4)<<6 | 3, rt: Word(0x80_00_00_20), rsReg: uint32(0x0), expectVal: signExtend64(0xF8_00_00_02)}, // sra t0, t1, 3 - {name: "sllv", funct: uint16(4), rt: Word(0x20), rs: Word(4), rsReg: uint32(0xa), expectVal: Word(0x20) << Word(4)}, // sllv t0, t1, t2 + {name: "sllv", funct: uint16(4), rt: Word(0x20), rs: Word(4), rsReg: uint32(0xa), expectVal: Word(0x200)}, // sllv t0, t1, t2 {name: "sllv with overflow", funct: uint16(4), rt: Word(0x8000_0000), rs: Word(1), rsReg: uint32(0xa), expectVal: 0x0}, {name: "sllv with sign extension", funct: uint16(4), rt: Word(0x0800_0000), rs: Word(4), rsReg: uint32(0xa), expectVal: signExtend64(0x8000_0000)}, {name: "sllv with max shift, sign extension", funct: uint16(4), rt: Word(0x01), rs: Word(31), rsReg: uint32(0xa), expectVal: signExtend64(0x8000_0000)}, {name: "sllv with max shift, overflow", funct: uint16(4), rt: Word(0x02), rs: Word(31), rsReg: uint32(0xa), expectVal: 0x0}, - {name: "srlv", funct: uint16(6), rt: Word(0x20_00), rs: Word(4), rsReg: uint32(0xa), expectVal: Word(0x20_00) >> Word(4)}, // srlv t0, t1, t2 + {name: "srlv", funct: uint16(6), rt: Word(0x20_00), rs: Word(4), rsReg: uint32(0xa), expectVal: Word(0x02_00)}, // srlv t0, t1, t2 {name: "srlv with sign extension", funct: uint16(6), rt: Word(0x8000_0000), rs: Word(0), rsReg: uint32(0xa), expectVal: signExtend64(0x8000_0000)}, // srlv t0, t1, t2 + {name: "srlv with zero extension", funct: uint16(6), rt: Word(0x8000_0000), rs: Word(1), rsReg: uint32(0xa), expectVal: 0x4000_0000}, // srlv t0, t1, t2 {name: "srav", funct: uint16(7), rt: Word(0x1deafbee), rs: Word(12), rsReg: uint32(0xa), expectVal: signExtend64(Word(0x0001deaf))}, // srav t0, t1, t2 {name: "srav with sign extension", funct: uint16(7), rt: Word(0xdeafbeef), rs: Word(12), rsReg: uint32(0xa), expectVal: signExtend64(Word(0xfffdeafb))}, // srav t0, t1, t2 } @@ -618,42 +636,80 @@ func TestEVM_SingleStep_JrJalr(t *testing.T) { cases := []struct { name string funct uint16 + rsReg uint32 + jumpTo Word rdReg uint32 + pc Word + nextPC Word expectLink bool + errorMsg string }{ - {name: "jr", funct: uint16(0x8), rdReg: uint32(0)}, // jr t0 - {name: "jalr", funct: uint16(0x9), rdReg: uint32(0x9), expectLink: true}, // jalr t1, t0 + {name: "jr", funct: uint16(0x8), rsReg: 8, jumpTo: 0x34, pc: 0, nextPC: 4}, // jr t0 + {name: "jr, delay slot", funct: uint16(0x8), rsReg: 8, jumpTo: 0x34, pc: 0, nextPC: 8, errorMsg: "jump in delay slot"}, // jr t0 + {name: "jalr", funct: uint16(0x9), rsReg: 8, jumpTo: 0x34, rdReg: uint32(0x9), expectLink: true, pc: 0, nextPC: 4}, // jalr t1, t0 + {name: "jalr, delay slot", funct: uint16(0x9), rsReg: 8, jumpTo: 0x34, rdReg: uint32(0x9), expectLink: true, pc: 0, nextPC: 100, errorMsg: "jump in delay slot"}, // jalr t1, t0 } for _, v := range versions { for i, tt := range cases { testName := fmt.Sprintf("%v (%v)", tt.name, v.Name) t.Run(testName, func(t *testing.T) { - goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(0), testutil.WithNextPC(4)) + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(i)), testutil.WithPC(tt.pc), testutil.WithNextPC(tt.nextPC)) state := goVm.GetState() - rsReg := uint32(8) - insn := rsReg<<21 | tt.rdReg<<11 | uint32(tt.funct) - state.GetRegistersRef()[rsReg] = Word(0x34) + insn := tt.rsReg<<21 | tt.rdReg<<11 | uint32(tt.funct) + state.GetRegistersRef()[tt.rsReg] = tt.jumpTo testutil.StoreInstruction(state.GetMemory(), 0, insn) step := state.GetStep() - // Setup expectations - expected := testutil.NewExpectedState(state) - expected.Step = state.GetStep() + 1 - expected.PC = state.GetCpu().NextPC - expected.NextPC = state.GetRegistersRef()[rsReg] - if tt.expectLink { - expected.Registers[tt.rdReg] = state.GetPC() + 8 - } - stepWitness, err := goVm.Step(true) - require.NoError(t, err) - // Check expectations - expected.Validate(t, state) - testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) + if tt.errorMsg != "" { + proofData := v.ProofGenerator(t, goVm.GetState()) + errorMatcher := testutil.CreateErrorStringMatcher(tt.errorMsg) + require.Panics(t, func() { _, _ = goVm.Step(false) }) + testutil.AssertEVMReverts(t, state, v.Contracts, nil, proofData, errorMatcher) + } else { + // Setup expectations + expected := testutil.NewExpectedState(state) + expected.Step = state.GetStep() + 1 + expected.PC = state.GetCpu().NextPC + expected.NextPC = tt.jumpTo + if tt.expectLink { + expected.Registers[tt.rdReg] = state.GetPC() + 8 + } + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) + } }) } } } +func TestEVM_SingleStep_Sync(t *testing.T) { + versions := GetMipsVersionTestCases(t) + syncInsn := uint32(0x0000_000F) + for _, v := range versions { + testName := fmt.Sprintf("Sync (%v)", v.Name) + t.Run(testName, func(t *testing.T) { + goVm := v.VMFactory(nil, os.Stdout, os.Stderr, testutil.CreateLogger(), testutil.WithRandomization(int64(248))) + state := goVm.GetState() + testutil.StoreInstruction(state.GetMemory(), state.GetPC(), syncInsn) + step := state.GetStep() + + // Setup expectations + expected := testutil.NewExpectedState(state) + expected.ExpectStep() + + stepWitness, err := goVm.Step(true) + require.NoError(t, err) + // Check expectations + expected.Validate(t, state) + testutil.ValidateEVM(t, stepWitness, step, goVm, v.StateHashFn, v.Contracts) + }) + } +} + func TestEVM_MMap(t *testing.T) { versions := GetMipsVersionTestCases(t) cases := []struct { From 2f3669d3c55bc68d7a89ee42fbcd7b65476a959a Mon Sep 17 00:00:00 2001 From: Sam Stokes <35908605+bitwiseguy@users.noreply.github.com> Date: Sat, 8 Mar 2025 01:03:30 -0500 Subject: [PATCH 106/130] op-deployer: add command to verify contracts (#14633) * op-deployer: add 'verify' contracts command * fix rate limiter values * verified SuperchainConfigImplAddress * verified superchain bundle (including constructor support) * cleanup logs and flags * cleanup: remove unused code * verified implementations bundle * verified opchain bundle * update searchRemappings comment * use more type-safe structs * use l2-chain-id flag instead of l2-chain-index * update to use new artifacts.Download function definition * read constructor args from deployment tx initcode * return error if contract creation query fails * use more informative contract-name flag description * calc num constructorArgSlots by parsing abi * add json tags (matching contract bundle) to deploy script outputs * bootstrap default to writing to bootstrap_.json file * pass contracts-locator flag to verify command * minimize flags required for bootstrap, verify * fix TestArtifactJSON * add etherscan unit tests * fix go lint errors * make artifact lookup compatible with fragmented opcm * fix go lint errors * use require instead of assert in tests --- op-chain-ops/foundry/artifact.go | 28 +- .../forge-artifacts/Owned.sol/Owned.json | 2 +- op-deployer/cmd/op-deployer/main.go | 7 + op-deployer/pkg/deployer/bootstrap/flags.go | 17 +- op-deployer/pkg/deployer/bootstrap/proxy.go | 2 +- .../pkg/deployer/bootstrap/superchain.go | 2 +- .../pkg/deployer/bootstrap/validator.go | 2 +- op-deployer/pkg/deployer/flags.go | 58 +++- op-deployer/pkg/deployer/inspect/l1.go | 34 +++ .../pkg/deployer/opcm/implementations.go | 36 +-- op-deployer/pkg/deployer/opcm/superchain.go | 10 +- op-deployer/pkg/deployer/verify/artifacts.go | 98 ++++++ .../pkg/deployer/verify/constructors.go | 61 ++++ .../pkg/deployer/verify/constructors_test.go | 171 +++++++++++ op-deployer/pkg/deployer/verify/etherscan.go | 262 +++++++++++++++++ .../pkg/deployer/verify/etherscan_test.go | 278 ++++++++++++++++++ op-deployer/pkg/deployer/verify/verifier.go | 194 ++++++++++++ 17 files changed, 1211 insertions(+), 51 deletions(-) create mode 100644 op-deployer/pkg/deployer/verify/artifacts.go create mode 100644 op-deployer/pkg/deployer/verify/constructors.go create mode 100644 op-deployer/pkg/deployer/verify/constructors_test.go create mode 100644 op-deployer/pkg/deployer/verify/etherscan.go create mode 100644 op-deployer/pkg/deployer/verify/etherscan_test.go create mode 100644 op-deployer/pkg/deployer/verify/verifier.go diff --git a/op-chain-ops/foundry/artifact.go b/op-chain-ops/foundry/artifact.go index 70e3085b096..a3e0a6e0767 100644 --- a/op-chain-ops/foundry/artifact.go +++ b/op-chain-ops/foundry/artifact.go @@ -59,6 +59,7 @@ func (a Artifact) MarshalJSON() ([]byte, error) { // foundry artifacts. type artifactMarshaling struct { ABI json.RawMessage `json:"abi"` + Source string `json:"source"` StorageLayout solc.StorageLayout `json:"storageLayout"` DeployedBytecode DeployedBytecode `json:"deployedBytecode"` Bytecode Bytecode `json:"bytecode"` @@ -77,7 +78,7 @@ type Metadata struct { Settings struct { // Remappings of the contract imports - Remappings json.RawMessage `json:"remappings"` + Remappings []string `json:"remappings"` // Optimizer settings affect the compiler output, but can be arbitrary. // We load them opaquely, to include it in the hash of what we run. Optimizer json.RawMessage `json:"optimizer"` @@ -102,6 +103,7 @@ type Metadata struct { type ContractSource struct { Keccak256 common.Hash `json:"keccak256"` URLs []string `json:"urls"` + Content string `json:"content"` License string `json:"license"` } @@ -153,3 +155,27 @@ func ReadArtifact(path string) (*Artifact, error) { } return &artifact, nil } + +// SearchRemappings applies the configured remappings to a given source path, +// or returns the source path unchanged if no remapping is found. It assumes that +// each remapping is of the form "alias/=actualPath". +func (a Artifact) SearchRemappings(sourcePath string) string { + for _, mapping := range a.Metadata.Settings.Remappings { + parts := strings.Split(mapping, "/=") + if len(parts) != 2 { + continue + } + alias := parts[0] + if !strings.HasSuffix(alias, "/") { + alias += "/" + } + actualPath := parts[1] + if !strings.HasSuffix(actualPath, "/") { + actualPath += "/" + } + if strings.HasPrefix(sourcePath, actualPath) { + return alias + sourcePath[len(actualPath):] + } + } + return sourcePath +} diff --git a/op-chain-ops/foundry/testdata/forge-artifacts/Owned.sol/Owned.json b/op-chain-ops/foundry/testdata/forge-artifacts/Owned.sol/Owned.json index 2646394b634..f8f68822ae9 100644 --- a/op-chain-ops/foundry/testdata/forge-artifacts/Owned.sol/Owned.json +++ b/op-chain-ops/foundry/testdata/forge-artifacts/Owned.sol/Owned.json @@ -1 +1 @@ -{"abi":[{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"setOwner","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"OwnerUpdated","inputs":[{"name":"user","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false}],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{"owner()":"8da5cb5b","setOwner(address)":"13af4035"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.15+commit.e14f2714\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnerUpdated\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"author\":\"Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Owned.sol)\",\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"Simple single owner authorization mixin.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/solmate/src/auth/Owned.sol\":\"Owned\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[\":@lib-keccak/=lib/lib-keccak/contracts/lib/\",\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/\",\":@rari-capital/solmate/=lib/solmate/\",\":@solady-test/=lib/lib-keccak/lib/solady/test/\",\":@solady/=lib/solady/src/\",\":automate/=lib/automate/contracts/\",\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/automate/lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":gelato/=lib/automate/contracts/\",\":hardhat/=lib/automate/node_modules/hardhat/\",\":kontrol-cheatcodes/=lib/kontrol-cheatcodes/src/\",\":lib-keccak/=lib/lib-keccak/contracts/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":prb-test/=lib/automate/lib/prb-test/src/\",\":prb/-est/=lib/automate/lib/prb-test/src/\",\":safe-contracts/=lib/safe-contracts/contracts/\",\":solady/=lib/solady/\",\":solmate/=lib/solmate/src/\"]},\"sources\":{\"lib/solmate/src/auth/Owned.sol\":{\"keccak256\":\"0x7e91c80b0dd1a14a19cb9e661b99924043adab6d9d893bbfcf3a6a3dc23a6743\",\"license\":\"AGPL-3.0-only\",\"urls\":[\"bzz-raw://515890d9fc87d6762dae2354a3a0714a26c652f0ea5bb631122be1968ef8c0e9\",\"dweb:/ipfs/QmTRpQ7uoAR1vCACKJm14Ba3oKVLqcA9reTwbHAPxawVpM\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.15+commit.e14f2714"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address","name":"user","type":"address","indexed":true},{"internalType":"address","name":"newOwner","type":"address","indexed":true}],"type":"event","name":"OwnerUpdated","anonymous":false},{"inputs":[],"stateMutability":"view","type":"function","name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}]},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"setOwner"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@lib-keccak/=lib/lib-keccak/contracts/lib/","@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/","@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/","@rari-capital/solmate/=lib/solmate/","@solady-test/=lib/lib-keccak/lib/solady/test/","@solady/=lib/solady/src/","automate/=lib/automate/contracts/","ds-test/=lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/automate/lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","gelato/=lib/automate/contracts/","hardhat/=lib/automate/node_modules/hardhat/","kontrol-cheatcodes/=lib/kontrol-cheatcodes/src/","lib-keccak/=lib/lib-keccak/contracts/","openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/","openzeppelin-contracts/=lib/openzeppelin-contracts/","prb-test/=lib/automate/lib/prb-test/src/","prb/-est/=lib/automate/lib/prb-test/src/","safe-contracts/=lib/safe-contracts/contracts/","solady/=lib/solady/","solmate/=lib/solmate/src/"],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"lib/solmate/src/auth/Owned.sol":"Owned"},"evmVersion":"london","libraries":{}},"sources":{"lib/solmate/src/auth/Owned.sol":{"keccak256":"0x7e91c80b0dd1a14a19cb9e661b99924043adab6d9d893bbfcf3a6a3dc23a6743","urls":["bzz-raw://515890d9fc87d6762dae2354a3a0714a26c652f0ea5bb631122be1968ef8c0e9","dweb:/ipfs/QmTRpQ7uoAR1vCACKJm14Ba3oKVLqcA9reTwbHAPxawVpM"],"license":"AGPL-3.0-only"}},"version":1},"storageLayout":{"storage":[{"astId":53517,"contract":"lib/solmate/src/auth/Owned.sol:Owned","label":"owner","offset":0,"slot":"0","type":"t_address"}],"types":{"t_address":{"encoding":"inplace","label":"address","numberOfBytes":"20"}}},"userdoc":{"version":1,"kind":"user","notice":"Simple single owner authorization mixin."},"devdoc":{"version":1,"kind":"dev","author":"Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Owned.sol)"},"ast":{"absolutePath":"lib/solmate/src/auth/Owned.sol","id":53567,"exportedSymbols":{"Owned":[53566]},"nodeType":"SourceUnit","src":"42:1398:68","nodes":[{"id":53508,"nodeType":"PragmaDirective","src":"42:24:68","nodes":[],"literals":["solidity",">=","0.8",".0"]},{"id":53566,"nodeType":"ContractDefinition","src":"212:1227:68","nodes":[{"id":53515,"nodeType":"EventDefinition","src":"421:67:68","nodes":[],"anonymous":false,"eventSelector":"8292fce18fa69edf4db7b94ea2e58241df0ae57f97e0a6c9b29067028bf92d76","name":"OwnerUpdated","nameLocation":"427:12:68","parameters":{"id":53514,"nodeType":"ParameterList","parameters":[{"constant":false,"id":53511,"indexed":true,"mutability":"mutable","name":"user","nameLocation":"456:4:68","nodeType":"VariableDeclaration","scope":53515,"src":"440:20:68","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":53510,"name":"address","nodeType":"ElementaryTypeName","src":"440:7:68","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":53513,"indexed":true,"mutability":"mutable","name":"newOwner","nameLocation":"478:8:68","nodeType":"VariableDeclaration","scope":53515,"src":"462:24:68","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":53512,"name":"address","nodeType":"ElementaryTypeName","src":"462:7:68","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"439:48:68"}},{"id":53517,"nodeType":"VariableDeclaration","src":"679:20:68","nodes":[],"constant":false,"functionSelector":"8da5cb5b","mutability":"mutable","name":"owner","nameLocation":"694:5:68","scope":53566,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":53516,"name":"address","nodeType":"ElementaryTypeName","src":"679:7:68","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"public"},{"id":53529,"nodeType":"ModifierDefinition","src":"706:102:68","nodes":[],"body":{"id":53528,"nodeType":"Block","src":"735:73:68","nodes":[],"statements":[{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_address","typeString":"address"},"id":53523,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":53520,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"753:3:68","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":53521,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"753:10:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"id":53522,"name":"owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53517,"src":"767:5:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"src":"753:19:68","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"554e415554484f52495a4544","id":53524,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"774:14:68","typeDescriptions":{"typeIdentifier":"t_stringliteral_269df367cd41cace5897a935d0e0858fe4543b5619d45e09af6b124c1bb3d528","typeString":"literal_string \"UNAUTHORIZED\""},"value":"UNAUTHORIZED"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_269df367cd41cace5897a935d0e0858fe4543b5619d45e09af6b124c1bb3d528","typeString":"literal_string \"UNAUTHORIZED\""}],"id":53519,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"745:7:68","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":53525,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"745:44:68","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":53526,"nodeType":"ExpressionStatement","src":"745:44:68"},{"id":53527,"nodeType":"PlaceholderStatement","src":"800:1:68"}]},"name":"onlyOwner","nameLocation":"715:9:68","parameters":{"id":53518,"nodeType":"ParameterList","parameters":[],"src":"724:2:68"},"virtual":true,"visibility":"internal"},{"id":53547,"nodeType":"FunctionDefinition","src":"996:107:68","nodes":[],"body":{"id":53546,"nodeType":"Block","src":"1024:79:68","nodes":[],"statements":[{"expression":{"id":53536,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":53534,"name":"owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53517,"src":"1034:5:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":53535,"name":"_owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53531,"src":"1042:6:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"src":"1034:14:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":53537,"nodeType":"ExpressionStatement","src":"1034:14:68"},{"eventCall":{"arguments":[{"arguments":[{"hexValue":"30","id":53541,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"1085:1:68","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"}],"id":53540,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"1077:7:68","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":53539,"name":"address","nodeType":"ElementaryTypeName","src":"1077:7:68","typeDescriptions":{}}},"id":53542,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1077:10:68","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":53543,"name":"_owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53531,"src":"1089:6:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_address","typeString":"address"}],"id":53538,"name":"OwnerUpdated","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53515,"src":"1064:12:68","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_address_$_t_address_$returns$__$","typeString":"function (address,address)"}},"id":53544,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1064:32:68","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":53545,"nodeType":"EmitStatement","src":"1059:37:68"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":53532,"nodeType":"ParameterList","parameters":[{"constant":false,"id":53531,"mutability":"mutable","name":"_owner","nameLocation":"1016:6:68","nodeType":"VariableDeclaration","scope":53547,"src":"1008:14:68","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":53530,"name":"address","nodeType":"ElementaryTypeName","src":"1008:7:68","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"1007:16:68"},"returnParameters":{"id":53533,"nodeType":"ParameterList","parameters":[],"src":"1024:0:68"},"scope":53566,"stateMutability":"nonpayable","virtual":false,"visibility":"internal"},{"id":53565,"nodeType":"FunctionDefinition","src":"1293:144:68","nodes":[],"body":{"id":53564,"nodeType":"Block","src":"1354:83:68","nodes":[],"statements":[{"expression":{"id":53556,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":53554,"name":"owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53517,"src":"1364:5:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":53555,"name":"newOwner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53549,"src":"1372:8:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"src":"1364:16:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":53557,"nodeType":"ExpressionStatement","src":"1364:16:68"},{"eventCall":{"arguments":[{"expression":{"id":53559,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"1409:3:68","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":53560,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"1409:10:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":53561,"name":"newOwner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53549,"src":"1421:8:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_address","typeString":"address"}],"id":53558,"name":"OwnerUpdated","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53515,"src":"1396:12:68","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_address_$_t_address_$returns$__$","typeString":"function (address,address)"}},"id":53562,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1396:34:68","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":53563,"nodeType":"EmitStatement","src":"1391:39:68"}]},"functionSelector":"13af4035","implemented":true,"kind":"function","modifiers":[{"id":53552,"kind":"modifierInvocation","modifierName":{"id":53551,"name":"onlyOwner","nodeType":"IdentifierPath","referencedDeclaration":53529,"src":"1344:9:68"},"nodeType":"ModifierInvocation","src":"1344:9:68"}],"name":"setOwner","nameLocation":"1302:8:68","parameters":{"id":53550,"nodeType":"ParameterList","parameters":[{"constant":false,"id":53549,"mutability":"mutable","name":"newOwner","nameLocation":"1319:8:68","nodeType":"VariableDeclaration","scope":53565,"src":"1311:16:68","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":53548,"name":"address","nodeType":"ElementaryTypeName","src":"1311:7:68","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"1310:18:68"},"returnParameters":{"id":53553,"nodeType":"ParameterList","parameters":[],"src":"1354:0:68"},"scope":53566,"stateMutability":"nonpayable","virtual":true,"visibility":"public"}],"abstract":true,"baseContracts":[],"canonicalName":"Owned","contractDependencies":[],"contractKind":"contract","documentation":{"id":53509,"nodeType":"StructuredDocumentation","src":"68:144:68","text":"@notice Simple single owner authorization mixin.\n @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Owned.sol)"},"fullyImplemented":true,"linearizedBaseContracts":[53566],"name":"Owned","nameLocation":"230:5:68","scope":53567,"usedErrors":[]}],"license":"AGPL-3.0-only"},"id":68} +{"abi":[{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"setOwner","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"event","name":"OwnerUpdated","inputs":[{"name":"user","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false}],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{"owner()":"8da5cb5b","setOwner(address)":"13af4035"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.15+commit.e14f2714\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnerUpdated\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"author\":\"Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Owned.sol)\",\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"notice\":\"Simple single owner authorization mixin.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/solmate/src/auth/Owned.sol\":\"Owned\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":999999},\"remappings\":[\":@lib-keccak/=lib/lib-keccak/contracts/lib/\",\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/\",\":@rari-capital/solmate/=lib/solmate/\",\":@solady-test/=lib/lib-keccak/lib/solady/test/\",\":@solady/=lib/solady/src/\",\":automate/=lib/automate/contracts/\",\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/automate/lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":gelato/=lib/automate/contracts/\",\":hardhat/=lib/automate/node_modules/hardhat/\",\":kontrol-cheatcodes/=lib/kontrol-cheatcodes/src/\",\":lib-keccak/=lib/lib-keccak/contracts/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":prb-test/=lib/automate/lib/prb-test/src/\",\":prb/-est/=lib/automate/lib/prb-test/src/\",\":safe-contracts/=lib/safe-contracts/contracts/\",\":solady/=lib/solady/\",\":solmate/=lib/solmate/src/\"]},\"sources\":{\"lib/solmate/src/auth/Owned.sol\":{\"content\":\"\",\"keccak256\":\"0x7e91c80b0dd1a14a19cb9e661b99924043adab6d9d893bbfcf3a6a3dc23a6743\",\"license\":\"AGPL-3.0-only\",\"urls\":[\"bzz-raw://515890d9fc87d6762dae2354a3a0714a26c652f0ea5bb631122be1968ef8c0e9\",\"dweb:/ipfs/QmTRpQ7uoAR1vCACKJm14Ba3oKVLqcA9reTwbHAPxawVpM\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.15+commit.e14f2714"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address","name":"user","type":"address","indexed":true},{"internalType":"address","name":"newOwner","type":"address","indexed":true}],"type":"event","name":"OwnerUpdated","anonymous":false},{"inputs":[],"stateMutability":"view","type":"function","name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}]},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"setOwner"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@lib-keccak/=lib/lib-keccak/contracts/lib/","@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/","@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/","@rari-capital/solmate/=lib/solmate/","@solady-test/=lib/lib-keccak/lib/solady/test/","@solady/=lib/solady/src/","automate/=lib/automate/contracts/","ds-test/=lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/automate/lib/openzeppelin-contracts/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","gelato/=lib/automate/contracts/","hardhat/=lib/automate/node_modules/hardhat/","kontrol-cheatcodes/=lib/kontrol-cheatcodes/src/","lib-keccak/=lib/lib-keccak/contracts/","openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/","openzeppelin-contracts/=lib/openzeppelin-contracts/","prb-test/=lib/automate/lib/prb-test/src/","prb/-est/=lib/automate/lib/prb-test/src/","safe-contracts/=lib/safe-contracts/contracts/","solady/=lib/solady/","solmate/=lib/solmate/src/"],"optimizer":{"enabled":true,"runs":999999},"metadata":{"bytecodeHash":"none"},"compilationTarget":{"lib/solmate/src/auth/Owned.sol":"Owned"},"evmVersion":"london","libraries":{}},"sources":{"lib/solmate/src/auth/Owned.sol":{"content":"","keccak256":"0x7e91c80b0dd1a14a19cb9e661b99924043adab6d9d893bbfcf3a6a3dc23a6743","urls":["bzz-raw://515890d9fc87d6762dae2354a3a0714a26c652f0ea5bb631122be1968ef8c0e9","dweb:/ipfs/QmTRpQ7uoAR1vCACKJm14Ba3oKVLqcA9reTwbHAPxawVpM"],"license":"AGPL-3.0-only"}},"version":1},"storageLayout":{"storage":[{"astId":53517,"contract":"lib/solmate/src/auth/Owned.sol:Owned","label":"owner","offset":0,"slot":"0","type":"t_address"}],"types":{"t_address":{"encoding":"inplace","label":"address","numberOfBytes":"20"}}},"userdoc":{"version":1,"kind":"user","notice":"Simple single owner authorization mixin."},"devdoc":{"version":1,"kind":"dev","author":"Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Owned.sol)"},"ast":{"absolutePath":"lib/solmate/src/auth/Owned.sol","id":53567,"exportedSymbols":{"Owned":[53566]},"nodeType":"SourceUnit","src":"42:1398:68","nodes":[{"id":53508,"nodeType":"PragmaDirective","src":"42:24:68","nodes":[],"literals":["solidity",">=","0.8",".0"]},{"id":53566,"nodeType":"ContractDefinition","src":"212:1227:68","nodes":[{"id":53515,"nodeType":"EventDefinition","src":"421:67:68","nodes":[],"anonymous":false,"eventSelector":"8292fce18fa69edf4db7b94ea2e58241df0ae57f97e0a6c9b29067028bf92d76","name":"OwnerUpdated","nameLocation":"427:12:68","parameters":{"id":53514,"nodeType":"ParameterList","parameters":[{"constant":false,"id":53511,"indexed":true,"mutability":"mutable","name":"user","nameLocation":"456:4:68","nodeType":"VariableDeclaration","scope":53515,"src":"440:20:68","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":53510,"name":"address","nodeType":"ElementaryTypeName","src":"440:7:68","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":53513,"indexed":true,"mutability":"mutable","name":"newOwner","nameLocation":"478:8:68","nodeType":"VariableDeclaration","scope":53515,"src":"462:24:68","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":53512,"name":"address","nodeType":"ElementaryTypeName","src":"462:7:68","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"439:48:68"}},{"id":53517,"nodeType":"VariableDeclaration","src":"679:20:68","nodes":[],"constant":false,"functionSelector":"8da5cb5b","mutability":"mutable","name":"owner","nameLocation":"694:5:68","scope":53566,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":53516,"name":"address","nodeType":"ElementaryTypeName","src":"679:7:68","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"public"},{"id":53529,"nodeType":"ModifierDefinition","src":"706:102:68","nodes":[],"body":{"id":53528,"nodeType":"Block","src":"735:73:68","nodes":[],"statements":[{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_address","typeString":"address"},"id":53523,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"expression":{"id":53520,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"753:3:68","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":53521,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"753:10:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"id":53522,"name":"owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53517,"src":"767:5:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"src":"753:19:68","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"554e415554484f52495a4544","id":53524,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"774:14:68","typeDescriptions":{"typeIdentifier":"t_stringliteral_269df367cd41cace5897a935d0e0858fe4543b5619d45e09af6b124c1bb3d528","typeString":"literal_string \"UNAUTHORIZED\""},"value":"UNAUTHORIZED"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_269df367cd41cace5897a935d0e0858fe4543b5619d45e09af6b124c1bb3d528","typeString":"literal_string \"UNAUTHORIZED\""}],"id":53519,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"745:7:68","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":53525,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"745:44:68","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":53526,"nodeType":"ExpressionStatement","src":"745:44:68"},{"id":53527,"nodeType":"PlaceholderStatement","src":"800:1:68"}]},"name":"onlyOwner","nameLocation":"715:9:68","parameters":{"id":53518,"nodeType":"ParameterList","parameters":[],"src":"724:2:68"},"virtual":true,"visibility":"internal"},{"id":53547,"nodeType":"FunctionDefinition","src":"996:107:68","nodes":[],"body":{"id":53546,"nodeType":"Block","src":"1024:79:68","nodes":[],"statements":[{"expression":{"id":53536,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":53534,"name":"owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53517,"src":"1034:5:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":53535,"name":"_owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53531,"src":"1042:6:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"src":"1034:14:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":53537,"nodeType":"ExpressionStatement","src":"1034:14:68"},{"eventCall":{"arguments":[{"arguments":[{"hexValue":"30","id":53541,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"1085:1:68","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"}],"id":53540,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"1077:7:68","typeDescriptions":{"typeIdentifier":"t_type$_t_address_$","typeString":"type(address)"},"typeName":{"id":53539,"name":"address","nodeType":"ElementaryTypeName","src":"1077:7:68","typeDescriptions":{}}},"id":53542,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1077:10:68","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":53543,"name":"_owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53531,"src":"1089:6:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_address","typeString":"address"}],"id":53538,"name":"OwnerUpdated","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53515,"src":"1064:12:68","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_address_$_t_address_$returns$__$","typeString":"function (address,address)"}},"id":53544,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1064:32:68","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":53545,"nodeType":"EmitStatement","src":"1059:37:68"}]},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":53532,"nodeType":"ParameterList","parameters":[{"constant":false,"id":53531,"mutability":"mutable","name":"_owner","nameLocation":"1016:6:68","nodeType":"VariableDeclaration","scope":53547,"src":"1008:14:68","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":53530,"name":"address","nodeType":"ElementaryTypeName","src":"1008:7:68","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"1007:16:68"},"returnParameters":{"id":53533,"nodeType":"ParameterList","parameters":[],"src":"1024:0:68"},"scope":53566,"stateMutability":"nonpayable","virtual":false,"visibility":"internal"},{"id":53565,"nodeType":"FunctionDefinition","src":"1293:144:68","nodes":[],"body":{"id":53564,"nodeType":"Block","src":"1354:83:68","nodes":[],"statements":[{"expression":{"id":53556,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"id":53554,"name":"owner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53517,"src":"1364:5:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"id":53555,"name":"newOwner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53549,"src":"1372:8:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"src":"1364:16:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"id":53557,"nodeType":"ExpressionStatement","src":"1364:16:68"},{"eventCall":{"arguments":[{"expression":{"id":53559,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"1409:3:68","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":53560,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberName":"sender","nodeType":"MemberAccess","src":"1409:10:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":53561,"name":"newOwner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53549,"src":"1421:8:68","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_address","typeString":"address"}],"id":53558,"name":"OwnerUpdated","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":53515,"src":"1396:12:68","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_address_$_t_address_$returns$__$","typeString":"function (address,address)"}},"id":53562,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"names":[],"nodeType":"FunctionCall","src":"1396:34:68","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":53563,"nodeType":"EmitStatement","src":"1391:39:68"}]},"functionSelector":"13af4035","implemented":true,"kind":"function","modifiers":[{"id":53552,"kind":"modifierInvocation","modifierName":{"id":53551,"name":"onlyOwner","nodeType":"IdentifierPath","referencedDeclaration":53529,"src":"1344:9:68"},"nodeType":"ModifierInvocation","src":"1344:9:68"}],"name":"setOwner","nameLocation":"1302:8:68","parameters":{"id":53550,"nodeType":"ParameterList","parameters":[{"constant":false,"id":53549,"mutability":"mutable","name":"newOwner","nameLocation":"1319:8:68","nodeType":"VariableDeclaration","scope":53565,"src":"1311:16:68","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":53548,"name":"address","nodeType":"ElementaryTypeName","src":"1311:7:68","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"1310:18:68"},"returnParameters":{"id":53553,"nodeType":"ParameterList","parameters":[],"src":"1354:0:68"},"scope":53566,"stateMutability":"nonpayable","virtual":true,"visibility":"public"}],"abstract":true,"baseContracts":[],"canonicalName":"Owned","contractDependencies":[],"contractKind":"contract","documentation":{"id":53509,"nodeType":"StructuredDocumentation","src":"68:144:68","text":"@notice Simple single owner authorization mixin.\n @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/auth/Owned.sol)"},"fullyImplemented":true,"linearizedBaseContracts":[53566],"name":"Owned","nameLocation":"230:5:68","scope":53567,"usedErrors":[]}],"license":"AGPL-3.0-only"},"id":68} diff --git a/op-deployer/cmd/op-deployer/main.go b/op-deployer/cmd/op-deployer/main.go index a3bd8363981..2560d6a0c02 100644 --- a/op-deployer/cmd/op-deployer/main.go +++ b/op-deployer/cmd/op-deployer/main.go @@ -6,6 +6,7 @@ import ( "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/clean" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/upgrade" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/verify" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/bootstrap" @@ -66,6 +67,12 @@ func main() { Usage: "cleans up various things", Subcommands: clean.Commands, }, + { + Name: "verify", + Usage: "verifies deployed contracts on Etherscan", + Flags: cliapp.ProtectFlags(deployer.VerifyFlags), + Action: verify.VerifyCLI, + }, } app.Writer = os.Stdout app.ErrWriter = os.Stderr diff --git a/op-deployer/pkg/deployer/bootstrap/flags.go b/op-deployer/pkg/deployer/bootstrap/flags.go index ba29f614116..e55bc204ed5 100644 --- a/op-deployer/pkg/deployer/bootstrap/flags.go +++ b/op-deployer/pkg/deployer/bootstrap/flags.go @@ -10,7 +10,6 @@ import ( const ( OutfileFlagName = "outfile" - ArtifactsLocatorFlagName = "artifacts-locator" WithdrawalDelaySecondsFlagName = "withdrawal-delay-seconds" MinProposalSizeBytesFlagName = "min-proposal-size-bytes" ChallengePeriodSecondsFlagName = "challenge-period-seconds" @@ -19,6 +18,7 @@ const ( MIPSVersionFlagName = "mips-version" ProxyOwnerFlagName = "proxy-owner" SuperchainProxyAdminOwnerFlagName = "superchain-proxy-admin-owner" + L1ContractsReleaseFlagName = "l1-contracts-release" ProtocolVersionsOwnerFlagName = "protocol-versions-owner" GuardianFlagName = "guardian" PausedFlagName = "paused" @@ -33,11 +33,6 @@ var ( EnvVars: deployer.PrefixEnvVar("OUTFILE"), Value: "-", } - ArtifactsLocatorFlag = &cli.StringFlag{ - Name: ArtifactsLocatorFlagName, - Usage: "Locator for artifacts.", - EnvVars: deployer.PrefixEnvVar("ARTIFACTS_LOCATOR"), - } WithdrawalDelaySecondsFlag = &cli.Uint64Flag{ Name: WithdrawalDelaySecondsFlagName, Usage: "Withdrawal delay in seconds.", @@ -114,7 +109,7 @@ var ( EnvVars: deployer.PrefixEnvVar("RECOMMENDED_PROTOCOL_VERSION"), } L1ContractsReleaseFlag = &cli.StringFlag{ - Name: "l1-contracts-release", + Name: L1ContractsReleaseFlagName, Usage: "Release version to set OPCM implementations for, of the format `op-contracts/vX.Y.Z`.", EnvVars: deployer.PrefixEnvVar("L1_CONTRACTS_RELEASE"), } @@ -149,7 +144,7 @@ var ImplementationsFlags = []cli.Flag{ deployer.L1RPCURLFlag, deployer.PrivateKeyFlag, OutfileFlag, - ArtifactsLocatorFlag, + deployer.ArtifactsLocatorFlag, L1ContractsReleaseFlag, MIPSVersionFlag, WithdrawalDelaySecondsFlag, @@ -167,7 +162,7 @@ var ProxyFlags = []cli.Flag{ deployer.L1RPCURLFlag, deployer.PrivateKeyFlag, OutfileFlag, - ArtifactsLocatorFlag, + deployer.ArtifactsLocatorFlag, ProxyOwnerFlag, } @@ -175,7 +170,7 @@ var SuperchainFlags = []cli.Flag{ deployer.L1RPCURLFlag, deployer.PrivateKeyFlag, OutfileFlag, - ArtifactsLocatorFlag, + deployer.ArtifactsLocatorFlag, SuperchainProxyAdminOwnerFlag, ProtocolVersionsOwnerFlag, GuardianFlag, @@ -188,7 +183,7 @@ var ValidatorFlags = []cli.Flag{ deployer.L1RPCURLFlag, deployer.PrivateKeyFlag, OutfileFlag, - ArtifactsLocatorFlag, + deployer.ArtifactsLocatorFlag, ConfigFileFlag, } diff --git a/op-deployer/pkg/deployer/bootstrap/proxy.go b/op-deployer/pkg/deployer/bootstrap/proxy.go index 06f4f762b5e..865dfe5b8a7 100644 --- a/op-deployer/pkg/deployer/bootstrap/proxy.go +++ b/op-deployer/pkg/deployer/bootstrap/proxy.go @@ -77,7 +77,7 @@ func ProxyCLI(cliCtx *cli.Context) error { l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) privateKey := cliCtx.String(deployer.PrivateKeyFlagName) outfile := cliCtx.String(OutfileFlagName) - artifactsURLStr := cliCtx.String(ArtifactsLocatorFlagName) + artifactsURLStr := cliCtx.String(deployer.ArtifactsLocatorFlagName) cacheDir := cliCtx.String(deployer.CacheDirFlag.Name) artifactsLocator := new(artifacts.Locator) diff --git a/op-deployer/pkg/deployer/bootstrap/superchain.go b/op-deployer/pkg/deployer/bootstrap/superchain.go index 95055f2a2ae..7628f257b34 100644 --- a/op-deployer/pkg/deployer/bootstrap/superchain.go +++ b/op-deployer/pkg/deployer/bootstrap/superchain.go @@ -87,7 +87,7 @@ func SuperchainCLI(cliCtx *cli.Context) error { l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) privateKey := cliCtx.String(deployer.PrivateKeyFlagName) - artifactsURLStr := cliCtx.String(ArtifactsLocatorFlagName) + artifactsURLStr := cliCtx.String(deployer.ArtifactsLocatorFlagName) artifactsLocator := new(artifacts.Locator) if err := artifactsLocator.UnmarshalText([]byte(artifactsURLStr)); err != nil { return fmt.Errorf("failed to parse artifacts URL: %w", err) diff --git a/op-deployer/pkg/deployer/bootstrap/validator.go b/op-deployer/pkg/deployer/bootstrap/validator.go index f32b00b13d4..5daaf890d56 100644 --- a/op-deployer/pkg/deployer/bootstrap/validator.go +++ b/op-deployer/pkg/deployer/bootstrap/validator.go @@ -95,7 +95,7 @@ func ValidatorCLI(cliCtx *cli.Context) error { l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) privateKey := cliCtx.String(deployer.PrivateKeyFlagName) outfile := cliCtx.String(OutfileFlagName) - artifactsURLStr := cliCtx.String(ArtifactsLocatorFlagName) + artifactsURLStr := cliCtx.String(deployer.ArtifactsLocatorFlagName) configFile := cliCtx.String(ConfigFileFlag.Name) cacheDir := cliCtx.String(deployer.CacheDirFlag.Name) diff --git a/op-deployer/pkg/deployer/flags.go b/op-deployer/pkg/deployer/flags.go index 09e300981a2..5ec0b2c4c83 100644 --- a/op-deployer/pkg/deployer/flags.go +++ b/op-deployer/pkg/deployer/flags.go @@ -13,15 +13,19 @@ import ( ) const ( - EnvVarPrefix = "DEPLOYER" - L1RPCURLFlagName = "l1-rpc-url" - CacheDirFlagName = "cache-dir" - L1ChainIDFlagName = "l1-chain-id" - L2ChainIDsFlagName = "l2-chain-ids" - WorkdirFlagName = "workdir" - OutdirFlagName = "outdir" - PrivateKeyFlagName = "private-key" - IntentTypeFlagName = "intent-type" + EnvVarPrefix = "DEPLOYER" + L1RPCURLFlagName = "l1-rpc-url" + CacheDirFlagName = "cache-dir" + L1ChainIDFlagName = "l1-chain-id" + ArtifactsLocatorFlagName = "artifacts-locator" + L2ChainIDsFlagName = "l2-chain-ids" + WorkdirFlagName = "workdir" + OutdirFlagName = "outdir" + PrivateKeyFlagName = "private-key" + IntentTypeFlagName = "intent-type" + EtherscanAPIKeyFlagName = "etherscan-api-key" + InputFileFlagName = "input-file" + ContractNameFlagName = "contract-name" ) type DeploymentTarget string @@ -48,14 +52,15 @@ func NewDeploymentTarget(s string) (DeploymentTarget, error) { } } -var homeDir string +var DefaultCacheDir string func init() { var err error - homeDir, err = os.UserHomeDir() + homeDir, err := os.UserHomeDir() if err != nil { panic(fmt.Sprintf("failed to get home directory: %s", err)) } + DefaultCacheDir = path.Join(homeDir, ".op-deployer/cache") } var ( @@ -67,12 +72,17 @@ var ( "L1_RPC_URL", }, } + ArtifactsLocatorFlag = &cli.StringFlag{ + Name: ArtifactsLocatorFlagName, + Usage: "Locator for artifacts.", + EnvVars: PrefixEnvVar("ARTIFACTS_LOCATOR"), + } CacheDirFlag = &cli.StringFlag{ Name: CacheDirFlagName, Usage: "Cache directory. " + "If set, the deployer will attempt to cache downloaded artifacts in the specified directory.", EnvVars: PrefixEnvVar("CACHE_DIR"), - Value: path.Join(homeDir, ".op-deployer/cache"), + Value: DefaultCacheDir, } L1ChainIDFlag = &cli.Uint64Flag{ Name: L1ChainIDFlagName, @@ -117,6 +127,22 @@ var ( "intent-config-type", }, } + EtherscanAPIKeyFlag = &cli.StringFlag{ + Name: EtherscanAPIKeyFlagName, + Usage: "etherscan API key for contract verification.", + EnvVars: PrefixEnvVar("ETHERSCAN_API_KEY"), + Required: true, + } + InputFileFlag = &cli.StringFlag{ + Name: InputFileFlagName, + Usage: "filepath of input file for command", + EnvVars: PrefixEnvVar("INPUT_FILE"), + } + ContractNameFlag = &cli.StringFlag{ + Name: ContractNameFlagName, + Usage: "contract name (matching a field within a contract bundle struct)", + EnvVars: PrefixEnvVar("CONTRACT_NAME"), + } ) var GlobalFlags = append([]cli.Flag{CacheDirFlag}, oplog.CLIFlags(EnvVarPrefix)...) @@ -141,6 +167,14 @@ var UpgradeFlags = []cli.Flag{ DeploymentTargetFlag, } +var VerifyFlags = []cli.Flag{ + L1RPCURLFlag, + ArtifactsLocatorFlag, + EtherscanAPIKeyFlag, + InputFileFlag, + ContractNameFlag, +} + func PrefixEnvVar(name string) []string { return op_service.PrefixEnvVar(EnvVarPrefix, name) } diff --git a/op-deployer/pkg/deployer/inspect/l1.go b/op-deployer/pkg/deployer/inspect/l1.go index cabcc997c43..e8bccb0238f 100644 --- a/op-deployer/pkg/deployer/inspect/l1.go +++ b/op-deployer/pkg/deployer/inspect/l1.go @@ -2,6 +2,7 @@ package inspect import ( "fmt" + "reflect" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" @@ -20,6 +21,39 @@ type L1Contracts struct { ImplementationsDeployment ImplementationsDeployment `json:"implementationsDeployment"` } +const ( + SuperchainBundle = "superchain" + ImplementationsBundle = "implementations" + OpChainBundle = "opchain" +) + +var ContractBundles = []string{ + SuperchainBundle, + ImplementationsBundle, + OpChainBundle, +} + +func (l L1Contracts) GetContractAddress(name string, bundleName string) (common.Address, error) { + var bundle interface{} + switch bundleName { + case SuperchainBundle: + bundle = l.SuperchainDeployment + case ImplementationsBundle: + bundle = l.ImplementationsDeployment + case OpChainBundle: + bundle = l.OpChainDeployment + default: + return common.Address{}, fmt.Errorf("invalid contract bundle type: %s", bundleName) + } + + field := reflect.ValueOf(bundle).FieldByName(name) + if !field.IsValid() { + return common.Address{}, fmt.Errorf("contract %s not found in %s bundle", name, bundleName) + } + + return field.Interface().(common.Address), nil +} + func (l L1Contracts) AsL1Deployments() *genesis.L1Deployments { return &genesis.L1Deployments{ AddressManager: l.OpChainDeployment.AddressManagerAddress, diff --git a/op-deployer/pkg/deployer/opcm/implementations.go b/op-deployer/pkg/deployer/opcm/implementations.go index 163e899fc9d..36efedde9a0 100644 --- a/op-deployer/pkg/deployer/opcm/implementations.go +++ b/op-deployer/pkg/deployer/opcm/implementations.go @@ -30,24 +30,24 @@ func (input *DeployImplementationsInput) InputSet() bool { } type DeployImplementationsOutput struct { - Opcm common.Address - OpcmContractsContainer common.Address - OpcmGameTypeAdder common.Address - OpcmDeployer common.Address - OpcmUpgrader common.Address - DelayedWETHImpl common.Address - OptimismPortalImpl common.Address - PreimageOracleSingleton common.Address - MipsSingleton common.Address - SystemConfigImpl common.Address - L1CrossDomainMessengerImpl common.Address - L1ERC721BridgeImpl common.Address - L1StandardBridgeImpl common.Address - OptimismMintableERC20FactoryImpl common.Address - DisputeGameFactoryImpl common.Address - AnchorStateRegistryImpl common.Address - SuperchainConfigImpl common.Address - ProtocolVersionsImpl common.Address + Opcm common.Address `json:"opcmAddress"` + OpcmContractsContainer common.Address `json:"opcmContractsContainerAddress"` + OpcmGameTypeAdder common.Address `json:"opcmGameTypeAdderAddress"` + OpcmDeployer common.Address `json:"opcmDeployerAddress"` + OpcmUpgrader common.Address `json:"opcmUpgraderAddress"` + DelayedWETHImpl common.Address `json:"delayedWETHImplAddress"` + OptimismPortalImpl common.Address `json:"optimismPortalImplAddress"` + PreimageOracleSingleton common.Address `json:"preimageOracleSingletonAddress"` + MipsSingleton common.Address `json:"mipsSingletonAddress"` + SystemConfigImpl common.Address `json:"systemConfigImplAddress"` + L1CrossDomainMessengerImpl common.Address `json:"l1CrossDomainMessengerImplAddress"` + L1ERC721BridgeImpl common.Address `json:"l1ERC721BridgeImplAddress"` + L1StandardBridgeImpl common.Address `json:"l1StandardBridgeImplAddress"` + OptimismMintableERC20FactoryImpl common.Address `json:"optimismMintableERC20FactoryImplAddress"` + DisputeGameFactoryImpl common.Address `json:"disputeGameFactoryImplAddress"` + AnchorStateRegistryImpl common.Address `json:"anchorStateRegistryImplAddress"` + SuperchainConfigImpl common.Address `json:"superchainConfigImplAddress"` + ProtocolVersionsImpl common.Address `json:"protocolVersionsImplAddress"` } func (output *DeployImplementationsOutput) CheckOutput(input common.Address) error { diff --git a/op-deployer/pkg/deployer/opcm/superchain.go b/op-deployer/pkg/deployer/opcm/superchain.go index 35811bd6934..abc9a1bd12d 100644 --- a/op-deployer/pkg/deployer/opcm/superchain.go +++ b/op-deployer/pkg/deployer/opcm/superchain.go @@ -26,11 +26,11 @@ func (dsi *DeploySuperchainInput) InputSet() bool { } type DeploySuperchainOutput struct { - SuperchainProxyAdmin common.Address - SuperchainConfigImpl common.Address - SuperchainConfigProxy common.Address - ProtocolVersionsImpl common.Address - ProtocolVersionsProxy common.Address + SuperchainProxyAdmin common.Address `json:"proxyAdminAddress"` + SuperchainConfigImpl common.Address `json:"superchainConfigImplAddress"` + SuperchainConfigProxy common.Address `json:"superchainConfigProxyAddress"` + ProtocolVersionsImpl common.Address `json:"protocolVersionsImplAddress"` + ProtocolVersionsProxy common.Address `json:"protocolVersionsProxyAddress"` } func (output *DeploySuperchainOutput) CheckOutput(input common.Address) error { diff --git a/op-deployer/pkg/deployer/verify/artifacts.go b/op-deployer/pkg/deployer/verify/artifacts.go new file mode 100644 index 00000000000..7e84f2025b2 --- /dev/null +++ b/op-deployer/pkg/deployer/verify/artifacts.go @@ -0,0 +1,98 @@ +package verify + +import ( + "encoding/json" + "fmt" + "path" + "strings" + + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum/go-ethereum/accounts/abi" +) + +type contractArtifact struct { + ContractName string + CompilerVersion string + Optimizer OptimizerSettings + EVMVersion string + Sources map[string]SourceContent + ConstructorArgs abi.Arguments +} + +// Map state.json struct fields to forge artifact paths +var contractNameExceptions = map[string]string{ + "OptimismPortalImpl": "OptimismPortal2.sol/OptimismPortal2.json", + "L1StandardBridgeProxy": "L1ChugSplashProxy.sol/L1ChugSplashProxy.json", + "L1CrossDomainMessengerProxy": "ResolvedDelegateProxy.sol/ResolvedDelegateProxy.json", + "Opcm": "OPContractsManager.sol/OPContractsManager.json", + "OpcmContractsContainer": "OPContractsManager.sol/OPContractsManagerContractsContainer.json", + "OpcmGameTypeAdder": "OPContractsManager.sol/OPContractsManagerGameTypeAdder.json", + "OpcmDeployer": "OPContractsManager.sol/OPContractsManagerDeployer.json", + "OpcmUpgrader": "OPContractsManager.sol/OPContractsManagerUpgrader.json", +} + +func getArtifactPath(name string) string { + lookupName := strings.TrimSuffix(name, "Address") + lookupName = strings.ToUpper(string(lookupName[0])) + lookupName[1:] + + if artifactPath, exists := contractNameExceptions[lookupName]; exists { + return artifactPath + } + + lookupName = strings.TrimSuffix(lookupName, "Proxy") + lookupName = strings.TrimSuffix(lookupName, "Impl") + lookupName = strings.TrimSuffix(lookupName, "Singleton") + + // If it was a proxy and not a special case, return "Proxy" + if strings.HasSuffix(name, "ProxyAddress") { + return path.Join("Proxy.sol", "Proxy.json") + } + + return path.Join(lookupName+".sol", lookupName+".json") +} + +func (v *Verifier) getContractArtifact(name string) (*contractArtifact, error) { + artifactPath := getArtifactPath(name) + + v.log.Info("Opening artifact", "path", artifactPath, "name", name) + f, err := v.artifactsFS.Open(artifactPath) + if err != nil { + return nil, fmt.Errorf("failed to open artifact: %w", err) + } + defer f.Close() + + var art foundry.Artifact + if err := json.NewDecoder(f).Decode(&art); err != nil { + return nil, fmt.Errorf("failed to decode artifact: %w", err) + } + + // Add all sources (main contract and dependencies) + sources := make(map[string]SourceContent) + for sourcePath, sourceInfo := range art.Metadata.Sources { + remappedKey := art.SearchRemappings(sourcePath) + sources[remappedKey] = SourceContent{Content: sourceInfo.Content} + v.log.Debug("added source contract", "originalPath", sourcePath, "remappedKey", remappedKey) + } + + var optimizer OptimizerSettings + if err := json.Unmarshal(art.Metadata.Settings.Optimizer, &optimizer); err != nil { + return nil, fmt.Errorf("failed to parse optimizer settings: %w", err) + } + + // Get the contract name from the compilation target + var contractName string + for contractFile, name := range art.Metadata.Settings.CompilationTarget { + contractName = contractFile + ":" + name + break + } + v.log.Info("Compilation target", "target", contractName) + + return &contractArtifact{ + ContractName: contractName, + CompilerVersion: art.Metadata.Compiler.Version, + Optimizer: optimizer, + EVMVersion: art.Metadata.Settings.EVMVersion, + Sources: sources, + ConstructorArgs: art.ABI.Constructor.Inputs, + }, nil +} diff --git a/op-deployer/pkg/deployer/verify/constructors.go b/op-deployer/pkg/deployer/verify/constructors.go new file mode 100644 index 00000000000..e355adea4df --- /dev/null +++ b/op-deployer/pkg/deployer/verify/constructors.go @@ -0,0 +1,61 @@ +package verify + +import ( + "context" + "encoding/hex" + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" +) + +func (v *Verifier) getConstructorArgs(ctx context.Context, address common.Address, artifact *contractArtifact) (string, error) { + argSlots := 0 + for _, arg := range artifact.ConstructorArgs { + argSlots += calculateTypeSlots(arg.Type) + } + if argSlots == 0 { + return "", nil + } + + v.log.Info("Extracting constructor args from initcode", "address", address.Hex(), "argSlots", argSlots) + txHash, err := v.etherscan.getContractCreation(address) + if err != nil { + return "", fmt.Errorf("failed to get contract creation tx: %w", err) + } + v.log.Info("Contract creation tx hash", "txHash", txHash.Hex()) + + tx, isPending, err := v.l1Client.TransactionByHash(ctx, txHash) + if err != nil { + return "", fmt.Errorf("failed to get transaction: %w", err) + } + + if isPending { + return "", fmt.Errorf("transaction is still pending") + } + + // tx.Data contains bytecode + constructor args, so we strip the + // constructor args off of the end + txInput := hex.EncodeToString(tx.Data()) + constructorArgs := txInput[len(txInput)-(argSlots*64):] + v.log.Info("Successfully extracted constructor args", "address", address.Hex()) + + return constructorArgs, nil +} + +// Helper function to calculate slots needed for a abi.Type, handling nested tuples +func calculateTypeSlots(t abi.Type) int { + if t.String() == "string" { + return 3 // 1 slot each for: offset, length, value (assuming value only takes 1 slot) + } else if strings.HasPrefix(t.String(), "(") { + // loop through nested tuple elements + totalSlots := 0 + for _, elem := range t.TupleElems { + totalSlots += calculateTypeSlots(*elem) + } + return totalSlots + } else { + return 1 + } +} diff --git a/op-deployer/pkg/deployer/verify/constructors_test.go b/op-deployer/pkg/deployer/verify/constructors_test.go new file mode 100644 index 00000000000..20a3dc0c2e2 --- /dev/null +++ b/op-deployer/pkg/deployer/verify/constructors_test.go @@ -0,0 +1,171 @@ +package verify + +import ( + "encoding/json" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/stretchr/testify/require" +) + +func TestCalculateTypeSlots(t *testing.T) { + t.Run("nested tuple", func(t *testing.T) { + constructorArgsJSON := `[ + { + "name": "_superchainConfig", + "type": "address", + "internalType": "contract ISuperchainConfig" + }, + { + "name": "_protocolVersions", + "type": "address", + "internalType": "contract IProtocolVersions" + }, + { + "name": "_superchainProxyAdmin", + "type": "address", + "internalType": "contract IProxyAdmin" + }, + { + "name": "_l1ContractsRelease", + "type": "string", + "internalType": "string" + }, + { + "name": "_blueprints", + "type": "tuple", + "internalType": "struct OPContractsManager.Blueprints", + "components": [ + { + "name": "addressManager", + "type": "address", + "internalType": "address" + }, + { + "name": "proxy", + "type": "address", + "internalType": "address" + }, + { + "name": "proxyAdmin", + "type": "address", + "internalType": "address" + }, + { + "name": "l1ChugSplashProxy", + "type": "address", + "internalType": "address" + }, + { + "name": "resolvedDelegateProxy", + "type": "address", + "internalType": "address" + }, + { + "name": "permissionedDisputeGame1", + "type": "address", + "internalType": "address" + }, + { + "name": "permissionedDisputeGame2", + "type": "address", + "internalType": "address" + }, + { + "name": "permissionlessDisputeGame1", + "type": "address", + "internalType": "address" + }, + { + "name": "permissionlessDisputeGame2", + "type": "address", + "internalType": "address" + } + ] + }, + { + "name": "_implementations", + "type": "tuple", + "internalType": "struct OPContractsManager.Implementations", + "components": [ + { + "name": "superchainConfigImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "protocolVersionsImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "l1ERC721BridgeImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "optimismPortalImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "systemConfigImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "optimismMintableERC20FactoryImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "l1CrossDomainMessengerImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "l1StandardBridgeImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "disputeGameFactoryImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "anchorStateRegistryImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "delayedWETHImpl", + "type": "address", + "internalType": "address" + }, + { + "name": "mipsImpl", + "type": "address", + "internalType": "address" + } + ] + }, + { + "name": "_upgradeController", + "type": "address", + "internalType": "address" + } + ]` + + var args abi.Arguments + err := json.Unmarshal([]byte(constructorArgsJSON), &args) + require.NoError(t, err) + + totalSlots := 0 + for _, arg := range args { + totalSlots += calculateTypeSlots(arg.Type) + } + + require.Equal(t, 28, totalSlots) + }) +} diff --git a/op-deployer/pkg/deployer/verify/etherscan.go b/op-deployer/pkg/deployer/verify/etherscan.go new file mode 100644 index 00000000000..15578a413a2 --- /dev/null +++ b/op-deployer/pkg/deployer/verify/etherscan.go @@ -0,0 +1,262 @@ +package verify + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strings" + "time" + + "github.com/ethereum/go-ethereum/common" + "golang.org/x/time/rate" +) + +type EtherscanGenericResp struct { + Status string `json:"status"` + Message string `json:"message"` + Result string `json:"result"` +} + +type EtherscanContractCreationResp struct { + Status string `json:"status"` + Message string `json:"message"` + Result []struct { + ContractCreator string `json:"contractCreator"` + TxHash string `json:"txHash"` + } `json:"result"` +} + +type EtherscanClient struct { + apiKey string + url string + rateLimiter *rate.Limiter +} + +func getAPIEndpoint(l1ChainID uint64) (string, error) { + switch l1ChainID { + case 1: + return "https://api.etherscan.io/api", nil // mainnet + case 11155111: + return "https://api-sepolia.etherscan.io/api", nil // sepolia + default: + return "", fmt.Errorf("unsupported L1 chain ID: %d", l1ChainID) + } +} + +func NewEtherscanClient(apiKey string, url string, rateLimiter *rate.Limiter) *EtherscanClient { + return &EtherscanClient{ + apiKey: apiKey, + url: url, + rateLimiter: rateLimiter, + } +} + +// sendRateLimitedRequest is a helper function which waits for a rate limit token +// before sending a request +func (c *EtherscanClient) sendRateLimitedRequest(req *http.Request) (*http.Response, error) { + if err := c.rateLimiter.Wait(context.Background()); err != nil { + return nil, fmt.Errorf("rate limiter error: %w", err) + } + return http.DefaultClient.Do(req) +} + +// getContractCreation returns the txHash of the contract creation tx +// (useful for extracting constructor args) +func (c *EtherscanClient) getContractCreation(address common.Address) (common.Hash, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s?module=contract&action=getcontractcreation&contractaddresses=%s&apikey=%s", + c.url, address.Hex(), c.apiKey), nil) + if err != nil { + return common.Hash{}, fmt.Errorf("failed to create contract creation request: %w", err) + } + + resp, err := c.sendRateLimitedRequest(req) + if err != nil { + return common.Hash{}, fmt.Errorf("failed to send contract creation request: %w", err) + } + defer resp.Body.Close() + + var creationResp EtherscanContractCreationResp + if err := json.NewDecoder(resp.Body).Decode(&creationResp); err != nil { + return common.Hash{}, fmt.Errorf("failed to decode contract creation response: %w", err) + } + if creationResp.Status != "1" { + return common.Hash{}, fmt.Errorf("contract creation query failed: %s", creationResp.Message) + } + + txHash := common.HexToHash(creationResp.Result[0].TxHash) + return txHash, nil +} + +func (c *EtherscanClient) verifySourceCode(address common.Address, artifact *contractArtifact, constructorArgs string) (string, error) { + optimized := "0" + if artifact.Optimizer.Enabled { + optimized = "1" + } + + standardInput := newStandardInput(artifact) + standardInputJSON, err := json.Marshal(standardInput) + if err != nil { + return "", fmt.Errorf("failed to generate standard input: %w", err) + } + + data := url.Values{ + "apikey": {c.apiKey}, + "module": {"contract"}, + "action": {"verifysourcecode"}, + "contractaddress": {address.Hex()}, + "codeformat": {"solidity-standard-json-input"}, + "sourceCode": {string(standardInputJSON)}, + "contractname": {artifact.ContractName}, + "compilerversion": {fmt.Sprintf("v%s", artifact.CompilerVersion)}, + "optimizationUsed": {optimized}, + "runs": {fmt.Sprintf("%d", artifact.Optimizer.Runs)}, + "evmversion": {artifact.EVMVersion}, + "constructorArguements": {constructorArgs}, + } + + req, err := http.NewRequest("POST", c.url, strings.NewReader(data.Encode())) + if err != nil { + return "", fmt.Errorf("failed to create verification request: %w", err) + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + resp, err := c.sendRateLimitedRequest(req) + if err != nil { + return "", fmt.Errorf("failed to submit verification request: %w", err) + } + defer resp.Body.Close() + + var result EtherscanGenericResp + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return "", fmt.Errorf("failed to decode response: %w", err) + } + + if result.Status != "1" { + return "", fmt.Errorf("verification request failed: status=%s message=%s result=%s", + result.Status, result.Message, result.Result) + } + + return result.Result, nil +} + +func (c *EtherscanClient) isVerified(address common.Address) (bool, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s?module=contract&action=getabi&address=%s&apikey=%s", + c.url, address.Hex(), c.apiKey), nil) + if err != nil { + return false, err + } + + resp, err := c.sendRateLimitedRequest(req) + if err != nil { + return false, err + } + defer resp.Body.Close() + + var result EtherscanGenericResp + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return false, err + } + + return result.Status == "1", nil +} + +func (c *EtherscanClient) pollVerificationStatus(reqId string) error { + req, err := http.NewRequest("GET", fmt.Sprintf("%s?apikey=%s&module=contract&action=checkverifystatus&guid=%s", + c.url, c.apiKey, reqId), nil) + if err != nil { + return fmt.Errorf("failed to create checkverifystatus request: %w", err) + } + + for i := 0; i < 10; i++ { // Try 10 times with increasing delays + resp, err := c.sendRateLimitedRequest(req) + if err != nil { + return fmt.Errorf("failed to send checkverifystatus request: %w", err) + } + defer resp.Body.Close() + + var result EtherscanGenericResp + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return fmt.Errorf("failed to decode checkverifystatus response: %w", err) + } + + if result.Status == "1" { + return nil + } + if result.Result == "Already Verified" { + return nil + } + if result.Result != "Pending in queue" { + return fmt.Errorf("verification failed: %s, %s", result.Result, result.Message) + } + time.Sleep(time.Duration(i+2) * time.Second) + } + return fmt.Errorf("verification timed out") +} + +type StandardInput struct { + Language string `json:"language"` + Sources map[string]SourceContent `json:"sources"` + Settings Settings `json:"settings"` +} + +type SourceContent struct { + Content string `json:"content"` +} + +type Settings struct { + Optimizer OptimizerSettings `json:"optimizer"` + EVMVersion string `json:"evmVersion"` + Metadata MetadataSettings `json:"metadata"` + OutputSelection OutputSelection `json:"outputSelection"` +} + +type OptimizerSettings struct { + Enabled bool `json:"enabled"` + Runs int `json:"runs"` +} + +type MetadataSettings struct { + UseLiteralContent bool `json:"useLiteralContent"` + BytecodeHash string `json:"bytecodeHash"` +} + +type OutputSelection struct { + All map[string]OutputSelectionDetails `json:"*"` +} + +type OutputSelectionDetails struct { + All []string `json:"*"` +} + +func newStandardInput(artifact *contractArtifact) StandardInput { + return StandardInput{ + Language: "Solidity", + Sources: artifact.Sources, + Settings: Settings{ + Optimizer: OptimizerSettings{ + Enabled: artifact.Optimizer.Enabled, + Runs: artifact.Optimizer.Runs, + }, + EVMVersion: artifact.EVMVersion, + Metadata: MetadataSettings{ + UseLiteralContent: true, + BytecodeHash: "none", + }, + OutputSelection: OutputSelection{ + All: map[string]OutputSelectionDetails{ + "*": { + All: []string{ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap", + "metadata", + }, + }, + }, + }, + }, + } +} diff --git a/op-deployer/pkg/deployer/verify/etherscan_test.go b/op-deployer/pkg/deployer/verify/etherscan_test.go new file mode 100644 index 00000000000..8bd0fe16bf7 --- /dev/null +++ b/op-deployer/pkg/deployer/verify/etherscan_test.go @@ -0,0 +1,278 @@ +package verify + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + "golang.org/x/time/rate" +) + +const ( + testAPIKey = "test_api_key" + testAddressHex = "0x1234567890123456789012345678901234567890" + testTxHashHex = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + testGUID = "verification_guid_12345" +) + +// createTestClient creates a new EtherscanClient with a mock server for testing +func createTestClient(t *testing.T, handler http.HandlerFunc) (*EtherscanClient, *httptest.Server) { + server := httptest.NewServer(handler) + t.Cleanup(func() { server.Close() }) + + // Use a fast rate limiter for testing + limiter := rate.NewLimiter(rate.Every(time.Millisecond), 10) + return NewEtherscanClient(testAPIKey, server.URL, limiter), server +} + +// createTestArtifact creates a contract artifact for testing +func createTestArtifact() *contractArtifact { + return &contractArtifact{ + ContractName: "TestContract", + CompilerVersion: "0.8.10", + EVMVersion: "london", + Optimizer: OptimizerSettings{ + Enabled: true, + Runs: 200, + }, + Sources: map[string]SourceContent{ + "TestContract.sol": {Content: "contract TestContract {}"}, + }, + } +} + +func TestGetAPIEndpoint(t *testing.T) { + tests := []struct { + name string + chainID uint64 + expected string + expectedError bool + }{ + {"Mainnet", 1, "https://api.etherscan.io/api", false}, + {"Sepolia", 11155111, "https://api-sepolia.etherscan.io/api", false}, + {"Unknown", 999, "", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := getAPIEndpoint(tt.chainID) + if tt.expectedError { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.expected, result) + } + }) + } +} + +func TestGetContractCreation(t *testing.T) { + client, _ := createTestClient(t, func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "GET", r.Method) + require.Contains(t, r.URL.String(), "module=contract") + require.Contains(t, r.URL.String(), "action=getcontractcreation") + + testAddr := common.HexToAddress(testAddressHex) + require.Contains(t, r.URL.String(), testAddr.Hex()) + + resp := EtherscanContractCreationResp{ + Status: "1", + Message: "OK", + Result: []struct { + ContractCreator string `json:"contractCreator"` + TxHash string `json:"txHash"` + }{ + { + ContractCreator: "0xabcdef1234567890abcdef1234567890abcdef12", + TxHash: testTxHashHex, + }, + }, + } + + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(resp) + require.NoError(t, err) + }) + + testAddr := common.HexToAddress(testAddressHex) + txHash, err := client.getContractCreation(testAddr) + + require.NoError(t, err) + require.Equal(t, testTxHashHex, txHash.Hex()) +} + +func TestGetContractCreationError(t *testing.T) { + client, _ := createTestClient(t, func(w http.ResponseWriter, r *http.Request) { + resp := EtherscanContractCreationResp{ + Status: "0", + Message: "Error", + Result: nil, + } + + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(resp) + require.NoError(t, err) + }) + + testAddr := common.HexToAddress(testAddressHex) + _, err := client.getContractCreation(testAddr) + + require.Error(t, err) + require.Contains(t, err.Error(), "contract creation query failed") +} + +func TestVerifySourceCode(t *testing.T) { + client, _ := createTestClient(t, func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "POST", r.Method) + require.Equal(t, "application/x-www-form-urlencoded", r.Header.Get("Content-Type")) + + err := r.ParseForm() + require.NoError(t, err) + + require.Equal(t, testAPIKey, r.Form.Get("apikey")) + require.Equal(t, "contract", r.Form.Get("module")) + require.Equal(t, "verifysourcecode", r.Form.Get("action")) + require.Equal(t, testAddressHex, r.Form.Get("contractaddress")) + + resp := EtherscanGenericResp{ + Status: "1", + Message: "OK", + Result: testGUID, + } + + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(resp) + require.NoError(t, err) + }) + + testAddr := common.HexToAddress(testAddressHex) + artifact := createTestArtifact() + + guid, err := client.verifySourceCode(testAddr, artifact, "0x1234") + + require.NoError(t, err) + require.Equal(t, testGUID, guid) +} + +func TestIsVerified(t *testing.T) { + tests := []struct { + name string + responseStatus string + expected bool + }{ + {"Verified", "1", true}, + {"Not Verified", "0", false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, _ := createTestClient(t, func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "GET", r.Method) + require.Contains(t, r.URL.String(), "module=contract") + require.Contains(t, r.URL.String(), "action=getabi") + + resp := EtherscanGenericResp{ + Status: tt.responseStatus, + Message: "OK", + Result: "test result", + } + + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(resp) + require.NoError(t, err) + }) + + testAddr := common.HexToAddress(testAddressHex) + result, err := client.isVerified(testAddr) + + require.NoError(t, err) + require.Equal(t, tt.expected, result) + }) + } +} + +func TestPollVerificationStatus(t *testing.T) { + tests := []struct { + name string + responses []EtherscanGenericResp + expectError bool + errorMessage string + }{ + { + name: "Success After Pending", + responses: []EtherscanGenericResp{ + {Status: "0", Message: "OK", Result: "Pending in queue"}, + {Status: "1", Message: "OK", Result: "Pass - Verified"}, + }, + expectError: false, + }, + { + name: "Already Verified", + responses: []EtherscanGenericResp{ + {Status: "0", Message: "OK", Result: "Already Verified"}, + }, + expectError: false, + }, + { + name: "Verification Failed", + responses: []EtherscanGenericResp{ + {Status: "0", Message: "OK", Result: "Fail - Unable to verify"}, + }, + expectError: true, + errorMessage: "verification failed", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + responseIndex := 0 + + client, _ := createTestClient(t, func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, "GET", r.Method) + require.Contains(t, r.URL.String(), "module=contract") + require.Contains(t, r.URL.String(), "action=checkverifystatus") + + // Get the appropriate response based on the current index + resp := tt.responses[responseIndex] + if responseIndex < len(tt.responses)-1 { + responseIndex++ + } + + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(resp) + require.NoError(t, err) + }) + + err := client.pollVerificationStatus("test_guid") + + if tt.expectError { + require.Error(t, err) + require.Contains(t, err.Error(), tt.errorMessage) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestNewStandardInput(t *testing.T) { + artifact := createTestArtifact() + + result := newStandardInput(artifact) + + require.Equal(t, "Solidity", result.Language) + require.Equal(t, artifact.Sources, result.Sources) + require.Equal(t, artifact.Optimizer.Enabled, result.Settings.Optimizer.Enabled) + require.Equal(t, artifact.Optimizer.Runs, result.Settings.Optimizer.Runs) + require.Equal(t, artifact.EVMVersion, result.Settings.EVMVersion) + require.True(t, result.Settings.Metadata.UseLiteralContent) + require.Equal(t, "none", result.Settings.Metadata.BytecodeHash) + + require.Contains(t, result.Settings.OutputSelection.All["*"].All, "abi") + require.Contains(t, result.Settings.OutputSelection.All["*"].All, "evm.bytecode.object") + require.Contains(t, result.Settings.OutputSelection.All["*"].All, "metadata") +} diff --git a/op-deployer/pkg/deployer/verify/verifier.go b/op-deployer/pkg/deployer/verify/verifier.go new file mode 100644 index 00000000000..816ce6df292 --- /dev/null +++ b/op-deployer/pkg/deployer/verify/verifier.go @@ -0,0 +1,194 @@ +package verify + +import ( + "context" + "encoding/json" + "fmt" + "os" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/urfave/cli/v2" + "golang.org/x/time/rate" + + "github.com/ethereum-optimism/optimism/op-chain-ops/foundry" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer" + "github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts" + "github.com/ethereum-optimism/optimism/op-service/ctxinterrupt" + oplog "github.com/ethereum-optimism/optimism/op-service/log" +) + +type Verifier struct { + l1ChainID uint64 + artifactsFS foundry.StatDirFs + log log.Logger + etherscan *EtherscanClient + l1Client *ethclient.Client + numVerified int + numSkipped int + numFailed int +} + +func NewVerifier(apiKey string, l1ChainID uint64, artifactsFS foundry.StatDirFs, l log.Logger, l1Client *ethclient.Client) (*Verifier, error) { + etherscanUrl, err := getAPIEndpoint(l1ChainID) + if err != nil { + return nil, fmt.Errorf("unsupported L1 chain ID: %d", l1ChainID) + } + + etherscan := NewEtherscanClient(apiKey, etherscanUrl, rate.NewLimiter(rate.Limit(3), 2)) + + return &Verifier{ + l1ChainID: l1ChainID, + artifactsFS: artifactsFS, + log: l, + l1Client: l1Client, + etherscan: etherscan, + }, nil +} + +func VerifyCLI(cliCtx *cli.Context) error { + logCfg := oplog.ReadCLIConfig(cliCtx) + l := oplog.NewLogger(oplog.AppOut(cliCtx), logCfg) + oplog.SetGlobalLogHandler(l.Handler()) + + l1RPCUrl := cliCtx.String(deployer.L1RPCURLFlagName) + etherscanAPIKey := cliCtx.String(deployer.EtherscanAPIKeyFlagName) + if etherscanAPIKey == "" { + return fmt.Errorf("etherscan-api-key is required") + } + + inputFile := cliCtx.String(deployer.InputFileFlagName) + if inputFile == "" { + return fmt.Errorf("input-file is required") + } + contractName := cliCtx.String(deployer.ContractNameFlagName) + + l1ContractsLocator := cliCtx.String(deployer.ArtifactsLocatorFlagName) + if l1ContractsLocator == "" { + return fmt.Errorf("artifacts-locator is required") + } + + ctx := ctxinterrupt.WithCancelOnInterrupt(cliCtx.Context) + + l1Client, err := ethclient.Dial(l1RPCUrl) + if err != nil { + return fmt.Errorf("failed to connect to L1: %w", err) + } + defer l1Client.Close() + + chainId, err := l1Client.ChainID(ctx) + if err != nil { + return fmt.Errorf("failed to get chain ID: %w", err) + } + l1ChainId := chainId.Uint64() + + locator, err := artifacts.NewLocatorFromURL(l1ContractsLocator) + if err != nil { + return fmt.Errorf("failed to parse l1 contracts release locator: %w", err) + } + artifactsFS, err := artifacts.Download(ctx, locator, nil, deployer.DefaultCacheDir) + if err != nil { + return fmt.Errorf("failed to get artifacts: %w", err) + } + l.Info("Downloaded artifacts", "path", artifactsFS) + + v, err := NewVerifier(etherscanAPIKey, l1ChainId, artifactsFS, l, l1Client) + if err != nil { + return fmt.Errorf("failed to create verifier: %w", err) + } + + defer func() { + v.log.Info("final results", "numVerified", v.numVerified, "numSkipped", v.numSkipped, "numFailed", v.numFailed) + }() + + if err := v.verifyContractBundle(ctx, inputFile, contractName); err != nil { + return err + } + v.log.Info("--- COMPLETE ---") + return nil +} + +func (v *Verifier) getContractBundle(filepath string) (map[string]common.Address, error) { + _, err := os.Stat(filepath) + if err != nil { + return nil, fmt.Errorf("input file not found: %s", filepath) + } + + bundleData, err := os.ReadFile(filepath) + if err != nil { + return nil, fmt.Errorf("failed to read bundle file %s: %w", filepath, err) + } + + var bundle map[string]common.Address + if err := json.Unmarshal(bundleData, &bundle); err != nil { + return nil, fmt.Errorf("failed to parse superchain bundle: %w", err) + } + + return bundle, nil +} + +func (v *Verifier) verifyContractBundle(ctx context.Context, filepath string, contractName string) error { + bundle, err := v.getContractBundle(filepath) + if err != nil { + return fmt.Errorf("failed to retrieve bundle: %w", err) + } + + if contractName != "" { + addr, ok := bundle[contractName] + if !ok { + return fmt.Errorf("contract %s not found in bundle", contractName) + } + if err := v.verifySingleContract(ctx, addr, contractName); err != nil { + return fmt.Errorf("failed to verify contract %s: %w", contractName, err) + } + return nil + } + + for contractName, addr := range bundle { + if addr != (common.Address{}) { // skip zero addresses + if err := v.verifySingleContract(ctx, addr, contractName); err != nil { + v.numFailed++ + v.log.Error("failed to verify contract", "name", contractName, "error", err) + } + } + } + return nil +} + +func (v *Verifier) verifySingleContract(ctx context.Context, address common.Address, contractName string) error { + verified, err := v.etherscan.isVerified(address) + if err != nil { + return fmt.Errorf("failed to check verification status: %w", err) + } + if verified { + v.log.Info("Contract is already verified", "name", contractName, "address", address.Hex()) + v.numSkipped++ + return nil + } + + v.log.Info("Formatting etherscan verify request", "name", contractName, "address", address.Hex()) + artifact, err := v.getContractArtifact(contractName) + if err != nil { + return fmt.Errorf("failed to get contract source: %w", err) + } + + constructorArgs, err := v.getConstructorArgs(ctx, address, artifact) + if err != nil { + return fmt.Errorf("failed to get constructor args: %w", err) + } + + reqId, err := v.etherscan.verifySourceCode(address, artifact, constructorArgs) + if err != nil { + return fmt.Errorf("failed to verify contract: %w", err) + } + v.log.Info("Verification request submitted", "name", contractName, "address", address.Hex()) + + if err = v.etherscan.pollVerificationStatus(reqId); err != nil { + return fmt.Errorf("failed when checking verification status: %w", err) + } + + v.log.Info("Verification complete", "name", contractName, "address", address.Hex()) + v.numVerified++ + return nil +} From e3a9294dcd185d854060c6446c350cf252f2988f Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Fri, 7 Mar 2025 23:05:41 -0700 Subject: [PATCH 107/130] op-validator: Add mainnet validators (#14734) --- op-validator/pkg/validations/addresses.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/op-validator/pkg/validations/addresses.go b/op-validator/pkg/validations/addresses.go index 57f8fef9d9f..2c25be969f3 100644 --- a/op-validator/pkg/validations/addresses.go +++ b/op-validator/pkg/validations/addresses.go @@ -12,6 +12,12 @@ const ( ) var addresses = map[uint64]map[string]common.Address{ + 1: { + // Bootstrapped on 03/07/2025 using OP Deployer. + VersionV180: common.HexToAddress("0x37fb5b21750d0e08a992350574bd1c24f4bcedf9"), + // Bootstrapped on 03/07/2025 using OP Deployer. + VersionV200: common.HexToAddress("0x12a9e38628e5a5b24d18b1956ed68a24fe4e3dc0"), + }, 11155111: { // Bootstrapped on 03/02/2025 using OP Deployer. VersionV180: common.HexToAddress("0x0a5bf8ebb4b177b2dcc6eba933db726a2e2e2b4d"), From eeb94117d7a5a7fa1c75352660692cf3f1910a47 Mon Sep 17 00:00:00 2001 From: Sebastian Stammler Date: Sat, 8 Mar 2025 12:38:24 +0100 Subject: [PATCH 108/130] all: Implement optional PectraBlobSchedule fork/feature (#14680) * op-node/rollup: Implement optional L1 Pectra fix * make simple test fail with excess blob gas * improve test, fix L1 miner * fix L1 miner again * geoknee feedback * update op-geth dependency to v1.101503.0-rc.2 * address proto's feedback, add test case * fix static chain cfg tests --- go.mod | 2 +- go.sum | 4 +- op-chain-ops/genesis/config.go | 11 ++ op-e2e/actions/helpers/l1_miner.go | 19 +-- op-e2e/actions/proofs/helpers/env.go | 10 ++ op-e2e/actions/proofs/helpers/matrix.go | 23 +++ .../proofs/pectra_blob_schedule_test.go | 136 ++++++++++++++++++ op-e2e/actions/proofs/simple_program_test.go | 30 ++-- op-e2e/e2eutils/setup.go | 1 + op-e2e/system/e2esys/setup.go | 1 + op-node/chaincfg/chains_test.go | 2 + op-node/rollup/derive/l1_block_info.go | 13 ++ op-node/rollup/superchain.go | 1 + op-node/rollup/types.go | 30 +++- op-node/service.go | 4 + op-service/eth/block_info.go | 5 + op-service/flags/flags.go | 24 ++-- op-service/testutils/l1info.go | 31 ++-- 18 files changed, 294 insertions(+), 53 deletions(-) create mode 100644 op-e2e/actions/proofs/pectra_blob_schedule_test.go diff --git a/go.mod b/go.mod index 993b5b6cb29..3537eecfdf2 100644 --- a/go.mod +++ b/go.mod @@ -256,7 +256,7 @@ require ( rsc.io/tmplfunc v0.0.3 // indirect ) -replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101503.0-rc.1 +replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101503.0-rc.2 //replace github.com/ethereum/go-ethereum => ../op-geth diff --git a/go.sum b/go.sum index 7f9ad6f0c91..4ed40285189 100644 --- a/go.sum +++ b/go.sum @@ -192,8 +192,8 @@ github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/u github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs= -github.com/ethereum-optimism/op-geth v1.101503.0-rc.1 h1:8qSXpCfLvLIPlkmwO4J+J9chi9NgZlhRckVJEjxS5Lg= -github.com/ethereum-optimism/op-geth v1.101503.0-rc.1/go.mod h1:QUo3fn+45vWqJWzJW+rIzRHUV7NmhhHLPdI87mAn1M8= +github.com/ethereum-optimism/op-geth v1.101503.0-rc.2 h1:CHIF+1pENaamq7jq8LZOTQ1FVJ+U2EzNM9iVAr9Lgi8= +github.com/ethereum-optimism/op-geth v1.101503.0-rc.2/go.mod h1:QUo3fn+45vWqJWzJW+rIzRHUV7NmhhHLPdI87mAn1M8= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250228185245-d4bb112dc979 h1:P37l7EFCz5KxE20+yPa3LWiH2Cg+xx6lQ4mOKYCZFPs= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250228185245-d4bb112dc979/go.mod h1:NZ816PzLU1TLv1RdAvYAb6KWOj4Zm5aInT0YpDVml2Y= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= diff --git a/op-chain-ops/genesis/config.go b/op-chain-ops/genesis/config.go index 3cd7f9d677e..9aef9f6b7c3 100644 --- a/op-chain-ops/genesis/config.go +++ b/op-chain-ops/genesis/config.go @@ -364,6 +364,12 @@ type UpgradeScheduleDeployConfig struct { // Set it to 0 to activate at genesis. Nil to disable Interop. L2GenesisInteropTimeOffset *hexutil.Uint64 `json:"l2GenesisInteropTimeOffset,omitempty"` + // Optional Forks + + // L2GenesisPectraBlobScheduleTimeOffset is the number of seconds after genesis block that the PectraBlobSchedule fix activates. + // Set it to 0 to activate at genesis. Nil to disable the PectraBlobSchedule fix. + L2GenesisPectraBlobScheduleTimeOffset *hexutil.Uint64 `json:"l2GenesisPectraBlobScheduleTimeOffset,omitempty"` + // When Cancun activates. Relative to L1 genesis. L1CancunTimeOffset *hexutil.Uint64 `json:"l1CancunTimeOffset,omitempty"` // When Prague activates. Relative to L1 genesis. @@ -493,6 +499,10 @@ func (d *UpgradeScheduleDeployConfig) HoloceneTime(genesisTime uint64) *uint64 { return offsetToUpgradeTime(d.L2GenesisHoloceneTimeOffset, genesisTime) } +func (d *UpgradeScheduleDeployConfig) PectraBlobScheduleTime(genesisTime uint64) *uint64 { + return offsetToUpgradeTime(d.L2GenesisPectraBlobScheduleTimeOffset, genesisTime) +} + func (d *UpgradeScheduleDeployConfig) IsthmusTime(genesisTime uint64) *uint64 { return offsetToUpgradeTime(d.L2GenesisIsthmusTimeOffset, genesisTime) } @@ -1036,6 +1046,7 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *types.Header, l2GenesisBlockHa FjordTime: d.FjordTime(l1StartTime), GraniteTime: d.GraniteTime(l1StartTime), HoloceneTime: d.HoloceneTime(l1StartTime), + PectraBlobScheduleTime: d.PectraBlobScheduleTime(l1StartTime), IsthmusTime: d.IsthmusTime(l1StartTime), InteropTime: d.InteropTime(l1StartTime), ProtocolVersionsAddress: d.ProtocolVersionsProxy, diff --git a/op-e2e/actions/helpers/l1_miner.go b/op-e2e/actions/helpers/l1_miner.go index b33a68c12c4..5152b7f8b45 100644 --- a/op-e2e/actions/helpers/l1_miner.go +++ b/op-e2e/actions/helpers/l1_miner.go @@ -103,7 +103,8 @@ func (s *L1Miner) ActL1StartBlock(timeDelta uint64) Action { if s.l1Cfg.Config.IsCancun(header.Number, header.Time) { header.BlobGasUsed = new(uint64) - header.ExcessBlobGas = new(uint64) + excessBlobGas := eip4844.CalcExcessBlobGas(s.l1Cfg.Config, parent, header.Time) + header.ExcessBlobGas = &excessBlobGas root := crypto.Keccak256Hash([]byte("fake-beacon-block-root"), header.Number.Bytes()) header.ParentBeaconRoot = &root @@ -206,6 +207,7 @@ func (s *L1Miner) ActL1SetFeeRecipient(coinbase common.Address) { // ActL1EndBlock finishes the new L1 block, and applies it to the chain as unsafe block func (s *L1Miner) ActL1EndBlock(t Testing) *types.Block { + t.Helper() if !s.l1Building { t.InvalidAction("cannot end L1 block when not building block") return nil @@ -220,14 +222,14 @@ func (s *L1Miner) ActL1EndBlock(t Testing) *types.Block { withdrawals = make([]*types.Withdrawal, 0) } - block := types.NewBlock(s.l1BuildingHeader, &types.Body{Transactions: s.L1Transactions, Withdrawals: withdrawals}, s.l1Receipts, trie.NewStackTrie(nil), types.DefaultBlockConfig) - isCancun := s.l1Cfg.Config.IsCancun(s.l1BuildingHeader.Number, s.l1BuildingHeader.Time) - if isCancun { - parent := s.l1Chain.GetHeaderByHash(s.l1BuildingHeader.ParentHash) - excessBlobGas := eip4844.CalcExcessBlobGas(s.l1Cfg.Config, parent, s.l1BuildingHeader.Time) - s.l1BuildingHeader.ExcessBlobGas = &excessBlobGas + if s.l1Cfg.Config.IsPrague(s.l1BuildingHeader.Number, s.l1BuildingHeader.Time) { + // Don't process requests for now. + s.l1BuildingHeader.RequestsHash = &types.EmptyRequestsHash } + block := types.NewBlock(s.l1BuildingHeader, &types.Body{Transactions: s.L1Transactions, Withdrawals: withdrawals}, s.l1Receipts, trie.NewStackTrie(nil), types.DefaultBlockConfig) + + isCancun := s.l1Cfg.Config.IsCancun(s.l1BuildingHeader.Number, s.l1BuildingHeader.Time) // Write state changes to db root, err := s.l1BuildingState.Commit(s.l1BuildingHeader.Number.Uint64(), s.l1Cfg.Config.IsEIP158(s.l1BuildingHeader.Number), isCancun) if err != nil { @@ -246,12 +248,13 @@ func (s *L1Miner) ActL1EndBlock(t Testing) *types.Block { } _, err = s.l1Chain.InsertChain(types.Blocks{block}) if err != nil { - t.Fatalf("failed to insert block into l1 chain") + t.Fatalf("failed to insert block into l1 chain: %v", err) } return block } func (s *L1Miner) ActEmptyBlock(t Testing) *types.Block { + t.Helper() s.ActL1StartBlock(12)(t) return s.ActL1EndBlock(t) } diff --git a/op-e2e/actions/proofs/helpers/env.go b/op-e2e/actions/proofs/helpers/env.go index e347d6b1245..d9490f2fd42 100644 --- a/op-e2e/actions/proofs/helpers/env.go +++ b/op-e2e/actions/proofs/helpers/env.go @@ -231,3 +231,13 @@ func NewOpProgramCfg( dfault.DependencySet = fi.DependencySet return dfault } + +// BatchAndMine batches the current unsafe chain to L1 and mines the L1 block containing the +// batcher transaction. +func (env *L2FaultProofEnv) BatchAndMine(t helpers.Testing) { + t.Helper() + env.Batcher.ActSubmitAll(t) + env.Miner.ActL1StartBlock(12)(t) + env.Miner.ActL1IncludeTxByHash(env.Batcher.LastSubmitted.Hash())(t) + env.Miner.ActL1EndBlock(t) +} diff --git a/op-e2e/actions/proofs/helpers/matrix.go b/op-e2e/actions/proofs/helpers/matrix.go index 4f161531ce8..c679f035841 100644 --- a/op-e2e/actions/proofs/helpers/matrix.go +++ b/op-e2e/actions/proofs/helpers/matrix.go @@ -5,6 +5,8 @@ import ( "testing" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" + "github.com/ethereum-optimism/optimism/op-program/client/claim" + "github.com/ethereum/go-ethereum/common" ) type RunTest[cfg any] func(t *testing.T, testCfg *TestCfg[cfg]) @@ -75,6 +77,27 @@ func (ts *TestMatrix[cfg]) AddTestCase( return ts } +func (ts *TestMatrix[cfg]) AddDefaultTestCases( + testCfg cfg, + forkMatrix ForkMatrix, + runTest RunTest[cfg], +) *TestMatrix[cfg] { + return ts.AddTestCase( + "HonestClaim", + testCfg, + forkMatrix, + runTest, + ExpectNoError(), + ).AddTestCase( + "JunkClaim", + testCfg, + forkMatrix, + runTest, + ExpectError(claim.ErrClaimNotValid), + WithL2Claim(common.HexToHash("0xdeadbeef")), + ) +} + type Hardfork struct { Name string Precedence int diff --git a/op-e2e/actions/proofs/pectra_blob_schedule_test.go b/op-e2e/actions/proofs/pectra_blob_schedule_test.go new file mode 100644 index 00000000000..c3c70ae37d3 --- /dev/null +++ b/op-e2e/actions/proofs/pectra_blob_schedule_test.go @@ -0,0 +1,136 @@ +package proofs_test + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" + actionsHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" + "github.com/ethereum-optimism/optimism/op-e2e/actions/proofs/helpers" + legacybindings "github.com/ethereum-optimism/optimism/op-e2e/bindings" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/predeploys" + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +type pectraBlobScheduleTestCfg struct { + offset *uint64 + expectCancunBBF bool +} + +func TestPectraBlobSchedule(gt *testing.T) { + matrix := helpers.NewMatrix[any]() + defer matrix.Run(gt) + + matrix.AddDefaultTestCases( + // aligned with an L1 timestamp + pectraBlobScheduleTestCfg{ptr(uint64(24)), true}, + helpers.NewForkMatrix(helpers.Holocene), + testPectraBlobSchedule, + ).AddDefaultTestCases( + // in the middle between two L1 timestamps + pectraBlobScheduleTestCfg{ptr(uint64(18)), true}, + helpers.NewForkMatrix(helpers.Holocene), + testPectraBlobSchedule, + ).AddDefaultTestCases( + pectraBlobScheduleTestCfg{nil, false}, + helpers.NewForkMatrix(helpers.Holocene), + testPectraBlobSchedule, + ) +} + +func testPectraBlobSchedule(gt *testing.T, testCfg *helpers.TestCfg[any]) { + tcfg := testCfg.Custom.(pectraBlobScheduleTestCfg) // two flavors of this test + t := actionsHelpers.NewDefaultTesting(gt) + testSetup := func(dc *genesis.DeployConfig) { + dc.L1PragueTimeOffset = ptr(hexutil.Uint64(0)) + dc.L2GenesisPectraBlobScheduleTimeOffset = (*hexutil.Uint64)(tcfg.offset) + // set genesis excess blob gas so there are >0 blob base fees for some blocks + dc.L1GenesisBlockExcessBlobGas = ptr(hexutil.Uint64(1e8)) + } + + env := helpers.NewL2FaultProofEnv(t, testCfg, helpers.NewTestParams(), helpers.NewBatcherCfg(), testSetup) + + // sanity check + if tcfg.offset != nil { + require.Equal(t, *env.Sd.RollupCfg.PectraBlobScheduleTime, env.Sd.L2Cfg.Timestamp+*tcfg.offset) + } + + engine := env.Engine + sequencer := env.Sequencer + miner := env.Miner + + l1_0 := miner.L1Chain().CurrentHeader() + t.Logf("Header0: Number: %v, Time: %v, ExcessBlobGas: %v", l1_0.Number, l1_0.Time, *l1_0.ExcessBlobGas) + require.NotZero(t, *l1_0.ExcessBlobGas) + require.Equal(t, env.Sd.L2Cfg.Timestamp, l1_0.Time, "we assume L1 and L2 genesis have the same time") + + ethCl := engine.EthClient() + l1Block, err := legacybindings.NewL1Block(predeploys.L1BlockAddr, ethCl) + require.NoError(t, err) + + miner.ActEmptyBlock(t) + l1_1 := miner.L1Chain().CurrentHeader() + t.Logf("Header1: Number: %v, Time: %v, ExcessBlobGas: %v", l1_1.Number, l1_1.Time, *l1_1.ExcessBlobGas) + if tcfg.offset != nil { + require.Less(t, l1_1.Time, *env.Sd.RollupCfg.PectraBlobScheduleTime) + } + + sequencer.ActL1HeadSignal(t) + sequencer.ActBuildToL1HeadUnsafe(t) + + cancunBBF1 := eth.CalcBlobFeeCancun(*l1_1.ExcessBlobGas) + pragueBBF1 := eth.CalcBlobFeeDefault(l1_1) + // Make sure they differ. + require.Less(t, pragueBBF1.Uint64(), cancunBBF1.Uint64()) + opts := &bind.CallOpts{} + bbf1, err := l1Block.BlobBaseFee(opts) + require.NoError(t, err) + t.Logf("BlobBaseFee1: %v", bbf1) + // This is the critical assertion of this test. With the PectraBlobSchedule set, the blob + // base fee is still calculated using the Cancun schedule, without it with the same as the + // Prague schedule of L1. + if tcfg.expectCancunBBF { + require.Equal(t, cancunBBF1, bbf1) + } else { + require.Equal(t, pragueBBF1, bbf1) + } + + miner.ActEmptyBlock(t) + l1_2 := miner.L1Chain().CurrentHeader() + t.Logf("Header2: Number: %v, Time: %v, ExcessBlobGas: %v", l1_2.Number, l1_2.Time, *l1_2.ExcessBlobGas) + if tcfg.offset != nil { + if *tcfg.offset%12 == 0 { + require.Equal(t, l1_2.Time, *env.Sd.RollupCfg.PectraBlobScheduleTime) + } else { + require.Greater(t, l1_2.Time, *env.Sd.RollupCfg.PectraBlobScheduleTime) + } + } + + sequencer.ActL1HeadSignal(t) + sequencer.ActBuildToL1HeadUnsafe(t) + + cancunBBF2 := eth.CalcBlobFeeCancun(*l1_2.ExcessBlobGas) + pragueBBF2 := eth.CalcBlobFeeDefault(l1_2) + require.Less(t, pragueBBF2.Uint64(), cancunBBF2.Uint64()) + bbf2, err := l1Block.BlobBaseFee(opts) + require.NoError(t, err) + t.Logf("BlobBaseFee2: %v", bbf2) + require.Equal(t, pragueBBF2, bbf2) + l2UnsafeHead := env.Engine.L2Chain().CurrentHeader() + + env.BatchAndMine(t) + env.Sequencer.ActL1HeadSignal(t) + env.Sequencer.ActL2PipelineFull(t) + + l2SafeHead := env.Engine.L2Chain().CurrentSafeBlock() + require.Equal(t, eth.HeaderBlockID(l2SafeHead), eth.HeaderBlockID(l2UnsafeHead), "derivation leads to the same block") + + env.RunFaultProofProgram(t, l2SafeHead.Number.Uint64(), testCfg.CheckResult, testCfg.InputParams...) +} + +func ptr[T any](v T) *T { + return &v +} diff --git a/op-e2e/actions/proofs/simple_program_test.go b/op-e2e/actions/proofs/simple_program_test.go index ab3218caf1e..91815bd97f9 100644 --- a/op-e2e/actions/proofs/simple_program_test.go +++ b/op-e2e/actions/proofs/simple_program_test.go @@ -1,18 +1,28 @@ -package proofs +package proofs_test import ( "testing" + "github.com/ethereum-optimism/optimism/op-batcher/flags" + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" actionsHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" "github.com/ethereum-optimism/optimism/op-e2e/actions/proofs/helpers" - "github.com/ethereum-optimism/optimism/op-program/client/claim" - "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/stretchr/testify/require" ) func runSimpleProgramTest(gt *testing.T, testCfg *helpers.TestCfg[any]) { t := actionsHelpers.NewDefaultTesting(gt) - env := helpers.NewL2FaultProofEnv(t, testCfg, helpers.NewTestParams(), helpers.NewBatcherCfg()) + testSetup := func(dc *genesis.DeployConfig) { + dc.L1PragueTimeOffset = ptr(hexutil.Uint64(0)) + // Set non-trivial excess blob gas so that the L1 miner's blob logic is + // properly tested. + dc.L1GenesisBlockExcessBlobGas = ptr(hexutil.Uint64(1e8)) + } + bcfg := helpers.NewBatcherCfg(func(c *actionsHelpers.BatcherCfg) { + c.DataAvailabilityType = flags.BlobsType + }) + env := helpers.NewL2FaultProofEnv(t, testCfg, helpers.NewTestParams(), bcfg, testSetup) // Build an empty block on L2 env.Sequencer.ActL2StartBlock(t) @@ -47,19 +57,9 @@ func Test_ProgramAction_SimpleEmptyChain(gt *testing.T) { matrix := helpers.NewMatrix[any]() defer matrix.Run(gt) - matrix.AddTestCase( - "HonestClaim", + matrix.AddDefaultTestCases( nil, helpers.LatestForkOnly, runSimpleProgramTest, - helpers.ExpectNoError(), - ) - matrix.AddTestCase( - "JunkClaim", - nil, - helpers.LatestForkOnly, - runSimpleProgramTest, - helpers.ExpectError(claim.ErrClaimNotValid), - helpers.WithL2Claim(common.HexToHash("0xdeadbeef")), ) } diff --git a/op-e2e/e2eutils/setup.go b/op-e2e/e2eutils/setup.go index 92fc08403c1..858e0766d71 100644 --- a/op-e2e/e2eutils/setup.go +++ b/op-e2e/e2eutils/setup.go @@ -208,6 +208,7 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) * FjordTime: deployConf.FjordTime(uint64(deployConf.L1GenesisBlockTimestamp)), GraniteTime: deployConf.GraniteTime(uint64(deployConf.L1GenesisBlockTimestamp)), HoloceneTime: deployConf.HoloceneTime(uint64(deployConf.L1GenesisBlockTimestamp)), + PectraBlobScheduleTime: deployConf.PectraBlobScheduleTime(uint64(deployConf.L1GenesisBlockTimestamp)), IsthmusTime: deployConf.IsthmusTime(uint64(deployConf.L1GenesisBlockTimestamp)), InteropTime: deployConf.InteropTime(uint64(deployConf.L1GenesisBlockTimestamp)), AltDAConfig: pcfg, diff --git a/op-e2e/system/e2esys/setup.go b/op-e2e/system/e2esys/setup.go index 8c63057660f..747f0497191 100644 --- a/op-e2e/system/e2esys/setup.go +++ b/op-e2e/system/e2esys/setup.go @@ -671,6 +671,7 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System, FjordTime: cfg.DeployConfig.FjordTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), GraniteTime: cfg.DeployConfig.GraniteTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), HoloceneTime: cfg.DeployConfig.HoloceneTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), + PectraBlobScheduleTime: cfg.DeployConfig.PectraBlobScheduleTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), IsthmusTime: cfg.DeployConfig.IsthmusTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), InteropTime: cfg.DeployConfig.InteropTime(uint64(cfg.DeployConfig.L1GenesisBlockTimestamp)), ProtocolVersionsAddress: cfg.L1Deployments.ProtocolVersionsProxy, diff --git a/op-node/chaincfg/chains_test.go b/op-node/chaincfg/chains_test.go index 8f741b7427c..1a067046e24 100644 --- a/op-node/chaincfg/chains_test.go +++ b/op-node/chaincfg/chains_test.go @@ -114,6 +114,7 @@ var sepoliaCfg = rollup.Config{ FjordTime: u64Ptr(1716998400), GraniteTime: u64Ptr(1723478400), HoloceneTime: u64Ptr(1732633200), + PectraBlobScheduleTime: u64Ptr(1742486400), ProtocolVersionsAddress: common.HexToAddress("0x79ADD5713B383DAa0a138d3C4780C7A1804a8090"), ChainOpConfig: defaultOpConfig, } @@ -152,6 +153,7 @@ var sepoliaDev0Cfg = rollup.Config{ FjordTime: u64Ptr(1715961600), GraniteTime: u64Ptr(1723046400), HoloceneTime: u64Ptr(1731682800), + PectraBlobScheduleTime: u64Ptr(1742486400), ProtocolVersionsAddress: common.HexToAddress("0x252CbE9517F731C618961D890D534183822dcC8d"), ChainOpConfig: defaultOpConfig, } diff --git a/op-node/rollup/derive/l1_block_info.go b/op-node/rollup/derive/l1_block_info.go index 0fae5de8118..36bcba90955 100644 --- a/op-node/rollup/derive/l1_block_info.go +++ b/op-node/rollup/derive/l1_block_info.go @@ -453,6 +453,19 @@ func L1InfoDeposit(rollupCfg *rollup.Config, sysCfg eth.SystemConfig, seqNumber if isEcotoneButNotFirstBlock(rollupCfg, l2Timestamp) { isIsthmusActivated := isIsthmusButNotFirstBlock(rollupCfg, l2Timestamp) l1BlockInfo.BlobBaseFee = block.BlobBaseFee() + + // Apply Cancun blob base fee calculation if this chain needs the L1 Pectra + // blob schedule fix (mostly Holesky and Sepolia OP-Stack chains). + if t := rollupCfg.PectraBlobScheduleTime; t != nil && block.Time() < *t { + if ebg := block.ExcessBlobGas(); ebg != nil { + l1BlockInfo.BlobBaseFee = eth.CalcBlobFeeCancun(*ebg) + } else { + // If L1 isn't on Cancun yet. It should already have been set + // to nil above in this case anyways. + l1BlockInfo.BlobBaseFee = nil + } + } + if l1BlockInfo.BlobBaseFee == nil { // The L2 spec states to use the MIN_BLOB_GASPRICE from EIP-4844 if not yet active on L1. l1BlockInfo.BlobBaseFee = big.NewInt(1) diff --git a/op-node/rollup/superchain.go b/op-node/rollup/superchain.go index b73c70e96fa..ac7de144191 100644 --- a/op-node/rollup/superchain.go +++ b/op-node/rollup/superchain.go @@ -88,6 +88,7 @@ func LoadOPStackRollupConfig(chainID uint64) (*Config, error) { FjordTime: hardforks.FjordTime, GraniteTime: hardforks.GraniteTime, HoloceneTime: hardforks.HoloceneTime, + PectraBlobScheduleTime: hardforks.PectraBlobScheduleTime, IsthmusTime: hardforks.IsthmusTime, BatchInboxAddress: chConfig.BatchInboxAddr, DepositContractAddress: *addrs.OptimismPortalProxy, diff --git a/op-node/rollup/types.go b/op-node/rollup/types.go index 7b144e09daa..2726fd6b4db 100644 --- a/op-node/rollup/types.go +++ b/op-node/rollup/types.go @@ -141,14 +141,24 @@ type Config struct { // L1 address that declares the protocol versions, optional (Beta feature) ProtocolVersionsAddress common.Address `json:"protocol_versions_address,omitempty"` - // AltDAConfig. We are in the process of migrating to the AltDAConfig from these legacy top level values - AltDAConfig *AltDAConfig `json:"alt_da,omitempty"` - // ChainOpConfig is the OptimismConfig of the execution layer ChainConfig. // It is used during safe chain consolidation to translate zero SystemConfig EIP1559 // parameters to the protocol values, like the execution layer does. // If missing, it is loaded by the op-node from the embedded superchain config at startup. ChainOpConfig *params.OptimismConfig `json:"chain_op_config,omitempty"` + + // Optional Features + + // AltDAConfig. We are in the process of migrating to the AltDAConfig from these legacy top level values + AltDAConfig *AltDAConfig `json:"alt_da,omitempty"` + + // PectraBlobScheduleTime sets the time until which (but not including) the blob base fee + // calculations for the L1 Block Info use the pre-Prague=Cancun blob parameters. + // This feature is optional and if not active, the L1 Block Info calculation uses the Prague + // blob parameters for the first L1 Prague block, as was intended. + // This feature (de)activates by L1 origin timestamp, to keep a consistent L1 block info per L2 + // epoch. + PectraBlobScheduleTime *uint64 `json:"pectra_blob_schedule_time,omitempty"` } // ValidateL1Config checks L1 config variables for errors. @@ -685,7 +695,8 @@ func (c *Config) LogDescription(log log.Logger, l2Chains map[string]string) { networkL1 = "unknown L1" } - log.Info("Rollup Config", "l2_chain_id", c.L2ChainID, "l2_network", networkL2, "l1_chain_id", c.L1ChainID, + ctx := []any{ + "l2_chain_id", c.L2ChainID, "l2_network", networkL2, "l1_chain_id", c.L1ChainID, "l1_network", networkL1, "l2_start_time", c.Genesis.L2Time, "l2_block_hash", c.Genesis.L2.Hash.String(), "l2_block_number", c.Genesis.L2.Number, "l1_block_hash", c.Genesis.L1.Hash.String(), "l1_block_number", c.Genesis.L1.Number, "regolith_time", fmtForkTimeOrUnset(c.RegolithTime), @@ -697,8 +708,15 @@ func (c *Config) LogDescription(log log.Logger, l2Chains map[string]string) { "holocene_time", fmtForkTimeOrUnset(c.HoloceneTime), "isthmus_time", fmtForkTimeOrUnset(c.IsthmusTime), "interop_time", fmtForkTimeOrUnset(c.InteropTime), - "alt_da", c.AltDAConfig != nil, - ) + } + if c.AltDAConfig != nil { + ctx = append(ctx, "alt_da", *c.AltDAConfig) + } + if c.PectraBlobScheduleTime != nil { + // only print in config if set at all + ctx = append(ctx, "pectra_blob_schedule_time", fmtForkTimeOrUnset(c.PectraBlobScheduleTime)) + } + log.Info("Rollup Config", ctx...) } func (c *Config) ParseRollupConfig(in io.Reader) error { diff --git a/op-node/service.go b/op-node/service.go index 9ed2e839273..81f269ed08c 100644 --- a/op-node/service.go +++ b/op-node/service.go @@ -266,6 +266,10 @@ func applyOverrides(ctx *cli.Context, rollupConfig *rollup.Config) { holocene := ctx.Uint64(opflags.HoloceneOverrideFlagName) rollupConfig.HoloceneTime = &holocene } + if ctx.IsSet(opflags.PectraBlobScheduleOverrideFlagName) { + pectrablobschedule := ctx.Uint64(opflags.PectraBlobScheduleOverrideFlagName) + rollupConfig.PectraBlobScheduleTime = &pectrablobschedule + } } func NewSyncConfig(ctx *cli.Context, log log.Logger) (*sync.Config, error) { diff --git a/op-service/eth/block_info.go b/op-service/eth/block_info.go index 9530c17ca66..0b1c7c46c41 100644 --- a/op-service/eth/block_info.go +++ b/op-service/eth/block_info.go @@ -21,6 +21,7 @@ type BlockInfo interface { // BlobBaseFee returns the result of computing the blob fee from excessDataGas, or nil if the // block isn't a Dencun (4844 capable) block BlobBaseFee() *big.Int + ExcessBlobGas() *uint64 ReceiptHash() common.Hash GasUsed() uint64 GasLimit() uint64 @@ -130,6 +131,10 @@ func (h *headerBlockInfo) BlobBaseFee() *big.Int { return CalcBlobFeeDefault(h.header) } +func (h *headerBlockInfo) ExcessBlobGas() *uint64 { + return h.header.ExcessBlobGas +} + func (h *headerBlockInfo) ReceiptHash() common.Hash { return h.header.ReceiptHash } diff --git a/op-service/flags/flags.go b/op-service/flags/flags.go index f8be269303b..a4fdbdb4816 100644 --- a/op-service/flags/flags.go +++ b/op-service/flags/flags.go @@ -11,14 +11,15 @@ import ( ) const ( - RollupConfigFlagName = "rollup.config" - NetworkFlagName = "network" - CanyonOverrideFlagName = "override.canyon" - DeltaOverrideFlagName = "override.delta" - EcotoneOverrideFlagName = "override.ecotone" - FjordOverrideFlagName = "override.fjord" - GraniteOverrideFlagName = "override.granite" - HoloceneOverrideFlagName = "override.holocene" + RollupConfigFlagName = "rollup.config" + NetworkFlagName = "network" + CanyonOverrideFlagName = "override.canyon" + DeltaOverrideFlagName = "override.delta" + EcotoneOverrideFlagName = "override.ecotone" + FjordOverrideFlagName = "override.fjord" + GraniteOverrideFlagName = "override.granite" + HoloceneOverrideFlagName = "override.holocene" + PectraBlobScheduleOverrideFlagName = "override.pectrablobschedule" ) func CLIFlags(envPrefix string, category string) []cli.Flag { @@ -65,6 +66,13 @@ func CLIFlags(envPrefix string, category string) []cli.Flag { Hidden: false, Category: category, }, + &cli.Uint64Flag{ + Name: PectraBlobScheduleOverrideFlagName, + Usage: "Manually specify the PectraBlobSchedule fork timestamp, overriding the bundled setting", + EnvVars: opservice.PrefixEnvVar(envPrefix, "OVERRIDE_PECTRABLOBSCHEDULE"), + Hidden: false, + Category: category, + }, CLINetworkFlag(envPrefix, category), CLIRollupConfigFlag(envPrefix, category), } diff --git a/op-service/testutils/l1info.go b/op-service/testutils/l1info.go index ec5f75e0801..cc41669ad05 100644 --- a/op-service/testutils/l1info.go +++ b/op-service/testutils/l1info.go @@ -15,19 +15,20 @@ var _ eth.BlockInfo = &MockBlockInfo{} type MockBlockInfo struct { // Prefixed all fields with "Info" to avoid collisions with the interface method names. - InfoHash common.Hash - InfoParentHash common.Hash - InfoCoinbase common.Address - InfoRoot common.Hash - InfoNum uint64 - InfoTime uint64 - InfoMixDigest [32]byte - InfoBaseFee *big.Int - InfoBlobBaseFee *big.Int - InfoReceiptRoot common.Hash - InfoGasUsed uint64 - InfoGasLimit uint64 - InfoHeaderRLP []byte + InfoHash common.Hash + InfoParentHash common.Hash + InfoCoinbase common.Address + InfoRoot common.Hash + InfoNum uint64 + InfoTime uint64 + InfoMixDigest [32]byte + InfoBaseFee *big.Int + InfoBlobBaseFee *big.Int + InfoExcessBlobGas *uint64 + InfoReceiptRoot common.Hash + InfoGasUsed uint64 + InfoGasLimit uint64 + InfoHeaderRLP []byte InfoParentBeaconRoot *common.Hash InfoWithdrawalsRoot *common.Hash @@ -69,6 +70,10 @@ func (l *MockBlockInfo) BlobBaseFee() *big.Int { return l.InfoBlobBaseFee } +func (l *MockBlockInfo) ExcessBlobGas() *uint64 { + return l.InfoExcessBlobGas +} + func (l *MockBlockInfo) ReceiptHash() common.Hash { return l.InfoReceiptRoot } From 4b36aa468bbd8422edc9248360c3ed92a16d0d26 Mon Sep 17 00:00:00 2001 From: Joshua Gutow Date: Sun, 9 Mar 2025 13:55:30 -0700 Subject: [PATCH 109/130] Don't split log.msg (#14735) Splitting the message makes it hard to grep to find the source of the error. --- op-node/rollup/sync/start.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/op-node/rollup/sync/start.go b/op-node/rollup/sync/start.go index 15a811bce57..d779ed22b39 100644 --- a/op-node/rollup/sync/start.go +++ b/op-node/rollup/sync/start.go @@ -134,8 +134,7 @@ func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain // Check if the execution engine corrupted, and forkchoice is ahead of the remaining chain: // in this case we must go back to the prior head, to reprocess the pruned finalized/safe data. if result.Unsafe.Number < result.Finalized.Number || result.Unsafe.Number < result.Safe.Number { - lgr.Error("Unsafe head is behind known finalized/safe blocks, "+ - "execution-engine chain must have been rewound without forkchoice update. Attempting recovery now.", + lgr.Error("Unsafe head is behind known finalized/safe blocks, execution-engine chain must have been rewound without forkchoice update. Attempting recovery now.", "unsafe_head", result.Unsafe, "safe_head", result.Safe, "finalized_head", result.Finalized) return &FindHeadsResult{Unsafe: result.Unsafe, Safe: result.Unsafe, Finalized: result.Unsafe}, nil } From 1b90c0522ace1939b54d1d9404a8ef2de5edfa5c Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Mon, 10 Mar 2025 10:51:26 +1000 Subject: [PATCH 110/130] op-challenger: Interop vm runner (#14669) * op-challenger: Add config option to set dependency set config * Fix expected error message * op-challenger: Update op-program executor to handle interop inputs. * op-challenger: Support super-cannon in run trace. * Fixes. * Remove refactoring to introduce ToSuper() --- op-challenger/runner/factory.go | 2 +- op-challenger/runner/game_inputs.go | 190 +++++++++++++++++++++++++++ op-challenger/runner/prestates.go | 8 ++ op-challenger/runner/runner.go | 131 ++++-------------- op-program/client/interop/interop.go | 4 + 5 files changed, 232 insertions(+), 103 deletions(-) create mode 100644 op-challenger/runner/game_inputs.go diff --git a/op-challenger/runner/factory.go b/op-challenger/runner/factory.go index f53927a1bb6..aeae94b4340 100644 --- a/op-challenger/runner/factory.go +++ b/op-challenger/runner/factory.go @@ -29,7 +29,7 @@ func createTraceProvider( dir string, ) (types.TraceProvider, error) { switch traceType { - case types.TraceTypeCannon: + case types.TraceTypeCannon, types.TraceTypeSuperCannon: serverExecutor := vm.NewOpProgramServerExecutor(logger) stateConverter := cannon.NewStateConverter(cfg.Cannon) prestate, err := prestateSource.getPrestate(ctx, logger, cfg.CannonAbsolutePreStateBaseURL, cfg.CannonAbsolutePreState, dir, stateConverter) diff --git a/op-challenger/runner/game_inputs.go b/op-challenger/runner/game_inputs.go new file mode 100644 index 00000000000..ab4d98b66c2 --- /dev/null +++ b/op-challenger/runner/game_inputs.go @@ -0,0 +1,190 @@ +package runner + +import ( + "context" + "errors" + "fmt" + "math/big" + "math/rand" + + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/super" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" + "github.com/ethereum-optimism/optimism/op-service/sources" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" +) + +func createGameInputs(ctx context.Context, log log.Logger, rollupClient *sources.RollupClient, supervisorClient *sources.SupervisorClient, typeName string, traceType types.TraceType) (utils.LocalGameInputs, error) { + switch traceType { + case types.TraceTypeSuperCannon, types.TraceTypeSuperPermissioned: + if supervisorClient == nil { + return utils.LocalGameInputs{}, fmt.Errorf("trace type %s requires supervisor rpc to be set", traceType) + } + return createGameInputsInterop(ctx, log, supervisorClient, typeName) + default: + if rollupClient == nil { + return utils.LocalGameInputs{}, fmt.Errorf("trace type %s requires rollup rpc to be set", traceType) + } + return createGameInputsSingle(ctx, log, rollupClient, typeName) + } +} + +func createGameInputsSingle(ctx context.Context, log log.Logger, client *sources.RollupClient, typeName string) (utils.LocalGameInputs, error) { + status, err := client.SyncStatus(ctx) + if err != nil { + return utils.LocalGameInputs{}, fmt.Errorf("failed to get rollup sync status: %w", err) + } + log.Info("Got sync status", "status", status, "type", typeName) + + if status.FinalizedL2.Number == 0 { + return utils.LocalGameInputs{}, errors.New("safe head is 0") + } + l1Head := status.FinalizedL1 + if status.FinalizedL1.Number > status.CurrentL1.Number { + // Restrict the L1 head to a block that has actually been processed by op-node. + // This only matters if op-node is behind and hasn't processed all finalized L1 blocks yet. + l1Head = status.CurrentL1 + log.Info("Node has not completed syncing finalized L1 block, using CurrentL1 instead", "type", typeName) + } else if status.FinalizedL1.Number == 0 { + // The node is resetting its pipeline and has set FinalizedL1 to 0, use the current L1 instead as it is the best + // hope of getting a non-zero L1 block + l1Head = status.CurrentL1 + log.Warn("Node has zero finalized L1 block, using CurrentL1 instead", "type", typeName) + } + log.Info("Using L1 head", "head", l1Head, "type", typeName) + if l1Head.Number == 0 { + return utils.LocalGameInputs{}, errors.New("l1 head is 0") + } + blockNumber, err := findL2BlockNumberToDispute(ctx, log, client, l1Head.Number, status.FinalizedL2.Number) + if err != nil { + return utils.LocalGameInputs{}, fmt.Errorf("failed to find l2 block number to dispute: %w", err) + } + claimOutput, err := client.OutputAtBlock(ctx, blockNumber) + if err != nil { + return utils.LocalGameInputs{}, fmt.Errorf("failed to get claim output: %w", err) + } + parentOutput, err := client.OutputAtBlock(ctx, blockNumber-1) + if err != nil { + return utils.LocalGameInputs{}, fmt.Errorf("failed to get claim output: %w", err) + } + localInputs := utils.LocalGameInputs{ + L1Head: l1Head.Hash, + L2Head: parentOutput.BlockRef.Hash, + L2OutputRoot: common.Hash(parentOutput.OutputRoot), + L2Claim: common.Hash(claimOutput.OutputRoot), + L2BlockNumber: new(big.Int).SetUint64(blockNumber), + } + return localInputs, nil +} + +func createGameInputsInterop(ctx context.Context, log log.Logger, client *sources.SupervisorClient, typeName string) (utils.LocalGameInputs, error) { + status, err := client.SyncStatus(ctx) + if err != nil { + return utils.LocalGameInputs{}, fmt.Errorf("failed to get rollup sync status: %w", err) + } + log.Info("Got sync status", "status", status, "type", typeName) + + claimTimestamp := status.FinalizedTimestamp + agreedTimestamp := claimTimestamp - 1 + if claimTimestamp == 0 { + return utils.LocalGameInputs{}, errors.New("finalized timestamp is 0") + } + l1Head := status.MinSyncedL1 + log.Info("Using L1 head", "head", l1Head, "type", typeName) + if l1Head.Number == 0 { + return utils.LocalGameInputs{}, errors.New("l1 head is 0") + } + + prestateProvider := super.NewSuperRootPrestateProvider(client, agreedTimestamp) + gameDepth := types.Depth(30) + provider := super.NewSuperTraceProvider(log, nil, prestateProvider, client, l1Head.ID(), gameDepth, agreedTimestamp, claimTimestamp+10) + var agreedPrestate []byte + var claim common.Hash + switch 2 { //rand.Intn(3) { + case 0: // Derive block on first chain + log.Info("Running first chain") + prestate, err := prestateProvider.AbsolutePreState(ctx) + if err != nil { + return utils.LocalGameInputs{}, fmt.Errorf("failed to get pre-state commitment: %w", err) + } + agreedPrestate = prestate.Marshal() + claim, err = provider.Get(ctx, types.NewPosition(gameDepth, big.NewInt(0))) + if err != nil { + return utils.LocalGameInputs{}, fmt.Errorf("failed to get claim: %w", err) + } + case 1: // Derive block on second chain + log.Info("Deriving second chain") + agreedPrestate, err = provider.GetPreimageBytes(ctx, types.NewPosition(gameDepth, big.NewInt(0))) + if err != nil { + return utils.LocalGameInputs{}, fmt.Errorf("failed to get agreed prestate at position 0: %w", err) + } + claim, err = provider.Get(ctx, types.NewPosition(gameDepth, big.NewInt(1))) + if err != nil { + return utils.LocalGameInputs{}, fmt.Errorf("failed to get claim: %w", err) + } + case 2: // Consolidate + log.Info("Running consolidate step") + step := int64(super.StepsPerTimestamp - 1) + agreedPrestate, err = provider.GetPreimageBytes(ctx, types.NewPosition(gameDepth, big.NewInt(step-1))) + if err != nil { + return utils.LocalGameInputs{}, fmt.Errorf("failed to get agreed prestate at position 0: %w", err) + } + claim, err = provider.Get(ctx, types.NewPosition(gameDepth, big.NewInt(step))) + if err != nil { + return utils.LocalGameInputs{}, fmt.Errorf("failed to get claim: %w", err) + } + } + localInputs := utils.LocalGameInputs{ + L1Head: l1Head.Hash, + AgreedPreState: agreedPrestate, + L2OutputRoot: crypto.Keccak256Hash(agreedPrestate), + L2Claim: claim, + L2BlockNumber: new(big.Int).SetUint64(claimTimestamp + 10), // Anything beyond the claim + } + return localInputs, nil +} + +func findL2BlockNumberToDispute(ctx context.Context, log log.Logger, client *sources.RollupClient, l1HeadNum uint64, l2BlockNum uint64) (uint64, error) { + // Try to find a L1 block prior to the batch that make l2BlockNum safe + // Limits how far back we search to 10 * 32 blocks + const skipSize = uint64(32) + for i := 0; i < 10; i++ { + if l1HeadNum < skipSize { + // Too close to genesis, give up and just use the original block + log.Info("Failed to find prior batch.") + return l2BlockNum, nil + } + l1HeadNum -= skipSize + prevSafeHead, err := client.SafeHeadAtL1Block(ctx, l1HeadNum) + if err != nil { + return 0, fmt.Errorf("failed to get prior safe head at L1 block %v: %w", l1HeadNum, err) + } + if prevSafeHead.SafeHead.Number < l2BlockNum { + switch rand.Intn(3) { + case 0: // First block of span batch + return prevSafeHead.SafeHead.Number + 1, nil + case 1: // Last block of span batch + return prevSafeHead.SafeHead.Number, nil + case 2: // Random block, probably but not guaranteed to be in the middle of a span batch + firstBlockInSpanBatch := prevSafeHead.SafeHead.Number + 1 + if l2BlockNum <= firstBlockInSpanBatch { + // There is only one block in the next batch so we just have to use it + return l2BlockNum, nil + } + offset := rand.Intn(int(l2BlockNum - firstBlockInSpanBatch)) + return firstBlockInSpanBatch + uint64(offset), nil + } + + } + if prevSafeHead.SafeHead.Number < l2BlockNum { + // We walked back far enough to be before the batch that included l2BlockNum + // So use the first block after the prior safe head as the disputed block. + // It must be the first block in a batch. + return prevSafeHead.SafeHead.Number + 1, nil + } + } + log.Warn("Failed to find prior batch", "l2BlockNum", l2BlockNum, "earliestCheckL1Block", l1HeadNum) + return l2BlockNum, nil +} diff --git a/op-challenger/runner/prestates.go b/op-challenger/runner/prestates.go index 19e3d40933d..54bc8fa474c 100644 --- a/op-challenger/runner/prestates.go +++ b/op-challenger/runner/prestates.go @@ -67,6 +67,14 @@ func (f *HashPrestateFetcher) getPrestate(ctx context.Context, _ log.Logger, pre return prestate, nil } +type LocalPrestateFetcher struct { + path string +} + +func (f *LocalPrestateFetcher) getPrestate(ctx context.Context, logger log.Logger, prestateBaseUrl *url.URL, _ string, dataDir string, stateConverter vm.StateConverter) (string, error) { + return f.path, nil +} + // NamedPrestateFetcher downloads a file with a specified name from the prestate base URL and uses it as the prestate. // The file is re-downloaded on each run rather than being cached. This makes it possible to run the latest builds // from develop. diff --git a/op-challenger/runner/runner.go b/op-challenger/runner/runner.go index 8ec7e95fb34..580b353e849 100644 --- a/op-challenger/runner/runner.go +++ b/op-challenger/runner/runner.go @@ -4,15 +4,15 @@ import ( "context" "errors" "fmt" - "math/big" - "math/rand" "os" "path/filepath" "regexp" + "strings" "sync" "sync/atomic" "time" + "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -85,9 +85,23 @@ func (r *Runner) Start(ctx context.Context) error { return fmt.Errorf("failed to start metrics: %w", err) } - rollupClient, err := dial.DialRollupClientWithTimeout(ctx, 1*time.Minute, r.log, r.cfg.RollupRpc) - if err != nil { - return fmt.Errorf("failed to dial rollup client: %w", err) + var rollupClient *sources.RollupClient + if r.cfg.RollupRpc != "" { + r.log.Info("Dialling rollup client", "url", r.cfg.RollupRpc) + cl, err := dial.DialRollupClientWithTimeout(ctx, 1*time.Minute, r.log, r.cfg.RollupRpc) + if err != nil { + return fmt.Errorf("failed to dial rollup client: %w", err) + } + rollupClient = cl + } + var supervisorClient *sources.SupervisorClient + if r.cfg.SupervisorRPC != "" { + r.log.Info("Dialling supervisor client", "url", r.cfg.SupervisorRPC) + rpcCl, err := dial.DialRPCClientWithTimeout(ctx, 1*time.Minute, r.log, r.cfg.SupervisorRPC) + if err != nil { + return fmt.Errorf("failed to dial rollup client: %w", err) + } + supervisorClient = sources.NewSupervisorClient(client.NewBaseRPCClient(rpcCl)) } l1Client, err := dial.DialRPCClientWithTimeout(ctx, 1*time.Minute, r.log, r.cfg.L1EthRpc) @@ -98,19 +112,19 @@ func (r *Runner) Start(ctx context.Context) error { for _, runConfig := range r.runConfigs { r.wg.Add(1) - go r.loop(ctx, runConfig, rollupClient, caller) + go r.loop(ctx, runConfig, rollupClient, supervisorClient, caller) } r.log.Info("Runners started", "num", len(r.runConfigs)) return nil } -func (r *Runner) loop(ctx context.Context, runConfig RunConfig, client *sources.RollupClient, caller *batching.MultiCaller) { +func (r *Runner) loop(ctx context.Context, runConfig RunConfig, rollupClient *sources.RollupClient, supervisorClient *sources.SupervisorClient, caller *batching.MultiCaller) { defer r.wg.Done() t := time.NewTicker(1 * time.Minute) defer t.Stop() for { - r.runAndRecordOnce(ctx, runConfig, client, caller) + r.runAndRecordOnce(ctx, runConfig, rollupClient, supervisorClient, caller) select { case <-t.C: case <-ctx.Done(): @@ -119,7 +133,7 @@ func (r *Runner) loop(ctx context.Context, runConfig RunConfig, client *sources. } } -func (r *Runner) runAndRecordOnce(ctx context.Context, runConfig RunConfig, client *sources.RollupClient, caller *batching.MultiCaller) { +func (r *Runner) runAndRecordOnce(ctx context.Context, runConfig RunConfig, rollupClient *sources.RollupClient, supervisorClient *sources.SupervisorClient, caller *batching.MultiCaller) { recordError := func(err error, traceType string, m Metricer, log log.Logger) { if errors.Is(err, ErrUnexpectedStatusCode) { log.Error("Incorrect status code", "type", runConfig.Name, "err", err) @@ -137,7 +151,11 @@ func (r *Runner) runAndRecordOnce(ctx context.Context, runConfig RunConfig, clie } var prestateSource prestateFetcher - if runConfig.PrestateFilename != "" { + if strings.HasPrefix(runConfig.PrestateFilename, "file:") { + path := runConfig.PrestateFilename[len("file:"):] + r.log.Info("Using local file prestate", "type", runConfig.TraceType, "path", path) + prestateSource = &LocalPrestateFetcher{path: path} + } else if runConfig.PrestateFilename != "" { r.log.Info("Using named prestate", "type", runConfig.TraceType, "filename", runConfig.PrestateFilename) prestateSource = &NamedPrestateFetcher{filename: runConfig.PrestateFilename} } else if runConfig.Prestate == (common.Hash{}) { @@ -153,7 +171,7 @@ func (r *Runner) runAndRecordOnce(ctx context.Context, runConfig RunConfig, clie prestateSource = &HashPrestateFetcher{prestateHash: runConfig.Prestate} } - localInputs, err := r.createGameInputs(ctx, client, runConfig.Name) + localInputs, err := createGameInputs(ctx, r.log, rollupClient, supervisorClient, runConfig.Name, runConfig.TraceType) if err != nil { recordError(err, runConfig.Name, r.m, r.log) return @@ -197,97 +215,6 @@ func (r *Runner) prepDatadir(name string) (string, error) { return dir, nil } -func (r *Runner) createGameInputs(ctx context.Context, client *sources.RollupClient, traceType string) (utils.LocalGameInputs, error) { - status, err := client.SyncStatus(ctx) - if err != nil { - return utils.LocalGameInputs{}, fmt.Errorf("failed to get rollup sync status: %w", err) - } - r.log.Info("Got sync status", "status", status, "type", traceType) - - if status.FinalizedL2.Number == 0 { - return utils.LocalGameInputs{}, errors.New("safe head is 0") - } - l1Head := status.FinalizedL1 - if status.FinalizedL1.Number > status.CurrentL1.Number { - // Restrict the L1 head to a block that has actually been processed by op-node. - // This only matters if op-node is behind and hasn't processed all finalized L1 blocks yet. - l1Head = status.CurrentL1 - r.log.Info("Node has not completed syncing finalized L1 block, using CurrentL1 instead", "type", traceType) - } else if status.FinalizedL1.Number == 0 { - // The node is resetting its pipeline and has set FinalizedL1 to 0, use the current L1 instead as it is the best - // hope of getting a non-zero L1 block - l1Head = status.CurrentL1 - r.log.Warn("Node has zero finalized L1 block, using CurrentL1 instead", "type", traceType) - } - r.log.Info("Using L1 head", "head", l1Head, "type", traceType) - if l1Head.Number == 0 { - return utils.LocalGameInputs{}, errors.New("l1 head is 0") - } - blockNumber, err := r.findL2BlockNumberToDispute(ctx, client, l1Head.Number, status.FinalizedL2.Number) - if err != nil { - return utils.LocalGameInputs{}, fmt.Errorf("failed to find l2 block number to dispute: %w", err) - } - claimOutput, err := client.OutputAtBlock(ctx, blockNumber) - if err != nil { - return utils.LocalGameInputs{}, fmt.Errorf("failed to get claim output: %w", err) - } - parentOutput, err := client.OutputAtBlock(ctx, blockNumber-1) - if err != nil { - return utils.LocalGameInputs{}, fmt.Errorf("failed to get claim output: %w", err) - } - localInputs := utils.LocalGameInputs{ - L1Head: l1Head.Hash, - L2Head: parentOutput.BlockRef.Hash, - L2OutputRoot: common.Hash(parentOutput.OutputRoot), - L2Claim: common.Hash(claimOutput.OutputRoot), - L2BlockNumber: new(big.Int).SetUint64(blockNumber), - } - return localInputs, nil -} - -func (r *Runner) findL2BlockNumberToDispute(ctx context.Context, client *sources.RollupClient, l1HeadNum uint64, l2BlockNum uint64) (uint64, error) { - // Try to find a L1 block prior to the batch that make l2BlockNum safe - // Limits how far back we search to 10 * 32 blocks - const skipSize = uint64(32) - for i := 0; i < 10; i++ { - if l1HeadNum < skipSize { - // Too close to genesis, give up and just use the original block - r.log.Info("Failed to find prior batch.") - return l2BlockNum, nil - } - l1HeadNum -= skipSize - prevSafeHead, err := client.SafeHeadAtL1Block(ctx, l1HeadNum) - if err != nil { - return 0, fmt.Errorf("failed to get prior safe head at L1 block %v: %w", l1HeadNum, err) - } - if prevSafeHead.SafeHead.Number < l2BlockNum { - switch rand.Intn(3) { - case 0: // First block of span batch - return prevSafeHead.SafeHead.Number + 1, nil - case 1: // Last block of span batch - return prevSafeHead.SafeHead.Number, nil - case 2: // Random block, probably but not guaranteed to be in the middle of a span batch - firstBlockInSpanBatch := prevSafeHead.SafeHead.Number + 1 - if l2BlockNum <= firstBlockInSpanBatch { - // There is only one block in the next batch so we just have to use it - return l2BlockNum, nil - } - offset := rand.Intn(int(l2BlockNum - firstBlockInSpanBatch)) - return firstBlockInSpanBatch + uint64(offset), nil - } - - } - if prevSafeHead.SafeHead.Number < l2BlockNum { - // We walked back far enough to be before the batch that included l2BlockNum - // So use the first block after the prior safe head as the disputed block. - // It must be the first block in a batch. - return prevSafeHead.SafeHead.Number + 1, nil - } - } - r.log.Warn("Failed to find prior batch", "l2BlockNum", l2BlockNum, "earliestCheckL1Block", l1HeadNum) - return l2BlockNum, nil -} - func (r *Runner) Stop(ctx context.Context) error { r.log.Info("Stopping") if !r.running.CompareAndSwap(true, false) { diff --git a/op-program/client/interop/interop.go b/op-program/client/interop/interop.go index ac977793806..5174ab04fdc 100644 --- a/op-program/client/interop/interop.go +++ b/op-program/client/interop/interop.go @@ -82,18 +82,21 @@ func stateTransition(logger log.Logger, bootInfo *boot.BootInfoInterop, l1Preima if err != nil { return common.Hash{}, err } + logger.Info("Loaded agreed state", "step", transitionState.Step) // Strictly, the state transition ends when superRoot.Timestamp == bootInfo.GameTimestamp. // Since the valid state transition ends at the game timestamp, there isn't any valid hash resulting from // an agreed prestate and so the program panics to make it clear that the setup is invalid. // The honest actor will never agree to a prestate where superRoot.Timestamp > bootInfo.GameTimestamp and so will // be unaffected by this if superRoot.Timestamp == bootInfo.GameTimestamp { + logger.Info("Already reached game timestamp. No derivation required.") return bootInfo.AgreedPrestate, nil } else if superRoot.Timestamp > bootInfo.GameTimestamp { panic(fmt.Errorf("agreed prestate timestamp %v is after the game timestamp %v", superRoot.Timestamp, bootInfo.GameTimestamp)) } expectedPendingProgress := transitionState.PendingProgress if transitionState.Step < uint64(len(superRoot.Chains)) { + logger.Info("Deriving optimistic block") block, err := deriveOptimisticBlock(logger, bootInfo, l1PreimageOracle, l2PreimageOracle, superRoot, transitionState, tasks) if errors.Is(err, ErrL1HeadReached) { return InvalidTransitionHash, nil @@ -102,6 +105,7 @@ func stateTransition(logger log.Logger, bootInfo *boot.BootInfoInterop, l1Preima } expectedPendingProgress = append(expectedPendingProgress, block) } else if transitionState.Step == ConsolidateStep { + logger.Info("Running consolidate step") // sanity check if len(transitionState.PendingProgress) >= ConsolidateStep { return common.Hash{}, fmt.Errorf("pending progress length does not match the expected step") From dc0842ef7f50207447088649d338935db497c407 Mon Sep 17 00:00:00 2001 From: Matthew Slipper Date: Sun, 9 Mar 2025 20:38:56 -0600 Subject: [PATCH 111/130] op-deployer: Upgrade contracts support to v3.0.0 (#14733) --- go.mod | 2 +- go.sum | 2 + .../deployer/integration_test/apply_test.go | 88 +++++++++++++++++-- op-deployer/pkg/deployer/standard/standard.go | 16 +++- .../pkg/deployer/standard/standard_test.go | 10 +++ 5 files changed, 105 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 3537eecfdf2..bb9756fe3c6 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/crate-crypto/go-kzg-4844 v1.1.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 - github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250228185245-d4bb112dc979 + github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250307201638-7c3999db14c6 github.com/ethereum/go-ethereum v1.15.3 github.com/fatih/color v1.18.0 github.com/fsnotify/fsnotify v1.8.0 diff --git a/go.sum b/go.sum index 4ed40285189..90f2fd3647b 100644 --- a/go.sum +++ b/go.sum @@ -196,6 +196,8 @@ github.com/ethereum-optimism/op-geth v1.101503.0-rc.2 h1:CHIF+1pENaamq7jq8LZOTQ1 github.com/ethereum-optimism/op-geth v1.101503.0-rc.2/go.mod h1:QUo3fn+45vWqJWzJW+rIzRHUV7NmhhHLPdI87mAn1M8= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250228185245-d4bb112dc979 h1:P37l7EFCz5KxE20+yPa3LWiH2Cg+xx6lQ4mOKYCZFPs= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250228185245-d4bb112dc979/go.mod h1:NZ816PzLU1TLv1RdAvYAb6KWOj4Zm5aInT0YpDVml2Y= +github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250307201638-7c3999db14c6 h1:EBEaI3oygl1npFpvdHghcWbKnhj2alEe4KqM2jG2XS8= +github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250307201638-7c3999db14c6/go.mod h1:NZ816PzLU1TLv1RdAvYAb6KWOj4Zm5aInT0YpDVml2Y= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= diff --git a/op-deployer/pkg/deployer/integration_test/apply_test.go b/op-deployer/pkg/deployer/integration_test/apply_test.go index 83884e07bac..efdd4235da5 100644 --- a/op-deployer/pkg/deployer/integration_test/apply_test.go +++ b/op-deployer/pkg/deployer/integration_test/apply_test.go @@ -7,6 +7,7 @@ import ( "fmt" "log/slog" "math/big" + "os" "strings" "testing" @@ -52,6 +53,72 @@ func (d *deployerKey) String() string { return "deployer-key" } +func TestLiveChain(t *testing.T) { + op_e2e.InitParallel(t) + + for _, network := range []string{"mainnet", "sepolia"} { + t.Run(network, func(t *testing.T) { + testLiveChainNetwork(t, network) + }) + } +} + +func testLiveChainNetwork(t *testing.T, network string) { + op_e2e.InitParallel(t) + lgr := testlog.Logger(t, slog.LevelInfo) + rpcURL := os.Getenv(fmt.Sprintf("%s_RPC_URL", strings.ToUpper(network))) + require.NotEmpty(t, rpcURL) + + forkedL1, cleanup, err := devnet.NewForked(lgr, rpcURL) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, cleanup()) + }) + + l1Client, err := ethclient.Dial(forkedL1.RPCUrl()) + require.NoError(t, err) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + l1ChainID, err := l1Client.ChainID(ctx) + require.NoError(t, err) + + pk, err := crypto.HexToECDSA("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80") + require.NoError(t, err) + dk, err := devkeys.NewMnemonicDevKeys(devkeys.TestMnemonic) + require.NoError(t, err) + + testCacheDir := testutils.IsolatedTestDirWithAutoCleanup(t) + + intent, st := newIntent( + t, + l1ChainID, + dk, + uint256.NewInt(9999), + artifacts.DefaultL1ContractsLocator, + artifacts.DefaultL2ContractsLocator, + ) + cg := ethClientCodeGetter(ctx, l1Client) + + require.NoError(t, deployer.ApplyPipeline( + ctx, + deployer.ApplyPipelineOpts{ + DeploymentTarget: deployer.DeploymentTargetLive, + L1RPCUrl: forkedL1.RPCUrl(), + DeployerPrivateKey: pk, + Intent: intent, + State: st, + Logger: lgr, + StateWriter: pipeline.NoopStateWriter(), + CacheDir: testCacheDir, + }, + )) + + validateSuperchainDeployment(t, st, cg, false) + validateOPChainDeployment(t, cg, st, intent, false) +} + func TestEndToEndApply(t *testing.T) { op_e2e.InitParallel(t) @@ -120,7 +187,7 @@ func TestEndToEndApply(t *testing.T) { }, )) - validateSuperchainDeployment(t, st, cg) + validateSuperchainDeployment(t, st, cg, true) validateOPChainDeployment(t, cg, st, intent, false) }) @@ -144,7 +211,7 @@ func TestEndToEndApply(t *testing.T) { }, )) - validateSuperchainDeployment(t, st, cg) + validateSuperchainDeployment(t, st, cg, true) validateOPChainDeployment(t, cg, st, intent, false) }) @@ -228,7 +295,7 @@ func TestApplyGenesisStrategy(t *testing.T) { require.NoError(t, deployer.ApplyPipeline(ctx, opts)) cg := stateDumpCodeGetter(st) - validateSuperchainDeployment(t, st, cg) + validateSuperchainDeployment(t, st, cg, true) for i := range intent.Chains { t.Run(fmt.Sprintf("chain-%d", i), func(t *testing.T) { @@ -650,20 +717,25 @@ func stateDumpCodeGetter(st *state.State) codeGetter { } } -func validateSuperchainDeployment(t *testing.T, st *state.State, cg codeGetter) { - addrs := []struct { +func validateSuperchainDeployment(t *testing.T, st *state.State, cg codeGetter, includeSuperchainImpls bool) { + type addrTuple struct { name string addr common.Address - }{ + } + addrs := []addrTuple{ {"SuperchainProxyAdmin", st.SuperchainDeployment.ProxyAdminAddress}, {"SuperchainConfigProxy", st.SuperchainDeployment.SuperchainConfigProxyAddress}, - {"SuperchainConfigImpl", st.SuperchainDeployment.SuperchainConfigImplAddress}, {"ProtocolVersionsProxy", st.SuperchainDeployment.ProtocolVersionsProxyAddress}, - {"ProtocolVersionsImpl", st.SuperchainDeployment.ProtocolVersionsImplAddress}, {"Opcm", st.ImplementationsDeployment.OpcmAddress}, {"PreimageOracleSingleton", st.ImplementationsDeployment.PreimageOracleSingletonAddress}, {"MipsSingleton", st.ImplementationsDeployment.MipsSingletonAddress}, } + + if includeSuperchainImpls { + addrs = append(addrs, addrTuple{"SuperchainConfigImpl", st.SuperchainDeployment.SuperchainConfigImplAddress}) + addrs = append(addrs, addrTuple{"ProtocolVersionsImpl", st.SuperchainDeployment.ProtocolVersionsImplAddress}) + } + for _, addr := range addrs { t.Run(addr.name, func(t *testing.T) { code := cg(t, addr.addr) diff --git a/op-deployer/pkg/deployer/standard/standard.go b/op-deployer/pkg/deployer/standard/standard.go index 1f43b33fe11..e9ba7d8d455 100644 --- a/op-deployer/pkg/deployer/standard/standard.go +++ b/op-deployer/pkg/deployer/standard/standard.go @@ -39,13 +39,14 @@ const ( ContractsV180Tag = "op-contracts/v1.8.0-rc.4" ContractsV170Beta1L2Tag = "op-contracts/v1.7.0-beta.1+l2-contracts" ContractsV200Tag = "op-contracts/v2.0.0-rc.1" + ContractsV300Tag = "op-contracts/v3.0.0-rc.1" ) var DisputeAbsolutePrestate = common.HexToHash("0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c") -var DefaultL1ContractsTag = ContractsV200Tag +var DefaultL1ContractsTag = ContractsV300Tag -var DefaultL2ContractsTag = ContractsV170Beta1L2Tag +var DefaultL2ContractsTag = ContractsV300Tag type TaggedRelease struct { ArtifactsHash common.Hash @@ -73,16 +74,20 @@ var taggedReleases = map[string]TaggedRelease{ ArtifactsHash: common.HexToHash("32e11c96e07b83619f419595facb273368dccfe2439287549e7b436c9b522204"), ContentHash: common.HexToHash("1cec51ed629c0394b8fb17ff2c6fa45c406c30f94ebbd37d4c90ede6c29ad608"), }, + ContractsV300Tag: { + ArtifactsHash: common.HexToHash("497e55cc6d9dfb74615d1bac0f4d05ae5ef995972689adcc036ee3adf4522677"), + ContentHash: common.HexToHash("e2527ea1ddcd47bc60310baea4c3183de1520880561b6c7837213290b925a2e8"), + }, } var _ embed.FS func IsSupportedL1Version(tag string) bool { - return tag == ContractsV200Tag + return tag == ContractsV300Tag } func IsSupportedL2Version(tag string) bool { - return tag == ContractsV170Beta1L2Tag + return tag == ContractsV300Tag } func L1VersionsFor(chainID uint64) (validation.Versions, error) { @@ -211,8 +216,11 @@ func DefaultHardforkScheduleForTag(tag string) *genesis.UpgradeScheduleDeployCon switch tag { case ContractsV160Tag, ContractsV170Beta1L2Tag: return sched + case ContractsV180Tag, ContractsV200Tag: + sched.ActivateForkAtGenesis(rollup.Holocene) default: sched.ActivateForkAtGenesis(rollup.Holocene) + sched.ActivateForkAtGenesis(rollup.Isthmus) } return sched diff --git a/op-deployer/pkg/deployer/standard/standard_test.go b/op-deployer/pkg/deployer/standard/standard_test.go index a35d77e7f11..1bdf7378899 100644 --- a/op-deployer/pkg/deployer/standard/standard_test.go +++ b/op-deployer/pkg/deployer/standard/standard_test.go @@ -14,9 +14,19 @@ import ( func TestDefaultHardforkScheduleForTag(t *testing.T) { sched := DefaultHardforkScheduleForTag(ContractsV160Tag) require.Nil(t, sched.HoloceneTime(0)) + require.Nil(t, sched.IsthmusTime(0)) sched = DefaultHardforkScheduleForTag(ContractsV180Tag) require.NotNil(t, sched.HoloceneTime(0)) + require.Nil(t, sched.IsthmusTime(0)) + + sched = DefaultHardforkScheduleForTag(ContractsV200Tag) + require.NotNil(t, sched.HoloceneTime(0)) + require.Nil(t, sched.IsthmusTime(0)) + + sched = DefaultHardforkScheduleForTag(ContractsV300Tag) + require.NotNil(t, sched.HoloceneTime(0)) + require.NotNil(t, sched.IsthmusTime(0)) } func TestStandardAddresses(t *testing.T) { From 1ef70a7444c9a3e83e5d94d0a8b8df556e100bd8 Mon Sep 17 00:00:00 2001 From: Sam Stokes <35908605+bitwiseguy@users.noreply.github.com> Date: Sun, 9 Mar 2025 22:47:27 -0400 Subject: [PATCH 112/130] op-deployer: add documentation for verify command (#14745) * op-deployer: add documentation for verify command * Update op-deployer/book/src/user-guide/verify.md * Update verify.md --------- Co-authored-by: Matthew Slipper --- op-deployer/book/src/SUMMARY.md | 3 +- op-deployer/book/src/user-guide/usage.md | 7 +- op-deployer/book/src/user-guide/verify.md | 82 +++++++++++++++++++++++ 3 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 op-deployer/book/src/user-guide/verify.md diff --git a/op-deployer/book/src/SUMMARY.md b/op-deployer/book/src/SUMMARY.md index 862b2bba9cd..570d59ea815 100644 --- a/op-deployer/book/src/SUMMARY.md +++ b/op-deployer/book/src/SUMMARY.md @@ -6,9 +6,10 @@ - [Installation](user-guide/installation.md) - [Usage](user-guide/usage.md) + - [bootstrap](user-guide/bootstrap.md) - [init](user-guide/init.md) - [apply](user-guide/apply.md) - - [bootstrap](user-guide/bootstrap.md) + - [verify](user-guide/verify.md) - [Known Limitations](user-guide/known-limitations.md) # Reference Guide diff --git a/op-deployer/book/src/user-guide/usage.md b/op-deployer/book/src/user-guide/usage.md index 03a5b7073e1..1eac72d4319 100644 --- a/op-deployer/book/src/user-guide/usage.md +++ b/op-deployer/book/src/user-guide/usage.md @@ -7,9 +7,12 @@ Deployer, you can use `op-deployer help` to view the available commands. This following sections provide in-depth information on the different commands available. +- [`op-deployer bootstrap`][bootstrap]: Deploys shared contract instances for use with future invocations of OP Deployer. - [`op-deployer init`][init]: Initializes a new intent and state file. - [`op-deployer apply`][apply]: Deploys a new OP Chain based on the supplied intent. -- `op-deployer bootstrap`: Deploys shared contract instances for use with future invocations of OP Deployer. +- [`op-deployer verify`][verify]: Verifies the source code of deployed contracts on Etherscan. +[bootstrap]: bootstrap.md [init]: init.md -[apply]: apply.md \ No newline at end of file +[apply]: apply.md +[verify]: verify.md diff --git a/op-deployer/book/src/user-guide/verify.md b/op-deployer/book/src/user-guide/verify.md new file mode 100644 index 00000000000..18cbb6be91d --- /dev/null +++ b/op-deployer/book/src/user-guide/verify.md @@ -0,0 +1,82 @@ +# The Verify Command + +Once you have deployed contracts via [bootstrap][bootstrap], you can use the `verify` command to verify the source code on Etherscan. Constructor args used in the verification request are extracted automatically from contract initcode via the tx that created the contract. + +[bootstrap]: bootstrap.md + +You can call the `verify` command like this: + +```shell +op-deployer verify \ + --l1-rpc-url \ + --input-file \ + --etherscan-api-key \ + --artifacts-locator +``` + +## CLI Args + +### `--l1-rpc-url` + +Defines the RPC URL of the L1 chain to deploy to (currently only supports mainnet and sepolia). + +### `--input-file` + +The full filepath to the input .json file. This file should be a key/value store where the key is a contract name and the value is the contract address. The output of the `bootstrap superchain|implementations` commands is a good example of this format, and those output files can be fed directly into `verify`. Unless the `--contract-name` flag is passed, all contracts in the input file will be verified. + +{ + "Opcm": "0x3a1f523a4bc09cd344a2745a108bb0398288094f", + "OpcmContractsContainer": "0x660aeaac7508258f622cfdc489c16c864b4d8629", + "OpcmGameTypeAdder": "0xc9060f6283b78e1feebfd1993cb6350b5626f115", + "OpcmDeployer": "0x88e39ea5cfe6c4d450305eec5fd90dd1fba87f45", + "OpcmUpgrader": "0xbf098a12edcf99f8e6db258b7ac567a1fd020f4b", + "DelayedWETHImpl": "0x5e40b9231b86984b5150507046e354dbfbed3d9e", + "OptimismPortalImpl": "0xb443da3e07052204a02d630a8933dac05a0d6fb4", + "PreimageOracleSingleton": "0x1fb8cdfc6831fc866ed9c51af8817da5c287add3", + "MipsSingleton": "0xf027f4a985560fb13324e943edf55ad6f1d15dc1", + "SystemConfigImpl": "0x340f923e5c7cbb2171146f64169ec9d5a9ffe647", + "L1CrossDomainMessengerImpl": "0x5d5a095665886119693f0b41d8dfee78da033e8b", + "L1ERC721BridgeImpl": "0x7ae1d3bd877a4c5ca257404ce26be93a02c98013", + "L1StandardBridgeImpl": "0x0b09ba359a106c9ea3b181cbc5f394570c7d2a7a", + "OptimismMintableERC20FactoryImpl": "0x5493f4677a186f64805fe7317d6993ba4863988f", + "DisputeGameFactoryImpl": "0x4bba758f006ef09402ef31724203f316ab74e4a0", + "AnchorStateRegistryImpl": "0x7b465370bb7a333f99edd19599eb7fb1c2d3f8d2", + "SuperchainConfigImpl": "0x4da82a327773965b8d4d85fa3db8249b387458e7", + "ProtocolVersionsImpl": "0x37e15e4d6dffa9e5e320ee1ec036922e563cb76c" +} + +### `--contract-name` (optional) + +Specifies a single contract name, matching a contract key within the input file, to verify. If not provided, all contracts in the input file will be verified. + +### `--artifacts-locator` + +The locator to forge-artifacts containing the output of the `forge build` command (i.e. compiled bytecode and solidity source code). This can be a local path (with a `file://` prefix), remote URL (with a `http://` or `https://` prefix), or standard contracts tag (with a `tag://op-contracts/v` prefix). + +## Output + +Output logs will be printed to the console and look something like the following. If the final results show `numFailed=0`, all contracts were verified successfully. +```sh +INFO [03-05|15:56:55.900] Formatting etherscan verify request name=superchainConfigProxyAddress address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A +INFO [03-05|15:56:55.900] Opening artifact path=Proxy.sol/Proxy.json name=superchainConfigProxyAddress +INFO [03-05|15:56:55.905] contractName name=src/universal/Proxy.sol:Proxy +INFO [03-05|15:56:55.905] Extracting constructor args from initcode address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A argSlots=1 +INFO [03-05|15:56:56.087] Contract creation tx hash txHash=0x71b377ccc11304afc32e1016c4828a34010a0d3d81701c7164fb19525ba4fbc4 +INFO [03-05|15:56:56.494] Successfully extracted constructor args address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A +INFO [03-05|15:56:56.683] Verification request submitted name=superchainConfigProxyAddress address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A +INFO [03-05|15:57:02.035] Verification complete name=superchainConfigProxyAddress address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A +INFO [03-05|15:57:02.208] Formatting etherscan verify request name=protocolVersionsImplAddress address=0x658812BEb9bF6286D03fBF1B5B936e1af490b768 +INFO [03-05|15:57:02.208] Opening artifact path=ProtocolVersions.sol/ProtocolVersions.json name=protocolVersionsImplAddress +INFO [03-05|15:57:02.215] contractName name=src/L1/ProtocolVersions.sol:ProtocolVersions +INFO [03-05|15:57:02.418] Verification request submitted name=protocolVersionsImplAddress address=0x658812BEb9bF6286D03fBF1B5B936e1af490b768 +INFO [03-05|15:57:07.789] Verification complete name=protocolVersionsImplAddress address=0x658812BEb9bF6286D03fBF1B5B936e1af490b768 +INFO [03-05|15:57:07.971] Contract is already verified name=protocolVersionsProxyAddress address=0x17C64430Fa08475D41801Dfe36bAFeE9667c6fA7 +INFO [03-05|15:57:07.971] --- COMPLETE --- +INFO [03-05|15:57:07.971] final results numVerified=4 numSkipped=1 numFailed=0 +``` + +## Known Limitations + +- Does not currently work for contracts in the `opchain` bundle (deployed via `op-deployer apply`) that have constructor args. Those constructors args cannot be extracted from the deployment `tx.Data()` since `OPContractsManager.deploy()` uses factory pattern with CREATE2 to deploy those contracts. + +- Currently only supports etherscan block explorers. Blockscout support is planned but not yet implemented. From 9c902da66f0d6d38b44ffa7ed1782311a4c19284 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Mon, 10 Mar 2025 14:23:53 +1000 Subject: [PATCH 113/130] ci: Fix preimage-reproducibility (#14742) * ci: Capture reproducibility build logs as artifacts * Install dependencies when mise.toml is present. --- .circleci/config.yml | 3 +++ op-program/scripts/build-prestates.sh | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3f21cf2fe24..6cdee5431b4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1220,6 +1220,9 @@ jobs: - utils/checkout-with-mise - setup_remote_docker - run: make -C op-program verify-reproducibility + - store_artifacts: + path: ./op-program/temp/logs + when: always - notify-failures-on-develop: mentions: "@proofs-team" diff --git a/op-program/scripts/build-prestates.sh b/op-program/scripts/build-prestates.sh index a36b598c142..c2bc41a85ef 100755 --- a/op-program/scripts/build-prestates.sh +++ b/op-program/scripts/build-prestates.sh @@ -34,6 +34,11 @@ do LOG_FILE="${LOGS_DIR}/build-${SHORT_VERSION}.txt" echo "Building Version: ${VERSION} Logs: ${LOG_FILE}" git checkout "${VERSION}" > "${LOG_FILE}" 2>&1 + if [ -f mise.toml ] + then + echo "Install dependencies with mise" >> "${LOG_FILE}" + mise install -v -y >> "${LOG_FILE}" 2>&1 + fi rm -rf "${BIN_DIR}" make reproducible-prestate >> "${LOG_FILE}" 2>&1 HASH=$(cat "${BIN_DIR}/prestate-proof.json" | jq -r .pre) From cd2b0c13ca993d95422c447fce0e14227b50a5cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 15:12:09 +1000 Subject: [PATCH 114/130] dependabot(gomod): bump github.com/prometheus/client_golang (#14628) Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.20.5 to 1.21.1. - [Release notes](https://github.com/prometheus/client_golang/releases) - [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/client_golang/compare/v1.20.5...v1.21.1) --- updated-dependencies: - dependency-name: github.com/prometheus/client_golang dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index bb9756fe3c6..45eec878564 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 github.com/pkg/errors v0.9.1 github.com/pkg/profile v1.7.0 - github.com/prometheus/client_golang v1.20.5 + github.com/prometheus/client_golang v1.21.1 github.com/protolambda/ctxlock v0.1.0 github.com/schollz/progressbar/v3 v3.18.0 github.com/stretchr/testify v1.10.0 @@ -209,7 +209,7 @@ require ( github.com/pion/webrtc/v3 v3.3.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/quic-go v0.46.0 // indirect @@ -249,7 +249,7 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect google.golang.org/grpc v1.57.1 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.36.1 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect lukechampine.com/blake3 v1.3.0 // indirect diff --git a/go.sum b/go.sum index 90f2fd3647b..45280a5b916 100644 --- a/go.sum +++ b/go.sum @@ -703,8 +703,8 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= +github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -716,8 +716,8 @@ github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -1115,8 +1115,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From ef780b289d5f6d081d8f8107126ba9ad812d0113 Mon Sep 17 00:00:00 2001 From: Park Changwan Date: Mon, 10 Mar 2025 18:41:06 +0900 Subject: [PATCH 115/130] kt-devnet: remove network params for supervisor (#14746) --- kurtosis-devnet/interop.yaml | 1 - kurtosis-devnet/templates/devnet.yaml | 1 - 2 files changed, 2 deletions(-) diff --git a/kurtosis-devnet/interop.yaml b/kurtosis-devnet/interop.yaml index 97899b40841..0a0b4d08b59 100644 --- a/kurtosis-devnet/interop.yaml +++ b/kurtosis-devnet/interop.yaml @@ -37,7 +37,6 @@ optimism_package: } extra_params: - {{ $flags.log_level }} - network: "kurtosis" chains: - participants: - el_type: op-geth diff --git a/kurtosis-devnet/templates/devnet.yaml b/kurtosis-devnet/templates/devnet.yaml index d38eb437932..233e90c4f91 100644 --- a/kurtosis-devnet/templates/devnet.yaml +++ b/kurtosis-devnet/templates/devnet.yaml @@ -15,7 +15,6 @@ optimism_package: image: {{ dig "overrides" "images" "op_supervisor" (localDockerImage "op-supervisor") $context }} extra_params: - {{ dig "overrides" "flags" "log_level" "!!str" $context }} - network: "kurtosis" {{ end }} chains: {{ range $l2_id, $l2 := $l2s }} From 64ce18aa23b82d3e2315cf4a6a06fe2b74c89739 Mon Sep 17 00:00:00 2001 From: Park Changwan Date: Mon, 10 Mar 2025 21:22:09 +0900 Subject: [PATCH 116/130] op-service: Expand EthClient for tx handling (#14747) --- op-e2e/actions/helpers/l2_proposer.go | 22 +------- op-service/sources/eth_client.go | 76 +++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 21 deletions(-) diff --git a/op-e2e/actions/helpers/l2_proposer.go b/op-e2e/actions/helpers/l2_proposer.go index dbed03a02c5..d657db583d6 100644 --- a/op-e2e/actions/helpers/l2_proposer.go +++ b/op-e2e/actions/helpers/l2_proposer.go @@ -198,33 +198,13 @@ func (p *L2Proposer) sendTx(t Testing, data []byte) { // will be created so pending must be used. func estimateGasPending(ctx context.Context, ec *ethclient.Client, msg ethereum.CallMsg) (uint64, error) { var hex hexutil.Uint64 - err := ec.Client().CallContext(ctx, &hex, "eth_estimateGas", toCallArg(msg), "pending") + err := ec.Client().CallContext(ctx, &hex, "eth_estimateGas", sources.ToCallArg(msg), "pending") if err != nil { return 0, err } return uint64(hex), nil } -func toCallArg(msg ethereum.CallMsg) interface{} { - arg := map[string]interface{}{ - "from": msg.From, - "to": msg.To, - } - if len(msg.Data) > 0 { - arg["data"] = hexutil.Bytes(msg.Data) - } - if msg.Value != nil { - arg["value"] = (*hexutil.Big)(msg.Value) - } - if msg.Gas != 0 { - arg["gas"] = hexutil.Uint64(msg.Gas) - } - if msg.GasPrice != nil { - arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice) - } - return arg -} - func (p *L2Proposer) fetchNextOutput(t Testing) (source.Proposal, bool, error) { if p.allocType.UsesProofs() { output, shouldPropose, err := p.driver.FetchDGFOutput(t.Ctx()) diff --git a/op-service/sources/eth_client.go b/op-service/sources/eth_client.go index dfde6e4a5e7..0008b05355d 100644 --- a/op-service/sources/eth_client.go +++ b/op-service/sources/eth_client.go @@ -324,6 +324,16 @@ func (s *EthClient) FetchReceipts(ctx context.Context, blockHash common.Hash) (e return info, receipts, nil } +// FetchReceipt returns a receipt associated with transaction. +func (s *EthClient) FetchReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { + var r *types.Receipt + err := s.client.CallContext(ctx, &r, "eth_getTransactionReceipt", txHash) + if err == nil && r == nil { + return nil, ethereum.NotFound + } + return r, err +} + // PayloadExecutionWitness generates a block from a payload and returns execution witness data. func (s *EthClient) PayloadExecutionWitness(ctx context.Context, parentHash common.Hash, payloadAttributes eth.PayloadAttributes) (*eth.ExecutionWitness, error) { var witness *eth.ExecutionWitness @@ -442,3 +452,69 @@ func (s *EthClient) BlockRefByHash(ctx context.Context, hash common.Hash) (eth.B s.blockRefsCache.Add(ref.Hash, ref) return ref, nil } + +func ToCallArg(msg ethereum.CallMsg) interface{} { + arg := map[string]interface{}{ + "from": msg.From, + "to": msg.To, + } + if len(msg.Data) > 0 { + arg["data"] = hexutil.Bytes(msg.Data) + } + if msg.Value != nil { + arg["value"] = (*hexutil.Big)(msg.Value) + } + if msg.Gas != 0 { + arg["gas"] = hexutil.Uint64(msg.Gas) + } + if msg.GasPrice != nil { + arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice) + } + return arg +} + +// SuggestGasPrice retrieves the currently suggested gas price to allow a timely +// execution of a transaction. +func (s *EthClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + var hex hexutil.Big + if err := s.client.CallContext(ctx, &hex, "eth_gasPrice"); err != nil { + return nil, err + } + return (*big.Int)(&hex), nil +} + +// Call executes a message call transaction but never mined into the blockchain. +func (s *EthClient) Call(ctx context.Context, msg ethereum.CallMsg) ([]byte, error) { + var hex hexutil.Bytes + err := s.client.CallContext(ctx, &hex, "eth_call", ToCallArg(msg), "pending") + if err != nil { + return nil, err + } + return hex, nil +} + +// EstimateGas tries to estimate the gas needed to execute a specific transaction. +func (s *EthClient) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) { + var hex hexutil.Uint64 + err := s.client.CallContext(ctx, &hex, "eth_estimateGas", ToCallArg(msg)) + if err != nil { + return 0, err + } + return uint64(hex), nil +} + +// SendTransaction submits a signed transaction. +func (s *EthClient) SendTransaction(ctx context.Context, tx *types.Transaction) error { + data, err := tx.MarshalBinary() + if err != nil { + return err + } + return s.client.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data)) +} + +// PendingNonceAt returns the account nonce of the given account in the pending state. +func (s *EthClient) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + var result hexutil.Uint64 + err := s.client.CallContext(ctx, &result, "eth_getTransactionCount", account, "pending") + return uint64(result), err +} From b0b5309b5aedb8c3532ea63dacb167ebf8ef4556 Mon Sep 17 00:00:00 2001 From: protolambda Date: Mon, 10 Mar 2025 13:57:44 +0100 Subject: [PATCH 117/130] op-supervisor,op-service: fix server-client API consistency (#14720) * op-supervisor,op-service: fix server-client API consistency * op-e2e: fix finality test, finalized-L1 getter now errors when not initialized --- op-e2e/interop/interop_test.go | 7 + op-service/sources/supervisor_client.go | 148 ++++++------------ op-supervisor/supervisor/backend/backend.go | 18 ++- op-supervisor/supervisor/backend/mock.go | 12 +- op-supervisor/supervisor/frontend/frontend.go | 54 +++---- 5 files changed, 95 insertions(+), 144 deletions(-) diff --git a/op-e2e/interop/interop_test.go b/op-e2e/interop/interop_test.go index 6ae812f1609..b4a5ba08c94 100644 --- a/op-e2e/interop/interop_test.go +++ b/op-e2e/interop/interop_test.go @@ -3,6 +3,7 @@ package interop import ( "context" "math/big" + "strings" "sync" "testing" "time" @@ -120,6 +121,9 @@ func TestInterop_SupervisorFinality(t *testing.T) { supervisor := s2.SupervisorClient() require.Eventually(t, func() bool { final, err := supervisor.FinalizedL1(context.Background()) + if err != nil && strings.Contains(err.Error(), "not initialized") { + return false + } require.NoError(t, err) return final.Number > 0 // this test takes about 30 seconds, with a longer Eventually timeout for CI @@ -370,6 +374,9 @@ func TestMultiNode(t *testing.T) { supervisor := s2.SupervisorClient() require.Eventually(t, func() bool { final, err := supervisor.FinalizedL1(context.Background()) + if err != nil && strings.Contains(err.Error(), "not initialized") { + return false + } require.NoError(t, err) return final.Number > 0 // this test takes about 30 seconds, with a longer Eventually timeout for CI diff --git a/op-service/sources/supervisor_client.go b/op-service/sources/supervisor_client.go index a656d9fe85a..78d60814a8a 100644 --- a/op-service/sources/supervisor_client.go +++ b/op-service/sources/supervisor_client.go @@ -13,10 +13,34 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ) +type SupervisorAdminAPI interface { + Start(ctx context.Context) error + Stop(ctx context.Context) error + AddL2RPC(ctx context.Context, rpc string, jwtSecret eth.Bytes32) error +} + +type SupervisorQueryAPI interface { + CheckMessage(ctx context.Context, identifier types.Identifier, payloadHash common.Hash, executingDescriptor types.ExecutingDescriptor) (types.SafetyLevel, error) + CheckMessages(ctx context.Context, messages []types.Message, minSafety types.SafetyLevel) error + CheckMessagesV2(ctx context.Context, messages []types.Message, minSafety types.SafetyLevel, executingDescriptor types.ExecutingDescriptor) error + CrossDerivedToSource(ctx context.Context, chainID eth.ChainID, derived eth.BlockID) (derivedFrom eth.BlockRef, err error) + LocalUnsafe(ctx context.Context, chainID eth.ChainID) (eth.BlockID, error) + CrossSafe(ctx context.Context, chainID eth.ChainID) (types.DerivedIDPair, error) + Finalized(ctx context.Context, chainID eth.ChainID) (eth.BlockID, error) + FinalizedL1(ctx context.Context) (eth.BlockRef, error) + SuperRootAtTimestamp(ctx context.Context, timestamp hexutil.Uint64) (eth.SuperRootResponse, error) + SyncStatus(ctx context.Context) (eth.SupervisorSyncStatus, error) + AllSafeDerivedAt(ctx context.Context, derivedFrom eth.BlockID) (derived map[eth.ChainID]eth.BlockID, err error) +} + type SupervisorClient struct { client client.RPC } +// This type-check keeps the Server API and Client API in sync. +var _ SupervisorQueryAPI = (*SupervisorClient)(nil) +var _ SupervisorAdminAPI = (*SupervisorClient)(nil) + func NewSupervisorClient(client client.RPC) *SupervisorClient { return &SupervisorClient{ client: client, @@ -25,10 +49,7 @@ func NewSupervisorClient(client client.RPC) *SupervisorClient { func (cl *SupervisorClient) Stop(ctx context.Context) error { var result error - err := cl.client.CallContext( - ctx, - &result, - "admin_stop") + err := cl.client.CallContext(ctx, &result, "admin_stop") if err != nil { return fmt.Errorf("failed to stop Supervisor: %w", err) } @@ -37,10 +58,7 @@ func (cl *SupervisorClient) Stop(ctx context.Context) error { func (cl *SupervisorClient) Start(ctx context.Context) error { var result error - err := cl.client.CallContext( - ctx, - &result, - "admin_start") + err := cl.client.CallContext(ctx, &result, "admin_start") if err != nil { return fmt.Errorf("failed to start Supervisor: %w", err) } @@ -49,26 +67,18 @@ func (cl *SupervisorClient) Start(ctx context.Context) error { func (cl *SupervisorClient) AddL2RPC(ctx context.Context, rpc string, auth eth.Bytes32) error { var result error - err := cl.client.CallContext( - ctx, - &result, - "admin_addL2RPC", - rpc, auth) + err := cl.client.CallContext(ctx, &result, "admin_addL2RPC", rpc, auth) if err != nil { return fmt.Errorf("failed to Add L2 to Supervisor (rpc: %s): %w", rpc, err) } return result } -func (cl *SupervisorClient) CheckMessage(ctx context.Context, identifier types.Identifier, logHash common.Hash, executingDescriptor types.ExecutingDescriptor) (types.SafetyLevel, error) { +func (cl *SupervisorClient) CheckMessage(ctx context.Context, identifier types.Identifier, logHash common.Hash, + executingDescriptor types.ExecutingDescriptor) (types.SafetyLevel, error) { + var result types.SafetyLevel - err := cl.client.CallContext( - ctx, - &result, - "supervisor_checkMessage", - identifier, - logHash, - executingDescriptor) + err := cl.client.CallContext(ctx, &result, "supervisor_checkMessage", identifier, logHash, executingDescriptor) if err != nil { return types.Invalid, fmt.Errorf("failed to check message (chain %s), (block %v), (index %v), (logHash %s), (executingTimestamp %v): %w", identifier.ChainID, @@ -81,112 +91,63 @@ func (cl *SupervisorClient) CheckMessage(ctx context.Context, identifier types.I return result, nil } -func (cl *SupervisorClient) UnsafeView(ctx context.Context, chainID eth.ChainID, unsafe types.ReferenceView) (types.ReferenceView, error) { - var result types.ReferenceView - err := cl.client.CallContext( - ctx, - &result, - "supervisor_unsafeView", - chainID, - unsafe) - if err != nil { - return types.ReferenceView{}, fmt.Errorf("failed to share unsafe block view %s (chain %s): %w", unsafe, chainID, err) - } - return result, nil +func (cl *SupervisorClient) CheckMessages(ctx context.Context, messages []types.Message, minSafety types.SafetyLevel) error { + return cl.client.CallContext(ctx, nil, "supervisor_checkMessages", messages, minSafety) } -func (cl *SupervisorClient) SafeView(ctx context.Context, chainID eth.ChainID, safe types.ReferenceView) (types.ReferenceView, error) { - var result types.ReferenceView - err := cl.client.CallContext( - ctx, - &result, - "supervisor_safeView", - chainID, - safe) - if err != nil { - return types.ReferenceView{}, fmt.Errorf("failed to share safe block view %s (chain %s): %w", safe, chainID, err) - } - return result, nil +func (cl *SupervisorClient) CheckMessagesV2(ctx context.Context, messages []types.Message, minSafety types.SafetyLevel, executingDescriptor types.ExecutingDescriptor) error { + return cl.client.CallContext(ctx, nil, "supervisor_checkMessagesV2", messages, minSafety, executingDescriptor) +} + +func (cl *SupervisorClient) CrossDerivedToSource(ctx context.Context, chainID eth.ChainID, derived eth.BlockID) (derivedFrom eth.BlockRef, err error) { + var result eth.BlockRef + err = cl.client.CallContext(ctx, &result, "supervisor_crossDerivedToSource", chainID, derived) + return result, err } func (cl *SupervisorClient) LocalUnsafe(ctx context.Context, chainID eth.ChainID) (eth.BlockID, error) { var result eth.BlockID - err := cl.client.CallContext( - ctx, - &result, - "supervisor_localUnsafe", - chainID) + err := cl.client.CallContext(ctx, &result, "supervisor_localUnsafe", chainID) return result, err } func (cl *SupervisorClient) CrossSafe(ctx context.Context, chainID eth.ChainID) (types.DerivedIDPair, error) { var result types.DerivedIDPair - err := cl.client.CallContext( - ctx, - &result, - "supervisor_crossSafe", - chainID) + err := cl.client.CallContext(ctx, &result, "supervisor_crossSafe", chainID) return result, err } func (cl *SupervisorClient) Finalized(ctx context.Context, chainID eth.ChainID) (eth.BlockID, error) { var result eth.BlockID - err := cl.client.CallContext( - ctx, - &result, - "supervisor_finalized", - chainID) + err := cl.client.CallContext(ctx, &result, "supervisor_finalized", chainID) return result, err } func (cl *SupervisorClient) FinalizedL1(ctx context.Context) (eth.BlockRef, error) { var result eth.BlockRef - err := cl.client.CallContext( - ctx, - &result, - "supervisor_finalizedL1") + err := cl.client.CallContext(ctx, &result, "supervisor_finalizedL1") return result, err } func (cl *SupervisorClient) CrossDerivedFrom(ctx context.Context, chainID eth.ChainID, derived eth.BlockID) (eth.BlockRef, error) { var result eth.BlockRef - err := cl.client.CallContext( - ctx, - &result, - "supervisor_crossDerivedFrom", - chainID, - derived) + err := cl.client.CallContext(ctx, &result, "supervisor_crossDerivedFrom", chainID, derived) return result, err } func (cl *SupervisorClient) UpdateLocalUnsafe(ctx context.Context, chainID eth.ChainID, head eth.BlockRef) error { - return cl.client.CallContext( - ctx, - nil, - "supervisor_updateLocalUnsafe", - chainID, - head) + return cl.client.CallContext(ctx, nil, "supervisor_updateLocalUnsafe", chainID, head) } func (cl *SupervisorClient) UpdateLocalSafe(ctx context.Context, chainID eth.ChainID, derivedFrom eth.L1BlockRef, lastDerived eth.BlockRef) error { - return cl.client.CallContext( - ctx, - nil, - "supervisor_updateLocalSafe", - chainID, - derivedFrom, - lastDerived) + return cl.client.CallContext(ctx, nil, "supervisor_updateLocalSafe", chainID, derivedFrom, lastDerived) } // SuperRootAtTimestamp returns the super root at the specified timestamp. // Returns ethereum.NotFound if one of the chain's has not yet reached the block required for the requested super root. func (cl *SupervisorClient) SuperRootAtTimestamp(ctx context.Context, timestamp hexutil.Uint64) (eth.SuperRootResponse, error) { var result eth.SuperRootResponse - err := cl.client.CallContext( - ctx, - &result, - "supervisor_superRootAtTimestamp", - timestamp) + err := cl.client.CallContext(ctx, &result, "supervisor_superRootAtTimestamp", timestamp) if isNotFound(err) { // Downstream users expect to get a properly typed error message for not found. return result, fmt.Errorf("%w: %v", ethereum.NotFound, err.Error()) @@ -196,20 +157,13 @@ func (cl *SupervisorClient) SuperRootAtTimestamp(ctx context.Context, timestamp func (cl *SupervisorClient) AllSafeDerivedAt(ctx context.Context, derivedFrom eth.BlockID) (map[eth.ChainID]eth.BlockID, error) { var result map[eth.ChainID]eth.BlockID - err := cl.client.CallContext( - ctx, - &result, - "supervisor_allSafeDerivedAt", - derivedFrom) + err := cl.client.CallContext(ctx, &result, "supervisor_allSafeDerivedAt", derivedFrom) return result, err } func (cl *SupervisorClient) SyncStatus(ctx context.Context) (eth.SupervisorSyncStatus, error) { var result eth.SupervisorSyncStatus - err := cl.client.CallContext( - ctx, - &result, - "supervisor_syncStatus") + err := cl.client.CallContext(ctx, &result, "supervisor_syncStatus") return result, err } diff --git a/op-supervisor/supervisor/backend/backend.go b/op-supervisor/supervisor/backend/backend.go index 33dd3ae0924..65a99451eb9 100644 --- a/op-supervisor/supervisor/backend/backend.go +++ b/op-supervisor/supervisor/backend/backend.go @@ -423,7 +423,7 @@ func (su *SupervisorBackend) DependencySet() depset.DependencySet { // Query methods // ---------------------------- -func (su *SupervisorBackend) CheckMessage(identifier types.Identifier, payloadHash common.Hash, executingDescriptor types.ExecutingDescriptor) (types.SafetyLevel, error) { +func (su *SupervisorBackend) CheckMessage(ctx context.Context, identifier types.Identifier, payloadHash common.Hash, executingDescriptor types.ExecutingDescriptor) (types.SafetyLevel, error) { logHash := types.PayloadHashToLogHash(payloadHash, identifier.Origin) chainID := identifier.ChainID blockNum := identifier.BlockNumber @@ -458,6 +458,7 @@ func (su *SupervisorBackend) CheckMessage(identifier types.Identifier, payloadHa } func (su *SupervisorBackend) CheckMessagesV2( + ctx context.Context, messages []types.Message, minSafety types.SafetyLevel, executingDescriptor types.ExecutingDescriptor) error { @@ -466,7 +467,7 @@ func (su *SupervisorBackend) CheckMessagesV2( for _, msg := range messages { su.logger.Debug("Checking message", "identifier", msg.Identifier, "payloadHash", msg.PayloadHash.String(), "executingTimestamp", executingDescriptor.Timestamp) - safety, err := su.CheckMessage(msg.Identifier, msg.PayloadHash, executingDescriptor) + safety, err := su.CheckMessage(ctx, msg.Identifier, msg.PayloadHash, executingDescriptor) if err != nil { su.logger.Error("Check message failed", "err", err, "identifier", msg.Identifier, "payloadHash", msg.PayloadHash.String(), "executingTimestamp", executingDescriptor.Timestamp) @@ -486,6 +487,7 @@ func (su *SupervisorBackend) CheckMessagesV2( } func (su *SupervisorBackend) CheckMessages( + ctx context.Context, messages []types.Message, minSafety types.SafetyLevel) error { su.logger.Debug("Checking messages", "count", len(messages), "minSafety", minSafety) @@ -495,7 +497,7 @@ func (su *SupervisorBackend) CheckMessages( "identifier", msg.Identifier, "payloadHash", msg.PayloadHash.String()) // Guarantee message expiry checks do not fail by setting the executing timestamp to the message timestamp // This is intentionally done to avoid breaking checkMessagesV1 which doesn't handle message expiry checks - safety, err := su.CheckMessage(msg.Identifier, msg.PayloadHash, types.ExecutingDescriptor{Timestamp: msg.Identifier.Timestamp}) + safety, err := su.CheckMessage(ctx, msg.Identifier, msg.PayloadHash, types.ExecutingDescriptor{Timestamp: msg.Identifier.Timestamp}) if err != nil { su.logger.Error("Check message failed", "err", err, "identifier", msg.Identifier, "payloadHash", msg.PayloadHash.String()) @@ -590,8 +592,12 @@ func (su *SupervisorBackend) Finalized(ctx context.Context, chainID eth.ChainID) return v.ID(), nil } -func (su *SupervisorBackend) FinalizedL1() eth.BlockRef { - return su.chainDBs.FinalizedL1() +func (su *SupervisorBackend) FinalizedL1(ctx context.Context) (eth.BlockRef, error) { + v := su.chainDBs.FinalizedL1() + if v == (eth.BlockRef{}) { + return eth.BlockRef{}, errors.New("finality of L1 is not initialized") + } + return v, nil } func (su *SupervisorBackend) IsLocalUnsafe(ctx context.Context, chainID eth.ChainID, block eth.BlockID) error { @@ -676,7 +682,7 @@ func (su *SupervisorBackend) SuperRootAtTimestamp(ctx context.Context, timestamp }, nil } -func (su *SupervisorBackend) SyncStatus() (eth.SupervisorSyncStatus, error) { +func (su *SupervisorBackend) SyncStatus(ctx context.Context) (eth.SupervisorSyncStatus, error) { return su.statusTracker.SyncStatus() } diff --git a/op-supervisor/supervisor/backend/mock.go b/op-supervisor/supervisor/backend/mock.go index c6de2b91df4..94a6f4682ae 100644 --- a/op-supervisor/supervisor/backend/mock.go +++ b/op-supervisor/supervisor/backend/mock.go @@ -47,15 +47,15 @@ func (m *MockBackend) AddL2RPC(ctx context.Context, rpc string, jwtSecret eth.By return nil } -func (m *MockBackend) CheckMessage(identifier types.Identifier, payloadHash common.Hash, executingDescriptor types.ExecutingDescriptor) (types.SafetyLevel, error) { +func (m *MockBackend) CheckMessage(ctx context.Context, identifier types.Identifier, payloadHash common.Hash, executingDescriptor types.ExecutingDescriptor) (types.SafetyLevel, error) { return types.CrossUnsafe, nil } -func (m *MockBackend) CheckMessages(messages []types.Message, minSafety types.SafetyLevel) error { +func (m *MockBackend) CheckMessages(ctx context.Context, messages []types.Message, minSafety types.SafetyLevel) error { return nil } -func (m *MockBackend) CheckMessagesV2(messages []types.Message, minSafety types.SafetyLevel, executingDescriptor types.ExecutingDescriptor) error { +func (m *MockBackend) CheckMessagesV2(ctx context.Context, messages []types.Message, minSafety types.SafetyLevel, executingDescriptor types.ExecutingDescriptor) error { return nil } @@ -71,8 +71,8 @@ func (m *MockBackend) Finalized(ctx context.Context, chainID eth.ChainID) (eth.B return eth.BlockID{}, nil } -func (m *MockBackend) FinalizedL1() eth.BlockRef { - return eth.BlockRef{} +func (m *MockBackend) FinalizedL1(ctx context.Context) (eth.BlockRef, error) { + return eth.BlockRef{}, nil } func (m *MockBackend) CrossDerivedToSource(ctx context.Context, chainID eth.ChainID, derived eth.BlockID) (source eth.BlockRef, err error) { @@ -83,7 +83,7 @@ func (m *MockBackend) SuperRootAtTimestamp(ctx context.Context, timestamp hexuti return eth.SuperRootResponse{}, nil } -func (m *MockBackend) SyncStatus() (eth.SupervisorSyncStatus, error) { +func (m *MockBackend) SyncStatus(ctx context.Context) (eth.SupervisorSyncStatus, error) { return eth.SupervisorSyncStatus{}, nil } diff --git a/op-supervisor/supervisor/frontend/frontend.go b/op-supervisor/supervisor/frontend/frontend.go index 91dfe3fe29e..5b4b239cd12 100644 --- a/op-supervisor/supervisor/frontend/frontend.go +++ b/op-supervisor/supervisor/frontend/frontend.go @@ -3,65 +3,49 @@ package frontend import ( "context" - "github.com/ethereum-optimism/optimism/op-service/eth" - "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" -) - -type AdminBackend interface { - Start(ctx context.Context) error - Stop(ctx context.Context) error - AddL2RPC(ctx context.Context, rpc string, jwtSecret eth.Bytes32) error -} -type QueryBackend interface { - CheckMessage(identifier types.Identifier, payloadHash common.Hash, executingDescriptor types.ExecutingDescriptor) (types.SafetyLevel, error) - CheckMessages(messages []types.Message, minSafety types.SafetyLevel) error - CheckMessagesV2(messages []types.Message, minSafety types.SafetyLevel, executingDescriptor types.ExecutingDescriptor) error - CrossDerivedToSource(ctx context.Context, chainID eth.ChainID, derived eth.BlockID) (derivedFrom eth.BlockRef, err error) - LocalUnsafe(ctx context.Context, chainID eth.ChainID) (eth.BlockID, error) - CrossSafe(ctx context.Context, chainID eth.ChainID) (types.DerivedIDPair, error) - Finalized(ctx context.Context, chainID eth.ChainID) (eth.BlockID, error) - FinalizedL1() eth.BlockRef - SuperRootAtTimestamp(ctx context.Context, timestamp hexutil.Uint64) (eth.SuperRootResponse, error) - SyncStatus() (eth.SupervisorSyncStatus, error) - AllSafeDerivedAt(ctx context.Context, derivedFrom eth.BlockID) (derived map[eth.ChainID]eth.BlockID, err error) -} + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/sources" + "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" +) type Backend interface { - AdminBackend - QueryBackend + sources.SupervisorAdminAPI + sources.SupervisorQueryAPI } type QueryFrontend struct { - Supervisor QueryBackend + Supervisor sources.SupervisorQueryAPI } -var _ QueryBackend = (*QueryFrontend)(nil) +var _ sources.SupervisorQueryAPI = (*QueryFrontend)(nil) // CheckMessage checks the safety-level of an individual message. // The payloadHash references the hash of the message-payload of the message. -func (q *QueryFrontend) CheckMessage(identifier types.Identifier, payloadHash common.Hash, executingDescriptor types.ExecutingDescriptor) (types.SafetyLevel, error) { - return q.Supervisor.CheckMessage(identifier, payloadHash, executingDescriptor) +func (q *QueryFrontend) CheckMessage(ctx context.Context, identifier types.Identifier, payloadHash common.Hash, executingDescriptor types.ExecutingDescriptor) (types.SafetyLevel, error) { + return q.Supervisor.CheckMessage(ctx, identifier, payloadHash, executingDescriptor) } // CheckMessagesV2 checks the safety-level of a collection of messages, // and returns if the minimum safety-level is met for all messages. func (q *QueryFrontend) CheckMessagesV2( + ctx context.Context, messages []types.Message, minSafety types.SafetyLevel, executingDescriptor types.ExecutingDescriptor) error { - return q.Supervisor.CheckMessagesV2(messages, minSafety, executingDescriptor) + return q.Supervisor.CheckMessagesV2(ctx, messages, minSafety, executingDescriptor) } // CheckMessages checks the safety-level of a collection of messages, // and returns if the minimum safety-level is met for all messages. // Deprecated: This method does not check for message expiry. func (q *QueryFrontend) CheckMessages( + ctx context.Context, messages []types.Message, minSafety types.SafetyLevel) error { - return q.Supervisor.CheckMessages(messages, minSafety) + return q.Supervisor.CheckMessages(ctx, messages, minSafety) } func (q *QueryFrontend) LocalUnsafe(ctx context.Context, chainID eth.ChainID) (eth.BlockID, error) { @@ -76,8 +60,8 @@ func (q *QueryFrontend) Finalized(ctx context.Context, chainID eth.ChainID) (eth return q.Supervisor.Finalized(ctx, chainID) } -func (q *QueryFrontend) FinalizedL1() eth.BlockRef { - return q.Supervisor.FinalizedL1() +func (q *QueryFrontend) FinalizedL1(ctx context.Context) (eth.BlockRef, error) { + return q.Supervisor.FinalizedL1(ctx) } // CrossDerivedFrom is deprecated, but remains for backwards compatibility to callers @@ -98,15 +82,15 @@ func (q *QueryFrontend) AllSafeDerivedAt(ctx context.Context, derivedFrom eth.Bl return q.Supervisor.AllSafeDerivedAt(ctx, derivedFrom) } -func (q *QueryFrontend) SyncStatus() (eth.SupervisorSyncStatus, error) { - return q.Supervisor.SyncStatus() +func (q *QueryFrontend) SyncStatus(ctx context.Context) (eth.SupervisorSyncStatus, error) { + return q.Supervisor.SyncStatus(ctx) } type AdminFrontend struct { Supervisor Backend } -var _ AdminBackend = (*AdminFrontend)(nil) +var _ sources.SupervisorAdminAPI = (*AdminFrontend)(nil) // Start starts the service, if it was previously stopped. func (a *AdminFrontend) Start(ctx context.Context) error { From 57f78e1bd11862ad46efbf4e1414765f20c53e87 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Tue, 11 Mar 2025 00:52:42 +1000 Subject: [PATCH 118/130] op-program: Update prestate reproducibility check to compare against list of prestates from superchain-registry (#14744) * op-program: Switch prestate verification to use the superchain-registry as source of truth * op-program: Update prestate reproducibility check to reference the same list superchain-registry now uses * Fix script * ci: Reduce timeout for prestate builds again. * Clarify cannon 32 was built. * Remove TODO --- .circleci/config.yml | 4 +- op-program/prestates/releases.go | 35 ------ op-program/prestates/releases.json | 170 -------------------------- op-program/prestates/releases_test.go | 26 ---- op-program/prestates/verify/verify.go | 120 +++++++++++++++--- op-program/scripts/build-prestates.sh | 4 +- 6 files changed, 108 insertions(+), 251 deletions(-) delete mode 100644 op-program/prestates/releases.go delete mode 100644 op-program/prestates/releases.json delete mode 100644 op-program/prestates/releases_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 6cdee5431b4..cbddd563fce 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1219,7 +1219,9 @@ jobs: steps: - utils/checkout-with-mise - setup_remote_docker - - run: make -C op-program verify-reproducibility + - run: + name: Verify reproducibility + command: make -C op-program verify-reproducibility - store_artifacts: path: ./op-program/temp/logs when: always diff --git a/op-program/prestates/releases.go b/op-program/prestates/releases.go deleted file mode 100644 index 71f159f0a1a..00000000000 --- a/op-program/prestates/releases.go +++ /dev/null @@ -1,35 +0,0 @@ -package prestates - -// This package is imported by the superchain-registry as part of chain validation -// tests. Please do not delete these files unless the downstream dependency is removed. - -import ( - _ "embed" - "encoding/json" - "fmt" -) - -//go:embed releases.json -var releasesJSON []byte - -type Release struct { - Version string `json:"version"` - Hash string `json:"hash"` - GovernanceApproved bool `json:"governanceApproved"` - Type ReleaseType `json:"type"` -} - -type ReleaseType string - -const Cannon64Type ReleaseType = "cannon64" - -// GetReleases reads the contents of the releases.json file -func GetReleases() ([]Release, error) { - var releases []Release - err := json.Unmarshal(releasesJSON, &releases) - if err != nil { - return nil, fmt.Errorf("failed to parse JSON: %w", err) - } - - return releases, nil -} diff --git a/op-program/prestates/releases.json b/op-program/prestates/releases.json deleted file mode 100644 index f66063232b7..00000000000 --- a/op-program/prestates/releases.json +++ /dev/null @@ -1,170 +0,0 @@ -[ - { - "version": "1.5.0-rc.3", - "hash": "0x039970872142f48b189d18dcbc03a3737338d098b0101713dc2d6710f9deb5ef", - "type": "cannon64" - }, - { - "version": "1.5.0-rc.3", - "hash": "0x039facea52b20c605c05efb0a33560a92de7074218998f75bcdf61e8989cb5d9" - }, - { - "version": "1.5.0-rc.2", - "hash": "0x03a7d967025dc434a9ca65154acdb88a7b658147b9b049f0b2f5ecfb9179b0fe", - "type": "cannon64" - }, - { - "version": "1.5.0-rc.2", - "hash": "0x035ac388b5cb22acf52a2063cfde108d09b1888655d21f02f595f9c3ea6cbdcd" - }, - { - "version": "1.5.0-rc.1", - "hash": "0x03f83792f653160f3274b0888e998077a27e1f74cb35bcb20d86021e769340aa", - "type": "cannon64" - }, - { - "version": "1.5.0-rc.1", - "hash": "0x03dfa3b3ac66e8fae9f338824237ebacff616df928cf7dada0e14be2531bc1f4" - }, - { - "version": "1.4.1-rc.3", - "hash": "0x03d7f817d7bb1321533aeeee5e0f2031cc69d167c4a17bf2816b4cc8b1be4077", - "type": "cannon64" - }, - { - "version": "1.4.1-rc.3", - "hash": "0x03ea123151750b03569369130a73c390b0b5e10c722ab42d762d116318325bb7" - }, - { - "version": "1.4.1-rc.2", - "hash": "0x0386cde2f2b1bde1189ac9c9b7d66774e6260eca778223def326bfe680c14ab9", - "type": "cannon64" - }, - { - "version": "1.4.1-rc.2", - "hash": "0x03045fd433fb5391c40751939d7cb5e9dfe83cf156f9395566a311e7fe9d3aa2" - }, - { - "version": "1.4.1-rc.1", - "hash": "0x0386cde2f2b1bde1189ac9c9b7d66774e6260eca778223def326bfe680c14ab9", - "type": "cannon64" - }, - { - "version": "1.4.1-rc.1", - "hash": "0x03045fd433fb5391c40751939d7cb5e9dfe83cf156f9395566a311e7fe9d3aa2" - }, - { - "version": "1.4.0", - "hash": "0x03b7eaa4e3cbce90381921a4b48008f4769871d64f93d113fcadca08ecee503b", - "type": "cannon64" - }, - { - "version": "1.4.0", - "hash": "0x03f89406817db1ed7fd8b31e13300444652cdb0b9c509a674de43483b2f83568", - "governanceApproved": true - }, - { - "version": "1.4.0-unichain-mainnet", - "hash": "0x03d5baff435a6828cfa13679e7ef34ac73d4d674550426c7d9a8005deb6a6db3", - "type": "cannon64" - }, - { - "version": "1.4.0-unichain-mainnet", - "hash": "0x0336751a224445089ba5456c8028376a0faf2bafa81d35f43fab8730258cdf37", - "governanceApproved": true - }, - { - "version": "1.4.0-rc.3", - "hash": "0x03b7eaa4e3cbce90381921a4b48008f4769871d64f93d113fcadca08ecee503b", - "type": "cannon64" - }, - { - "version": "1.4.0-rc.3", - "hash": "0x03f89406817db1ed7fd8b31e13300444652cdb0b9c509a674de43483b2f83568" - }, - { - "version": "1.4.0-rc.2", - "hash": "0x0348ce2059f718af75729c2c56860551b46b665956a641b3cb2cd51e50b7b725", - "type": "cannon64" - }, - { - "version": "1.4.0-rc.2", - "hash": "0x0364e4e72922e7d649338f558f8a14b50ca31922a1484e73ea03987fb1516095" - }, - { - "version": "1.4.0-rc.1", - "hash": "0x032e5d6119ee983cb87deae3eef16ea6086f2347433c99f1820d60f36a24a6e6", - "type": "cannon64" - }, - { - "version": "1.4.0-rc.1", - "hash": "0x03925193e3e89f87835bbdf3a813f60b2aa818a36bbe71cd5d8fd7e79f5e8afe" - }, - { - "version": "1.3.1-ink", - "hash": "0x03c50b9fd04bdadc228205f340767bbf2d01a030aec39903120d3559d94bb8cc" - }, - { - "version": "1.3.1", - "hash": "0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c", - "governanceApproved": true - }, - { - "version": "1.3.1-rc.2", - "hash": "0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c" - }, - { - "version": "1.3.1-rc.1", - "hash": "0x03e806a2859a875267a563462a06d4d1d1b455a9efee959a46e21e54b6caf69a" - }, - { - "version": "1.3.0-rc.3", - "hash": "0x030de10d9da911a2b180ecfae2aeaba8758961fc28262ce989458c6f9a547922" - }, - { - "version": "1.3.0-rc.2", - "hash": "0x0385c3f8ee78491001d92b90b07d0cf387b7b52ab9b83b4d87c994e92cf823ba" - }, - { - "version": "1.3.0-rc.1", - "hash": "0x0367c4aa897bffbded0b523f277ca892298dc3c691baf37bc2099b86024f9673" - }, - { - "version": "1.2.0", - "hash": "0x03617abec0b255dc7fc7a0513a2c2220140a1dcd7a1c8eca567659bd67e05cea", - "governanceApproved": true - }, - { - "version": "1.1.0", - "hash": "0x03e69d3de5155f4a80da99dd534561cbddd4f9dd56c9ecc704d6886625711d2b" - }, - { - "version": "1.0.1", - "hash": "0x0398bdd93e2e9313befdf82beb709da6a4daf35ce1abb42d8a998ec9bc1c572e" - }, - { - "version": "1.0.0", - "hash": "0x037ef3c1a487960b0e633d3e513df020c43432769f41a634d18a9595cbf53c55", - "governanceApproved": true - }, - { - "version": "0.3.1", - "hash": "0x037ef3c1a487960b0e633d3e513df020c43432769f41a634d18a9595cbf53c55" - }, - { - "version": "0.3.0", - "hash": "0x034c8cc69f22c35ae386a97136715dd48aaf97fd190942a111bfa680c2f2f421" - }, - { - "version": "0.2.0", - "hash": "0x031e3b504740d0b1264e8cf72b6dde0d497184cfb3f98e451c6be8b33bd3f808" - }, - { - "version": "0.1.0", - "hash": "0x038942ec840131a63c49fa514a3f0577ae401fd5584d56ad50cdf5a8b41d4538" - }, - { - "version": "0.0.1", - "hash": "0x03babef4b4c6d866d56e6356d961839fd9475931d11e0ea507420a87b0cadbdd" - } -] diff --git a/op-program/prestates/releases_test.go b/op-program/prestates/releases_test.go deleted file mode 100644 index 88a5507897e..00000000000 --- a/op-program/prestates/releases_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package prestates - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestGetReleases(t *testing.T) { - releases, err := GetReleases() - require.NoError(t, err, "expected no error while parsing embedded releases.json") - - foundGovernanceApproved := false - foundCannon64Release := false - for _, release := range releases { - if release.GovernanceApproved { - foundGovernanceApproved = true - break - } - if release.Type == Cannon64Type { - foundCannon64Release = true - } - } - require.True(t, foundGovernanceApproved, "expected to find at least one GovernanceApproved release") - require.True(t, foundCannon64Release, "expected to find at least one Cannon64 release") -} diff --git a/op-program/prestates/verify/verify.go b/op-program/prestates/verify/verify.go index 20d08d6023c..41bc92e19e6 100644 --- a/op-program/prestates/verify/verify.go +++ b/op-program/prestates/verify/verify.go @@ -4,15 +4,24 @@ import ( "encoding/json" "flag" "fmt" + "io" + "net/http" "os" "slices" - "github.com/ethereum-optimism/optimism/op-program/prestates" + "github.com/BurntSushi/toml" ) +// standardPrestatesUrl is the URL to the TOML file in superchain registry that defines the list of standard prestates +// Note that this explicitly points to the main branch and is not pinned to a specific version. The verification check +// intends to +const standardPrestatesUrl = "https://raw.githubusercontent.com/ethereum-optimism/superchain-registry/refs/heads/main/validation/standard/standard-prestates.toml" + func main() { var inputFile string flag.StringVar(&inputFile, "input", "", "Releases JSON file to verify") + var expectedFile string + flag.StringVar(&expectedFile, "expected", "", "Override the expected TOML file") flag.Parse() if inputFile == "" { _, _ = fmt.Fprintln(os.Stderr, "Must specify --input") @@ -31,14 +40,14 @@ func main() { _, _ = fmt.Fprintf(os.Stderr, "Failed to read input file: %v\n", err.Error()) os.Exit(2) } - var actual []prestates.Release + var actual []Release err = json.Unmarshal(input, &actual) if err != nil { _, _ = fmt.Fprintf(os.Stderr, "Failed to parse JSON: %v\n", err.Error()) os.Exit(2) } - expected, err := prestates.GetReleases() + expected, err := loadReleases(expectedFile) if err != nil { _, _ = fmt.Fprintf(os.Stderr, "Failed to load expected releases: %v\n", err.Error()) os.Exit(2) @@ -52,36 +61,71 @@ func main() { } return -1 } - sortFunc := func(a, b prestates.Release) int { + sortFunc := func(a, b Release) int { if a.Version > b.Version { return 1 } else if a.Version == b.Version { - return stringCompare(string(a.Type), string(b.Type)) + return stringCompare(a.Type, b.Type) } return -1 } slices.SortFunc(actual, sortFunc) - slices.SortFunc(expected, sortFunc) differs := false report := "" - for i := 0; i < max(len(actual), len(expected)); i++ { - get := func(arr []prestates.Release, idx int) string { - if i >= len(arr) { - return "" - } else { - return formatRelease(arr[i]) + for _, release := range actual { + var expectedPrestate Prestate + standardVersion := expected.Prestates[release.Version] + for _, prestate := range standardVersion { + if prestate.Type == release.Type { + expectedPrestate = prestate + break } } - expectedStr := get(expected, i) - actualStr := get(actual, i) + var expectedStr string + if expectedPrestate == (Prestate{}) { + expectedStr = "" + } else { + expectedStr = formatRelease(Release{ + Version: release.Version, + Type: expectedPrestate.Type, + Hash: expectedPrestate.Hash, + }) + } + actualStr := formatRelease(release) releaseDiffers := expectedStr != actualStr marker := "✅" if releaseDiffers { marker = "❌" + differs = true + } + report += fmt.Sprintf("%v Expected: %v\tActual: %v\n", marker, expectedStr, actualStr) + } + // Verify there aren't any additional entries in expected + totalExpected := 0 + for version, prestates := range expected.Prestates { + for _, prestate := range prestates { + totalExpected++ + // Try to find an actual release matching this expected one + contains := slices.ContainsFunc(actual, func(release Release) bool { + return release.Version == version && release.Type == prestate.Type + }) + if contains { + continue + } + expectedStr := formatRelease(Release{ + Version: version, + Hash: prestate.Hash, + Type: prestate.Type, + }) + report += fmt.Sprintf("❌ Expected: %v\tActual: \n", expectedStr) + differs = true } - report += fmt.Sprintf("%v %d\tExpected: %v\tActual: %v\n", marker, i, expectedStr, actualStr) - differs = differs || releaseDiffers + } + // Sanity check entries are not duplicated in the standard prestates + if totalExpected != len(actual) { + report += fmt.Sprintf("❌ Found %v expected prestates but %v actual\n", totalExpected, len(actual)) + differs = true } fmt.Println(report) if differs { @@ -89,6 +133,48 @@ func main() { } } -func formatRelease(release prestates.Release) string { +func formatRelease(release Release) string { return fmt.Sprintf("%-13v %s %-10v", release.Version, release.Hash, release.Type) } + +func loadReleases(overrideFile string) (*Prestates, error) { + var data []byte + if overrideFile != "" { + d, err := os.ReadFile(overrideFile) + if err != nil { + return nil, fmt.Errorf("failed to read override file (%v): %w", overrideFile, err) + } + data = d + } else { + resp, err := http.Get(standardPrestatesUrl) + if err != nil { + return nil, fmt.Errorf("failed to download standard prestates from %v: %w", standardPrestatesUrl, err) + } + defer resp.Body.Close() + data, err = io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read standard prestates from %v: %w", standardPrestatesUrl, err) + } + } + var standardPrestates Prestates + err := toml.Unmarshal(data, &standardPrestates) + if err != nil { + return nil, fmt.Errorf("failed to parse standard prestates from %v: %w", standardPrestatesUrl, err) + } + return &standardPrestates, nil +} + +type Prestates struct { + Prestates map[string][]Prestate `toml:"prestates"` +} + +type Prestate struct { + Type string `toml:"type"` + Hash string `toml:"hash"` +} + +type Release struct { + Version string `json:"version"` + Hash string `json:"hash"` + Type string `json:"type"` +} diff --git a/op-program/scripts/build-prestates.sh b/op-program/scripts/build-prestates.sh index c2bc41a85ef..2eee2b2febe 100755 --- a/op-program/scripts/build-prestates.sh +++ b/op-program/scripts/build-prestates.sh @@ -48,8 +48,8 @@ do else cp "${BIN_DIR}/prestate.json" "${STATES_DIR}/${HASH}.json" fi - VERSIONS_JSON=$(echo "${VERSIONS_JSON}" | jq ". += [{\"version\": \"${SHORT_VERSION}\", \"hash\": \"${HASH}\"}]") - echo "Built ${VERSION}: ${HASH}" + VERSIONS_JSON=$(echo "${VERSIONS_JSON}" | jq ". += [{\"version\": \"${SHORT_VERSION}\", \"hash\": \"${HASH}\", \"type\": \"cannon32\"}]") + echo "Built cannon32 ${VERSION}: ${HASH}" if [ -f "${BIN_DIR}/prestate-proof-mt64.json" ]; then HASH=$(cat "${BIN_DIR}/prestate-proof-mt64.json" | jq -r .pre) From a466a38e1d9a1b28187a8b6fff78932de60a3541 Mon Sep 17 00:00:00 2001 From: Teddy Knox Date: Mon, 10 Mar 2025 10:57:13 -0400 Subject: [PATCH 119/130] Fix problem with kurtosis-devnet deploy (#14729) --- .../pkg/kurtosis/sources/deployer/deployer.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go b/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go index 3cbae8a8ca5..0b8055abf93 100644 --- a/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go +++ b/kurtosis-devnet/pkg/kurtosis/sources/deployer/deployer.go @@ -338,12 +338,17 @@ func (d *Deployer) ExtractData(ctx context.Context) (*DeployerData, error) { state.Deployments[id] = deployment } - l1ValidatorWallets, err := d.getL1ValidatorWallets(deployerArtifact) + l1GenesisArtifact, err := fs.GetArtifact(ctx, d.genesisArtifactName) if err != nil { return nil, err } - l1ChainID, err := d.getL1ChainID(deployerArtifact) + l1ValidatorWallets, err := d.getL1ValidatorWallets(l1GenesisArtifact) + if err != nil { + return nil, err + } + + l1ChainID, err := d.getL1ChainID(l1GenesisArtifact) if err != nil { return nil, err } From e673e6199fa4f578a85c68a20709787dc58a0b89 Mon Sep 17 00:00:00 2001 From: zhiqiangxu <652732310@qq.com> Date: Mon, 10 Mar 2025 23:20:01 +0800 Subject: [PATCH 120/130] allow specifying a custom package (#14750) --- kurtosis-devnet/justfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kurtosis-devnet/justfile b/kurtosis-devnet/justfile index 8404bf4ebf9..3e479aa44c2 100644 --- a/kurtosis-devnet/justfile +++ b/kurtosis-devnet/justfile @@ -71,7 +71,7 @@ op-wheel-image TAG='op-wheel:devnet': (_docker_build_stack TAG "op-wheel-target" KURTOSIS_PACKAGE := "github.com/ethpandaops/optimism-package" # Devnet template recipe -devnet TEMPLATE_FILE DATA_FILE="" NAME="": +devnet TEMPLATE_FILE DATA_FILE="" NAME="" PACKAGE=KURTOSIS_PACKAGE: #!/usr/bin/env bash export DEVNET_NAME={{NAME}} if [ -z "{{NAME}}" ]; then @@ -82,7 +82,7 @@ devnet TEMPLATE_FILE DATA_FILE="" NAME="": fi fi export ENCL_NAME="$DEVNET_NAME"-devnet - go run cmd/main.go -kurtosis-package {{KURTOSIS_PACKAGE}} \ + go run cmd/main.go -kurtosis-package {{PACKAGE}} \ -environment "tests/$ENCL_NAME.json" \ -template "{{TEMPLATE_FILE}}" \ -data "{{DATA_FILE}}" \ From 75699750c551554a135cd58472951e8bfd0d35d2 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Tue, 11 Mar 2025 01:58:42 +1000 Subject: [PATCH 121/130] op-program: Add a host subcommand to list available chain configs. (#14699) * op-program: Add a host subcommand to list available chain configs. * op-program: Also list custom chain configs --- op-program/chainconfig/chaincfg.go | 32 +++++++- op-program/chainconfig/chaincfg_test.go | 6 ++ op-program/host/cmd/main.go | 4 + op-program/host/subcmds/configs_cmd.go | 98 +++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 op-program/host/subcmds/configs_cmd.go diff --git a/op-program/chainconfig/chaincfg.go b/op-program/chainconfig/chaincfg.go index 7c638f669cd..77fcf1ada7d 100644 --- a/op-program/chainconfig/chaincfg.go +++ b/op-program/chainconfig/chaincfg.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "os" + "strings" "github.com/ethereum-optimism/optimism/op-service/superutil" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset" @@ -25,6 +26,29 @@ func OPSepoliaChainConfig() *params.ChainConfig { //go:embed configs/*json var customChainConfigFS embed.FS +func CustomChainIDs() ([]eth.ChainID, error) { + return customChainIDs(customChainConfigFS) +} + +func customChainIDs(customChainFS embed.FS) ([]eth.ChainID, error) { + entries, err := customChainFS.ReadDir("configs") + if err != nil { + return nil, fmt.Errorf("failed to list custom configs: %w", err) + } + var chainIDs []eth.ChainID + for _, entry := range entries { + if strings.HasSuffix(entry.Name(), "-genesis-l2.json") { + chainID, err := eth.ParseDecimalChainID(strings.TrimSuffix(entry.Name(), "-genesis-l2.json")) + if err != nil { + return nil, fmt.Errorf("incorrectly named genesis-l2 config (%s): %w", entry.Name(), err) + } + chainIDs = append(chainIDs, chainID) + } + } + + return chainIDs, nil +} + func RollupConfigByChainID(chainID eth.ChainID) (*rollup.Config, error) { config, err := rollup.LoadOPStackRollupConfig(eth.EvilChainIDToUInt64(chainID)) if err == nil { @@ -37,7 +61,7 @@ func rollupConfigByChainID(chainID eth.ChainID, customChainFS embed.FS) (*rollup // Load custom rollup configs from embed FS file, err := customChainFS.Open(fmt.Sprintf("configs/%v-rollup.json", chainID)) if errors.Is(err, os.ErrNotExist) { - return nil, fmt.Errorf("no rollup config available for chain ID: %d", chainID) + return nil, fmt.Errorf("no rollup config available for chain ID: %v", chainID) } else if err != nil { return nil, fmt.Errorf("failed to get rollup config for chain ID %v: %w", chainID, err) } @@ -59,7 +83,7 @@ func chainConfigByChainID(chainID eth.ChainID, customChainFS embed.FS) (*params. // Load from custom chain configs from embed FS data, err := customChainFS.ReadFile(fmt.Sprintf("configs/%v-genesis-l2.json", chainID)) if errors.Is(err, os.ErrNotExist) { - return nil, fmt.Errorf("no chain config available for chain ID: %d", chainID) + return nil, fmt.Errorf("no chain config available for chain ID: %v", chainID) } else if err != nil { return nil, fmt.Errorf("failed to get chain config for chain ID %v: %w", chainID, err) } @@ -92,7 +116,7 @@ func dependencySetByChainID(chainID eth.ChainID, customChainFS embed.FS) (depset // Load custom dependency set configs from embed FS data, err := customChainFS.ReadFile("configs/depsets.json") if errors.Is(err, os.ErrNotExist) { - return nil, fmt.Errorf("no dependency set available for chain ID: %d", chainID) + return nil, fmt.Errorf("no dependency set available for chain ID: %v", chainID) } else if err != nil { return nil, fmt.Errorf("failed to get dependency set for chain ID %v: %w", chainID, err) } @@ -108,5 +132,5 @@ func dependencySetByChainID(chainID eth.ChainID, customChainFS embed.FS) (depset return depSet, nil } } - return nil, fmt.Errorf("no dependency set config includes chain ID: %d", chainID) + return nil, fmt.Errorf("no dependency set config includes chain ID: %v", chainID) } diff --git a/op-program/chainconfig/chaincfg_test.go b/op-program/chainconfig/chaincfg_test.go index a493e1b4643..be097afa3b3 100644 --- a/op-program/chainconfig/chaincfg_test.go +++ b/op-program/chainconfig/chaincfg_test.go @@ -43,3 +43,9 @@ func TestGetCustomDependencySetConfig(t *testing.T) { _, err = dependencySetByChainID(eth.ChainIDFromUInt64(900), test.TestCustomChainConfigFS) require.Error(t, err) } + +func TestListCustomChainIDs(t *testing.T) { + actual, err := customChainIDs(test.TestCustomChainConfigFS) + require.NoError(t, err) + require.Equal(t, []eth.ChainID{eth.ChainIDFromUInt64(901)}, actual) +} diff --git a/op-program/host/cmd/main.go b/op-program/host/cmd/main.go index 75dc1383983..aa083892373 100644 --- a/op-program/host/cmd/main.go +++ b/op-program/host/cmd/main.go @@ -6,6 +6,7 @@ import ( "github.com/ethereum-optimism/optimism/op-program/host" "github.com/ethereum-optimism/optimism/op-program/host/config" "github.com/ethereum-optimism/optimism/op-program/host/flags" + "github.com/ethereum-optimism/optimism/op-program/host/subcmds" "github.com/ethereum-optimism/optimism/op-program/host/version" opservice "github.com/ethereum-optimism/optimism/op-service" oplog "github.com/ethereum-optimism/optimism/op-service/log" @@ -44,6 +45,9 @@ func run(args []string, action ConfigAction) error { app.Name = "op-program" app.Usage = "Optimism Fault Proof Program" app.Description = "The Optimism Fault Proof Program fault proof program that runs through the rollup state-transition to verify an L2 output from L1 inputs." + app.Commands = []*cli.Command{ + subcmds.ConfigsCommand, + } app.Action = func(ctx *cli.Context) error { logger, err := setupLogging(ctx) if err != nil { diff --git a/op-program/host/subcmds/configs_cmd.go b/op-program/host/subcmds/configs_cmd.go new file mode 100644 index 00000000000..5619dda2d83 --- /dev/null +++ b/op-program/host/subcmds/configs_cmd.go @@ -0,0 +1,98 @@ +package subcmds + +import ( + "fmt" + + "github.com/ethereum-optimism/optimism/op-node/chaincfg" + "github.com/ethereum-optimism/optimism/op-program/chainconfig" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum/superchain" + "github.com/urfave/cli/v2" +) + +var ( + ConfigsChainIDFlag = &cli.StringFlag{ + Name: "chain-id", + Usage: "Chain ID to report chain configuration for", + } + ConfigsNetworkFlag = &cli.StringFlag{ + Name: "network", + Usage: "Network to report chain configuration for", + } +) + +var ConfigsCommand = &cli.Command{ + Name: "configs", + Usage: "List the supported chain configurations", + Description: "List the supported chain configurations.", + Action: ListConfigs, + Flags: []cli.Flag{ + ConfigsChainIDFlag, + ConfigsNetworkFlag, + }, +} + +func ListConfigs(ctx *cli.Context) error { + if ctx.IsSet(ConfigsChainIDFlag.Name) { + chainID, err := eth.ParseDecimalChainID(ctx.String(ConfigsChainIDFlag.Name)) + if err != nil { + return fmt.Errorf("invalid chain ID: %w", err) + } + if err := listChain(chainID); err != nil { + return err + } + } + if ctx.IsSet(ConfigsNetworkFlag.Name) { + if err := listNamedChain(ctx.String(ConfigsNetworkFlag.Name)); err != nil { + return err + } + } + if !ctx.IsSet(ConfigsChainIDFlag.Name) && !ctx.IsSet(ConfigsNetworkFlag.Name) { + return listAllChains() + } + return nil +} + +func listAllChains() error { + chainNames := superchain.ChainNames() + for _, name := range chainNames { + if err := listNamedChain(name); err != nil { + return err + } + } + customChainIDs, err := chainconfig.CustomChainIDs() + if err != nil { + return err + } + for _, chainID := range customChainIDs { + if err := listChain(chainID); err != nil { + return err + } + } + return nil +} + +func listNamedChain(name string) error { + ch := chaincfg.ChainByName(name) + chainID := eth.ChainIDFromUInt64(ch.ChainID) + err := listChain(chainID) + if err != nil { + return err + } + return nil +} + +func listChain(chainID eth.ChainID) error { + cfg, err := chainconfig.RollupConfigByChainID(chainID) + if err != nil { + return err + } + // Double check the L2 genesis is really available + _, err = chainconfig.ChainConfigByChainID(chainID) + if err != nil { + return err + } + description := cfg.Description(chaincfg.L2ChainIDToNetworkDisplayName) + fmt.Println(description) + return nil +} From ab06b2e81b4525f6f1e45e928989b21ab904e26f Mon Sep 17 00:00:00 2001 From: Sebastian Stammler Date: Mon, 10 Mar 2025 17:15:19 +0100 Subject: [PATCH 122/130] go: update SR to cb1b645b35813b8dd3a900b9f99c46614e466a2c via op-geth (#14755) * go: update SR to cb1b645b35813b8dd3a900b9f99c46614e466a2c via op-geth to pull in updated sepolia-dev-0 sepolia blob schedule fix times * fix chain config test --- go.mod | 2 +- go.sum | 6 ++---- op-node/chaincfg/chains_test.go | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 45eec878564..2c01aacc9de 100644 --- a/go.mod +++ b/go.mod @@ -256,7 +256,7 @@ require ( rsc.io/tmplfunc v0.0.3 // indirect ) -replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101503.0-rc.2 +replace github.com/ethereum/go-ethereum => github.com/ethereum-optimism/op-geth v1.101503.0-rc.3 //replace github.com/ethereum/go-ethereum => ../op-geth diff --git a/go.sum b/go.sum index 45280a5b916..58021aa4364 100644 --- a/go.sum +++ b/go.sum @@ -192,10 +192,8 @@ github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/u github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs= -github.com/ethereum-optimism/op-geth v1.101503.0-rc.2 h1:CHIF+1pENaamq7jq8LZOTQ1FVJ+U2EzNM9iVAr9Lgi8= -github.com/ethereum-optimism/op-geth v1.101503.0-rc.2/go.mod h1:QUo3fn+45vWqJWzJW+rIzRHUV7NmhhHLPdI87mAn1M8= -github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250228185245-d4bb112dc979 h1:P37l7EFCz5KxE20+yPa3LWiH2Cg+xx6lQ4mOKYCZFPs= -github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250228185245-d4bb112dc979/go.mod h1:NZ816PzLU1TLv1RdAvYAb6KWOj4Zm5aInT0YpDVml2Y= +github.com/ethereum-optimism/op-geth v1.101503.0-rc.3 h1:tSKg1RpC70EPsZDyL2dVrggpldEaRVg25rDeB2pZYrk= +github.com/ethereum-optimism/op-geth v1.101503.0-rc.3/go.mod h1:QUo3fn+45vWqJWzJW+rIzRHUV7NmhhHLPdI87mAn1M8= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250307201638-7c3999db14c6 h1:EBEaI3oygl1npFpvdHghcWbKnhj2alEe4KqM2jG2XS8= github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20250307201638-7c3999db14c6/go.mod h1:NZ816PzLU1TLv1RdAvYAb6KWOj4Zm5aInT0YpDVml2Y= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= diff --git a/op-node/chaincfg/chains_test.go b/op-node/chaincfg/chains_test.go index 1a067046e24..fa297ea23c9 100644 --- a/op-node/chaincfg/chains_test.go +++ b/op-node/chaincfg/chains_test.go @@ -153,7 +153,7 @@ var sepoliaDev0Cfg = rollup.Config{ FjordTime: u64Ptr(1715961600), GraniteTime: u64Ptr(1723046400), HoloceneTime: u64Ptr(1731682800), - PectraBlobScheduleTime: u64Ptr(1742486400), + PectraBlobScheduleTime: u64Ptr(1741687200), ProtocolVersionsAddress: common.HexToAddress("0x252CbE9517F731C618961D890D534183822dcC8d"), ChainOpConfig: defaultOpConfig, } From 4d832faba7d71ab883c4a6e5d65dcf0ebe925168 Mon Sep 17 00:00:00 2001 From: George Knee Date: Mon, 10 Mar 2025 17:34:33 +0000 Subject: [PATCH 123/130] op-batcher: force use of TerminalString throughout syncActions logging (#14687) Depending on the logger, it may not call this automatically --- op-batcher/batcher/sync_actions.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/op-batcher/batcher/sync_actions.go b/op-batcher/batcher/sync_actions.go index a6645e6ae74..fe4abedddc2 100644 --- a/op-batcher/batcher/sync_actions.go +++ b/op-batcher/batcher/sync_actions.go @@ -57,11 +57,11 @@ func computeSyncActions[T channelStatuser]( ) (syncActions, bool) { m := l.With( - "syncStatus.headL1", newSyncStatus.HeadL1, - "syncStatus.currentL1", newSyncStatus.CurrentL1, - "syncStatus.localSafeL2", newSyncStatus.LocalSafeL2, - "syncStatus.safeL2", newSyncStatus.SafeL2, - "syncStatus.unsafeL2", newSyncStatus.UnsafeL2, + "syncStatus.headL1", newSyncStatus.HeadL1.TerminalString(), + "syncStatus.currentL1", newSyncStatus.CurrentL1.TerminalString(), + "syncStatus.localSafeL2", newSyncStatus.LocalSafeL2.TerminalString(), + "syncStatus.safeL2", newSyncStatus.SafeL2.TerminalString(), + "syncStatus.unsafeL2", newSyncStatus.UnsafeL2.TerminalString(), ) safeL2 := newSyncStatus.SafeL2 @@ -78,7 +78,7 @@ func computeSyncActions[T channelStatuser]( if newSyncStatus.CurrentL1.Number < prevCurrentL1.Number { // This can happen when the sequencer restarts - m.Warn("sequencer currentL1 reversed", "prevCurrentL1", prevCurrentL1) + m.Warn("sequencer currentL1 reversed", "prevCurrentL1", prevCurrentL1.TerminalString()) return syncActions{}, true } @@ -94,7 +94,7 @@ func computeSyncActions[T channelStatuser]( s := syncActions{ blocksToLoad: allUnsafeBlocks, } - m.Info("no blocks in state", "syncActions", s) + m.Info("no blocks in state", "syncActions", s.TerminalString()) return s, false } @@ -112,7 +112,7 @@ func computeSyncActions[T channelStatuser]( if nextSafeBlockNum < oldestBlockInStateNum { m.Warn("next safe block is below oldest block in state", - "syncActions", startAfresh, + "syncActions", startAfresh.TerminalString(), "oldestBlockInStateNum", oldestBlockInStateNum) return startAfresh, false } @@ -128,16 +128,16 @@ func computeSyncActions[T channelStatuser]( // The sequencer may have derived the safe chain // from channels sent by a previous batcher instance. m.Warn("safe head above newest block in state, clearing channel manager state", - "syncActions", startAfresh, - "newestBlockInState", eth.ToBlockID(newestBlockInState), + "syncActions", startAfresh.TerminalString(), + "newestBlockInState", eth.ToBlockID(newestBlockInState).TerminalString(), ) return startAfresh, false } if numBlocksToDequeue > 0 && blocks[numBlocksToDequeue-1].Hash() != safeL2.Hash { m.Warn("safe chain reorg, clearing channel manager state", - "syncActions", startAfresh, - "existingBlock", eth.ToBlockID(blocks[numBlocksToDequeue-1])) + "syncActions", startAfresh.TerminalString(), + "existingBlock", eth.ToBlockID(blocks[numBlocksToDequeue-1]).TerminalString()) return startAfresh, false } @@ -152,8 +152,8 @@ func computeSyncActions[T channelStatuser]( // that the derivation pipeline may have stalled // e.g. because of Holocene strict ordering rules. m.Warn("sequencer did not make expected progress", - "syncActions", startAfresh, - "existingBlock", ch.LatestL2()) + "syncActions", startAfresh.TerminalString(), + "existingBlock", ch.LatestL2().TerminalString()) return startAfresh, false } } @@ -179,6 +179,6 @@ func computeSyncActions[T channelStatuser]( channelsToPrune: numChannelsToPrune, blocksToLoad: allUnsafeBlocksAboveState, } - m.Debug("computed sync actions", "syncActions", a) + m.Debug("computed sync actions", "syncActions", a.TerminalString()) return a, false } From bdde878b12057ab2e8d596aa7f33fa388ccf6641 Mon Sep 17 00:00:00 2001 From: Axel Kingsley Date: Mon, 10 Mar 2025 14:35:42 -0500 Subject: [PATCH 124/130] update TODO #11693 and #14765 (#14766) --- op-supervisor/supervisor/backend/cross/safe_update.go | 2 -- op-supervisor/supervisor/backend/cross/unsafe_update.go | 3 --- op-supervisor/supervisor/backend/db/query.go | 8 ++++++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/op-supervisor/supervisor/backend/cross/safe_update.go b/op-supervisor/supervisor/backend/cross/safe_update.go index f942c851c5b..2e7c58bcf0c 100644 --- a/op-supervisor/supervisor/backend/cross/safe_update.go +++ b/op-supervisor/supervisor/backend/cross/safe_update.go @@ -36,8 +36,6 @@ type CrossSafeDeps interface { func CrossSafeUpdate(logger log.Logger, chainID eth.ChainID, d CrossSafeDeps) error { logger.Debug("Cross-safe update call") - // TODO(#11693): establish L1 reorg-lock of scopeDerivedFrom - // defer unlock once we are done checking the chain candidate, err := scopedCrossSafeUpdate(logger, chainID, d) if err == nil { // if we made progress, and no errors, then there is no need to bump the L1 scope yet. diff --git a/op-supervisor/supervisor/backend/cross/unsafe_update.go b/op-supervisor/supervisor/backend/cross/unsafe_update.go index 2550142acf8..47147838fbf 100644 --- a/op-supervisor/supervisor/backend/cross/unsafe_update.go +++ b/op-supervisor/supervisor/backend/cross/unsafe_update.go @@ -50,9 +50,6 @@ func CrossUnsafeUpdate(logger log.Logger, chainID eth.ChainID, d CrossUnsafeDeps hazards, err := CrossUnsafeHazards(d, logger, chainID, candidate) if err != nil { - // TODO(#11693): reorgs can be detected by checking if the error is ErrConflict, - // missing data is identified by ErrFuture, - // and other errors (e.g. DB issues) are identifier by remaining error kinds. return fmt.Errorf("failed to check for cross-chain hazards: %w", err) } diff --git a/op-supervisor/supervisor/backend/db/query.go b/op-supervisor/supervisor/backend/db/query.go index a103ef2f863..3fac6911104 100644 --- a/op-supervisor/supervisor/backend/db/query.go +++ b/op-supervisor/supervisor/backend/db/query.go @@ -29,6 +29,9 @@ func (db *ChainsDB) LatestBlockNum(chain eth.ChainID) (num uint64, ok bool) { return bl.Number, ok } +// IsCrossUnsafe checks if the given block is less than the cross-unsafe block number. +// It does not check if the block is actually cross-unsafe (ie, known in the database). +// TODO(#14765): Check Consistency of IsCrossUnsafe and return error if inconsistent func (db *ChainsDB) IsCrossUnsafe(chainID eth.ChainID, block eth.BlockID) error { v, ok := db.crossUnsafe.Get(chainID) if !ok { @@ -41,10 +44,12 @@ func (db *ChainsDB) IsCrossUnsafe(chainID eth.ChainID, block eth.BlockID) error if block.Number > crossUnsafe.Number { return types.ErrFuture } - // TODO(#11693): make cross-unsafe reorg safe return nil } +// ParentBlock returns a block with a number one less than the given block number. +// It does not check if the block obtained is truly the parent of the given block. +// TODO(#14765): Check Consistency of ParentBlock (or replace with FindSealedBlock) func (db *ChainsDB) ParentBlock(chainID eth.ChainID, parentOf eth.BlockID) (parent eth.BlockID, err error) { logDB, ok := db.logDBs.Get(chainID) if !ok { @@ -53,7 +58,6 @@ func (db *ChainsDB) ParentBlock(chainID eth.ChainID, parentOf eth.BlockID) (pare if parentOf.Number == 0 { return eth.BlockID{}, nil } - // TODO(#11693): make parent-lookup reorg safe got, err := logDB.FindSealedBlock(parentOf.Number - 1) if err != nil { return eth.BlockID{}, err From 08e374417d41e98103cbd97b6c4e68f2473a64e9 Mon Sep 17 00:00:00 2001 From: Inphi Date: Mon, 10 Mar 2025 16:24:08 -0400 Subject: [PATCH 125/130] op-program: Fix todo (#14773) --- op-program/chainconfig/chaincfg.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/op-program/chainconfig/chaincfg.go b/op-program/chainconfig/chaincfg.go index 77fcf1ada7d..46cb7818f7f 100644 --- a/op-program/chainconfig/chaincfg.go +++ b/op-program/chainconfig/chaincfg.go @@ -108,7 +108,7 @@ func mustLoadChainConfig(name string) *params.ChainConfig { } func DependencySetByChainID(chainID eth.ChainID) (depset.DependencySet, error) { - // TODO(#13887): Load from the superchain registry when available. + // TODO(#14771): Load from the superchain registry when available. return dependencySetByChainID(chainID, customChainConfigFS) } From cbe992160b4978a49dd92949106531f7bd6e2029 Mon Sep 17 00:00:00 2001 From: smartcontracts Date: Mon, 10 Mar 2025 16:06:47 -0600 Subject: [PATCH 126/130] feat: interop portal updates (#14664) * feat: AnchorStateRegistry as source of truth Updates the OptimismPortal to use the AnchorStateRegistry as the source of truth for the validity of Dispute Game contracts. * feat: new eth lockbox (#285) * feat: create new eth lockbox contract with interface * chore: add it on the deployment scripts (wip) * feat: create the test base to check if the setup for it is working * feat: add all eth lockbox tests * fix: opcm deployment script issue * fix: lockbox tests * feat: add no withdrawal tx unlock eth check * chore: check proper initialization on test * fix: pre-pr fixes * chore: run pre-pr * fix: opcm tests related to eth lockbox * feat: add admin owner check on authorize portal and lockbox as well * refactor: add prefix to errors and declare them on contract * chore add no withdrawal tx comment * feat: authorize portal in lockbox on the opcm * chore: add one more check for eth lockbox * fix: intializable and specs tests * fix: pre-pr * feat: integrate portal with lockbox (#291) * feat: integrate portal with lockbox * fix: migrate natspec Co-authored-by: Disco <131301107+0xDiscotech@users.noreply.github.com> * fix: delete natspec Co-authored-by: Disco <131301107+0xDiscotech@users.noreply.github.com> * fix: natspec nits * chore: natspec wording * chore: function order * chore: remove internals * feat: add eth lockbox as portal arg on opcm * feat: add checks on scripts and unit tests * feat: lock and unlock tests * feat: add migrate liquidity tests * fix: other failing portal tests * chore: run pre-pr * chore: improve lock and unlock checks on tests * chore: undo portal version change and run pre-pr * refactor: enhance expect call checks * fix: fork test string error * fix: just test failing tests * refactor: pao base * refactor: setup portal and lockbox integration on initialization * feat: update lockbox setter * chore: update pao naming on tests * chore: portal comment * chore: update version and run pre-pr * fix: spec * fix: lockbox storage layout * fix: pr comments and failing test * feat: portal upgrade test * chore: remove portal balance check on chain assertions * fix: error string on chain assertions * feat: add test for portal unsafe target * fix: approval --------- Co-authored-by: Disco <131301107+0xDiscotech@users.noreply.github.com> * fix: semver lock * fix: failing tests after merge * fix: interface import * chore: add lockbox pao matches final system owner check * fix: pr comments (#293) * chore: remove unused function * chore: add pao validation checks over portal and lockbox * refactor: use interface as arg type instead of address on functions * fix: comment max length * refactor: update lockbox param type as interface * refactor: use interface as type on migrate liquidity and authorize lockbox functions * chore: run pre pr * fix: high fuzz runs failing test using mostly assume not forge address * refactor: use ioptimis portal instead of ioptimism portal2 everywhere * chore: undo changes on unrelated files to the pr * feat: add Super Root specific method to OptimismPortal Updates the OptimismPortal to include a method that allows users to prove against Super Roots for interop. * fix: pr second comments (#295) * chore: undo initializable v5 test changes * chore: remove portal already authorized check * chore: remove portal already authorized check * refactor: rename pao to proxy admin owner * fix: pre pr * fix: test upgrade fails (#296) * fix: test upgrade fails * feat: handle fork state on eth lockbox tests * chore: enhance comments * chore: run pre-pr * fix: semgrep * fix: comments * chore: address path where is not a fork to get the lockbox * chore: run pre pr * fix: pr fixes (#298) * chore: enhance opcm comment * fix: same proxy admin owner typo * feat: add insufficient balance check * chore: add natspec comments on lockbox migration process * chore: pre pr * fix: add lockbox on implementations struct to fix go test (#300) * feat: AnchorStateRegistry as source of truth Updates the OptimismPortal to use the AnchorStateRegistry as the source of truth for the validity of Dispute Game contracts. * feat: add Super Root specific method to OptimismPortal Updates the OptimismPortal to include a method that allows users to prove against Super Roots for interop. * feat: interop portal OPCM updates * fix: remove old respectedGameType check from portal test * fix: deploy new dispute games in OPCM * feat: add upgrade 15 test path * feat: integrate into opcm fork tests * fix: corrected checks for ASR * fix: rebase tweaks * fix: OPCM integration * fix: OPCM stack too deep * fix: pre pr * fix: update portal implementation on opcm upgrade * fix: call upgrade on portal without upgrading any impl * fix: iopcm interface for older versions already deployed * refactor: use minimal interface for opcm without lockbox * fix: import * chore: undo changes on opcm carried when resolving conflicts (#302) * fix: include lockbox on reinitialization test and add todos with issue number (#303) * fix: include lockbox on reinitialization test * fix: polish nits * refactor: get eth lockbox on test condition * chore: add todos pointing to the issue * fix: add ETHLockbox to op-deployer * fix: add evm tags to ETHLockboxImpl * fix: evm tags for ethLockboxProxy too * fix: remove certain ETHLockbox checks * fix: properly add ETHLockbox to state * fix: handle deposit tests and kill L2oo tests * fix: undo change in user test * fix: kill more L2OO tests * fix: bug in withdrawal validity test * fix: pr review (#309) * chore: remove unnecessary cast * refactor: add lockbox on eth migrated event * chore: rename pa owner to pa owned base * refactor: add amount on liquidity received and migrated events * fix: format * refactor: add amount on liquidity migrated event * chore: pre-pr --------- Co-authored-by: agusduha * fix: some remaining merge issues * fix: go linting error * fix: remove old test skips * fix: unskip v2 upgrade tests * fix: skip v2 upgrade tests again --------- Co-authored-by: Disco <131301107+0xDiscotech@users.noreply.github.com> Co-authored-by: AgusDuha <81362284+agusduha@users.noreply.github.com> Co-authored-by: agusduha --- op-chain-ops/genesis/config.go | 3 + op-chain-ops/interopgen/configs.go | 19 +- op-chain-ops/interopgen/deploy.go | 37 +- op-chain-ops/interopgen/deployments.go | 2 + op-chain-ops/interopgen/recipe.go | 19 +- op-chain-ops/solc/types.go | 2 + .../pkg/deployer/bootstrap/validator.go | 1 + .../pkg/deployer/bootstrap/validator_test.go | 1 + op-deployer/pkg/deployer/inspect/l1.go | 6 + .../deployer/integration_test/apply_test.go | 13 +- .../pkg/deployer/opcm/dispute_game_factory.go | 1 - .../pkg/deployer/opcm/implementations.go | 1 + op-deployer/pkg/deployer/opcm/opchain.go | 3 + .../pkg/deployer/pipeline/dispute_games.go | 9 +- .../pkg/deployer/pipeline/implementations.go | 1 + op-deployer/pkg/deployer/pipeline/opchain.go | 16 +- op-deployer/pkg/deployer/standard/standard.go | 1 + .../pkg/deployer/state/chain_intent.go | 1 + op-deployer/pkg/deployer/state/state.go | 2 + .../deployer/upgrade/v2_0_0/upgrade_test.go | 2 + op-e2e/actions/helpers/user_test.go | 10 +- op-e2e/actions/proposer/l2_proposer_test.go | 7 - op-e2e/config/init.go | 39 +- op-e2e/system/bridge/validity_test.go | 23 +- op-e2e/system/bridge/withdrawal_test.go | 4 - op-e2e/system/helpers/tx_helper.go | 4 +- op-e2e/system/helpers/withdrawal_helper.go | 7 + op-e2e/system/proofs/proposer_l2oo_test.go | 83 -- packages/contracts-bedrock/foundry.toml | 8 + .../interfaces/L1/IETHLockbox.sol | 37 + .../interfaces/L1/IOPContractsManager.sol | 62 +- .../L1/IOPContractsManagerLegacyUpgrade.sol | 21 + .../interfaces/L1/IOPPrestateUpdater.sol | 142 --- .../interfaces/L1/IOptimismPortal2.sol | 83 +- .../interfaces/L1/IOptimismPortalInterop.sol | 81 +- .../interfaces/L1/IProxyAdminOwnedBase.sol | 6 + .../interfaces/L1/ISystemConfig.sol | 8 +- .../interfaces/L1/ISystemConfigInterop.sol | 7 +- .../dispute/IAnchorStateRegistry.sol | 26 +- .../interfaces/safe/IDeputyGuardianModule.sol | 13 +- .../universal/IReinitializableBase.sol | 11 + packages/contracts-bedrock/justfile | 7 +- .../scripts/checks/check-frozen-files.sh | 1 + .../scripts/checks/interfaces/main.go | 3 + .../scripts/checks/reinitializer/main.go | 25 +- .../scripts/checks/reinitializer/main_test.go | 112 +++ .../scripts/deploy/ChainAssertions.sol | 38 +- .../scripts/deploy/Deploy.s.sol | 13 +- .../deploy/DeployImplementations.s.sol | 77 +- .../scripts/deploy/DeployOPChain.s.sol | 66 +- .../deploy/ReadImplementationAddresses.s.sol | 10 + .../scripts/deploy/SetDisputeGameImpl.s.sol | 27 +- .../scripts/go-ffi/differential-testing.go | 61 +- .../contracts-bedrock/scripts/go-ffi/utils.go | 85 ++ .../scripts/libraries/DeployUtils.sol | 15 +- .../scripts/libraries/Types.sol | 1 + .../snapshots/abi/AnchorStateRegistry.json | 136 ++- .../snapshots/abi/DeputyGuardianModule.json | 54 +- .../snapshots/abi/ETHLockbox.json | 321 +++++++ .../snapshots/abi/OPContractsManager.json | 25 + .../OPContractsManagerContractsContainer.json | 10 + .../abi/OPContractsManagerDeployer.json | 15 + .../OPContractsManagerDeployerInterop.json | 15 + .../abi/OPContractsManagerGameTypeAdder.json | 10 + .../abi/OPContractsManagerUpgrader.json | 10 + .../snapshots/abi/OptimismPortal2.json | 385 +++++++-- .../snapshots/abi/OptimismPortalInterop.json | 385 +++++++-- .../snapshots/abi/SystemConfig.json | 49 ++ .../snapshots/abi/SystemConfigInterop.json | 54 ++ .../snapshots/semver-lock.json | 32 +- .../storageLayout/AnchorStateRegistry.json | 32 +- .../snapshots/storageLayout/ETHLockbox.json | 37 + .../OPContractsManagerContractsContainer.json | 2 +- .../storageLayout/OptimismPortal2.json | 33 +- .../storageLayout/OptimismPortalInterop.json | 33 +- .../snapshots/storageLayout/SystemConfig.json | 7 + .../storageLayout/SystemConfigInterop.json | 7 + .../contracts-bedrock/src/L1/ETHLockbox.sol | 173 ++++ .../src/L1/OPContractsManager.sol | 240 ++++-- .../src/L1/OptimismPortal2.sol | 649 ++++++++------ .../src/L1/OptimismPortalInterop.sol | 17 +- .../src/L1/ProxyAdminOwnedBase.sol | 26 + .../contracts-bedrock/src/L1/SystemConfig.sol | 26 +- .../src/L1/SystemConfigInterop.sol | 10 +- .../src/dispute/AnchorStateRegistry.sol | 106 ++- .../src/libraries/Encoding.sol | 32 + .../src/libraries/Hashing.sol | 7 + .../src/libraries/PortalErrors.sol | 40 - .../contracts-bedrock/src/libraries/Types.sol | 18 + .../src/safe/DeputyGuardianModule.sol | 67 +- .../src/universal/ReinitializableBase.sol | 25 + .../test/L1/ETHLockbox.t.sol | 484 +++++++++++ .../test/L1/L1StandardBridge.t.sol | 38 +- .../test/L1/OPContractsManager.t.sol | 193 ++++- .../test/L1/OptimismPortal2.t.sol | 815 ++++++++++++++---- .../test/L1/OptimismPortalInterop.t.sol | 5 +- .../test/L1/SystemConfig.t.sol | 76 +- .../test/dispute/AnchorStateRegistry.t.sol | 362 ++++++-- .../test/dispute/FaultDisputeGame.t.sol | 6 + .../test/dispute/SuperFaultDisputeGame.t.sol | 7 +- .../test/invariants/OptimismPortal2.t.sol | 9 +- .../test/invariants/SystemConfig.t.sol | 3 +- .../test/kontrol/proofs/OptimismPortal2.k.sol | 5 +- .../test/libraries/Encoding.t.sol | 198 ++++- .../test/libraries/Hashing.t.sol | 54 ++ .../test/opcm/DeployImplementations.t.sol | 24 +- .../test/opcm/SetDisputeGameImpl.t.sol | 29 +- .../test/opcm/UpgradeOPChain.t.sol | 12 +- .../test/safe/DeputyGuardianModule.t.sol | 165 ++-- .../test/safe/DeputyPauseModule.t.sol | 3 +- .../contracts-bedrock/test/setup/Events.sol | 4 + .../test/setup/FFIInterface.sol | 22 + .../test/setup/ForkLive.s.sol | 47 +- .../contracts-bedrock/test/setup/Setup.sol | 15 +- .../test/universal/ReinitializableBase.t.sol | 36 + .../test/universal/Specs.t.sol | 116 ++- .../test/vendor/Initializable.t.sol | 55 +- 117 files changed, 5664 insertions(+), 1570 deletions(-) delete mode 100644 op-e2e/system/proofs/proposer_l2oo_test.go create mode 100644 packages/contracts-bedrock/interfaces/L1/IETHLockbox.sol create mode 100644 packages/contracts-bedrock/interfaces/L1/IOPContractsManagerLegacyUpgrade.sol delete mode 100644 packages/contracts-bedrock/interfaces/L1/IOPPrestateUpdater.sol create mode 100644 packages/contracts-bedrock/interfaces/L1/IProxyAdminOwnedBase.sol create mode 100644 packages/contracts-bedrock/interfaces/universal/IReinitializableBase.sol create mode 100644 packages/contracts-bedrock/snapshots/abi/ETHLockbox.json create mode 100644 packages/contracts-bedrock/snapshots/storageLayout/ETHLockbox.json create mode 100644 packages/contracts-bedrock/src/L1/ETHLockbox.sol create mode 100644 packages/contracts-bedrock/src/L1/ProxyAdminOwnedBase.sol delete mode 100644 packages/contracts-bedrock/src/libraries/PortalErrors.sol create mode 100644 packages/contracts-bedrock/src/universal/ReinitializableBase.sol create mode 100644 packages/contracts-bedrock/test/L1/ETHLockbox.t.sol create mode 100644 packages/contracts-bedrock/test/universal/ReinitializableBase.t.sol diff --git a/op-chain-ops/genesis/config.go b/op-chain-ops/genesis/config.go index 9aef9f6b7c3..a249365efa6 100644 --- a/op-chain-ops/genesis/config.go +++ b/op-chain-ops/genesis/config.go @@ -1111,6 +1111,8 @@ type L1Deployments struct { OptimismMintableERC20FactoryProxy common.Address `json:"OptimismMintableERC20FactoryProxy"` OptimismPortal common.Address `json:"OptimismPortal"` OptimismPortalProxy common.Address `json:"OptimismPortalProxy"` + ETHLockbox common.Address `json:"ETHLockbox"` + ETHLockboxProxy common.Address `json:"ETHLockboxProxy"` ProxyAdmin common.Address `json:"ProxyAdmin"` SystemConfig common.Address `json:"SystemConfig"` SystemConfigProxy common.Address `json:"SystemConfigProxy"` @@ -1156,6 +1158,7 @@ func (d *L1Deployments) Check(deployConfig *DeployConfig) error { name == "DataAvailabilityChallengeProxy") { continue } + if val.Field(i).Interface().(common.Address) == (common.Address{}) { return fmt.Errorf("%s is not set", name) } diff --git a/op-chain-ops/interopgen/configs.go b/op-chain-ops/interopgen/configs.go index c3113d240e1..a365fd6cb78 100644 --- a/op-chain-ops/interopgen/configs.go +++ b/op-chain-ops/interopgen/configs.go @@ -74,15 +74,16 @@ type L2Config struct { Challenger common.Address SystemConfigOwner common.Address genesis.L2InitializationConfig - Prefund map[common.Address]*big.Int - SaltMixer string - GasLimit uint64 - DisputeGameType uint32 - DisputeAbsolutePrestate common.Hash - DisputeMaxGameDepth uint64 - DisputeSplitDepth uint64 - DisputeClockExtension uint64 - DisputeMaxClockDuration uint64 + Prefund map[common.Address]*big.Int + SaltMixer string + GasLimit uint64 + DisputeGameUsesSuperRoots bool + DisputeGameType uint32 + DisputeAbsolutePrestate common.Hash + DisputeMaxGameDepth uint64 + DisputeSplitDepth uint64 + DisputeClockExtension uint64 + DisputeMaxClockDuration uint64 } func (c *L2Config) Check(log log.Logger) error { diff --git a/op-chain-ops/interopgen/deploy.go b/op-chain-ops/interopgen/deploy.go index 4ef1d32b658..84a06245132 100644 --- a/op-chain-ops/interopgen/deploy.go +++ b/op-chain-ops/interopgen/deploy.go @@ -198,24 +198,25 @@ func DeployL2ToL1(l1Host *script.Host, superCfg *SuperchainConfig, superDeployme l1Host.SetTxOrigin(cfg.Deployer) output, err := opcm.DeployOPChain(l1Host, opcm.DeployOPChainInput{ - OpChainProxyAdminOwner: cfg.ProxyAdminOwner, - SystemConfigOwner: cfg.SystemConfigOwner, - Batcher: cfg.BatchSenderAddress, - UnsafeBlockSigner: cfg.P2PSequencerAddress, - Proposer: cfg.Proposer, - Challenger: cfg.Challenger, - BasefeeScalar: cfg.GasPriceOracleBaseFeeScalar, - BlobBaseFeeScalar: cfg.GasPriceOracleBlobBaseFeeScalar, - L2ChainId: new(big.Int).SetUint64(cfg.L2ChainID), - Opcm: superDeployment.Opcm, - SaltMixer: cfg.SaltMixer, - GasLimit: cfg.GasLimit, - DisputeGameType: cfg.DisputeGameType, - DisputeAbsolutePrestate: cfg.DisputeAbsolutePrestate, - DisputeMaxGameDepth: cfg.DisputeMaxGameDepth, - DisputeSplitDepth: cfg.DisputeSplitDepth, - DisputeClockExtension: cfg.DisputeClockExtension, - DisputeMaxClockDuration: cfg.DisputeMaxClockDuration, + OpChainProxyAdminOwner: cfg.ProxyAdminOwner, + SystemConfigOwner: cfg.SystemConfigOwner, + Batcher: cfg.BatchSenderAddress, + UnsafeBlockSigner: cfg.P2PSequencerAddress, + Proposer: cfg.Proposer, + Challenger: cfg.Challenger, + BasefeeScalar: cfg.GasPriceOracleBaseFeeScalar, + BlobBaseFeeScalar: cfg.GasPriceOracleBlobBaseFeeScalar, + L2ChainId: new(big.Int).SetUint64(cfg.L2ChainID), + Opcm: superDeployment.Opcm, + SaltMixer: cfg.SaltMixer, + GasLimit: cfg.GasLimit, + DisputeGameUsesSuperRoots: cfg.DisputeGameUsesSuperRoots, + DisputeGameType: cfg.DisputeGameType, + DisputeAbsolutePrestate: cfg.DisputeAbsolutePrestate, + DisputeMaxGameDepth: cfg.DisputeMaxGameDepth, + DisputeSplitDepth: cfg.DisputeSplitDepth, + DisputeClockExtension: cfg.DisputeClockExtension, + DisputeMaxClockDuration: cfg.DisputeMaxClockDuration, }) if err != nil { return nil, fmt.Errorf("failed to deploy L2 OP chain: %w", err) diff --git a/op-chain-ops/interopgen/deployments.go b/op-chain-ops/interopgen/deployments.go index 6b9e34b7a87..48a2b93b224 100644 --- a/op-chain-ops/interopgen/deployments.go +++ b/op-chain-ops/interopgen/deployments.go @@ -16,6 +16,7 @@ type Implementations struct { OpcmUpgrader common.Address `json:"OPCMUpgrader"` DelayedWETHImpl common.Address `json:"DelayedWETHImpl"` OptimismPortalImpl common.Address `json:"OptimismPortalImpl"` + ETHLockboxImpl common.Address `json:"ETHLockboxImpl"` PreimageOracleSingleton common.Address `json:"PreimageOracleSingleton"` MipsSingleton common.Address `json:"MipsSingleton"` SystemConfigImpl common.Address `json:"SystemConfigImpl"` @@ -51,6 +52,7 @@ type L2OpchainDeployment struct { L1CrossDomainMessengerProxy common.Address `json:"L1CrossDomainMessengerProxy"` // Fault proof contracts below. OptimismPortalProxy common.Address `json:"OptimismPortalProxy"` + ETHLockboxProxy common.Address `json:"ETHLockboxProxy"` DisputeGameFactoryProxy common.Address `json:"DisputeGameFactoryProxy"` AnchorStateRegistryProxy common.Address `json:"AnchorStateRegistryProxy"` FaultDisputeGame common.Address `json:"FaultDisputeGame"` diff --git a/op-chain-ops/interopgen/recipe.go b/op-chain-ops/interopgen/recipe.go index 6cf3265925c..b835716ea58 100644 --- a/op-chain-ops/interopgen/recipe.go +++ b/op-chain-ops/interopgen/recipe.go @@ -275,15 +275,16 @@ func (r *InteropDevL2Recipe) build(l1ChainID uint64, addrs devkeys.Addresses) (* UseAltDA: false, }, }, - Prefund: make(map[common.Address]*big.Int), - SaltMixer: "", - GasLimit: 60_000_000, - DisputeGameType: 1, // PERMISSIONED_CANNON Game Type - DisputeAbsolutePrestate: common.HexToHash("0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c"), - DisputeMaxGameDepth: 73, - DisputeSplitDepth: 30, - DisputeClockExtension: 10800, // 3 hours (input in seconds) - DisputeMaxClockDuration: 302400, // 3.5 days (input in seconds) + Prefund: make(map[common.Address]*big.Int), + SaltMixer: "", + GasLimit: 60_000_000, + DisputeGameUsesSuperRoots: true, + DisputeGameType: 1, // PERMISSIONED_CANNON Game Type + DisputeAbsolutePrestate: common.HexToHash("0x038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c"), + DisputeMaxGameDepth: 73, + DisputeSplitDepth: 30, + DisputeClockExtension: 10800, // 3 hours (input in seconds) + DisputeMaxClockDuration: 302400, // 3.5 days (input in seconds) } l2Users := devkeys.ChainUserKeys(new(big.Int).SetUint64(r.ChainID)) diff --git a/op-chain-ops/solc/types.go b/op-chain-ops/solc/types.go index ab6568183ac..a7d24d208da 100644 --- a/op-chain-ops/solc/types.go +++ b/op-chain-ops/solc/types.go @@ -263,6 +263,8 @@ type Expression struct { ReferencedDeclaration int `json:"referencedDeclaration,omitempty"` ArgumentTypes []AstTypeDescriptions `json:"argumentTypes,omitempty"` Value interface{} `json:"value,omitempty"` + Kind string `json:"kind,omitempty"` + Expression *Expression `json:"expression,omitempty"` } type ForgeArtifact struct { diff --git a/op-deployer/pkg/deployer/bootstrap/validator.go b/op-deployer/pkg/deployer/bootstrap/validator.go index 5daaf890d56..b37283fe975 100644 --- a/op-deployer/pkg/deployer/bootstrap/validator.go +++ b/op-deployer/pkg/deployer/bootstrap/validator.go @@ -47,6 +47,7 @@ type ValidatorInput struct { ProtocolVersionsImpl common.Address `json:"protocolVersionsImpl"` L1ERC721BridgeImpl common.Address `json:"l1ERC721BridgeImpl"` OptimismPortalImpl common.Address `json:"optimismPortalImpl"` + ETHLockboxImpl common.Address `json:"ethLockboxImpl" evm:"ethLockboxImpl"` SystemConfigImpl common.Address `json:"systemConfigImpl"` OptimismMintableERC20FactoryImpl common.Address `json:"optimismMintableERC20FactoryImpl"` L1CrossDomainMessengerImpl common.Address `json:"l1CrossDomainMessengerImpl"` diff --git a/op-deployer/pkg/deployer/bootstrap/validator_test.go b/op-deployer/pkg/deployer/bootstrap/validator_test.go index ff58e714867..e2450b599be 100644 --- a/op-deployer/pkg/deployer/bootstrap/validator_test.go +++ b/op-deployer/pkg/deployer/bootstrap/validator_test.go @@ -62,6 +62,7 @@ func testValidator(t *testing.T, forkRPCURL string, loc *artifacts.Locator, rele ProtocolVersionsImpl: common.Address{'2'}, L1ERC721BridgeImpl: common.Address{'3'}, OptimismPortalImpl: common.Address{'4'}, + ETHLockboxImpl: common.Address{'5'}, SystemConfigImpl: common.Address{'5'}, OptimismMintableERC20FactoryImpl: common.Address{'6'}, L1CrossDomainMessengerImpl: common.Address{'7'}, diff --git a/op-deployer/pkg/deployer/inspect/l1.go b/op-deployer/pkg/deployer/inspect/l1.go index e8bccb0238f..b5205c42c96 100644 --- a/op-deployer/pkg/deployer/inspect/l1.go +++ b/op-deployer/pkg/deployer/inspect/l1.go @@ -71,6 +71,8 @@ func (l L1Contracts) AsL1Deployments() *genesis.L1Deployments { OptimismMintableERC20FactoryProxy: l.OpChainDeployment.OptimismMintableERC20FactoryProxyAddress, OptimismPortal: l.ImplementationsDeployment.OptimismPortalImplAddress, OptimismPortalProxy: l.OpChainDeployment.OptimismPortalProxyAddress, + ETHLockbox: l.ImplementationsDeployment.ETHLockboxImplAddress, + ETHLockboxProxy: l.OpChainDeployment.ETHLockboxProxyAddress, ProxyAdmin: l.OpChainDeployment.ProxyAdminAddress, SystemConfig: l.ImplementationsDeployment.SystemConfigImplAddress, SystemConfigProxy: l.OpChainDeployment.SystemConfigProxyAddress, @@ -98,6 +100,7 @@ type OpChainDeployment struct { L1StandardBridgeProxyAddress common.Address `json:"l1StandardBridgeProxyAddress"` L1CrossDomainMessengerProxyAddress common.Address `json:"l1CrossDomainMessengerProxyAddress"` OptimismPortalProxyAddress common.Address `json:"optimismPortalProxyAddress"` + ETHLockboxProxyAddress common.Address `json:"ethLockboxProxyAddress"` DisputeGameFactoryProxyAddress common.Address `json:"disputeGameFactoryProxyAddress"` AnchorStateRegistryProxyAddress common.Address `json:"anchorStateRegistryProxyAddress"` AnchorStateRegistryImplAddress common.Address `json:"anchorStateRegistryImplAddress"` @@ -113,6 +116,7 @@ type ImplementationsDeployment struct { OpcmAddress common.Address `json:"opcmAddress"` DelayedWETHImplAddress common.Address `json:"delayedWETHImplAddress"` OptimismPortalImplAddress common.Address `json:"optimismPortalImplAddress"` + ETHLockboxImplAddress common.Address `json:"ethLockboxImplAddress"` PreimageOracleSingletonAddress common.Address `json:"preimageOracleSingletonAddress"` MipsSingletonAddress common.Address `json:"mipsSingletonAddress"` SystemConfigImplAddress common.Address `json:"systemConfigImplAddress"` @@ -169,6 +173,7 @@ func L1(globalState *state.State, chainID common.Hash) (*L1Contracts, error) { L1StandardBridgeProxyAddress: chainState.L1StandardBridgeProxyAddress, L1CrossDomainMessengerProxyAddress: chainState.L1CrossDomainMessengerProxyAddress, OptimismPortalProxyAddress: chainState.OptimismPortalProxyAddress, + ETHLockboxProxyAddress: chainState.ETHLockboxProxyAddress, DisputeGameFactoryProxyAddress: chainState.DisputeGameFactoryProxyAddress, AnchorStateRegistryProxyAddress: chainState.AnchorStateRegistryProxyAddress, FaultDisputeGameAddress: chainState.FaultDisputeGameAddress, @@ -182,6 +187,7 @@ func L1(globalState *state.State, chainID common.Hash) (*L1Contracts, error) { OpcmAddress: globalState.ImplementationsDeployment.OpcmAddress, DelayedWETHImplAddress: globalState.ImplementationsDeployment.DelayedWETHImplAddress, OptimismPortalImplAddress: globalState.ImplementationsDeployment.OptimismPortalImplAddress, + ETHLockboxImplAddress: globalState.ImplementationsDeployment.ETHLockboxImplAddress, PreimageOracleSingletonAddress: globalState.ImplementationsDeployment.PreimageOracleSingletonAddress, MipsSingletonAddress: globalState.ImplementationsDeployment.MipsSingletonAddress, SystemConfigImplAddress: globalState.ImplementationsDeployment.SystemConfigImplAddress, diff --git a/op-deployer/pkg/deployer/integration_test/apply_test.go b/op-deployer/pkg/deployer/integration_test/apply_test.go index efdd4235da5..134d9a34c2c 100644 --- a/op-deployer/pkg/deployer/integration_test/apply_test.go +++ b/op-deployer/pkg/deployer/integration_test/apply_test.go @@ -364,7 +364,7 @@ func TestProofParamOverrides(t *testing.T) { { "disputeGameFinalityDelaySeconds", uint64Caster, - st.ImplementationsDeployment.OptimismPortalImplAddress, + st.ImplementationsDeployment.AnchorStateRegistryImplAddress, }, { "faultGameAbsolutePrestate", @@ -529,6 +529,7 @@ func TestAdditionalDisputeGames(t *testing.T) { intent.Chains[0].AdditionalDisputeGames = []state.AdditionalDisputeGame{ { ChainProofParams: state.ChainProofParams{ + DisputeGameUsesSuperRoots: false, DisputeGameType: 255, DisputeAbsolutePrestate: standard.DisputeAbsolutePrestate, DisputeMaxGameDepth: 50, @@ -747,10 +748,11 @@ func validateSuperchainDeployment(t *testing.T, st *state.State, cg codeGetter, func validateOPChainDeployment(t *testing.T, cg codeGetter, st *state.State, intent *state.Intent, govEnabled bool) { // Validate that the implementation addresses are always set, even in subsequent deployments // that pull from an existing OPCM deployment. - implAddrs := []struct { + type addrTuple struct { name string addr common.Address - }{ + } + implAddrs := []addrTuple{ {"DelayedWETHImplAddress", st.ImplementationsDeployment.DelayedWETHImplAddress}, {"OptimismPortalImplAddress", st.ImplementationsDeployment.OptimismPortalImplAddress}, {"SystemConfigImplAddress", st.ImplementationsDeployment.SystemConfigImplAddress}, @@ -762,6 +764,11 @@ func validateOPChainDeployment(t *testing.T, cg codeGetter, st *state.State, int {"MipsSingletonAddress", st.ImplementationsDeployment.MipsSingletonAddress}, {"PreimageOracleSingletonAddress", st.ImplementationsDeployment.PreimageOracleSingletonAddress}, } + + if !intent.L1ContractsLocator.IsTag() { + implAddrs = append(implAddrs, addrTuple{"ETHLockboxImplAddress", st.ImplementationsDeployment.ETHLockboxImplAddress}) + } + for _, addr := range implAddrs { require.NotEmpty(t, addr.addr, "%s should be set", addr.name) code := cg(t, addr.addr) diff --git a/op-deployer/pkg/deployer/opcm/dispute_game_factory.go b/op-deployer/pkg/deployer/opcm/dispute_game_factory.go index 6e79f0683a9..61480523510 100644 --- a/op-deployer/pkg/deployer/opcm/dispute_game_factory.go +++ b/op-deployer/pkg/deployer/opcm/dispute_game_factory.go @@ -8,7 +8,6 @@ import ( type SetDisputeGameImplInput struct { Factory common.Address Impl common.Address - Portal common.Address AnchorStateRegistry common.Address GameType uint32 } diff --git a/op-deployer/pkg/deployer/opcm/implementations.go b/op-deployer/pkg/deployer/opcm/implementations.go index 36efedde9a0..917754cbd56 100644 --- a/op-deployer/pkg/deployer/opcm/implementations.go +++ b/op-deployer/pkg/deployer/opcm/implementations.go @@ -37,6 +37,7 @@ type DeployImplementationsOutput struct { OpcmUpgrader common.Address `json:"opcmUpgraderAddress"` DelayedWETHImpl common.Address `json:"delayedWETHImplAddress"` OptimismPortalImpl common.Address `json:"optimismPortalImplAddress"` + ETHLockboxImpl common.Address `json:"ethLockboxImplAddress" evm:"ethLockboxImpl"` PreimageOracleSingleton common.Address `json:"preimageOracleSingletonAddress"` MipsSingleton common.Address `json:"mipsSingletonAddress"` SystemConfigImpl common.Address `json:"systemConfigImplAddress"` diff --git a/op-deployer/pkg/deployer/opcm/opchain.go b/op-deployer/pkg/deployer/opcm/opchain.go index a9015044b9e..813b7435170 100644 --- a/op-deployer/pkg/deployer/opcm/opchain.go +++ b/op-deployer/pkg/deployer/opcm/opchain.go @@ -33,6 +33,7 @@ type DeployOPChainInput struct { SaltMixer string GasLimit uint64 + DisputeGameUsesSuperRoots bool DisputeGameType uint32 DisputeAbsolutePrestate common.Hash DisputeMaxGameDepth uint64 @@ -63,6 +64,7 @@ type DeployOPChainOutput struct { L1CrossDomainMessengerProxy common.Address // Fault proof contracts below. OptimismPortalProxy common.Address + ETHLockboxProxy common.Address `evm:"ethLockboxProxy"` DisputeGameFactoryProxy common.Address AnchorStateRegistryProxy common.Address FaultDisputeGame common.Address @@ -92,6 +94,7 @@ type ReadImplementationAddressesInput struct { type ReadImplementationAddressesOutput struct { DelayedWETH common.Address OptimismPortal common.Address + ETHLockbox common.Address `evm:"ethLockbox"` SystemConfig common.Address L1CrossDomainMessenger common.Address L1ERC721Bridge common.Address diff --git a/op-deployer/pkg/deployer/pipeline/dispute_games.go b/op-deployer/pkg/deployer/pipeline/dispute_games.go index dd95398556c..5363ba7c497 100644 --- a/op-deployer/pkg/deployer/pipeline/dispute_games.go +++ b/op-deployer/pkg/deployer/pipeline/dispute_games.go @@ -127,13 +127,12 @@ func deployDisputeGame( lgr.Info("setting dispute game impl on factory", "respected", game.MakeRespected) sdgiInput := opcm.SetDisputeGameImplInput{ - Factory: thisState.DisputeGameFactoryProxyAddress, - Impl: out.DisputeGameImpl, - GameType: game.DisputeGameType, - AnchorStateRegistry: thisState.AnchorStateRegistryProxyAddress, + Factory: thisState.DisputeGameFactoryProxyAddress, + Impl: out.DisputeGameImpl, + GameType: game.DisputeGameType, } if game.MakeRespected { - sdgiInput.Portal = thisState.OptimismPortalProxyAddress + sdgiInput.AnchorStateRegistry = thisState.AnchorStateRegistryProxyAddress } if err := opcm.SetDisputeGameImpl( env.L1ScriptHost, diff --git a/op-deployer/pkg/deployer/pipeline/implementations.go b/op-deployer/pkg/deployer/pipeline/implementations.go index ef591210b6d..a88bec43543 100644 --- a/op-deployer/pkg/deployer/pipeline/implementations.go +++ b/op-deployer/pkg/deployer/pipeline/implementations.go @@ -72,6 +72,7 @@ func DeployImplementations(env *Env, intent *state.Intent, st *state.State) erro OpcmUpgraderAddress: dio.OpcmUpgrader, DelayedWETHImplAddress: dio.DelayedWETHImpl, OptimismPortalImplAddress: dio.OptimismPortalImpl, + ETHLockboxImplAddress: dio.ETHLockboxImpl, PreimageOracleSingletonAddress: dio.PreimageOracleSingleton, MipsSingletonAddress: dio.MipsSingleton, SystemConfigImplAddress: dio.SystemConfigImpl, diff --git a/op-deployer/pkg/deployer/pipeline/opchain.go b/op-deployer/pkg/deployer/pipeline/opchain.go index 03d5b375671..3007aaa1f47 100644 --- a/op-deployer/pkg/deployer/pipeline/opchain.go +++ b/op-deployer/pkg/deployer/pipeline/opchain.go @@ -58,6 +58,7 @@ func DeployOPChain(env *Env, intent *state.Intent, st *state.State, chainID comm st.ImplementationsDeployment.DelayedWETHImplAddress = impls.DelayedWETH st.ImplementationsDeployment.OptimismPortalImplAddress = impls.OptimismPortal + st.ImplementationsDeployment.ETHLockboxImplAddress = impls.ETHLockbox st.ImplementationsDeployment.SystemConfigImplAddress = impls.SystemConfig st.ImplementationsDeployment.L1CrossDomainMessengerImplAddress = impls.L1CrossDomainMessenger st.ImplementationsDeployment.L1ERC721BridgeImplAddress = impls.L1ERC721Bridge @@ -73,12 +74,13 @@ func DeployOPChain(env *Env, intent *state.Intent, st *state.State, chainID comm func makeDCI(intent *state.Intent, thisIntent *state.ChainIntent, chainID common.Hash, st *state.State) (opcm.DeployOPChainInput, error) { proofParams, err := jsonutil.MergeJSON( state.ChainProofParams{ - DisputeGameType: standard.DisputeGameType, - DisputeAbsolutePrestate: standard.DisputeAbsolutePrestate, - DisputeMaxGameDepth: standard.DisputeMaxGameDepth, - DisputeSplitDepth: standard.DisputeSplitDepth, - DisputeClockExtension: standard.DisputeClockExtension, - DisputeMaxClockDuration: standard.DisputeMaxClockDuration, + DisputeGameUsesSuperRoots: standard.DisputeGameUsesSuperRoots, + DisputeGameType: standard.DisputeGameType, + DisputeAbsolutePrestate: standard.DisputeAbsolutePrestate, + DisputeMaxGameDepth: standard.DisputeMaxGameDepth, + DisputeSplitDepth: standard.DisputeSplitDepth, + DisputeClockExtension: standard.DisputeClockExtension, + DisputeMaxClockDuration: standard.DisputeMaxClockDuration, }, intent.GlobalDeployOverrides, thisIntent.DeployOverrides, @@ -100,6 +102,7 @@ func makeDCI(intent *state.Intent, thisIntent *state.ChainIntent, chainID common Opcm: st.ImplementationsDeployment.OpcmAddress, SaltMixer: st.Create2Salt.String(), // passing through salt generated at state initialization GasLimit: standard.GasLimit, + DisputeGameUsesSuperRoots: proofParams.DisputeGameUsesSuperRoots, DisputeGameType: proofParams.DisputeGameType, DisputeAbsolutePrestate: proofParams.DisputeAbsolutePrestate, DisputeMaxGameDepth: proofParams.DisputeMaxGameDepth, @@ -123,6 +126,7 @@ func makeChainState(chainID common.Hash, dco opcm.DeployOPChainOutput) *state.Ch L1StandardBridgeProxyAddress: dco.L1StandardBridgeProxy, L1CrossDomainMessengerProxyAddress: dco.L1CrossDomainMessengerProxy, OptimismPortalProxyAddress: dco.OptimismPortalProxy, + ETHLockboxProxyAddress: dco.ETHLockboxProxy, DisputeGameFactoryProxyAddress: dco.DisputeGameFactoryProxy, AnchorStateRegistryProxyAddress: dco.AnchorStateRegistryProxy, FaultDisputeGameAddress: dco.FaultDisputeGame, diff --git a/op-deployer/pkg/deployer/standard/standard.go b/op-deployer/pkg/deployer/standard/standard.go index e9ba7d8d455..72f65d5d2ae 100644 --- a/op-deployer/pkg/deployer/standard/standard.go +++ b/op-deployer/pkg/deployer/standard/standard.go @@ -26,6 +26,7 @@ const ( ProofMaturityDelaySeconds uint64 = 604800 DisputeGameFinalityDelaySeconds uint64 = 302400 MIPSVersion uint64 = 1 + DisputeGameUsesSuperRoots bool = false DisputeGameType uint32 = 1 // PERMISSIONED game type DisputeMaxGameDepth uint64 = 73 DisputeSplitDepth uint64 = 30 diff --git a/op-deployer/pkg/deployer/state/chain_intent.go b/op-deployer/pkg/deployer/state/chain_intent.go index f374466ea41..b53a8d0a8bc 100644 --- a/op-deployer/pkg/deployer/state/chain_intent.go +++ b/op-deployer/pkg/deployer/state/chain_intent.go @@ -17,6 +17,7 @@ const ( ) type ChainProofParams struct { + DisputeGameUsesSuperRoots bool `json:"disputeGameUsesSuperRoots" toml:"disputeGameUsesSuperRoots"` DisputeGameType uint32 `json:"respectedGameType" toml:"respectedGameType"` DisputeAbsolutePrestate common.Hash `json:"faultGameAbsolutePrestate" toml:"faultGameAbsolutePrestate"` DisputeMaxGameDepth uint64 `json:"faultGameMaxDepth" toml:"faultGameMaxDepth"` diff --git a/op-deployer/pkg/deployer/state/state.go b/op-deployer/pkg/deployer/state/state.go index 0ec6c923f30..25f99234d9e 100644 --- a/op-deployer/pkg/deployer/state/state.go +++ b/op-deployer/pkg/deployer/state/state.go @@ -76,6 +76,7 @@ type ImplementationsDeployment struct { OpcmUpgraderAddress common.Address `json:"opcmUpgraderAddress"` DelayedWETHImplAddress common.Address `json:"delayedWETHImplAddress"` OptimismPortalImplAddress common.Address `json:"optimismPortalImplAddress"` + ETHLockboxImplAddress common.Address `json:"ethLockboxImplAddress"` PreimageOracleSingletonAddress common.Address `json:"preimageOracleSingletonAddress"` MipsSingletonAddress common.Address `json:"mipsSingletonAddress"` SystemConfigImplAddress common.Address `json:"systemConfigImplAddress"` @@ -106,6 +107,7 @@ type ChainState struct { L1StandardBridgeProxyAddress common.Address `json:"l1StandardBridgeProxyAddress"` L1CrossDomainMessengerProxyAddress common.Address `json:"l1CrossDomainMessengerProxyAddress"` OptimismPortalProxyAddress common.Address `json:"optimismPortalProxyAddress"` + ETHLockboxProxyAddress common.Address `json:"ethLockboxProxyAddress"` DisputeGameFactoryProxyAddress common.Address `json:"disputeGameFactoryProxyAddress"` AnchorStateRegistryProxyAddress common.Address `json:"anchorStateRegistryProxyAddress"` FaultDisputeGameAddress common.Address `json:"faultDisputeGameAddress"` diff --git a/op-deployer/pkg/deployer/upgrade/v2_0_0/upgrade_test.go b/op-deployer/pkg/deployer/upgrade/v2_0_0/upgrade_test.go index 95585401b11..b025855751b 100644 --- a/op-deployer/pkg/deployer/upgrade/v2_0_0/upgrade_test.go +++ b/op-deployer/pkg/deployer/upgrade/v2_0_0/upgrade_test.go @@ -21,6 +21,7 @@ import ( ) func TestUpgradeOPChainInput_OpChainConfigs(t *testing.T) { + t.Skip("skipping for upgrade 15") input := &UpgradeOPChainInput{ Prank: common.Address{0xaa}, Opcm: common.Address{0xbb}, @@ -54,6 +55,7 @@ func TestUpgradeOPChainInput_OpChainConfigs(t *testing.T) { } func TestUpgrader_Upgrade(t *testing.T) { + t.Skip("skipping for upgrade 15") lgr := testlog.Logger(t, slog.LevelDebug) forkedL1, stopL1, err := devnet.NewForkedSepolia(lgr) diff --git a/op-e2e/actions/helpers/user_test.go b/op-e2e/actions/helpers/user_test.go index 00b27dea1fe..ae3a30feabf 100644 --- a/op-e2e/actions/helpers/user_test.go +++ b/op-e2e/actions/helpers/user_test.go @@ -67,10 +67,6 @@ func TestCrossLayerUser_Standard(t *testing.T) { testCrossLayerUser(t, config.AllocTypeStandard) } -func TestCrossLayerUser_L2OO(t *testing.T) { - testCrossLayerUser(t, config.AllocTypeL2OO) -} - // TestCrossLayerUser tests that common actions of the CrossLayerUser actor work in various hardfork configurations: // - transact on L1 // - transact on L2 @@ -308,6 +304,12 @@ func runCrossLayerUserTest(gt *testing.T, test hardforkScheduledTest) { require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status, "proposal failed") } + // Mine an empty block so that the timestamp is updated. Otherwise ActProveWithdrawal will fail + // because it tries to estimate gas based on the current timestamp, which is the same timestamp + // as the dispute game creation timestamp, which causes proveWithdrawalTransaction to revert. + miner.ActL1StartBlock(12)(t) + miner.ActL1EndBlock(t) + // prove our withdrawal on L1 alice.ActProveWithdrawal(t) // include proved withdrawal in new L1 block diff --git a/op-e2e/actions/proposer/l2_proposer_test.go b/op-e2e/actions/proposer/l2_proposer_test.go index 917fff2bafd..da5000d8550 100644 --- a/op-e2e/actions/proposer/l2_proposer_test.go +++ b/op-e2e/actions/proposer/l2_proposer_test.go @@ -28,17 +28,10 @@ func TestProposerBatchType(t *testing.T) { t.Run("SingularBatch/Standard", func(t *testing.T) { runProposerTest(t, nil, config.AllocTypeStandard) }) - t.Run("SingularBatch/L2OO", func(t *testing.T) { - runProposerTest(t, nil, config.AllocTypeL2OO) - }) t.Run("SpanBatch/Standard", func(t *testing.T) { deltaTimeOffset := hexutil.Uint64(0) runProposerTest(t, &deltaTimeOffset, config.AllocTypeStandard) }) - t.Run("SpanBatch/L2OO", func(t *testing.T) { - deltaTimeOffset := hexutil.Uint64(0) - runProposerTest(t, &deltaTimeOffset, config.AllocTypeL2OO) - }) } func runProposerTest(gt *testing.T, deltaTimeOffset *hexutil.Uint64, allocType config.AllocType) { diff --git a/op-e2e/config/init.go b/op-e2e/config/init.go index b13a6002342..74c54093a2e 100644 --- a/op-e2e/config/init.go +++ b/op-e2e/config/init.go @@ -436,12 +436,13 @@ func defaultIntent(root string, loc *artifacts.Locator, deployer common.Address, { ChainProofParams: state.ChainProofParams{ // Fast game - DisputeGameType: 254, - DisputeAbsolutePrestate: defaultPrestate, - DisputeMaxGameDepth: 14 + 3 + 1, - DisputeSplitDepth: 14, - DisputeClockExtension: 0, - DisputeMaxClockDuration: 0, + DisputeGameUsesSuperRoots: false, + DisputeGameType: 254, + DisputeAbsolutePrestate: defaultPrestate, + DisputeMaxGameDepth: 14 + 3 + 1, + DisputeSplitDepth: 14, + DisputeClockExtension: 0, + DisputeMaxClockDuration: 0, }, VMType: state.VMTypeAlphabet, UseCustomOracle: true, @@ -452,23 +453,25 @@ func defaultIntent(root string, loc *artifacts.Locator, deployer common.Address, { ChainProofParams: state.ChainProofParams{ // Alphabet game - DisputeGameType: 255, - DisputeAbsolutePrestate: defaultPrestate, - DisputeMaxGameDepth: 14 + 3 + 1, - DisputeSplitDepth: 14, - DisputeClockExtension: 0, - DisputeMaxClockDuration: 1200, + DisputeGameUsesSuperRoots: false, + DisputeGameType: 255, + DisputeAbsolutePrestate: defaultPrestate, + DisputeMaxGameDepth: 14 + 3 + 1, + DisputeSplitDepth: 14, + DisputeClockExtension: 0, + DisputeMaxClockDuration: 1200, }, VMType: state.VMTypeAlphabet, }, { ChainProofParams: state.ChainProofParams{ - DisputeGameType: 0, - DisputeAbsolutePrestate: cannonPrestate(root, allocType), - DisputeMaxGameDepth: 50, - DisputeSplitDepth: 14, - DisputeClockExtension: 0, - DisputeMaxClockDuration: 1200, + DisputeGameUsesSuperRoots: false, + DisputeGameType: 0, + DisputeAbsolutePrestate: cannonPrestate(root, allocType), + DisputeMaxGameDepth: 50, + DisputeSplitDepth: 14, + DisputeClockExtension: 0, + DisputeMaxClockDuration: 1200, }, VMType: cannonVMType(allocType), }, diff --git a/op-e2e/system/bridge/validity_test.go b/op-e2e/system/bridge/validity_test.go index 4c4ceff0780..efd5900889d 100644 --- a/op-e2e/system/bridge/validity_test.go +++ b/op-e2e/system/bridge/validity_test.go @@ -266,10 +266,6 @@ func TestMixedDepositValidity(t *testing.T) { } } -func TestMixedWithdrawalValidity_L2OO(t *testing.T) { - testMixedWithdrawalValidity(t, config.AllocTypeL2OO) -} - func TestMixedWithdrawalValidity_Standard(t *testing.T) { testMixedWithdrawalValidity(t, config.AllocTypeStandard) } @@ -449,6 +445,12 @@ func testMixedWithdrawalValidity(t *testing.T, allocType config.AllocType) { receiptCl := ethclient.NewClient(rpcClient) blockCl := ethclient.NewClient(rpcClient) + // Mine an empty block so that the timestamp is updated. Otherwise, + // proveWithdrawalTransaction gas estimation may fail because the current timestamp is + // the same as the dispute game creation timestamp. + err = wait.ForNextBlock(ctx, l1Client) + require.NoError(t, err) + // Now create the withdrawal params, err := helpers.ProveWithdrawalParameters(context.Background(), proofCl, receiptCl, blockCl, tx.Hash(), header, l2OutputOracle, disputeGameFactory, optimismPortal2, cfg.AllocType) require.Nil(t, err) @@ -575,7 +577,20 @@ func testMixedWithdrawalValidity(t *testing.T, allocType config.AllocType) { require.NoError(t, err) } + // Do a large deposit into the OptimismPortal so there's a balance to withdraw + depositAmount := big.NewInt(1_000_000_000_000) + transactor.Account.L1Opts.Value = depositAmount + tx, err := depositContract.DepositTransaction(transactor.Account.L1Opts, fromAddr, depositAmount, 120_000, false, nil) + require.NoError(t, err) + receipt, err := wait.ForReceiptOK(ctx, l1Client, tx.Hash()) + require.NoError(t, err) + require.Equal(t, receipt.Status, types.ReceiptStatusSuccessful) + + // Increase the expected nonce + transactor.ExpectedL1Nonce++ + // Finalize withdrawal + transactor.Account.L1Opts.Value = big.NewInt(0) _, err = depositContract.FinalizeWithdrawalTransaction( transactor.Account.L1Opts, withdrawalTransaction, diff --git a/op-e2e/system/bridge/withdrawal_test.go b/op-e2e/system/bridge/withdrawal_test.go index 1f56fe4c4ad..12b5fe12e22 100644 --- a/op-e2e/system/bridge/withdrawal_test.go +++ b/op-e2e/system/bridge/withdrawal_test.go @@ -9,10 +9,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestWithdrawals_L2OO(t *testing.T) { - testWithdrawals(t, config.AllocTypeL2OO) -} - func TestWithdrawals_Standard(t *testing.T) { testWithdrawals(t, config.AllocTypeStandard) } diff --git a/op-e2e/system/helpers/tx_helper.go b/op-e2e/system/helpers/tx_helper.go index 10c16c0e746..fefddf8f98b 100644 --- a/op-e2e/system/helpers/tx_helper.go +++ b/op-e2e/system/helpers/tx_helper.go @@ -53,7 +53,9 @@ func SendDepositTx(t *testing.T, cfg e2esys.SystemConfig, l1Client *ethclient.Cl t.Logf("SendDepositTx: included on L1") // Wait for transaction to be included on L2 - reconstructedDep, err := derive.UnmarshalDepositLogEvent(l1Receipt.Logs[0]) + // The last log is the deposit log + idx := len(l1Receipt.Logs) - 1 + reconstructedDep, err := derive.UnmarshalDepositLogEvent(l1Receipt.Logs[idx]) require.NoError(t, err, "Could not reconstruct L2 Deposit") tx = types.NewTx(reconstructedDep) l2Receipt, err := wait.ForReceipt(ctx, l2Client, tx.Hash(), l2Opts.ExpectedStatus) diff --git a/op-e2e/system/helpers/withdrawal_helper.go b/op-e2e/system/helpers/withdrawal_helper.go index a2a57c9e3bb..787495e4aa5 100644 --- a/op-e2e/system/helpers/withdrawal_helper.go +++ b/op-e2e/system/helpers/withdrawal_helper.go @@ -127,12 +127,19 @@ func ProveWithdrawal(t *testing.T, cfg e2esys.SystemConfig, clients ClientProvid require.NoError(t, err) } + // Wait for another block to be mined so that the timestamp increases. Otherwise, + // proveWithdrawalTransaction gas estimation may fail because the current timestamp is the same + // as the dispute game creation timestamp. + err = wait.ForNextBlock(ctx, l1Client) + require.NoError(t, err) + receiptCl := clients.NodeClient(l2NodeName) headerCl := clients.NodeClient(l2NodeName) proofCl := gethclient.New(receiptCl.Client()) ctx, cancel = context.WithTimeout(context.Background(), 30*time.Second) defer cancel() + // Get the latest header header, err := receiptCl.HeaderByNumber(ctx, new(big.Int).SetUint64(blockNumber)) require.NoError(t, err) diff --git a/op-e2e/system/proofs/proposer_l2oo_test.go b/op-e2e/system/proofs/proposer_l2oo_test.go deleted file mode 100644 index bf718c61653..00000000000 --- a/op-e2e/system/proofs/proposer_l2oo_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package proofs - -import ( - "context" - "math/big" - "testing" - "time" - - op_e2e "github.com/ethereum-optimism/optimism/op-e2e" - "github.com/ethereum-optimism/optimism/op-e2e/config" - - "github.com/ethereum-optimism/optimism/op-e2e/bindings" - "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" - "github.com/ethereum-optimism/optimism/op-e2e/system/e2esys" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/stretchr/testify/require" -) - -func TestL2OutputSubmitter(t *testing.T) { - op_e2e.InitParallel(t) - - cfg := e2esys.DefaultSystemConfig(t, e2esys.WithAllocType(config.AllocTypeL2OO)) - cfg.NonFinalizedProposals = true // speed up the time till we see output proposals - - sys, err := cfg.Start(t) - require.Nil(t, err, "Error starting up system") - - l1Client := sys.NodeClient("l1") - - rollupClient := sys.RollupClient("sequencer") - - // OutputOracle is already deployed - l2OutputOracle, err := bindings.NewL2OutputOracleCaller(cfg.L1Deployments.L2OutputOracleProxy, l1Client) - require.Nil(t, err) - - initialOutputBlockNumber, err := l2OutputOracle.LatestBlockNumber(&bind.CallOpts{}) - require.Nil(t, err) - - // Wait until the second output submission from L2. The output submitter submits outputs from the - // unsafe portion of the chain which gets reorged on startup. The sequencer has an out of date view - // when it creates it's first block and uses and old L1 Origin. It then does not submit a batch - // for that block and subsequently reorgs to match what the verifier derives when running the - // reconcillation process. - l2Verif := sys.NodeClient("verifier") - _, err = geth.WaitForBlock(big.NewInt(6), l2Verif) - require.Nil(t, err) - - // Wait for batch submitter to update L2 output oracle. - timeoutCh := time.After(15 * time.Second) - ticker := time.NewTicker(1 * time.Second) - defer ticker.Stop() - for { - l2ooBlockNumber, err := l2OutputOracle.LatestBlockNumber(&bind.CallOpts{}) - require.Nil(t, err) - - // Wait for the L2 output oracle to have been changed from the initial - // timestamp set in the contract constructor. - if l2ooBlockNumber.Cmp(initialOutputBlockNumber) > 0 { - // Retrieve the l2 output committed at this updated timestamp. - committedL2Output, err := l2OutputOracle.GetL2OutputAfter(&bind.CallOpts{}, l2ooBlockNumber) - require.NotEqual(t, [32]byte{}, committedL2Output.OutputRoot, "Empty L2 Output") - require.Nil(t, err) - - // Fetch the corresponding L2 block and assert the committed L2 - // output matches the block's state root. - // - // NOTE: This assertion will change once the L2 output format is - // finalized. - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - l2Output, err := rollupClient.OutputAtBlock(ctx, l2ooBlockNumber.Uint64()) - require.Nil(t, err) - require.Equal(t, l2Output.OutputRoot[:], committedL2Output.OutputRoot[:]) - break - } - - select { - case <-timeoutCh: - t.Fatalf("State root oracle not updated") - case <-ticker.C: - } - } -} diff --git a/packages/contracts-bedrock/foundry.toml b/packages/contracts-bedrock/foundry.toml index 7f94d2bc885..e256ab97e99 100644 --- a/packages/contracts-bedrock/foundry.toml +++ b/packages/contracts-bedrock/foundry.toml @@ -28,7 +28,10 @@ compilation_restrictions = [ { paths = "src/dispute/SuperFaultDisputeGame.sol", optimizer_runs = 5000 }, { paths = "src/dispute/SuperPermissionedDisputeGame.sol", optimizer_runs = 5000 }, { paths = "src/L1/OPContractsManager.sol", optimizer_runs = 5000 }, + { paths = "src/L1/OPContractsManagerInterop.sol", optimizer_runs = 5000 }, { paths = "src/L1/StandardValidator.sol", optimizer_runs = 5000 }, + { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 5000 }, + { paths = "src/L1/OptimismPortalInterop.sol", optimizer_runs = 5000 }, ] extra_output = ['devdoc', 'userdoc', 'metadata', 'storageLayout'] @@ -139,8 +142,13 @@ additional_compiler_profiles = [ compilation_restrictions = [ { paths = "src/dispute/FaultDisputeGame.sol", optimizer_runs = 0 }, { paths = "src/dispute/PermissionedDisputeGame.sol", optimizer_runs = 0 }, + { paths = "src/dispute/SuperFaultDisputeGame.sol", optimizer_runs = 0 }, + { paths = "src/dispute/SuperPermissionedDisputeGame.sol", optimizer_runs = 0 }, { paths = "src/L1/OPContractsManager.sol", optimizer_runs = 0 }, + { paths = "src/L1/OPContractsManagerInterop.sol", optimizer_runs = 0 }, { paths = "src/L1/StandardValidator.sol", optimizer_runs = 0 }, + { paths = "src/L1/OptimismPortal2.sol", optimizer_runs = 0 }, + { paths = "src/L1/OptimismPortalInterop.sol", optimizer_runs = 0 }, ] ################################################################ diff --git a/packages/contracts-bedrock/interfaces/L1/IETHLockbox.sol b/packages/contracts-bedrock/interfaces/L1/IETHLockbox.sol new file mode 100644 index 00000000000..5d4abd72c79 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L1/IETHLockbox.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; + +interface IETHLockbox is IProxyAdminOwnedBase, ISemver { + error ETHLockbox_Unauthorized(); + error ETHLockbox_Paused(); + error ETHLockbox_InsufficientBalance(); + error ETHLockbox_NoWithdrawalTransactions(); + error ETHLockbox_DifferentProxyAdminOwner(); + + event Initialized(uint8 version); + event ETHLocked(address indexed portal, uint256 amount); + event ETHUnlocked(address indexed portal, uint256 amount); + event PortalAuthorized(address indexed portal); + event LockboxAuthorized(address indexed lockbox); + event LiquidityMigrated(address indexed lockbox, uint256 amount); + event LiquidityReceived(address indexed lockbox, uint256 amount); + + function initialize(ISuperchainConfig _superchainConfig, IOptimismPortal2[] calldata _portals) external; + function superchainConfig() external view returns (ISuperchainConfig); + function paused() external view returns (bool); + function authorizedPortals(address) external view returns (bool); + function authorizedLockboxes(address) external view returns (bool); + function receiveLiquidity() external payable; + function lockETH() external payable; + function unlockETH(uint256 _value) external; + function authorizePortal(IOptimismPortal2 _portal) external; + function authorizeLockbox(IETHLockbox _lockbox) external; + function migrateLiquidity(IETHLockbox _lockbox) external; + + function __constructor__() external; +} diff --git a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol index 37701010a88..d2f83d685d9 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOPContractsManager.sol @@ -21,6 +21,7 @@ import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.s import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; interface IOPContractsManagerContractsContainer { function __constructor__( @@ -38,16 +39,20 @@ interface IOPContractsManagerGameTypeAdder { uint256 indexed l2ChainId, GameType indexed gameType, address newDisputeGame, address oldDisputeGame ); - function __constructor__( - IOPContractsManagerContractsContainer _contractsContainer - ) - external; + function __constructor__(IOPContractsManagerContractsContainer _contractsContainer) external; - function addGameType(IOPContractsManager.AddGameInput[] memory _gameConfigs, address _superchainConfig) + function addGameType( + IOPContractsManager.AddGameInput[] memory _gameConfigs, + address _superchainConfig + ) external returns (IOPContractsManager.AddGameOutput[] memory); - function updatePrestate(IOPContractsManager.OpChainConfig[] memory _prestateUpdateInputs, address _superchainConfig) external; + function updatePrestate( + IOPContractsManager.OpChainConfig[] memory _prestateUpdateInputs, + address _superchainConfig + ) + external; function contractsContainer() external view returns (IOPContractsManagerContractsContainer); } @@ -55,12 +60,13 @@ interface IOPContractsManagerGameTypeAdder { interface IOPContractsManagerDeployer { event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); - function __constructor__( - IOPContractsManagerContractsContainer _contractsContainer - ) - external; + function __constructor__(IOPContractsManagerContractsContainer _contractsContainer) external; - function deploy(IOPContractsManager.DeployInput memory _input, address _superchainConfig, address _deployer) + function deploy( + IOPContractsManager.DeployInput memory _input, + address _superchainConfig, + address _deployer + ) external returns (IOPContractsManager.DeployOutput memory); @@ -70,17 +76,13 @@ interface IOPContractsManagerDeployer { interface IOPContractsManagerUpgrader { event Upgraded(uint256 indexed l2ChainId, address indexed systemConfig, address indexed upgrader); - function __constructor__( - IOPContractsManagerContractsContainer _contractsContainer - ) - external; + function __constructor__(IOPContractsManagerContractsContainer _contractsContainer) external; function upgrade(IOPContractsManager.OpChainConfig[] memory _opChainConfigs) external; function contractsContainer() external view returns (IOPContractsManagerContractsContainer); } - interface IOPContractsManager { // -------- Structs -------- @@ -106,6 +108,7 @@ interface IOPContractsManager { string saltMixer; uint64 gasLimit; // Configurable dispute game parameters. + bool disputeGameUsesSuperRoots; GameType disputeGameType; Claim disputeAbsolutePrestate; uint256 disputeMaxGameDepth; @@ -123,6 +126,7 @@ interface IOPContractsManager { IOptimismMintableERC20Factory optimismMintableERC20FactoryProxy; IL1StandardBridge l1StandardBridgeProxy; IL1CrossDomainMessenger l1CrossDomainMessengerProxy; + IETHLockbox ethLockboxProxy; // Fault proof contracts below. IOptimismPortal2 optimismPortalProxy; IDisputeGameFactory disputeGameFactoryProxy; @@ -156,6 +160,7 @@ interface IOPContractsManager { address protocolVersionsImpl; address l1ERC721BridgeImpl; address optimismPortalImpl; + address ethLockboxImpl; address systemConfigImpl; address optimismMintableERC20FactoryImpl; address l1CrossDomainMessengerImpl; @@ -171,6 +176,7 @@ interface IOPContractsManager { ISystemConfig systemConfigProxy; IProxyAdmin proxyAdmin; Claim absolutePrestate; + bool disputeGameUsesSuperRoots; } struct AddGameInput { @@ -303,3 +309,27 @@ interface IOPContractsManager { function setRC(bool _isRC) external; } + +/// @notice Minimal interface only used for calling `implementations()` method but without retrieving the ETHLockbox +/// on it, since the OPCM contracts already deployed on mainnet don't have it. +/// @dev Only used for testing. +interface IOPCMImplementationsWithoutLockbox { + /// @notice The implementation contracts for the OP Stack, without the newly added ETHLockbox. + struct Implementations { + address superchainConfigImpl; + address protocolVersionsImpl; + address l1ERC721BridgeImpl; + address optimismPortalImpl; + address systemConfigImpl; + address optimismMintableERC20FactoryImpl; + address l1CrossDomainMessengerImpl; + address l1StandardBridgeImpl; + address disputeGameFactoryImpl; + address anchorStateRegistryImpl; + address delayedWETHImpl; + address mipsImpl; + } + + /// @notice Returns the implementation contracts without the ETHLockbox. + function implementations() external view returns (Implementations memory); +} diff --git a/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerLegacyUpgrade.sol b/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerLegacyUpgrade.sol new file mode 100644 index 00000000000..057139005a9 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L1/IOPContractsManagerLegacyUpgrade.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { Claim } from "src/dispute/lib/Types.sol"; + +/// @title IOPContractsManagerLegacyUpgrade +/// @notice Interface for the legacy OPContractsManager upgrade function. +/// This interface is used to test Upgrade 13 and 14 paths and can be safely removed +/// after those upgrades are completed. Only difference in the new struct is the added +/// disputeGameUsesSuperRoots boolean. +interface IOPContractsManagerLegacyUpgrade { + struct OpChainConfig { + ISystemConfig systemConfigProxy; + IProxyAdmin proxyAdmin; + Claim absolutePrestate; + } + + function upgrade(OpChainConfig[] memory _opChainConfigs) external; +} diff --git a/packages/contracts-bedrock/interfaces/L1/IOPPrestateUpdater.sol b/packages/contracts-bedrock/interfaces/L1/IOPPrestateUpdater.sol deleted file mode 100644 index 7456fed5231..00000000000 --- a/packages/contracts-bedrock/interfaces/L1/IOPPrestateUpdater.sol +++ /dev/null @@ -1,142 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// Libraries -import { GameType } from "src/dispute/lib/Types.sol"; - -// Interfaces -import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; -import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; -import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; -import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; -import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; - -interface IOPPrestateUpdater { - // -------- Constants and Variables -------- - - function version() external pure returns (string memory); - - /// @notice Address of the SuperchainConfig contract shared by all chains. - function superchainConfig() external view returns (ISuperchainConfig); - - /// @notice Address of the ProtocolVersions contract shared by all chains. - function protocolVersions() external view returns (IProtocolVersions); - - /// @notice Address of the ProxyAdmin contract shared by all chains. - function superchainProxyAdmin() external view returns (IProxyAdmin); - - /// @notice L1 smart contracts release deployed by this version of OPCM. This is used in opcm to signal which - /// version of the L1 smart contracts is deployed. It takes the format of `op-contracts/vX.Y.Z`. - function l1ContractsRelease() external view returns (string memory); - - // -------- Events -------- - - /// @notice Emitted when a new OP Stack chain is deployed. - /// @param l2ChainId Chain ID of the new chain. - /// @param deployer Address that deployed the chain. - /// @param deployOutput ABI-encoded output of the deployment. - event Deployed(uint256 indexed l2ChainId, address indexed deployer, bytes deployOutput); - - /// @notice Emitted when a chain is upgraded - /// @param systemConfig Address of the chain's SystemConfig contract - /// @param upgrader Address that initiated the upgrade - event Upgraded(uint256 indexed l2ChainId, ISystemConfig indexed systemConfig, address indexed upgrader); - - /// @notice Emitted when a new game type is added to a chain - /// @param l2ChainId Chain ID of the chain - /// @param gameType Type of the game being added - /// @param newDisputeGame Address of the deployed dispute game - /// @param oldDisputeGame Address of the old dispute game - event GameTypeAdded(uint256 indexed l2ChainId, GameType indexed gameType, IDisputeGame newDisputeGame, IDisputeGame oldDisputeGame); - - // -------- Errors -------- - - error BytesArrayTooLong(); - error DeploymentFailed(); - error EmptyInitcode(); - error IdentityPrecompileCallFailed(); - error NotABlueprint(); - error ReservedBitsSet(); - error UnexpectedPreambleData(bytes data); - error UnsupportedERCVersion(uint8 version); - error OnlyUpgradeController(); - error PrestateNotSet(); - - /// @notice Thrown when an address is the zero address. - error AddressNotFound(address who); - - /// @notice Throw when a contract address has no code. - error AddressHasNoCode(address who); - - /// @notice Thrown when a release version is already set. - error AlreadyReleased(); - - /// @notice Thrown when an invalid `l2ChainId` is provided to `deploy`. - error InvalidChainId(); - - /// @notice Thrown when a role's address is not valid. - error InvalidRoleAddress(string role); - - /// @notice Thrown when the latest release is not set upon initialization. - error LatestReleaseNotSet(); - - /// @notice Thrown when the starting anchor root is not provided. - error InvalidStartingAnchorRoot(); - - /// @notice Thrown when certain methods are called outside of a DELEGATECALL. - error OnlyDelegatecall(); - - /// @notice Thrown when game configs passed to addGameType are invalid. - error InvalidGameConfigs(); - - /// @notice Thrown when the SuperchainConfig of the chain does not match the SuperchainConfig of this OPCM. - error SuperchainConfigMismatch(ISystemConfig systemConfig); - - error SuperchainProxyAdminMismatch(); - - /// @notice Thrown when a function from the parent (OPCM) is not implemented. - error NotImplemented(); - - /// @notice Thrown when the prestate of a permissioned disputed game is 0. - error PrestateRequired(); - - // -------- Methods -------- - - function __constructor__( - ISuperchainConfig _superchainConfig, - IProtocolVersions _protocolVersions, - IOPContractsManager.Blueprints memory _blueprints - ) - external; - - function deploy(IOPContractsManager.DeployInput calldata _input) external returns (IOPContractsManager.DeployOutput memory); - - /// @notice Upgrades the implementation of all proxies in the specified chains - /// @param _opChainConfigs The chains to upgrade - function upgrade(IOPContractsManager.OpChainConfig[] memory _opChainConfigs) external; - - /// @notice addGameType deploys a new dispute game and links it to the DisputeGameFactory. The inputted _gameConfigs - /// must be added in ascending GameType order. - function addGameType(IOPContractsManager.AddGameInput[] memory _gameConfigs) external returns (IOPContractsManager.AddGameOutput[] memory); - - /// @notice Maps an L2 chain ID to an L1 batch inbox address as defined by the standard - /// configuration's convention. This convention is `versionByte || keccak256(bytes32(chainId))[:19]`, - /// where || denotes concatenation`, versionByte is 0x00, and chainId is a uint256. - /// https://specs.optimism.io/protocol/configurability.html#consensus-parameters - function chainIdToBatchInboxAddress(uint256 _l2ChainId) external pure returns (address); - - /// @notice Returns the blueprint contract addresses. - function blueprints() external view returns (IOPContractsManager.Blueprints memory); - - /// @notice Returns the implementation contract addresses. - function implementations() external view returns (IOPContractsManager.Implementations memory); - - function upgradeController() external view returns (address); - - function isRC() external view returns (bool); - - function setRC(bool _isRC) external; - - function updatePrestate(IOPContractsManager.OpChainConfig[] memory _prestateUpdateInputs) external; -} diff --git a/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol b/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol index bbf12c3fce4..f9cfc532543 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol @@ -2,48 +2,59 @@ pragma solidity ^0.8.0; import { Types } from "src/libraries/Types.sol"; -import { GameType, Timestamp } from "src/dispute/lib/LibUDT.sol"; +import { GameType } from "src/dispute/lib/LibUDT.sol"; import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; -interface IOptimismPortal2 { - error AlreadyFinalized(); - error BadTarget(); - error Blacklisted(); - error CallPaused(); +interface IOptimismPortal2 is IProxyAdminOwnedBase { + error OptimismPortal_Unauthorized(); error ContentLengthMismatch(); error EmptyItem(); - error GasEstimation(); error InvalidDataRemainder(); - error InvalidDisputeGame(); - error InvalidGameType(); error InvalidHeader(); - error InvalidMerkleProof(); - error InvalidProof(); - error LargeCalldata(); - error NonReentrant(); + error ReinitializableBase_ZeroInitVersion(); + error OptimismPortal_AlreadyFinalized(); + error OptimismPortal_BadTarget(); + error OptimismPortal_CallPaused(); + error OptimismPortal_CalldataTooLarge(); + error OptimismPortal_GasEstimation(); + error OptimismPortal_GasLimitTooLow(); + error OptimismPortal_ImproperDisputeGame(); + error OptimismPortal_InvalidDisputeGame(); + error OptimismPortal_InvalidMerkleProof(); + error OptimismPortal_InvalidOutputRootProof(); + error OptimismPortal_InvalidProofTimestamp(); + error OptimismPortal_InvalidRootClaim(); + error OptimismPortal_NoReentrancy(); + error OptimismPortal_ProofNotOldEnough(); + error OptimismPortal_Unproven(); + error OptimismPortal_InvalidOutputRootIndex(); + error OptimismPortal_InvalidSuperRootProof(); + error OptimismPortal_InvalidOutputRootChainId(); + error OptimismPortal_WrongProofMethod(); + error Encoding_EmptySuperRoot(); + error Encoding_InvalidSuperRootVersion(); error OutOfGas(); - error ProposalNotValidated(); - error SmallGasLimit(); - error Unauthorized(); error UnexpectedList(); error UnexpectedString(); - error Unproven(); - error LegacyGame(); - event DisputeGameBlacklisted(IDisputeGame indexed disputeGame); event Initialized(uint8 version); - event RespectedGameTypeSet(GameType indexed newGameType, Timestamp indexed updatedAt); event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData); event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success); event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to); event WithdrawalProvenExtension1(bytes32 indexed withdrawalHash, address indexed proofSubmitter); + event ETHMigrated(address indexed lockbox, uint256 ethBalance); + event LockboxUpdated(address oldLockbox, address newLockbox); receive() external payable; - function blacklistDisputeGame(IDisputeGame _disputeGame) external; + function anchorStateRegistry() external view returns (IAnchorStateRegistry); + function ethLockbox() external view returns (IETHLockbox); function checkWithdrawal(bytes32 _withdrawalHash, address _proofSubmitter) external view; function depositTransaction( address _to, @@ -54,10 +65,10 @@ interface IOptimismPortal2 { ) external payable; - function disputeGameBlacklist(IDisputeGame) external view returns (bool); function disputeGameFactory() external view returns (IDisputeGameFactory); function disputeGameFinalityDelaySeconds() external view returns (uint256); function donateETH() external payable; + function updateLockbox(IETHLockbox _newLockbox) external; function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external; function finalizeWithdrawalTransactionExternalProof( Types.WithdrawalTransaction memory _tx, @@ -67,12 +78,14 @@ interface IOptimismPortal2 { function finalizedWithdrawals(bytes32) external view returns (bool); function guardian() external view returns (address); function initialize( - IDisputeGameFactory _disputeGameFactory, ISystemConfig _systemConfig, ISuperchainConfig _superchainConfig, - GameType _initialRespectedGameType + IAnchorStateRegistry _anchorStateRegistry, + IETHLockbox _ethLockbox, + bool _superRootsActive ) external; + function initVersion() external view returns (uint8); function l2Sender() external view returns (address); function minimumGasLimit(uint64 _byteCount) external pure returns (uint64); function numProofSubmitters(bytes32 _withdrawalHash) external view returns (uint256); @@ -87,19 +100,35 @@ interface IOptimismPortal2 { bytes[] memory _withdrawalProof ) external; + function proveWithdrawalTransaction( + Types.WithdrawalTransaction memory _tx, + IDisputeGame _disputeGameProxy, + uint256 _outputRootIndex, + Types.SuperRootProof memory _superRootProof, + Types.OutputRootProof memory _outputRootProof, + bytes[] memory _withdrawalProof + ) + external; function provenWithdrawals( bytes32, address ) external view - returns (IDisputeGame disputeGameProxy, uint64 timestamp); // nosemgrep + returns (IDisputeGame disputeGameProxy, uint64 timestamp); function respectedGameType() external view returns (GameType); function respectedGameTypeUpdatedAt() external view returns (uint64); - function setRespectedGameType(GameType _gameType) external; function superchainConfig() external view returns (ISuperchainConfig); + function superRootsActive() external view returns (bool); function systemConfig() external view returns (ISystemConfig); + function upgrade( + IAnchorStateRegistry _anchorStateRegistry, + IETHLockbox _ethLockbox, + bool _superRootsActive + ) + external; function version() external pure returns (string memory); + function migrateLiquidity() external; - function __constructor__(uint256 _proofMaturityDelaySeconds, uint256 _disputeGameFinalityDelaySeconds) external; + function __constructor__(uint256 _proofMaturityDelaySeconds) external; } diff --git a/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol b/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol index 6dec13a8335..7cd674a0c18 100644 --- a/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol +++ b/packages/contracts-bedrock/interfaces/L1/IOptimismPortalInterop.sol @@ -2,49 +2,60 @@ pragma solidity ^0.8.0; import { Types } from "src/libraries/Types.sol"; -import { GameType, Timestamp } from "src/dispute/lib/LibUDT.sol"; +import { GameType } from "src/dispute/lib/LibUDT.sol"; import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { ConfigType } from "interfaces/L2/IL1BlockInterop.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; +import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; -interface IOptimismPortalInterop { - error AlreadyFinalized(); - error BadTarget(); - error Blacklisted(); - error CallPaused(); +interface IOptimismPortalInterop is IProxyAdminOwnedBase { error ContentLengthMismatch(); error EmptyItem(); - error GasEstimation(); error InvalidDataRemainder(); - error InvalidDisputeGame(); - error InvalidGameType(); error InvalidHeader(); - error InvalidMerkleProof(); - error InvalidProof(); - error LargeCalldata(); - error NonReentrant(); + error ReinitializableBase_ZeroInitVersion(); + error OptimismPortal_AlreadyFinalized(); + error OptimismPortal_BadTarget(); + error OptimismPortal_CallPaused(); + error OptimismPortal_CalldataTooLarge(); + error OptimismPortal_GasEstimation(); + error OptimismPortal_GasLimitTooLow(); + error OptimismPortal_ImproperDisputeGame(); + error OptimismPortal_InvalidDisputeGame(); + error OptimismPortal_InvalidMerkleProof(); + error OptimismPortal_InvalidOutputRootProof(); + error OptimismPortal_InvalidProofTimestamp(); + error OptimismPortal_InvalidRootClaim(); + error OptimismPortal_NoReentrancy(); + error OptimismPortal_ProofNotOldEnough(); + error OptimismPortal_Unauthorized(); + error OptimismPortal_Unproven(); + error OptimismPortal_InvalidOutputRootIndex(); + error OptimismPortal_InvalidSuperRootProof(); + error OptimismPortal_InvalidOutputRootChainId(); + error OptimismPortal_WrongProofMethod(); + error Encoding_EmptySuperRoot(); + error Encoding_InvalidSuperRootVersion(); error OutOfGas(); - error ProposalNotValidated(); - error SmallGasLimit(); - error Unauthorized(); error UnexpectedList(); error UnexpectedString(); - error Unproven(); - error LegacyGame(); - event DisputeGameBlacklisted(IDisputeGame indexed disputeGame); event Initialized(uint8 version); - event RespectedGameTypeSet(GameType indexed newGameType, Timestamp indexed updatedAt); event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData); event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success); event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to); event WithdrawalProvenExtension1(bytes32 indexed withdrawalHash, address indexed proofSubmitter); + event ETHMigrated(address indexed lockbox, uint256 ethBalance); + event LockboxUpdated(address oldLockbox, address newLockbox); receive() external payable; - function blacklistDisputeGame(IDisputeGame _disputeGame) external; + function anchorStateRegistry() external view returns (IAnchorStateRegistry); + function ethLockbox() external view returns (IETHLockbox); function checkWithdrawal(bytes32 _withdrawalHash, address _proofSubmitter) external view; function depositTransaction( address _to, @@ -55,25 +66,28 @@ interface IOptimismPortalInterop { ) external payable; - function disputeGameBlacklist(IDisputeGame) external view returns (bool); function disputeGameFactory() external view returns (IDisputeGameFactory); function disputeGameFinalityDelaySeconds() external view returns (uint256); function donateETH() external payable; + function updateLockbox(IETHLockbox _newLockbox) external; function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external; function finalizeWithdrawalTransactionExternalProof( Types.WithdrawalTransaction memory _tx, address _proofSubmitter ) external; + function migrateLiquidity() external; function finalizedWithdrawals(bytes32) external view returns (bool); function guardian() external view returns (address); function initialize( - IDisputeGameFactory _disputeGameFactory, ISystemConfig _systemConfig, ISuperchainConfig _superchainConfig, - GameType _initialRespectedGameType + IAnchorStateRegistry _anchorStateRegistry, + IETHLockbox _ethLockbox, + bool _superRootsActive ) external; + function initVersion() external view returns (uint8); function l2Sender() external view returns (address); function minimumGasLimit(uint64 _byteCount) external pure returns (uint64); function numProofSubmitters(bytes32 _withdrawalHash) external view returns (uint256); @@ -88,6 +102,15 @@ interface IOptimismPortalInterop { bytes[] memory _withdrawalProof ) external; + function proveWithdrawalTransaction( + Types.WithdrawalTransaction memory _tx, + IDisputeGame _disputeGameProxy, + uint256 _outputRootIndex, + Types.SuperRootProof memory _superRootProof, + Types.OutputRootProof memory _outputRootProof, + bytes[] memory _withdrawalProof + ) + external; function provenWithdrawals( bytes32, address @@ -98,10 +121,16 @@ interface IOptimismPortalInterop { function respectedGameType() external view returns (GameType); function respectedGameTypeUpdatedAt() external view returns (uint64); function setConfig(ConfigType _type, bytes memory _value) external; - function setRespectedGameType(GameType _gameType) external; function superchainConfig() external view returns (ISuperchainConfig); + function superRootsActive() external view returns (bool); function systemConfig() external view returns (ISystemConfig); + function upgrade( + IAnchorStateRegistry _anchorStateRegistry, + IETHLockbox _ethLockbox, + bool _superRootsActive + ) + external; function version() external pure returns (string memory); - function __constructor__(uint256 _proofMaturityDelaySeconds, uint256 _disputeGameFinalityDelaySeconds) external; + function __constructor__(uint256 _proofMaturityDelaySeconds) external; } diff --git a/packages/contracts-bedrock/interfaces/L1/IProxyAdminOwnedBase.sol b/packages/contracts-bedrock/interfaces/L1/IProxyAdminOwnedBase.sol new file mode 100644 index 00000000000..73346bff112 --- /dev/null +++ b/packages/contracts-bedrock/interfaces/L1/IProxyAdminOwnedBase.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IProxyAdminOwnedBase { + function proxyAdminOwner() external view returns (address); +} diff --git a/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol b/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol index e5677516fbf..151c4780801 100644 --- a/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol +++ b/packages/contracts-bedrock/interfaces/L1/ISystemConfig.sol @@ -22,6 +22,8 @@ interface ISystemConfig { address optimismMintableERC20Factory; } + error ReinitializableBase_ZeroInitVersion(); + event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data); event Initialized(uint8 version); event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); @@ -54,12 +56,15 @@ interface ISystemConfig { address _unsafeBlockSigner, IResourceMetering.ResourceConfig memory _config, address _batchInbox, - Addresses memory _addresses + Addresses memory _addresses, + uint256 _l2ChainId ) external; + function initVersion() external view returns (uint8); function l1CrossDomainMessenger() external view returns (address addr_); function l1ERC721Bridge() external view returns (address addr_); function l1StandardBridge() external view returns (address addr_); + function l2ChainId() external view returns (uint256); function maximumGasLimit() external pure returns (uint64); function minimumGasLimit() external view returns (uint64); function operatorFeeConstant() external view returns (uint64); @@ -81,6 +86,7 @@ interface ISystemConfig { function startBlock() external view returns (uint256 startBlock_); function transferOwnership(address newOwner) external; // nosemgrep function unsafeBlockSigner() external view returns (address addr_); + function upgrade(uint256 _l2ChainId) external; function version() external pure returns (string memory); function __constructor__() external; diff --git a/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol b/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol index 19cac2b4154..6842e0f7013 100644 --- a/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol +++ b/packages/contracts-bedrock/interfaces/L1/ISystemConfigInterop.sol @@ -5,6 +5,8 @@ import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; interface ISystemConfigInterop { + error ReinitializableBase_ZeroInitVersion(); + event ConfigUpdate(uint256 indexed version, ISystemConfig.UpdateType indexed updateType, bytes data); event Initialized(uint8 version); event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); @@ -30,6 +32,7 @@ interface ISystemConfigInterop { function l1CrossDomainMessenger() external view returns (address addr_); function l1ERC721Bridge() external view returns (address addr_); function l1StandardBridge() external view returns (address addr_); + function l2ChainId() external view returns (uint256); function maximumGasLimit() external pure returns (uint64); function minimumGasLimit() external view returns (uint64); function operatorFeeConstant() external view returns (uint64); @@ -65,9 +68,11 @@ interface ISystemConfigInterop { IResourceMetering.ResourceConfig memory _config, address _batchInbox, ISystemConfig.Addresses memory _addresses, - address _dependencyManager + address _dependencyManager, + uint256 _l2ChainId ) external; + function initVersion() external view returns (uint8); function version() external pure returns (string memory); function __constructor__() external; diff --git a/packages/contracts-bedrock/interfaces/dispute/IAnchorStateRegistry.sol b/packages/contracts-bedrock/interfaces/dispute/IAnchorStateRegistry.sol index a6f7d3a45c4..d6c96648e63 100644 --- a/packages/contracts-bedrock/interfaces/dispute/IAnchorStateRegistry.sol +++ b/packages/contracts-bedrock/interfaces/dispute/IAnchorStateRegistry.sol @@ -5,30 +5,33 @@ import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { GameType, Hash, OutputRoot } from "src/dispute/lib/Types.sol"; interface IAnchorStateRegistry { - error AnchorStateRegistry_Unauthorized(); - error AnchorStateRegistry_InvalidAnchorGame(); error AnchorStateRegistry_AnchorGameBlacklisted(); + error AnchorStateRegistry_InvalidAnchorGame(); + error AnchorStateRegistry_Unauthorized(); - event AnchorNotUpdated(IFaultDisputeGame indexed game); event AnchorUpdated(IFaultDisputeGame indexed game); + event DisputeGameBlacklisted(IDisputeGame indexed disputeGame); event Initialized(uint8 version); + event RespectedGameTypeSet(GameType gameType); + event RetirementTimestampSet(uint256 timestamp); function anchorGame() external view returns (IFaultDisputeGame); function anchors(GameType) external view returns (Hash, uint256); + function blacklistDisputeGame(IDisputeGame _disputeGame) external; + function disputeGameBlacklist(IDisputeGame) external view returns (bool); function getAnchorRoot() external view returns (Hash, uint256); + function disputeGameFinalityDelaySeconds() external view returns (uint256); function disputeGameFactory() external view returns (IDisputeGameFactory); function initialize( ISuperchainConfig _superchainConfig, IDisputeGameFactory _disputeGameFactory, - IOptimismPortal2 _portal, - OutputRoot memory _startingAnchorRoot + OutputRoot memory _startingAnchorRoot, + GameType _startingRespectedGameType ) external; - function isGameBlacklisted(IDisputeGame _game) external view returns (bool); function isGameProper(IDisputeGame _game) external view returns (bool); function isGameRegistered(IDisputeGame _game) external view returns (bool); @@ -37,11 +40,16 @@ interface IAnchorStateRegistry { function isGameRetired(IDisputeGame _game) external view returns (bool); function isGameFinalized(IDisputeGame _game) external view returns (bool); function isGameClaimValid(IDisputeGame _game) external view returns (bool); - function portal() external view returns (IOptimismPortal2); + function paused() external view returns (bool); function respectedGameType() external view returns (GameType); + function retirementTimestamp() external view returns (uint64); function setAnchorState(IDisputeGame _game) external; + function setRespectedGameType(GameType _gameType) external; function superchainConfig() external view returns (ISuperchainConfig); + function updateRetirementTimestamp() external; function version() external view returns (string memory); - function __constructor__() external; + function __constructor__( + uint256 _disputeGameFinalityDelaySeconds + ) external; } diff --git a/packages/contracts-bedrock/interfaces/safe/IDeputyGuardianModule.sol b/packages/contracts-bedrock/interfaces/safe/IDeputyGuardianModule.sol index a5c0e331302..d4d76649a50 100644 --- a/packages/contracts-bedrock/interfaces/safe/IDeputyGuardianModule.sol +++ b/packages/contracts-bedrock/interfaces/safe/IDeputyGuardianModule.sol @@ -2,22 +2,21 @@ pragma solidity ^0.8.0; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; -import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { ISemver } from "interfaces/universal/ISemver.sol"; import { GameType, Timestamp } from "src/dispute/lib/Types.sol"; import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol"; interface IDeputyGuardianModule is ISemver { - error ExecutionFailed(string); - error Unauthorized(); + error DeputyGuardianModule_ExecutionFailed(string); + error DeputyGuardianModule_Unauthorized(); event Paused(string identifier); event Unpaused(); event DisputeGameBlacklisted(IDisputeGame indexed game); event RespectedGameTypeSet(GameType indexed gameType, Timestamp indexed updatedAt); + event RetirementTimestampUpdated(Timestamp indexed updatedAt); function version() external view returns (string memory); function __constructor__(Safe _safe, ISuperchainConfig _superchainConfig, address _deputyGuardian) external; @@ -26,7 +25,7 @@ interface IDeputyGuardianModule is ISemver { function deputyGuardian() external view returns (address deputyGuardian_); function pause() external; function unpause() external; - function setAnchorState(IAnchorStateRegistry _registry, IFaultDisputeGame _game) external; - function blacklistDisputeGame(IOptimismPortal2 _portal, IDisputeGame _game) external; - function setRespectedGameType(IOptimismPortal2 _portal, GameType _gameType) external; + function blacklistDisputeGame(IAnchorStateRegistry _anchorStateRegistry, IDisputeGame _game) external; + function setRespectedGameType(IAnchorStateRegistry _anchorStateRegistry, GameType _gameType) external; + function updateRetirementTimestamp(IAnchorStateRegistry _anchorStateRegistry) external; } diff --git a/packages/contracts-bedrock/interfaces/universal/IReinitializableBase.sol b/packages/contracts-bedrock/interfaces/universal/IReinitializableBase.sol new file mode 100644 index 00000000000..d6d096a743b --- /dev/null +++ b/packages/contracts-bedrock/interfaces/universal/IReinitializableBase.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IReinitializableBase { + error ReinitializableBase_ZeroInitVersion(); + + function initVersion() external view returns (uint8); + + // ReinitializerBase is abstract, so it has no constructor in its interface. + function __constructor__() external; +} diff --git a/packages/contracts-bedrock/justfile b/packages/contracts-bedrock/justfile index 09c0f1f8a23..0cde991fcc2 100644 --- a/packages/contracts-bedrock/justfile +++ b/packages/contracts-bedrock/justfile @@ -27,6 +27,10 @@ forge-build-dev *ARGS: build-source: forge build --skip "/**/test/**" --skip "/**/scripts/**" +# Builds source contracts and scripts, skipping tests. +build-no-tests: + forge build --skip "/**/test/**" + # Builds the contracts. build *ARGS: lint-fix-no-fail just forge-build {{ARGS}} @@ -65,7 +69,7 @@ test-dev *ARGS: build-go-ffi # Default block number for the forked upgrade path. export sepoliaBlockNumber := "7701807" -export mainnetBlockNumber := "21971446" +export mainnetBlockNumber := "21983965" export pinnedBlockNumber := if env_var_or_default("FORK_BASE_CHAIN", "") == "mainnet" { mainnetBlockNumber @@ -303,6 +307,7 @@ check: semver-diff-check-no-build \ validate-deploy-configs \ validate-spacers-no-build \ + reinitializer-check-no-build \ interfaces-check-no-build \ lint-forge-tests-check-no-build diff --git a/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh b/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh index c2a9e6688af..435cd301a35 100755 --- a/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh +++ b/packages/contracts-bedrock/scripts/checks/check-frozen-files.sh @@ -49,6 +49,7 @@ changed_contracts=$(jq -r ' # All files in semver-lock.json should be in this list. ALLOWED_FILES=( "src/L1/DataAvailabilityChallenge.sol:DataAvailabilityChallenge" + "src/L1/ETHLockbox.sol:ETHLockbox" "src/L1/L1CrossDomainMessenger.sol:L1CrossDomainMessenger" "src/L1/L1ERC721Bridge.sol:L1ERC721Bridge" "src/L1/L1StandardBridge.sol:L1StandardBridge" diff --git a/packages/contracts-bedrock/scripts/checks/interfaces/main.go b/packages/contracts-bedrock/scripts/checks/interfaces/main.go index 211730d3adc..0ecc45e1903 100644 --- a/packages/contracts-bedrock/scripts/checks/interfaces/main.go +++ b/packages/contracts-bedrock/scripts/checks/interfaces/main.go @@ -26,6 +26,9 @@ var excludeContracts = []string{ // EAS "IEAS", "ISchemaResolver", "ISchemaRegistry", + // Misc stuff that can be ignored + "IOPContractsManagerLegacyUpgrade", + // TODO: Interfaces that need to be fixed "IInitializable", "IOptimismMintableERC20", "ILegacyMintableERC20", "KontrolCheatsBase", "ISystemConfigInterop", "IResolvedDelegateProxy", diff --git a/packages/contracts-bedrock/scripts/checks/reinitializer/main.go b/packages/contracts-bedrock/scripts/checks/reinitializer/main.go index c41e403b985..e44c955d038 100644 --- a/packages/contracts-bedrock/scripts/checks/reinitializer/main.go +++ b/packages/contracts-bedrock/scripts/checks/reinitializer/main.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "math" "os" "strconv" "strings" @@ -103,15 +104,23 @@ func getReinitializerValue(node *solc.AstNode) (uint64, error) { for _, modifier := range node.Modifiers { if modifier.ModifierName.Name == "reinitializer" { - valStr, ok := modifier.Arguments[0].Value.(string) - if !ok { - return 0, fmt.Errorf("reinitializer value is not a string") + if modifier.Arguments[0].Kind == "functionCall" { + if modifier.Arguments[0].Expression.Name == "initVersion" { + return math.MaxUint64, nil // uint64 max representing initVersion call + } else { + return 0, fmt.Errorf("reinitializer value is not a call to initVersion") + } + } else { + valStr, ok := modifier.Arguments[0].Value.(string) + if !ok { + return 0, fmt.Errorf("reinitializer value is not a string") + } + val, err := strconv.Atoi(valStr) + if err != nil { + return 0, fmt.Errorf("reinitializer value is not an integer") + } + return uint64(val), nil } - val, err := strconv.Atoi(valStr) - if err != nil { - return 0, fmt.Errorf("reinitializer value is not an integer") - } - return uint64(val), nil } } diff --git a/packages/contracts-bedrock/scripts/checks/reinitializer/main_test.go b/packages/contracts-bedrock/scripts/checks/reinitializer/main_test.go index 93ca35173b6..0f9fc382e2d 100644 --- a/packages/contracts-bedrock/scripts/checks/reinitializer/main_test.go +++ b/packages/contracts-bedrock/scripts/checks/reinitializer/main_test.go @@ -1,6 +1,7 @@ package main import ( + "math" "testing" "github.com/ethereum-optimism/optimism/op-chain-ops/solc" @@ -155,6 +156,38 @@ func TestGetReinitializerValue(t *testing.T) { want: 0, wantErr: true, }, + { + name: "Valid reinitializer with initVersion call", + node: &solc.AstNode{ + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{ + Kind: "functionCall", + Expression: &solc.Expression{Name: "initVersion"}, + }}, + }, + }, + }, + want: math.MaxUint64, + wantErr: false, + }, + { + name: "Invalid function call - not initVersion", + node: &solc.AstNode{ + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{ + Kind: "functionCall", + Expression: &solc.Expression{Name: "someOtherFunction"}, + }}, + }, + }, + }, + want: 0, + wantErr: true, + }, } for _, tt := range tests { @@ -484,6 +517,85 @@ func TestCheckArtifact(t *testing.T) { }, wantErr: false, // Should return nil without error }, + { + name: "Matching reinitializer values with initVersion", + artifact: &solc.ForgeArtifact{ + Ast: solc.Ast{ + Nodes: []solc.AstNode{ + { + NodeType: "ContractDefinition", + Nodes: []solc.AstNode{ + { + NodeType: "FunctionDefinition", + Name: "initialize", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{ + Kind: "functionCall", + Expression: &solc.Expression{Name: "initVersion"}, + }}, + }, + }, + }, + { + NodeType: "FunctionDefinition", + Name: "upgrade", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{ + Kind: "functionCall", + Expression: &solc.Expression{Name: "initVersion"}, + }}, + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "Mismatched reinitializer values - one with initVersion, one with constant", + artifact: &solc.ForgeArtifact{ + Ast: solc.Ast{ + Nodes: []solc.AstNode{ + { + NodeType: "ContractDefinition", + Nodes: []solc.AstNode{ + { + NodeType: "FunctionDefinition", + Name: "initialize", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{ + Kind: "functionCall", + Expression: &solc.Expression{Name: "initVersion"}, + }}, + }, + }, + }, + { + NodeType: "FunctionDefinition", + Name: "upgrade", + Modifiers: []solc.AstNode{ + { + ModifierName: &solc.Expression{Name: "reinitializer"}, + Arguments: []solc.Expression{{Value: "2"}}, + }, + }, + }, + }, + }, + }, + }, + }, + wantErr: true, + }, } for _, tt := range tests { diff --git a/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol b/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol index 026cdb178f9..a8457150ec4 100644 --- a/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol +++ b/packages/contracts-bedrock/scripts/deploy/ChainAssertions.sol @@ -22,7 +22,7 @@ import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { ProtocolVersion, IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; @@ -32,6 +32,7 @@ import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMin import { IPreimageOracle } from "interfaces/cannon/IPreimageOracle.sol"; import { IMIPS } from "interfaces/cannon/IMIPS.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; library ChainAssertions { Vm internal constant vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); @@ -367,7 +368,7 @@ library ChainAssertions { internal view { - IOptimismPortal2 portal = IOptimismPortal2(payable(_contracts.OptimismPortal)); + IOptimismPortal portal = IOptimismPortal(payable(_contracts.OptimismPortal)); console.log( "Running chain assertions on the OptimismPortal2 %s at %s", _isProxy ? "proxy" : "implementation", @@ -385,20 +386,49 @@ library ChainAssertions { if (_isProxy) { require(address(portal.disputeGameFactory()) == _contracts.DisputeGameFactory, "CHECK-OP2-20"); + require(address(portal.anchorStateRegistry()) == _contracts.AnchorStateRegistry, "CHECK-OP2-25"); require(address(portal.systemConfig()) == _contracts.SystemConfig, "CHECK-OP2-30"); require(portal.guardian() == guardian, "CHECK-OP2-40"); require(address(portal.superchainConfig()) == address(_contracts.SuperchainConfig), "CHECK-OP2-50"); require(portal.paused() == ISuperchainConfig(_contracts.SuperchainConfig).paused(), "CHECK-OP2-60"); require(portal.l2Sender() == Constants.DEFAULT_L2_SENDER, "CHECK-OP2-70"); + require(address(portal.ethLockbox()) == _contracts.ETHLockbox, "CHECK-OP2-80"); } else { - require(address(portal.disputeGameFactory()) == address(0), "CHECK-OP2-80"); + require(address(portal.anchorStateRegistry()) == address(0), "CHECK-OP2-80"); require(address(portal.systemConfig()) == address(0), "CHECK-OP2-90"); require(address(portal.superchainConfig()) == address(0), "CHECK-OP2-100"); require(portal.l2Sender() == address(0), "CHECK-OP2-110"); + require(address(portal.ethLockbox()) == address(0), "CHECK-OP2-120"); } // This slot is the custom gas token _balance and this check ensures // that it stays unset for forwards compatibility with custom gas token. - require(vm.load(address(portal), bytes32(uint256(61))) == bytes32(0), "CHECK-OP2-120"); + require(vm.load(address(portal), bytes32(uint256(61))) == bytes32(0), "CHECK-OP2-130"); + } + + /// @notice Asserts that the ETHLockbox is setup correctly + function checkETHLockbox(Types.ContractSet memory _contracts, DeployConfig _cfg, bool _isProxy) internal view { + IETHLockbox ethLockbox = IETHLockbox(_contracts.ETHLockbox); + ISuperchainConfig superchainConfig = ISuperchainConfig(_contracts.SuperchainConfig); + + console.log( + "Running chain assertions on the ETHLockbox %s at %s", + _isProxy ? "proxy" : "implementation", + address(ethLockbox) + ); + + require(address(ethLockbox) != address(0), "CHECK-ELB-10"); + + // Check that the contract is initialized + DeployUtils.assertInitialized({ _contractAddress: address(ethLockbox), _isProxy: _isProxy, _slot: 0, _offset: 0 }); + + if (_isProxy) { + require(ethLockbox.superchainConfig() == superchainConfig, "CHECK-ELB-20"); + require(ethLockbox.authorizedPortals(_contracts.OptimismPortal), "CHECK-ELB-30"); + require(ethLockbox.proxyAdminOwner() == _cfg.finalSystemOwner(), "CHECK-ELB-40"); + } else { + require(address(ethLockbox.superchainConfig()) == address(0), "CHECK-ELB-50"); + require(ethLockbox.authorizedPortals(_contracts.OptimismPortal) == false, "CHECK-ELB-60"); + } } /// @notice Asserts that the ProtocolVersions is setup correctly diff --git a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol index f4638d20801..1a353f2f063 100644 --- a/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/Deploy.s.sol @@ -35,7 +35,6 @@ import { GameType, Claim, GameTypes, OutputRoot, Hash } from "src/dispute/lib/Ty import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; import { IProxy } from "interfaces/universal/IProxy.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IDataAvailabilityChallenge } from "interfaces/L1/IDataAvailabilityChallenge.sol"; @@ -119,6 +118,7 @@ contract Deploy is Deployer { AnchorStateRegistry: artifacts.getAddress("AnchorStateRegistryProxy"), OptimismMintableERC20Factory: artifacts.getAddress("OptimismMintableERC20FactoryProxy"), OptimismPortal: artifacts.getAddress("OptimismPortalProxy"), + ETHLockbox: artifacts.getAddress("ETHLockboxProxy"), SystemConfig: artifacts.getAddress("SystemConfigProxy"), L1ERC721Bridge: artifacts.getAddress("L1ERC721BridgeProxy"), ProtocolVersions: artifacts.getAddress("ProtocolVersionsProxy"), @@ -138,6 +138,7 @@ contract Deploy is Deployer { AnchorStateRegistry: artifacts.getAddress("AnchorStateRegistryImpl"), OptimismMintableERC20Factory: artifacts.getAddress("OptimismMintableERC20FactoryImpl"), OptimismPortal: artifacts.getAddress("OptimismPortal2Impl"), + ETHLockbox: artifacts.getAddress("ETHLockboxImpl"), SystemConfig: artifacts.getAddress("SystemConfigImpl"), L1ERC721Bridge: artifacts.getAddress("L1ERC721BridgeImpl"), ProtocolVersions: artifacts.getAddress("ProtocolVersionsImpl"), @@ -206,7 +207,7 @@ contract Deploy is Deployer { // Set the respected game type according to the deploy config vm.startPrank(ISuperchainConfig(artifacts.mustGetAddress("SuperchainConfigProxy")).guardian()); - IOptimismPortal2(artifacts.mustGetAddress("OptimismPortalProxy")).setRespectedGameType( + IAnchorStateRegistry(artifacts.mustGetAddress("AnchorStateRegistryProxy")).setRespectedGameType( GameType.wrap(uint32(cfg.respectedGameType())) ); vm.stopPrank(); @@ -318,6 +319,7 @@ contract Deploy is Deployer { AnchorStateRegistry: address(0), OptimismMintableERC20Factory: address(dio.optimismMintableERC20FactoryImpl()), OptimismPortal: address(dio.optimismPortalImpl()), + ETHLockbox: address(dio.ethLockboxImpl()), SystemConfig: address(dio.systemConfigImpl()), L1ERC721Bridge: address(dio.l1ERC721BridgeImpl()), ProtocolVersions: address(dio.protocolVersionsImpl()), @@ -328,6 +330,7 @@ contract Deploy is Deployer { ChainAssertions.checkL1StandardBridge({ _contracts: impls, _isProxy: false }); ChainAssertions.checkL1ERC721Bridge({ _contracts: impls, _isProxy: false }); ChainAssertions.checkOptimismPortal2({ _contracts: impls, _cfg: cfg, _isProxy: false }); + ChainAssertions.checkETHLockbox({ _contracts: impls, _cfg: cfg, _isProxy: false }); ChainAssertions.checkOptimismMintableERC20Factory({ _contracts: impls, _isProxy: false }); ChainAssertions.checkDisputeGameFactory({ _contracts: impls, _expectedOwner: address(0), _isProxy: false }); ChainAssertions.checkDelayedWETH({ _contracts: impls, _cfg: cfg, _isProxy: false, _expectedOwner: address(0) }); @@ -372,6 +375,7 @@ contract Deploy is Deployer { artifacts.save("OptimismMintableERC20FactoryProxy", address(deployOutput.optimismMintableERC20FactoryProxy)); artifacts.save("L1StandardBridgeProxy", address(deployOutput.l1StandardBridgeProxy)); artifacts.save("L1CrossDomainMessengerProxy", address(deployOutput.l1CrossDomainMessengerProxy)); + artifacts.save("ETHLockboxProxy", address(deployOutput.ethLockboxProxy)); // Fault Proof contracts artifacts.save("DisputeGameFactoryProxy", address(deployOutput.disputeGameFactoryProxy)); @@ -380,7 +384,6 @@ contract Deploy is Deployer { artifacts.save("PermissionedDisputeGame", address(deployOutput.permissionedDisputeGame)); artifacts.save("OptimismPortalProxy", address(deployOutput.optimismPortalProxy)); artifacts.save("OptimismPortal2Proxy", address(deployOutput.optimismPortalProxy)); - // Check if the permissionless game implementation is already set IDisputeGameFactory factory = IDisputeGameFactory(artifacts.mustGetAddress("DisputeGameFactoryProxy")); address permissionlessGameImpl = address(factory.gameImpls(GameTypes.CANNON)); @@ -524,7 +527,8 @@ contract Deploy is Deployer { disputeGameFactory: artifacts.mustGetAddress("DisputeGameFactoryProxy"), optimismPortal: artifacts.mustGetAddress("OptimismPortalProxy"), optimismMintableERC20Factory: artifacts.mustGetAddress("OptimismMintableERC20FactoryProxy") - }) + }), + cfg.l2ChainID() ) ) }); @@ -965,6 +969,7 @@ contract Deploy is Deployer { ), saltMixer: saltMixer, gasLimit: uint64(cfg.l2GenesisBlockGasLimit()), + disputeGameUsesSuperRoots: false, disputeGameType: GameTypes.PERMISSIONED_CANNON, disputeAbsolutePrestate: Claim.wrap(bytes32(cfg.faultGameAbsolutePrestate())), disputeMaxGameDepth: cfg.faultGameMaxDepth(), diff --git a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol index 5cd04147345..776803cb6df 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployImplementations.s.sol @@ -24,7 +24,8 @@ import { IOPContractsManagerUpgrader, IOPContractsManagerContractsContainer } from "interfaces/L1/IOPContractsManager.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; @@ -158,7 +159,8 @@ contract DeployImplementationsOutput is BaseDeployIO { IOPContractsManagerDeployer internal _opcmDeployer; IOPContractsManagerUpgrader internal _opcmUpgrader; IDelayedWETH internal _delayedWETHImpl; - IOptimismPortal2 internal _optimismPortalImpl; + IOptimismPortal internal _optimismPortalImpl; + IETHLockbox internal _ethLockboxImpl; IPreimageOracle internal _preimageOracleSingleton; IMIPS internal _mipsSingleton; ISystemConfig internal _systemConfigImpl; @@ -182,7 +184,8 @@ contract DeployImplementationsOutput is BaseDeployIO { else if (_sel == this.opcmUpgrader.selector) _opcmUpgrader = IOPContractsManagerUpgrader(_addr); else if (_sel == this.superchainConfigImpl.selector) _superchainConfigImpl = ISuperchainConfig(_addr); else if (_sel == this.protocolVersionsImpl.selector) _protocolVersionsImpl = IProtocolVersions(_addr); - else if (_sel == this.optimismPortalImpl.selector) _optimismPortalImpl = IOptimismPortal2(payable(_addr)); + else if (_sel == this.optimismPortalImpl.selector) _optimismPortalImpl = IOptimismPortal(payable(_addr)); + else if (_sel == this.ethLockboxImpl.selector) _ethLockboxImpl = IETHLockbox(payable(_addr)); else if (_sel == this.delayedWETHImpl.selector) _delayedWETHImpl = IDelayedWETH(payable(_addr)); else if (_sel == this.preimageOracleSingleton.selector) _preimageOracleSingleton = IPreimageOracle(_addr); else if (_sel == this.mipsSingleton.selector) _mipsSingleton = IMIPS(_addr); @@ -217,7 +220,8 @@ contract DeployImplementationsOutput is BaseDeployIO { address(this.l1StandardBridgeImpl()), address(this.optimismMintableERC20FactoryImpl()), address(this.disputeGameFactoryImpl()), - address(this.anchorStateRegistryImpl()) + address(this.anchorStateRegistryImpl()), + address(this.ethLockboxImpl()) ); DeployUtils.assertValidContractAddresses(Solarray.extend(addrs1, addrs2)); @@ -260,11 +264,16 @@ contract DeployImplementationsOutput is BaseDeployIO { return _protocolVersionsImpl; } - function optimismPortalImpl() public view returns (IOptimismPortal2) { + function optimismPortalImpl() public view returns (IOptimismPortal) { DeployUtils.assertValidContractAddress(address(_optimismPortalImpl)); return _optimismPortalImpl; } + function ethLockboxImpl() public view returns (IETHLockbox) { + DeployUtils.assertValidContractAddress(address(_ethLockboxImpl)); + return _ethLockboxImpl; + } + function delayedWETHImpl() public view returns (IDelayedWETH) { DeployUtils.assertValidContractAddress(address(_delayedWETHImpl)); return _delayedWETHImpl; @@ -327,6 +336,7 @@ contract DeployImplementationsOutput is BaseDeployIO { assertValidOpcm(_dii); assertValidOptimismMintableERC20FactoryImpl(_dii); assertValidOptimismPortalImpl(_dii); + assertValidETHLockboxImpl(_dii); assertValidPreimageOracleSingleton(_dii); assertValidSystemConfigImpl(_dii); } @@ -339,11 +349,11 @@ contract DeployImplementationsOutput is BaseDeployIO { } function assertValidOptimismPortalImpl(DeployImplementationsInput) internal view { - IOptimismPortal2 portal = optimismPortalImpl(); + IOptimismPortal portal = optimismPortalImpl(); DeployUtils.assertInitialized({ _contractAddress: address(portal), _isProxy: false, _slot: 0, _offset: 0 }); - require(address(portal.disputeGameFactory()) == address(0), "PORTAL-10"); + require(address(portal.anchorStateRegistry()) == address(0), "PORTAL-10"); require(address(portal.systemConfig()) == address(0), "PORTAL-20"); require(address(portal.superchainConfig()) == address(0), "PORTAL-30"); require(portal.l2Sender() == address(0), "PORTAL-40"); @@ -351,6 +361,17 @@ contract DeployImplementationsOutput is BaseDeployIO { // This slot is the custom gas token _balance and this check ensures // that it stays unset for forwards compatibility with custom gas token. require(vm.load(address(portal), bytes32(uint256(61))) == bytes32(0), "PORTAL-50"); + + require(address(portal.ethLockbox()) == address(0), "PORTAL-60"); + } + + function assertValidETHLockboxImpl(DeployImplementationsInput) internal view { + IETHLockbox lockbox = ethLockboxImpl(); + + DeployUtils.assertInitialized({ _contractAddress: address(lockbox), _isProxy: false, _slot: 0, _offset: 0 }); + + require(address(lockbox.superchainConfig()) == address(0), "ELB-10"); + require(lockbox.authorizedPortals(address(optimismPortalImpl())) == false, "ELB-20"); } function assertValidDelayedWETHImpl(DeployImplementationsInput _dii) internal view { @@ -485,11 +506,12 @@ contract DeployImplementations is Script { deployL1StandardBridgeImpl(_dio); deployOptimismMintableERC20FactoryImpl(_dio); deployOptimismPortalImpl(_dii, _dio); + deployETHLockboxImpl(_dio); deployDelayedWETHImpl(_dii, _dio); deployPreimageOracleSingleton(_dii, _dio); deployMipsSingleton(_dii, _dio); deployDisputeGameFactoryImpl(_dio); - deployAnchorStateRegistryImpl(_dio); + deployAnchorStateRegistryImpl(_dii, _dio); // Deploy the OP Contracts Manager with the new implementations set. deployOPContractsManager(_dii, _dio); @@ -516,6 +538,7 @@ contract DeployImplementations is Script { protocolVersionsImpl: address(_dio.protocolVersionsImpl()), l1ERC721BridgeImpl: address(_dio.l1ERC721BridgeImpl()), optimismPortalImpl: address(_dio.optimismPortalImpl()), + ethLockboxImpl: address(_dio.ethLockboxImpl()), systemConfigImpl: address(_dio.systemConfigImpl()), optimismMintableERC20FactoryImpl: address(_dio.optimismMintableERC20FactoryImpl()), l1CrossDomainMessengerImpl: address(_dio.l1CrossDomainMessengerImpl()), @@ -680,6 +703,18 @@ contract DeployImplementations is Script { _dio.set(_dio.optimismMintableERC20FactoryImpl.selector, address(impl)); } + function deployETHLockboxImpl(DeployImplementationsOutput _dio) public virtual { + IETHLockbox impl = IETHLockbox( + DeployUtils.createDeterministic({ + _name: "ETHLockbox", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IETHLockbox.__constructor__, ())), + _salt: _salt + }) + ); + vm.label(address(impl), "ETHLockboxImpl"); + _dio.set(_dio.ethLockboxImpl.selector, address(impl)); + } + // --- Fault Proofs Contracts --- // The fault proofs contracts are configured as follows: @@ -725,14 +760,11 @@ contract DeployImplementations is Script { virtual { uint256 proofMaturityDelaySeconds = _dii.proofMaturityDelaySeconds(); - uint256 disputeGameFinalityDelaySeconds = _dii.disputeGameFinalityDelaySeconds(); - IOptimismPortal2 impl = IOptimismPortal2( + IOptimismPortal impl = IOptimismPortal( DeployUtils.createDeterministic({ _name: "OptimismPortal2", _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IOptimismPortal2.__constructor__, (proofMaturityDelaySeconds, disputeGameFinalityDelaySeconds) - ) + abi.encodeCall(IOptimismPortal.__constructor__, (proofMaturityDelaySeconds)) ), _salt: _salt }) @@ -810,11 +842,20 @@ contract DeployImplementations is Script { _dio.set(_dio.disputeGameFactoryImpl.selector, address(impl)); } - function deployAnchorStateRegistryImpl(DeployImplementationsOutput _dio) public virtual { + function deployAnchorStateRegistryImpl( + DeployImplementationsInput _dii, + DeployImplementationsOutput _dio + ) + public + virtual + { + uint256 disputeGameFinalityDelaySeconds = _dii.disputeGameFinalityDelaySeconds(); IAnchorStateRegistry impl = IAnchorStateRegistry( DeployUtils.createDeterministic({ _name: "AnchorStateRegistry", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IAnchorStateRegistry.__constructor__, ())), + _args: DeployUtils.encodeConstructor( + abi.encodeCall(IAnchorStateRegistry.__constructor__, (disputeGameFinalityDelaySeconds)) + ), _salt: _salt }) ); @@ -963,19 +1004,15 @@ contract DeployImplementationsInterop is DeployImplementations { override { uint256 proofMaturityDelaySeconds = _dii.proofMaturityDelaySeconds(); - uint256 disputeGameFinalityDelaySeconds = _dii.disputeGameFinalityDelaySeconds(); IOptimismPortalInterop impl = IOptimismPortalInterop( DeployUtils.createDeterministic({ _name: "OptimismPortalInterop", _args: DeployUtils.encodeConstructor( - abi.encodeCall( - IOptimismPortalInterop.__constructor__, (proofMaturityDelaySeconds, disputeGameFinalityDelaySeconds) - ) + abi.encodeCall(IOptimismPortalInterop.__constructor__, (proofMaturityDelaySeconds)) ), _salt: _salt }) ); - vm.label(address(impl), "OptimismPortalImpl"); _dio.set(_dio.optimismPortalImpl.selector, address(impl)); } diff --git a/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol b/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol index 9541db0bbab..2978582420a 100644 --- a/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/DeployOPChain.s.sol @@ -27,12 +27,13 @@ import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; import { Claim, Duration, GameType, GameTypes, Hash } from "src/dispute/lib/Types.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; contract DeployOPChainInput is BaseDeployIO { address internal _opChainProxyAdminOwner; @@ -51,6 +52,7 @@ contract DeployOPChainInput is BaseDeployIO { uint64 internal _gasLimit; // Configurable dispute game inputs + bool internal _disputeGameUsesSuperRoots; GameType internal _disputeGameType; Claim internal _disputeAbsolutePrestate; uint256 internal _disputeMaxGameDepth; @@ -98,6 +100,9 @@ contract DeployOPChainInput is BaseDeployIO { _operatorFeeScalar = SafeCast.toUint32(_value); } else if (_sel == this.operatorFeeConstant.selector) { _operatorFeeConstant = SafeCast.toUint64(_value); + } else if (_sel == this.disputeGameUsesSuperRoots.selector) { + require(_value == 0 || _value == 1, "DeployOPChainInput: invalid disputeGameUsesSuperRoots"); + _disputeGameUsesSuperRoots = _value == 1; } else { revert("DeployOPChainInput: unknown selector"); } @@ -194,6 +199,10 @@ contract DeployOPChainInput is BaseDeployIO { return _gasLimit; } + function disputeGameUsesSuperRoots() public view returns (bool) { + return _disputeGameUsesSuperRoots; + } + function disputeGameType() public view returns (GameType) { return _disputeGameType; } @@ -239,7 +248,8 @@ contract DeployOPChainOutput is BaseDeployIO { IOptimismMintableERC20Factory internal _optimismMintableERC20FactoryProxy; IL1StandardBridge internal _l1StandardBridgeProxy; IL1CrossDomainMessenger internal _l1CrossDomainMessengerProxy; - IOptimismPortal2 internal _optimismPortalProxy; + IOptimismPortal internal _optimismPortalProxy; + IETHLockbox internal _ethLockboxProxy; IDisputeGameFactory internal _disputeGameFactoryProxy; IAnchorStateRegistry internal _anchorStateRegistryProxy; IFaultDisputeGame internal _faultDisputeGame; @@ -257,7 +267,8 @@ contract DeployOPChainOutput is BaseDeployIO { else if (_sel == this.optimismMintableERC20FactoryProxy.selector) _optimismMintableERC20FactoryProxy = IOptimismMintableERC20Factory(_addr) ; else if (_sel == this.l1StandardBridgeProxy.selector) _l1StandardBridgeProxy = IL1StandardBridge(payable(_addr)) ; else if (_sel == this.l1CrossDomainMessengerProxy.selector) _l1CrossDomainMessengerProxy = IL1CrossDomainMessenger(_addr) ; - else if (_sel == this.optimismPortalProxy.selector) _optimismPortalProxy = IOptimismPortal2(payable(_addr)) ; + else if (_sel == this.optimismPortalProxy.selector) _optimismPortalProxy = IOptimismPortal(payable(_addr)) ; + else if (_sel == this.ethLockboxProxy.selector) _ethLockboxProxy = IETHLockbox(payable(_addr)) ; else if (_sel == this.disputeGameFactoryProxy.selector) _disputeGameFactoryProxy = IDisputeGameFactory(_addr) ; else if (_sel == this.anchorStateRegistryProxy.selector) _anchorStateRegistryProxy = IAnchorStateRegistry(_addr) ; else if (_sel == this.faultDisputeGame.selector) _faultDisputeGame = IFaultDisputeGame(_addr) ; @@ -308,12 +319,18 @@ contract DeployOPChainOutput is BaseDeployIO { return _l1CrossDomainMessengerProxy; } - function optimismPortalProxy() public returns (IOptimismPortal2) { + function optimismPortalProxy() public returns (IOptimismPortal) { DeployUtils.assertValidContractAddress(address(_optimismPortalProxy)); DeployUtils.assertERC1967ImplementationSet(address(_optimismPortalProxy)); return _optimismPortalProxy; } + function ethLockboxProxy() public returns (IETHLockbox) { + DeployUtils.assertValidContractAddress(address(_ethLockboxProxy)); + DeployUtils.assertERC1967ImplementationSet(address(_ethLockboxProxy)); + return _ethLockboxProxy; + } + function disputeGameFactoryProxy() public returns (IDisputeGameFactory) { DeployUtils.assertValidContractAddress(address(_disputeGameFactoryProxy)); DeployUtils.assertERC1967ImplementationSet(address(_disputeGameFactoryProxy)); @@ -371,6 +388,7 @@ contract DeployOPChain is Script { startingAnchorRoot: _doi.startingAnchorRoot(), saltMixer: _doi.saltMixer(), gasLimit: _doi.gasLimit(), + disputeGameUsesSuperRoots: _doi.disputeGameUsesSuperRoots(), disputeGameType: _doi.disputeGameType(), disputeAbsolutePrestate: _doi.disputeAbsolutePrestate(), disputeMaxGameDepth: _doi.disputeMaxGameDepth(), @@ -390,6 +408,7 @@ contract DeployOPChain is Script { vm.label(address(deployOutput.l1StandardBridgeProxy), "l1StandardBridgeProxy"); vm.label(address(deployOutput.l1CrossDomainMessengerProxy), "l1CrossDomainMessengerProxy"); vm.label(address(deployOutput.optimismPortalProxy), "optimismPortalProxy"); + vm.label(address(deployOutput.ethLockboxProxy), "ethLockboxProxy"); vm.label(address(deployOutput.disputeGameFactoryProxy), "disputeGameFactoryProxy"); vm.label(address(deployOutput.anchorStateRegistryProxy), "anchorStateRegistryProxy"); // vm.label(address(deployOutput.faultDisputeGame), "faultDisputeGame"); @@ -408,6 +427,7 @@ contract DeployOPChain is Script { _doo.set(_doo.l1StandardBridgeProxy.selector, address(deployOutput.l1StandardBridgeProxy)); _doo.set(_doo.l1CrossDomainMessengerProxy.selector, address(deployOutput.l1CrossDomainMessengerProxy)); _doo.set(_doo.optimismPortalProxy.selector, address(deployOutput.optimismPortalProxy)); + _doo.set(_doo.ethLockboxProxy.selector, address(deployOutput.ethLockboxProxy)); _doo.set(_doo.disputeGameFactoryProxy.selector, address(deployOutput.disputeGameFactoryProxy)); _doo.set(_doo.anchorStateRegistryProxy.selector, address(deployOutput.anchorStateRegistryProxy)); // _doo.set(_doo.faultDisputeGame.selector, address(deployOutput.faultDisputeGame)); @@ -440,7 +460,8 @@ contract DeployOPChain is Script { address(_doo.anchorStateRegistryProxy()), address(_doo.permissionedDisputeGame()), // address(_doo.faultDisputeGame()), - address(_doo.delayedWETHPermissionedGameProxy()) + address(_doo.delayedWETHPermissionedGameProxy()), + address(_doo.ethLockboxProxy()) ); // TODO: Eventually switch from Permissioned to Permissionless. Add this address back in. // address(_delayedWETHPermissionlessGameProxy) @@ -459,6 +480,7 @@ contract DeployOPChain is Script { assertValidL1StandardBridge(_doi, _doo); assertValidOptimismMintableERC20Factory(_doi, _doo); assertValidOptimismPortal(_doi, _doo); + assertValidETHLockbox(_doi, _doo); assertValidPermissionedDisputeGame(_doi, _doo); assertValidSystemConfig(_doi, _doo); assertValidAddressManager(_doi, _doo); @@ -609,19 +631,32 @@ contract DeployOPChain is Script { } function assertValidOptimismPortal(DeployOPChainInput _doi, DeployOPChainOutput _doo) internal { - IOptimismPortal2 portal = _doo.optimismPortalProxy(); + IOptimismPortal portal = _doo.optimismPortalProxy(); ISuperchainConfig superchainConfig = ISuperchainConfig(address(_doi.opcm().superchainConfig())); - require(address(portal.disputeGameFactory()) == address(_doo.disputeGameFactoryProxy()), "PORTAL-10"); - require(address(portal.systemConfig()) == address(_doo.systemConfigProxy()), "PORTAL-20"); - require(address(portal.superchainConfig()) == address(superchainConfig), "PORTAL-30"); - require(portal.guardian() == superchainConfig.guardian(), "PORTAL-40"); - require(portal.paused() == superchainConfig.paused(), "PORTAL-50"); - require(portal.l2Sender() == Constants.DEFAULT_L2_SENDER, "PORTAL-60"); + require(address(portal.anchorStateRegistry()) == address(_doo.anchorStateRegistryProxy()), "PORTAL-10"); + require(address(portal.disputeGameFactory()) == address(_doo.disputeGameFactoryProxy()), "PORTAL-20"); + require(address(portal.systemConfig()) == address(_doo.systemConfigProxy()), "PORTAL-30"); + require(address(portal.superchainConfig()) == address(superchainConfig), "PORTAL-40"); + require(portal.guardian() == superchainConfig.guardian(), "PORTAL-50"); + require(portal.paused() == superchainConfig.paused(), "PORTAL-60"); + require(portal.l2Sender() == Constants.DEFAULT_L2_SENDER, "PORTAL-70"); // This slot is the custom gas token _balance and this check ensures // that it stays unset for forwards compatibility with custom gas token. - require(vm.load(address(portal), bytes32(uint256(61))) == bytes32(0), "PORTAL-70"); + require(vm.load(address(portal), bytes32(uint256(61))) == bytes32(0), "PORTAL-80"); + + // Check once the portal is updated to use the new lockbox. + require(address(portal.ethLockbox()) == address(_doo.ethLockboxProxy()), "PORTAL-90"); + require(portal.proxyAdminOwner() == _doi.opChainProxyAdminOwner(), "PORTAL-100"); + } + + function assertValidETHLockbox(DeployOPChainInput _doi, DeployOPChainOutput _doo) internal { + IETHLockbox lockbox = _doo.ethLockboxProxy(); + + require(address(lockbox.superchainConfig()) == address(_doi.opcm().superchainConfig()), "ETHLOCKBOX-10"); + require(lockbox.authorizedPortals(address(_doo.optimismPortalProxy())), "ETHLOCKBOX-20"); + require(lockbox.proxyAdminOwner() == _doi.opChainProxyAdminOwner(), "ETHLOCKBOX-30"); } function assertValidDisputeGameFactory(DeployOPChainInput _doi, DeployOPChainOutput _doo) internal { @@ -702,6 +737,11 @@ contract DeployOPChain is Script { == DeployUtils.assertERC1967ImplementationSet(address(_doo.anchorStateRegistryProxy())), "OPCPA-110" ); + require( + admin.getProxyImplementation(address(_doo.ethLockboxProxy())) + == DeployUtils.assertERC1967ImplementationSet(address(_doo.ethLockboxProxy())), + "OPCPA-120" + ); } // -------- Utilities -------- diff --git a/packages/contracts-bedrock/scripts/deploy/ReadImplementationAddresses.s.sol b/packages/contracts-bedrock/scripts/deploy/ReadImplementationAddresses.s.sol index 211281b506d..e7b7f76edfd 100644 --- a/packages/contracts-bedrock/scripts/deploy/ReadImplementationAddresses.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/ReadImplementationAddresses.s.sol @@ -30,6 +30,7 @@ contract ReadImplementationAddressesInput is DeployOPChainOutput { contract ReadImplementationAddressesOutput is BaseDeployIO { address internal _delayedWETH; address internal _optimismPortal; + address internal _ethLockbox; address internal _systemConfig; address internal _l1CrossDomainMessenger; address internal _l1ERC721Bridge; @@ -43,6 +44,7 @@ contract ReadImplementationAddressesOutput is BaseDeployIO { require(_addr != address(0), "ReadImplementationAddressesOutput: cannot set zero address"); if (_sel == this.delayedWETH.selector) _delayedWETH = _addr; else if (_sel == this.optimismPortal.selector) _optimismPortal = _addr; + else if (_sel == this.ethLockbox.selector) _ethLockbox = _addr; else if (_sel == this.systemConfig.selector) _systemConfig = _addr; else if (_sel == this.l1CrossDomainMessenger.selector) _l1CrossDomainMessenger = _addr; else if (_sel == this.l1ERC721Bridge.selector) _l1ERC721Bridge = _addr; @@ -64,6 +66,11 @@ contract ReadImplementationAddressesOutput is BaseDeployIO { return _optimismPortal; } + function ethLockbox() public view returns (address) { + require(_ethLockbox != address(0), "ReadImplementationAddressesOutput: ethLockbox not set"); + return _ethLockbox; + } + function systemConfig() public view returns (address) { require(_systemConfig != address(0), "ReadImplementationAddressesOutput: systemConfig not set"); return _systemConfig; @@ -154,5 +161,8 @@ contract ReadImplementationAddresses is Script { address preimageOracle = address(IMIPS(mipsLogic).oracle()); _rio.set(_rio.preimageOracleSingleton.selector, preimageOracle); + + address ethLockbox = _rii.opcm().implementations().ethLockboxImpl; + _rio.set(_rio.ethLockbox.selector, ethLockbox); } } diff --git a/packages/contracts-bedrock/scripts/deploy/SetDisputeGameImpl.s.sol b/packages/contracts-bedrock/scripts/deploy/SetDisputeGameImpl.s.sol index 17e3d5a99dc..77d4880f02a 100644 --- a/packages/contracts-bedrock/scripts/deploy/SetDisputeGameImpl.s.sol +++ b/packages/contracts-bedrock/scripts/deploy/SetDisputeGameImpl.s.sol @@ -7,11 +7,11 @@ import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { BaseDeployIO } from "scripts/deploy/BaseDeployIO.sol"; import { GameType } from "src/dispute/lib/Types.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; contract SetDisputeGameImplInput is BaseDeployIO { IDisputeGameFactory internal _factory; - IOptimismPortal2 internal _portal; + IAnchorStateRegistry internal _anchorStateRegistry; IFaultDisputeGame internal _impl; uint32 internal _gameType; @@ -20,7 +20,7 @@ contract SetDisputeGameImplInput is BaseDeployIO { require(_addr != address(0), "SetDisputeGameImplInput: cannot set zero address"); if (_sel == this.factory.selector) _factory = IDisputeGameFactory(_addr); - else if (_sel == this.portal.selector) _portal = IOptimismPortal2(payable(_addr)); + else if (_sel == this.anchorStateRegistry.selector) _anchorStateRegistry = IAnchorStateRegistry(_addr); else if (_sel == this.impl.selector) _impl = IFaultDisputeGame(_addr); else revert("SetDisputeGameImplInput: unknown selector"); } @@ -36,8 +36,8 @@ contract SetDisputeGameImplInput is BaseDeployIO { return _factory; } - function portal() public view returns (IOptimismPortal2) { - return _portal; + function anchorStateRegistry() public view returns (IAnchorStateRegistry) { + return _anchorStateRegistry; } function impl() public view returns (IFaultDisputeGame) { @@ -57,15 +57,15 @@ contract SetDisputeGameImpl is Script { require(address(factory.gameImpls(gameType)) == address(0), "SDGI-10"); IFaultDisputeGame impl = _input.impl(); - IOptimismPortal2 portal = _input.portal(); + IAnchorStateRegistry anchorStateRegistry = _input.anchorStateRegistry(); vm.broadcast(msg.sender); factory.setImplementation(gameType, impl); - if (address(portal) != address(0)) { - require(address(portal.disputeGameFactory()) == address(factory), "SDGI-20"); + if (address(anchorStateRegistry) != address(0)) { + require(address(anchorStateRegistry.disputeGameFactory()) == address(factory), "SDGI-20"); vm.broadcast(msg.sender); - portal.setRespectedGameType(gameType); + anchorStateRegistry.setRespectedGameType(gameType); } assertValid(_input); @@ -75,9 +75,12 @@ contract SetDisputeGameImpl is Script { GameType gameType = GameType.wrap(_input.gameType()); require(address(_input.factory().gameImpls(gameType)) == address(_input.impl()), "SDGI-30"); - if (address(_input.portal()) != address(0)) { - require(address(_input.portal().disputeGameFactory()) == address(_input.factory()), "SDGI-40"); - require(GameType.unwrap(_input.portal().respectedGameType()) == GameType.unwrap(gameType), "SDGI-50"); + if (address(_input.anchorStateRegistry()) != address(0)) { + require(address(_input.anchorStateRegistry().disputeGameFactory()) == address(_input.factory()), "SDGI-40"); + require( + GameType.unwrap(_input.anchorStateRegistry().respectedGameType()) == GameType.unwrap(gameType), + "SDGI-50" + ); } } } diff --git a/packages/contracts-bedrock/scripts/go-ffi/differential-testing.go b/packages/contracts-bedrock/scripts/go-ffi/differential-testing.go index e35d2a82c30..7c328d23f26 100644 --- a/packages/contracts-bedrock/scripts/go-ffi/differential-testing.go +++ b/packages/contracts-bedrock/scripts/go-ffi/differential-testing.go @@ -38,14 +38,15 @@ var ( {Type: fixedBytes}, } - uint32Type, _ = abi.NewType("uint32", "", nil) - // Plain address type addressType, _ = abi.NewType("address", "", nil) // Plain uint8 type uint8Type, _ = abi.NewType("uint8", "", nil) + // Plain uint32 type + uint32Type, _ = abi.NewType("uint32", "", nil) + // Plain uint256 type uint256Type, _ = abi.NewType("uint256", "", nil) @@ -102,6 +103,19 @@ var ( {Name: "symbol", Type: fixedBytes}, } + // Super root proof tuple (uint8, uint64, OutputRootWithChainId[]) + superRootProof, _ = abi.NewType("tuple", "SuperRootProof", []abi.ArgumentMarshaling{ + {Name: "version", Type: "bytes1"}, + {Name: "timestamp", Type: "uint64"}, + {Name: "outputRoots", Type: "tuple[]", Components: []abi.ArgumentMarshaling{ + {Name: "chainId", Type: "uint256"}, + {Name: "root", Type: "bytes32"}, + }}, + }) + superRootProofArgs = abi.Arguments{ + {Type: superRootProof}, + } + // Dependency tuple (uint256) dependencyArgs = abi.Arguments{{Name: "chainId", Type: uint256Type}} ) @@ -520,6 +534,49 @@ func DiffTestUtils() { packed, err := bytesArgs.Pack(&encoded) checkErr(err, "Error encoding output") + fmt.Print(hexutil.Encode(packed)) + case "encodeSuperRootProof": + // Parse input argument as abi encoded super root proof + if len(args) < 2 { + panic("Error: encodeSuperRoot requires at least 1 argument") + } + + // Parse the input as hex data + superRootProofData := common.FromHex(args[1]) + proof, err := parseSuperRootProof(superRootProofData) + checkErr(err, "Error parsing super root proof") + + // Encode super root proof + encoded, err := encodeSuperRootProof(proof) + checkErr(err, "Error encoding super root") + + // Pack encoded super root + packed, err := bytesArgs.Pack(&encoded) + checkErr(err, "Error encoding output") + + fmt.Print(hexutil.Encode(packed)) + case "hashSuperRootProof": + // Parse input argument as abi encoded super root proof + if len(args) < 2 { + panic("Error: hashSuperRootProof requires at least 1 argument") + } + + // Parse the input as hex data + superRootProofData := common.FromHex(args[1]) + proof, err := parseSuperRootProof(superRootProofData) + checkErr(err, "Error parsing super root proof") + + // Encode super root proof + encoded, err := encodeSuperRootProof(proof) + checkErr(err, "Error encoding super root proof") + + // Hash super root proof + hash := crypto.Keccak256Hash(encoded) + + // Pack hash + packed, err := fixedBytesArgs.Pack(&hash) + checkErr(err, "Error encoding output") + fmt.Print(hexutil.Encode(packed)) default: panic(fmt.Errorf("Unknown command: %s", args[0])) diff --git a/packages/contracts-bedrock/scripts/go-ffi/utils.go b/packages/contracts-bedrock/scripts/go-ffi/utils.go index 0c535c03fee..157dd4d85c5 100644 --- a/packages/contracts-bedrock/scripts/go-ffi/utils.go +++ b/packages/contracts-bedrock/scripts/go-ffi/utils.go @@ -1,6 +1,7 @@ package main import ( + "encoding/binary" "errors" "fmt" "math/big" @@ -13,6 +14,18 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) +type OutputRootWithChainId struct { + ChainId *big.Int + Root common.Hash +} + +// Define a proper type for SuperRootProof +type SuperRootProof struct { + Version uint8 + Timestamp uint64 + OutputRoots []OutputRootWithChainId +} + var UnknownNonceVersion = errors.New("Unknown nonce version") // checkOk checks if ok is false, and panics if so. @@ -50,6 +63,78 @@ func encodeCrossDomainMessage(nonce *big.Int, sender common.Address, target comm return encoded, err } +// parseSuperRootProof parses an abi encoded super root proof into a SuperRootProof struct. +func parseSuperRootProof(abiEncodedProof []byte) (*SuperRootProof, error) { + // Parse the input as hex data + unpacked, err := superRootProofArgs.Unpack(abiEncodedProof) + if err != nil { + return nil, err + } + + // The Unpack method returns a slice of interface{}, so we need to get the first element + if len(unpacked) != 1 { + return nil, errors.New("unexpected number of values after unpacking super root proof") + } + + // Use an anonymous struct matching the tuple’s layout. + tmp := unpacked[0].(struct { + Version [1]uint8 `json:"version"` + Timestamp uint64 `json:"timestamp"` + OutputRoots []struct { + ChainId *big.Int `json:"chainId"` + Root [32]byte `json:"root"` + } `json:"outputRoots"` + }) + + // Convert into our desired SuperRootProof type. + proof := SuperRootProof{ + Version: tmp.Version[0], + Timestamp: tmp.Timestamp, + } + for _, o := range tmp.OutputRoots { + proof.OutputRoots = append(proof.OutputRoots, OutputRootWithChainId{ + ChainId: o.ChainId, + Root: common.BytesToHash(o.Root[:]), + }) + } + + return &proof, nil +} + +// encodeSuperRootProof encodes a super root proof into a byte array. +func encodeSuperRootProof(superRootProof *SuperRootProof) ([]byte, error) { + // Version must match the expected version (0x01) + if superRootProof.Version != 0x01 { + return nil, errors.New("invalid super root version") + } + + // Output roots must not be empty + if len(superRootProof.OutputRoots) == 0 { + return nil, errors.New("empty super root") + } + + // Start with version byte and timestamp + encoded := []byte{superRootProof.Version} + + // Add timestamp as bytes8 (uint64) + timestampBytes := make([]byte, 8) + binary.BigEndian.PutUint64(timestampBytes, superRootProof.Timestamp) + encoded = append(encoded, timestampBytes...) + + // Add each output root (chainId + root) + for _, outputRoot := range superRootProof.OutputRoots { + // Append chainId bytes (padded to 32 bytes) + chainIdBytes := make([]byte, 32) + outputRoot.ChainId.FillBytes(chainIdBytes) + encoded = append(encoded, chainIdBytes...) + + // Append root hash (already 32 bytes) + encoded = append(encoded, outputRoot.Root.Bytes()...) + } + + return encoded, nil +} + // hashWithdrawal hashes a withdrawal transaction. func hashWithdrawal(nonce *big.Int, sender common.Address, target common.Address, value *big.Int, gasLimit *big.Int, data []byte) (common.Hash, error) { wd := crossdomain.Withdrawal{ diff --git a/packages/contracts-bedrock/scripts/libraries/DeployUtils.sol b/packages/contracts-bedrock/scripts/libraries/DeployUtils.sol index 1cec8d04778..b4c5bd7b141 100644 --- a/packages/contracts-bedrock/scripts/libraries/DeployUtils.sol +++ b/packages/contracts-bedrock/scripts/libraries/DeployUtils.sol @@ -17,6 +17,7 @@ import { IProxy } from "interfaces/universal/IProxy.sol"; import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; import { IL1ChugSplashProxy, IStaticL1ChugSplashProxy } from "interfaces/legacy/IL1ChugSplashProxy.sol"; import { IResolvedDelegateProxy } from "interfaces/legacy/IResolvedDelegateProxy.sol"; +import { IReinitializableBase } from "interfaces/universal/IReinitializableBase.sol"; library DeployUtils { Vm internal constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); @@ -360,7 +361,19 @@ library DeployUtils { bytes32 slotVal = vm.load(_contractAddress, bytes32(_slot)); uint8 val = uint8((uint256(slotVal) >> (_offset * 8)) & 0xFF); if (_isProxy) { - require(val == 1, "DeployUtils: storage value is not 1 at the given slot and offset"); + // Using a try/catch here to check if the contract has an initVersion() defined. + // EIP-150 safe because we require that we have at least 200k gas before the call which + // is more than enough to avoid running out of gas when 63/64 of the gas is provided to + // the initVersion() call (which simply reads an immutable variable). Since this is + // only ever triggered as part of a script, we can safely assume we'll have the gas. + require(gasleft() > 200_000, "DeployUtils: insufficient gas for initVersion() call"); + + // eip150-safe + try IReinitializableBase(_contractAddress).initVersion() returns (uint8 initVersion_) { + require(val == initVersion_, "DeployUtils: storage value is incorrect at the given slot and offset"); + } catch { + require(val == 1, "DeployUtils: storage value is not set at the given slot and offset"); + } } else { require(val == type(uint8).max, "DeployUtils: storage value is not 0xff at the given slot and offset"); } diff --git a/packages/contracts-bedrock/scripts/libraries/Types.sol b/packages/contracts-bedrock/scripts/libraries/Types.sol index 01d0995aec6..f8c6eb90264 100644 --- a/packages/contracts-bedrock/scripts/libraries/Types.sol +++ b/packages/contracts-bedrock/scripts/libraries/Types.sol @@ -14,6 +14,7 @@ library Types { address AnchorStateRegistry; address OptimismMintableERC20Factory; address OptimismPortal; + address ETHLockbox; address SystemConfig; address L1ERC721Bridge; address ProtocolVersions; diff --git a/packages/contracts-bedrock/snapshots/abi/AnchorStateRegistry.json b/packages/contracts-bedrock/snapshots/abi/AnchorStateRegistry.json index 25919d1d4bf..3aa3c26096c 100644 --- a/packages/contracts-bedrock/snapshots/abi/AnchorStateRegistry.json +++ b/packages/contracts-bedrock/snapshots/abi/AnchorStateRegistry.json @@ -1,6 +1,12 @@ [ { - "inputs": [], + "inputs": [ + { + "internalType": "uint256", + "name": "_disputeGameFinalityDelaySeconds", + "type": "uint256" + } + ], "stateMutability": "nonpayable", "type": "constructor" }, @@ -41,6 +47,38 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "contract IDisputeGame", + "name": "_disputeGame", + "type": "address" + } + ], + "name": "blacklistDisputeGame", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IDisputeGame", + "name": "", + "type": "address" + } + ], + "name": "disputeGameBlacklist", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "disputeGameFactory", @@ -54,6 +92,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "disputeGameFinalityDelaySeconds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "getAnchorRoot", @@ -84,11 +135,6 @@ "name": "_disputeGameFactory", "type": "address" }, - { - "internalType": "contract IOptimismPortal2", - "name": "_portal", - "type": "address" - }, { "components": [ { @@ -105,6 +151,11 @@ "internalType": "struct OutputRoot", "name": "_startingAnchorRoot", "type": "tuple" + }, + { + "internalType": "GameType", + "name": "_startingRespectedGameType", + "type": "uint32" } ], "name": "initialize", @@ -266,12 +317,12 @@ }, { "inputs": [], - "name": "portal", + "name": "paused", "outputs": [ { - "internalType": "contract IOptimismPortal2", + "internalType": "bool", "name": "", - "type": "address" + "type": "bool" } ], "stateMutability": "view", @@ -290,6 +341,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "retirementTimestamp", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -303,6 +367,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "GameType", + "name": "_gameType", + "type": "uint32" + } + ], + "name": "setRespectedGameType", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "superchainConfig", @@ -316,6 +393,13 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "updateRetirementTimestamp", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "version", @@ -339,7 +423,7 @@ "type": "address" } ], - "name": "AnchorNotUpdated", + "name": "AnchorUpdated", "type": "event" }, { @@ -347,12 +431,12 @@ "inputs": [ { "indexed": true, - "internalType": "contract IFaultDisputeGame", - "name": "game", + "internalType": "contract IDisputeGame", + "name": "disputeGame", "type": "address" } ], - "name": "AnchorUpdated", + "name": "DisputeGameBlacklisted", "type": "event" }, { @@ -368,6 +452,32 @@ "name": "Initialized", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "GameType", + "name": "gameType", + "type": "uint32" + } + ], + "name": "RespectedGameTypeSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "RetirementTimestampSet", + "type": "event" + }, { "inputs": [], "name": "AnchorStateRegistry_AnchorGameBlacklisted", diff --git a/packages/contracts-bedrock/snapshots/abi/DeputyGuardianModule.json b/packages/contracts-bedrock/snapshots/abi/DeputyGuardianModule.json index f1006749351..ab662e4ee49 100644 --- a/packages/contracts-bedrock/snapshots/abi/DeputyGuardianModule.json +++ b/packages/contracts-bedrock/snapshots/abi/DeputyGuardianModule.json @@ -23,8 +23,8 @@ { "inputs": [ { - "internalType": "contract IOptimismPortal2", - "name": "_portal", + "internalType": "contract IAnchorStateRegistry", + "name": "_anchorStateRegistry", "type": "address" }, { @@ -75,25 +75,7 @@ "inputs": [ { "internalType": "contract IAnchorStateRegistry", - "name": "_registry", - "type": "address" - }, - { - "internalType": "contract IFaultDisputeGame", - "name": "_game", - "type": "address" - } - ], - "name": "setAnchorState", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract IOptimismPortal2", - "name": "_portal", + "name": "_anchorStateRegistry", "type": "address" }, { @@ -127,6 +109,19 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "contract IAnchorStateRegistry", + "name": "_anchorStateRegistry", + "type": "address" + } + ], + "name": "updateRetirementTimestamp", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "version", @@ -185,6 +180,19 @@ "name": "RespectedGameTypeSet", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "Timestamp", + "name": "updatedAt", + "type": "uint64" + } + ], + "name": "RetirementTimestampUpdated", + "type": "event" + }, { "anonymous": false, "inputs": [], @@ -199,12 +207,12 @@ "type": "string" } ], - "name": "ExecutionFailed", + "name": "DeputyGuardianModule_ExecutionFailed", "type": "error" }, { "inputs": [], - "name": "Unauthorized", + "name": "DeputyGuardianModule_Unauthorized", "type": "error" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/ETHLockbox.json b/packages/contracts-bedrock/snapshots/abi/ETHLockbox.json new file mode 100644 index 00000000000..d0e9c8fb9a6 --- /dev/null +++ b/packages/contracts-bedrock/snapshots/abi/ETHLockbox.json @@ -0,0 +1,321 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "contract IETHLockbox", + "name": "_lockbox", + "type": "address" + } + ], + "name": "authorizeLockbox", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IOptimismPortal2", + "name": "_portal", + "type": "address" + } + ], + "name": "authorizePortal", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "authorizedLockboxes", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "authorizedPortals", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ISuperchainConfig", + "name": "_superchainConfig", + "type": "address" + }, + { + "internalType": "contract IOptimismPortal2[]", + "name": "_portals", + "type": "address[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "lockETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IETHLockbox", + "name": "_lockbox", + "type": "address" + } + ], + "name": "migrateLiquidity", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proxyAdminOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "receiveLiquidity", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "superchainConfig", + "outputs": [ + { + "internalType": "contract ISuperchainConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_value", + "type": "uint256" + } + ], + "name": "unlockETH", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "portal", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ETHLocked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "portal", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ETHUnlocked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "lockbox", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "LiquidityMigrated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "lockbox", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "LiquidityReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "lockbox", + "type": "address" + } + ], + "name": "LockboxAuthorized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "portal", + "type": "address" + } + ], + "name": "PortalAuthorized", + "type": "event" + }, + { + "inputs": [], + "name": "ETHLockbox_DifferentProxyAdminOwner", + "type": "error" + }, + { + "inputs": [], + "name": "ETHLockbox_InsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "ETHLockbox_NoWithdrawalTransactions", + "type": "error" + }, + { + "inputs": [], + "name": "ETHLockbox_Paused", + "type": "error" + }, + { + "inputs": [], + "name": "ETHLockbox_Unauthorized", + "type": "error" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json index a12ea8e9411..381040d946e 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManager.json @@ -293,6 +293,11 @@ "name": "gasLimit", "type": "uint64" }, + { + "internalType": "bool", + "name": "disputeGameUsesSuperRoots", + "type": "bool" + }, { "internalType": "GameType", "name": "disputeGameType", @@ -368,6 +373,11 @@ "name": "l1CrossDomainMessengerProxy", "type": "address" }, + { + "internalType": "contract IETHLockbox", + "name": "ethLockboxProxy", + "type": "address" + }, { "internalType": "contract IOptimismPortal2", "name": "optimismPortalProxy", @@ -438,6 +448,11 @@ "name": "optimismPortalImpl", "type": "address" }, + { + "internalType": "address", + "name": "ethLockboxImpl", + "type": "address" + }, { "internalType": "address", "name": "systemConfigImpl", @@ -622,6 +637,11 @@ "internalType": "Claim", "name": "absolutePrestate", "type": "bytes32" + }, + { + "internalType": "bool", + "name": "disputeGameUsesSuperRoots", + "type": "bool" } ], "internalType": "struct OPContractsManager.OpChainConfig[]", @@ -652,6 +672,11 @@ "internalType": "Claim", "name": "absolutePrestate", "type": "bytes32" + }, + { + "internalType": "bool", + "name": "disputeGameUsesSuperRoots", + "type": "bool" } ], "internalType": "struct OPContractsManager.OpChainConfig[]", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json index 45af4462a56..9e09b81cb68 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerContractsContainer.json @@ -75,6 +75,11 @@ "name": "optimismPortalImpl", "type": "address" }, + { + "internalType": "address", + "name": "ethLockboxImpl", + "type": "address" + }, { "internalType": "address", "name": "systemConfigImpl", @@ -210,6 +215,11 @@ "name": "optimismPortalImpl", "type": "address" }, + { + "internalType": "address", + "name": "ethLockboxImpl", + "type": "address" + }, { "internalType": "address", "name": "systemConfigImpl", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json index d543175c828..a3e0783fd0d 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployer.json @@ -186,6 +186,11 @@ "name": "gasLimit", "type": "uint64" }, + { + "internalType": "bool", + "name": "disputeGameUsesSuperRoots", + "type": "bool" + }, { "internalType": "GameType", "name": "disputeGameType", @@ -271,6 +276,11 @@ "name": "l1CrossDomainMessengerProxy", "type": "address" }, + { + "internalType": "contract IETHLockbox", + "name": "ethLockboxProxy", + "type": "address" + }, { "internalType": "contract IOptimismPortal2", "name": "optimismPortalProxy", @@ -341,6 +351,11 @@ "name": "optimismPortalImpl", "type": "address" }, + { + "internalType": "address", + "name": "ethLockboxImpl", + "type": "address" + }, { "internalType": "address", "name": "systemConfigImpl", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployerInterop.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployerInterop.json index d543175c828..a3e0783fd0d 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployerInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerDeployerInterop.json @@ -186,6 +186,11 @@ "name": "gasLimit", "type": "uint64" }, + { + "internalType": "bool", + "name": "disputeGameUsesSuperRoots", + "type": "bool" + }, { "internalType": "GameType", "name": "disputeGameType", @@ -271,6 +276,11 @@ "name": "l1CrossDomainMessengerProxy", "type": "address" }, + { + "internalType": "contract IETHLockbox", + "name": "ethLockboxProxy", + "type": "address" + }, { "internalType": "contract IOptimismPortal2", "name": "optimismPortalProxy", @@ -341,6 +351,11 @@ "name": "optimismPortalImpl", "type": "address" }, + { + "internalType": "address", + "name": "ethLockboxImpl", + "type": "address" + }, { "internalType": "address", "name": "systemConfigImpl", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json index a2088238caf..75a4caf4046 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerGameTypeAdder.json @@ -244,6 +244,11 @@ "name": "optimismPortalImpl", "type": "address" }, + { + "internalType": "address", + "name": "ethLockboxImpl", + "type": "address" + }, { "internalType": "address", "name": "systemConfigImpl", @@ -311,6 +316,11 @@ "internalType": "Claim", "name": "absolutePrestate", "type": "bytes32" + }, + { + "internalType": "bool", + "name": "disputeGameUsesSuperRoots", + "type": "bool" } ], "internalType": "struct OPContractsManager.OpChainConfig[]", diff --git a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json index 8efb6ad77e6..d9ac40df7ca 100644 --- a/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json +++ b/packages/contracts-bedrock/snapshots/abi/OPContractsManagerUpgrader.json @@ -141,6 +141,11 @@ "name": "optimismPortalImpl", "type": "address" }, + { + "internalType": "address", + "name": "ethLockboxImpl", + "type": "address" + }, { "internalType": "address", "name": "systemConfigImpl", @@ -208,6 +213,11 @@ "internalType": "Claim", "name": "absolutePrestate", "type": "bytes32" + }, + { + "internalType": "bool", + "name": "disputeGameUsesSuperRoots", + "type": "bool" } ], "internalType": "struct OPContractsManager.OpChainConfig[]", diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json b/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json index 71b8677f7dd..5a60bf69cd6 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json @@ -5,11 +5,6 @@ "internalType": "uint256", "name": "_proofMaturityDelaySeconds", "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_disputeGameFinalityDelaySeconds", - "type": "uint256" } ], "stateMutability": "nonpayable", @@ -20,16 +15,16 @@ "type": "receive" }, { - "inputs": [ + "inputs": [], + "name": "anchorStateRegistry", + "outputs": [ { - "internalType": "contract IDisputeGame", - "name": "_disputeGame", + "internalType": "contract IAnchorStateRegistry", + "name": "", "type": "address" } ], - "name": "blacklistDisputeGame", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { @@ -83,25 +78,6 @@ "stateMutability": "payable", "type": "function" }, - { - "inputs": [ - { - "internalType": "contract IDisputeGame", - "name": "", - "type": "address" - } - ], - "name": "disputeGameBlacklist", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "disputeGameFactory", @@ -135,6 +111,19 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [], + "name": "ethLockbox", + "outputs": [ + { + "internalType": "contract IETHLockbox", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -263,12 +252,20 @@ "type": "function" }, { - "inputs": [ + "inputs": [], + "name": "initVersion", + "outputs": [ { - "internalType": "contract IDisputeGameFactory", - "name": "_disputeGameFactory", - "type": "address" - }, + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ { "internalType": "contract ISystemConfig", "name": "_systemConfig", @@ -280,9 +277,19 @@ "type": "address" }, { - "internalType": "GameType", - "name": "_initialRespectedGameType", - "type": "uint32" + "internalType": "contract IAnchorStateRegistry", + "name": "_anchorStateRegistry", + "type": "address" + }, + { + "internalType": "contract IETHLockbox", + "name": "_ethLockbox", + "type": "address" + }, + { + "internalType": "bool", + "name": "_superRootsActive", + "type": "bool" } ], "name": "initialize", @@ -303,6 +310,13 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "migrateLiquidity", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -496,6 +510,127 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gasLimit", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct Types.WithdrawalTransaction", + "name": "_tx", + "type": "tuple" + }, + { + "internalType": "contract IDisputeGame", + "name": "_disputeGameProxy", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_outputRootIndex", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bytes1", + "name": "version", + "type": "bytes1" + }, + { + "internalType": "uint64", + "name": "timestamp", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "root", + "type": "bytes32" + } + ], + "internalType": "struct Types.OutputRootWithChainId[]", + "name": "outputRoots", + "type": "tuple[]" + } + ], + "internalType": "struct Types.SuperRootProof", + "name": "_superRootProof", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "version", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "messagePasserStorageRoot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "latestBlockhash", + "type": "bytes32" + } + ], + "internalType": "struct Types.OutputRootProof", + "name": "_outputRootProof", + "type": "tuple" + }, + { + "internalType": "bytes[]", + "name": "_withdrawalProof", + "type": "bytes[]" + } + ], + "name": "proveWithdrawalTransaction", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -525,6 +660,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "proxyAdminOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "respectedGameType", @@ -552,16 +700,16 @@ "type": "function" }, { - "inputs": [ + "inputs": [], + "name": "superRootsActive", + "outputs": [ { - "internalType": "GameType", - "name": "_gameType", - "type": "uint32" + "internalType": "bool", + "name": "", + "type": "bool" } ], - "name": "setRespectedGameType", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { @@ -590,6 +738,42 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "contract IETHLockbox", + "name": "_newLockbox", + "type": "address" + } + ], + "name": "updateLockbox", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IAnchorStateRegistry", + "name": "_anchorStateRegistry", + "type": "address" + }, + { + "internalType": "contract IETHLockbox", + "name": "_ethLockbox", + "type": "address" + }, + { + "internalType": "bool", + "name": "_superRootsActive", + "type": "bool" + } + ], + "name": "upgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "version", @@ -608,12 +792,18 @@ "inputs": [ { "indexed": true, - "internalType": "contract IDisputeGame", - "name": "disputeGame", + "internalType": "address", + "name": "lockbox", "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "ethBalance", + "type": "uint256" } ], - "name": "DisputeGameBlacklisted", + "name": "ETHMigrated", "type": "event" }, { @@ -633,19 +823,19 @@ "anonymous": false, "inputs": [ { - "indexed": true, - "internalType": "GameType", - "name": "newGameType", - "type": "uint32" + "indexed": false, + "internalType": "address", + "name": "oldLockbox", + "type": "address" }, { - "indexed": true, - "internalType": "Timestamp", - "name": "updatedAt", - "type": "uint64" + "indexed": false, + "internalType": "address", + "name": "newLockbox", + "type": "address" } ], - "name": "RespectedGameTypeSet", + "name": "LockboxUpdated", "type": "event" }, { @@ -744,117 +934,152 @@ }, { "inputs": [], - "name": "AlreadyFinalized", + "name": "ContentLengthMismatch", "type": "error" }, { "inputs": [], - "name": "BadTarget", + "name": "EmptyItem", "type": "error" }, { "inputs": [], - "name": "Blacklisted", + "name": "Encoding_EmptySuperRoot", "type": "error" }, { "inputs": [], - "name": "CallPaused", + "name": "Encoding_InvalidSuperRootVersion", "type": "error" }, { "inputs": [], - "name": "ContentLengthMismatch", + "name": "InvalidDataRemainder", "type": "error" }, { "inputs": [], - "name": "EmptyItem", + "name": "InvalidHeader", "type": "error" }, { "inputs": [], - "name": "GasEstimation", + "name": "OptimismPortal_AlreadyFinalized", "type": "error" }, { "inputs": [], - "name": "InvalidDataRemainder", + "name": "OptimismPortal_BadTarget", "type": "error" }, { "inputs": [], - "name": "InvalidDisputeGame", + "name": "OptimismPortal_CallPaused", "type": "error" }, { "inputs": [], - "name": "InvalidGameType", + "name": "OptimismPortal_CalldataTooLarge", "type": "error" }, { "inputs": [], - "name": "InvalidHeader", + "name": "OptimismPortal_GasEstimation", "type": "error" }, { "inputs": [], - "name": "InvalidMerkleProof", + "name": "OptimismPortal_GasLimitTooLow", "type": "error" }, { "inputs": [], - "name": "InvalidProof", + "name": "OptimismPortal_ImproperDisputeGame", "type": "error" }, { "inputs": [], - "name": "LargeCalldata", + "name": "OptimismPortal_InvalidDisputeGame", "type": "error" }, { "inputs": [], - "name": "LegacyGame", + "name": "OptimismPortal_InvalidMerkleProof", "type": "error" }, { "inputs": [], - "name": "NonReentrant", + "name": "OptimismPortal_InvalidOutputRootChainId", "type": "error" }, { "inputs": [], - "name": "OutOfGas", + "name": "OptimismPortal_InvalidOutputRootIndex", "type": "error" }, { "inputs": [], - "name": "ProposalNotValidated", + "name": "OptimismPortal_InvalidOutputRootProof", "type": "error" }, { "inputs": [], - "name": "SmallGasLimit", + "name": "OptimismPortal_InvalidProofTimestamp", "type": "error" }, { "inputs": [], - "name": "Unauthorized", + "name": "OptimismPortal_InvalidRootClaim", "type": "error" }, { "inputs": [], - "name": "UnexpectedList", + "name": "OptimismPortal_InvalidSuperRootProof", "type": "error" }, { "inputs": [], - "name": "UnexpectedString", + "name": "OptimismPortal_NoReentrancy", + "type": "error" + }, + { + "inputs": [], + "name": "OptimismPortal_ProofNotOldEnough", + "type": "error" + }, + { + "inputs": [], + "name": "OptimismPortal_Unauthorized", "type": "error" }, { "inputs": [], - "name": "Unproven", + "name": "OptimismPortal_Unproven", + "type": "error" + }, + { + "inputs": [], + "name": "OptimismPortal_WrongProofMethod", + "type": "error" + }, + { + "inputs": [], + "name": "OutOfGas", + "type": "error" + }, + { + "inputs": [], + "name": "ReinitializableBase_ZeroInitVersion", + "type": "error" + }, + { + "inputs": [], + "name": "UnexpectedList", + "type": "error" + }, + { + "inputs": [], + "name": "UnexpectedString", "type": "error" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json b/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json index 2dd27e68eaf..4835f00aa2c 100644 --- a/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/OptimismPortalInterop.json @@ -5,11 +5,6 @@ "internalType": "uint256", "name": "_proofMaturityDelaySeconds", "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_disputeGameFinalityDelaySeconds", - "type": "uint256" } ], "stateMutability": "nonpayable", @@ -20,16 +15,16 @@ "type": "receive" }, { - "inputs": [ + "inputs": [], + "name": "anchorStateRegistry", + "outputs": [ { - "internalType": "contract IDisputeGame", - "name": "_disputeGame", + "internalType": "contract IAnchorStateRegistry", + "name": "", "type": "address" } ], - "name": "blacklistDisputeGame", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { @@ -83,25 +78,6 @@ "stateMutability": "payable", "type": "function" }, - { - "inputs": [ - { - "internalType": "contract IDisputeGame", - "name": "", - "type": "address" - } - ], - "name": "disputeGameBlacklist", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [], "name": "disputeGameFactory", @@ -135,6 +111,19 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [], + "name": "ethLockbox", + "outputs": [ + { + "internalType": "contract IETHLockbox", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -263,12 +252,20 @@ "type": "function" }, { - "inputs": [ + "inputs": [], + "name": "initVersion", + "outputs": [ { - "internalType": "contract IDisputeGameFactory", - "name": "_disputeGameFactory", - "type": "address" - }, + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ { "internalType": "contract ISystemConfig", "name": "_systemConfig", @@ -280,9 +277,19 @@ "type": "address" }, { - "internalType": "GameType", - "name": "_initialRespectedGameType", - "type": "uint32" + "internalType": "contract IAnchorStateRegistry", + "name": "_anchorStateRegistry", + "type": "address" + }, + { + "internalType": "contract IETHLockbox", + "name": "_ethLockbox", + "type": "address" + }, + { + "internalType": "bool", + "name": "_superRootsActive", + "type": "bool" } ], "name": "initialize", @@ -303,6 +310,13 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "migrateLiquidity", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -496,6 +510,127 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gasLimit", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct Types.WithdrawalTransaction", + "name": "_tx", + "type": "tuple" + }, + { + "internalType": "contract IDisputeGame", + "name": "_disputeGameProxy", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_outputRootIndex", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bytes1", + "name": "version", + "type": "bytes1" + }, + { + "internalType": "uint64", + "name": "timestamp", + "type": "uint64" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "root", + "type": "bytes32" + } + ], + "internalType": "struct Types.OutputRootWithChainId[]", + "name": "outputRoots", + "type": "tuple[]" + } + ], + "internalType": "struct Types.SuperRootProof", + "name": "_superRootProof", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "version", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "messagePasserStorageRoot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "latestBlockhash", + "type": "bytes32" + } + ], + "internalType": "struct Types.OutputRootProof", + "name": "_outputRootProof", + "type": "tuple" + }, + { + "internalType": "bytes[]", + "name": "_withdrawalProof", + "type": "bytes[]" + } + ], + "name": "proveWithdrawalTransaction", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -525,6 +660,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "proxyAdminOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "respectedGameType", @@ -570,16 +718,16 @@ "type": "function" }, { - "inputs": [ + "inputs": [], + "name": "superRootsActive", + "outputs": [ { - "internalType": "GameType", - "name": "_gameType", - "type": "uint32" + "internalType": "bool", + "name": "", + "type": "bool" } ], - "name": "setRespectedGameType", - "outputs": [], - "stateMutability": "nonpayable", + "stateMutability": "view", "type": "function" }, { @@ -608,6 +756,42 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "contract IETHLockbox", + "name": "_newLockbox", + "type": "address" + } + ], + "name": "updateLockbox", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IAnchorStateRegistry", + "name": "_anchorStateRegistry", + "type": "address" + }, + { + "internalType": "contract IETHLockbox", + "name": "_ethLockbox", + "type": "address" + }, + { + "internalType": "bool", + "name": "_superRootsActive", + "type": "bool" + } + ], + "name": "upgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "version", @@ -626,12 +810,18 @@ "inputs": [ { "indexed": true, - "internalType": "contract IDisputeGame", - "name": "disputeGame", + "internalType": "address", + "name": "lockbox", "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "ethBalance", + "type": "uint256" } ], - "name": "DisputeGameBlacklisted", + "name": "ETHMigrated", "type": "event" }, { @@ -651,19 +841,19 @@ "anonymous": false, "inputs": [ { - "indexed": true, - "internalType": "GameType", - "name": "newGameType", - "type": "uint32" + "indexed": false, + "internalType": "address", + "name": "oldLockbox", + "type": "address" }, { - "indexed": true, - "internalType": "Timestamp", - "name": "updatedAt", - "type": "uint64" + "indexed": false, + "internalType": "address", + "name": "newLockbox", + "type": "address" } ], - "name": "RespectedGameTypeSet", + "name": "LockboxUpdated", "type": "event" }, { @@ -762,117 +952,152 @@ }, { "inputs": [], - "name": "AlreadyFinalized", + "name": "ContentLengthMismatch", "type": "error" }, { "inputs": [], - "name": "BadTarget", + "name": "EmptyItem", "type": "error" }, { "inputs": [], - "name": "Blacklisted", + "name": "Encoding_EmptySuperRoot", "type": "error" }, { "inputs": [], - "name": "CallPaused", + "name": "Encoding_InvalidSuperRootVersion", "type": "error" }, { "inputs": [], - "name": "ContentLengthMismatch", + "name": "InvalidDataRemainder", "type": "error" }, { "inputs": [], - "name": "EmptyItem", + "name": "InvalidHeader", "type": "error" }, { "inputs": [], - "name": "GasEstimation", + "name": "OptimismPortal_AlreadyFinalized", "type": "error" }, { "inputs": [], - "name": "InvalidDataRemainder", + "name": "OptimismPortal_BadTarget", "type": "error" }, { "inputs": [], - "name": "InvalidDisputeGame", + "name": "OptimismPortal_CallPaused", "type": "error" }, { "inputs": [], - "name": "InvalidGameType", + "name": "OptimismPortal_CalldataTooLarge", "type": "error" }, { "inputs": [], - "name": "InvalidHeader", + "name": "OptimismPortal_GasEstimation", "type": "error" }, { "inputs": [], - "name": "InvalidMerkleProof", + "name": "OptimismPortal_GasLimitTooLow", "type": "error" }, { "inputs": [], - "name": "InvalidProof", + "name": "OptimismPortal_ImproperDisputeGame", "type": "error" }, { "inputs": [], - "name": "LargeCalldata", + "name": "OptimismPortal_InvalidDisputeGame", "type": "error" }, { "inputs": [], - "name": "LegacyGame", + "name": "OptimismPortal_InvalidMerkleProof", "type": "error" }, { "inputs": [], - "name": "NonReentrant", + "name": "OptimismPortal_InvalidOutputRootChainId", "type": "error" }, { "inputs": [], - "name": "OutOfGas", + "name": "OptimismPortal_InvalidOutputRootIndex", "type": "error" }, { "inputs": [], - "name": "ProposalNotValidated", + "name": "OptimismPortal_InvalidOutputRootProof", "type": "error" }, { "inputs": [], - "name": "SmallGasLimit", + "name": "OptimismPortal_InvalidProofTimestamp", "type": "error" }, { "inputs": [], - "name": "Unauthorized", + "name": "OptimismPortal_InvalidRootClaim", "type": "error" }, { "inputs": [], - "name": "UnexpectedList", + "name": "OptimismPortal_InvalidSuperRootProof", "type": "error" }, { "inputs": [], - "name": "UnexpectedString", + "name": "OptimismPortal_NoReentrancy", + "type": "error" + }, + { + "inputs": [], + "name": "OptimismPortal_ProofNotOldEnough", + "type": "error" + }, + { + "inputs": [], + "name": "OptimismPortal_Unauthorized", "type": "error" }, { "inputs": [], - "name": "Unproven", + "name": "OptimismPortal_Unproven", + "type": "error" + }, + { + "inputs": [], + "name": "OptimismPortal_WrongProofMethod", + "type": "error" + }, + { + "inputs": [], + "name": "OutOfGas", + "type": "error" + }, + { + "inputs": [], + "name": "ReinitializableBase_ZeroInitVersion", + "type": "error" + }, + { + "inputs": [], + "name": "UnexpectedList", + "type": "error" + }, + { + "inputs": [], + "name": "UnexpectedString", "type": "error" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/SystemConfig.json b/packages/contracts-bedrock/snapshots/abi/SystemConfig.json index 5009ab31ca8..8f11882ac1b 100644 --- a/packages/contracts-bedrock/snapshots/abi/SystemConfig.json +++ b/packages/contracts-bedrock/snapshots/abi/SystemConfig.json @@ -283,6 +283,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "initVersion", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -393,6 +406,11 @@ "internalType": "struct SystemConfig.Addresses", "name": "_addresses", "type": "tuple" + }, + { + "internalType": "uint256", + "name": "_l2ChainId", + "type": "uint256" } ], "name": "initialize", @@ -439,6 +457,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "l2ChainId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "maximumGasLimit", @@ -758,6 +789,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_l2ChainId", + "type": "uint256" + } + ], + "name": "upgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "version", @@ -827,5 +871,10 @@ ], "name": "OwnershipTransferred", "type": "event" + }, + { + "inputs": [], + "name": "ReinitializableBase_ZeroInitVersion", + "type": "error" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json b/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json index 8bcac1f13cc..c11d382aa52 100644 --- a/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json +++ b/packages/contracts-bedrock/snapshots/abi/SystemConfigInterop.json @@ -304,6 +304,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "initVersion", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -414,6 +427,11 @@ "internalType": "struct SystemConfig.Addresses", "name": "_addresses", "type": "tuple" + }, + { + "internalType": "uint256", + "name": "_l2ChainId", + "type": "uint256" } ], "name": "initialize", @@ -536,6 +554,11 @@ "internalType": "address", "name": "_dependencyManager", "type": "address" + }, + { + "internalType": "uint256", + "name": "_l2ChainId", + "type": "uint256" } ], "name": "initialize", @@ -582,6 +605,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "l2ChainId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "maximumGasLimit", @@ -914,6 +950,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_l2ChainId", + "type": "uint256" + } + ], + "name": "upgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [], "name": "version", @@ -983,5 +1032,10 @@ ], "name": "OwnershipTransferred", "type": "event" + }, + { + "inputs": [], + "name": "ReinitializableBase_ZeroInitVersion", + "type": "error" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 5667bfa1580..ea49df2bac3 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -3,6 +3,10 @@ "initCodeHash": "0xacbae98cc7c0f7ecbf36dc44bbf7cb0a011e6e6b781e28b9dbf947e31482b30d", "sourceCodeHash": "0xe772f7db8033e4a738850cb28ac4849d3a454c93732135a8a10d4f7cb498088e" }, + "src/L1/ETHLockbox.sol:ETHLockbox": { + "initCodeHash": "0x599593bf96170081d2fd191bb2f61eaa2d094faafb6a583a34b3eea2dc49e5c6", + "sourceCodeHash": "0x34da4a8a0e32cabce97056f10cc1c8231f14a1cd706ae63ee0bef3c1f65f3e13" + }, "src/L1/L1CrossDomainMessenger.sol:L1CrossDomainMessenger": { "initCodeHash": "0x5a6272f6bd4346da460b7ff2ebc426d158d7ffc65dc0f2c0651a9e6d545bfd03", "sourceCodeHash": "0xf11fa72481dbe43dad4e7a48645fcf92d0feeefa0b98b282042bdda568508372" @@ -16,16 +20,16 @@ "sourceCodeHash": "0x44797707aea8c63dec049a02d69ea056662a06e5cf320028ab8b388634bf1c67" }, "src/L1/OPContractsManager.sol:OPContractsManager": { - "initCodeHash": "0x216fe3ceef5d3e839d18a00383793bd326f20944fc3b55ef099a26a22141de18", - "sourceCodeHash": "0xd1de3414a3db731447cb6172df23a00cb1664783de56a93fb29f87d6bc8b8bb0" + "initCodeHash": "0x9e7496b9720bae99e5db18dc18c99b5c06cd755c6d4079e0d38c56d625fe7b4e", + "sourceCodeHash": "0x8302de0ccd551512f2430d545ea55c938b002701d2292fd7aad2b4d2b975727b" }, "src/L1/OptimismPortal2.sol:OptimismPortal2": { - "initCodeHash": "0xd1651b8a6f4d25611a0105d5cc7c1da3921417bd44da870ec63bf5ccd1bc7c63", - "sourceCodeHash": "0xce7373d8c7df47caa8b090f3afb3d2539677f12cb3eff7fc0ab1fd85638f05c1" + "initCodeHash": "0x774dd0c79fb11ea7ecbbffb28d1c005b72790577adbeeeb881c2a180e2feaadf", + "sourceCodeHash": "0x2d36fbd2326ceafe89ca1a3b80c223ad7f934e89e996d20b847498f015316bdf" }, "src/L1/OptimismPortalInterop.sol:OptimismPortalInterop": { - "initCodeHash": "0xd59854648bf205dfbea96b483b2937441c32e9ef66b002468c2c14c0d6661728", - "sourceCodeHash": "0xd00b267dcf125e77c10b28c088be4378ec779927e3bcfeb6aa9a7f3d51370490" + "initCodeHash": "0x9c35223461ee313b77eafb727c51300596975b5134ff51847d8fab424b4dcd0b", + "sourceCodeHash": "0xa1ecf8a19638dd24d80f62e2faef132afa15c6945c14133fe58709015fcb14a6" }, "src/L1/ProtocolVersions.sol:ProtocolVersions": { "initCodeHash": "0x5a76c8530cb24cf23d3baacc6eefaac226382af13f1e2a35535d2ec2b0573b29", @@ -36,12 +40,12 @@ "sourceCodeHash": "0xfd56e63e76b1f203cceeb9bbb14396ae803cbbbf7e80ca0ee11fb586321812af" }, "src/L1/SystemConfig.sol:SystemConfig": { - "initCodeHash": "0xd5b8b8eb47763556d9953019d1f81b1d790f15433aa9696b159a3fc45ecee148", - "sourceCodeHash": "0x6bfbc78b0fef2f65beff11a81f924728a7bd439a56986997621099551805aff9" + "initCodeHash": "0x471ac69544fdee81d0734b87151297ad4cf91ca1d43a2c95e9e644c0424eed65", + "sourceCodeHash": "0x8c3ccb8f3718c00c3f9bf7c866a22d87ea18a87a6e9454c31d1c97638107434c" }, "src/L1/SystemConfigInterop.sol:SystemConfigInterop": { - "initCodeHash": "0xbef4696b2dcb6d43c3b3c438338bfb2224a1ea5002ed0612ec36a7821d7e3da2", - "sourceCodeHash": "0x1653aaa4d2b44d34ca1f9f2b4971eeb7594c8c2d27771b1f68b8d38cb79f2368" + "initCodeHash": "0xcdca84b074ddfd6b4e4df1a57d3500c1d48ecf88852af47f6351e9ae24b6fc2a", + "sourceCodeHash": "0x71914fa1408befaef3a06a67404e0ab580d9d945e068ba23063ea6588f0f68a6" }, "src/L2/BaseFeeVault.sol:BaseFeeVault": { "initCodeHash": "0xc403d4c555d8e69a2699e01d192ae7327136701fa02da10a6d75a584b3c364c9", @@ -156,8 +160,8 @@ "sourceCodeHash": "0x03c160168986ffc8d26a90c37366e7ad6da03f49d83449e1f8b3de0f4b590f6f" }, "src/dispute/AnchorStateRegistry.sol:AnchorStateRegistry": { - "initCodeHash": "0x08cc5a5e41eadb6c411fa6387ddc0cf12be360855599dd622cce84c0ba081e77", - "sourceCodeHash": "0xe0aaa79f7184724ff0fba2e92e85f652f936fecd099288edb0a0f6b0e0240f34" + "initCodeHash": "0xb905a31a816dc7354e9153a6cbf08d968c6d631e5383bd64c5ff1825bf284825", + "sourceCodeHash": "0xf785b369133782f614ed792766f5ec05d79b639db29abc2af2a4074f8600fb36" }, "src/dispute/DelayedWETH.sol:DelayedWETH": { "initCodeHash": "0xdd0b5e523f3b53563fe0b6e6165fb73605b14910ffa32a7cbed855cdebab47c6", @@ -196,8 +200,8 @@ "sourceCodeHash": "0x62c9a6182d82692fb9c173ddb0d7978bcff2d1d4dc8cd2f10625e1e65bda6888" }, "src/safe/DeputyGuardianModule.sol:DeputyGuardianModule": { - "initCodeHash": "0x5eaf823d81995ce1f703f26e31049c54c1d4902dd9873a0b4645d470f2f459a2", - "sourceCodeHash": "0x17236a91c4171ae9525eae0e59fa65bb2dc320d62677cfc7d7eb942f182619fb" + "initCodeHash": "0xf0ccc9997a120b2642bf4b15b5c12b041219c5e748c0c5e24af36f9cde62f175", + "sourceCodeHash": "0xa7dc83c6278118b9a7b4633633eeaa7e5b466bf24fdd184925ab620555caf0aa" }, "src/safe/DeputyPauseModule.sol:DeputyPauseModule": { "initCodeHash": "0xa3b7bf0c93b41f39ebc18a81322b90127a633d684ae9f86c2f2a1c48fe7f1372", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/AnchorStateRegistry.json b/packages/contracts-bedrock/snapshots/storageLayout/AnchorStateRegistry.json index fac376a90d1..4d981001f8f 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/AnchorStateRegistry.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/AnchorStateRegistry.json @@ -27,25 +27,39 @@ "slot": "1", "type": "contract IDisputeGameFactory" }, - { - "bytes": "20", - "label": "portal", - "offset": 0, - "slot": "2", - "type": "contract IOptimismPortal2" - }, { "bytes": "20", "label": "anchorGame", "offset": 0, - "slot": "3", + "slot": "2", "type": "contract IFaultDisputeGame" }, { "bytes": "64", "label": "startingAnchorRoot", "offset": 0, - "slot": "4", + "slot": "3", "type": "struct OutputRoot" + }, + { + "bytes": "32", + "label": "disputeGameBlacklist", + "offset": 0, + "slot": "5", + "type": "mapping(contract IDisputeGame => bool)" + }, + { + "bytes": "4", + "label": "respectedGameType", + "offset": 0, + "slot": "6", + "type": "GameType" + }, + { + "bytes": "8", + "label": "retirementTimestamp", + "offset": 4, + "slot": "6", + "type": "uint64" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/ETHLockbox.json b/packages/contracts-bedrock/snapshots/storageLayout/ETHLockbox.json new file mode 100644 index 00000000000..4ba870d921e --- /dev/null +++ b/packages/contracts-bedrock/snapshots/storageLayout/ETHLockbox.json @@ -0,0 +1,37 @@ +[ + { + "bytes": "1", + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "uint8" + }, + { + "bytes": "1", + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "bool" + }, + { + "bytes": "20", + "label": "superchainConfig", + "offset": 2, + "slot": "0", + "type": "contract ISuperchainConfig" + }, + { + "bytes": "32", + "label": "authorizedPortals", + "offset": 0, + "slot": "1", + "type": "mapping(address => bool)" + }, + { + "bytes": "32", + "label": "authorizedLockboxes", + "offset": 0, + "slot": "2", + "type": "mapping(address => bool)" + } +] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json index 98248abd1d8..a8ce2f3c54c 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerContractsContainer.json @@ -7,7 +7,7 @@ "type": "struct OPContractsManager.Blueprints" }, { - "bytes": "384", + "bytes": "416", "label": "implementation", "offset": 0, "slot": "9", diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal2.json b/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal2.json index 654695522e0..a66c4b71b4d 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal2.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortal2.json @@ -78,10 +78,10 @@ }, { "bytes": "20", - "label": "disputeGameFactory", + "label": "spacer_56_0_20", "offset": 0, "slot": "56", - "type": "contract IDisputeGameFactory" + "type": "address" }, { "bytes": "32", @@ -92,21 +92,21 @@ }, { "bytes": "32", - "label": "disputeGameBlacklist", + "label": "spacer_58_0_32", "offset": 0, "slot": "58", - "type": "mapping(contract IDisputeGame => bool)" + "type": "bytes32" }, { "bytes": "4", - "label": "respectedGameType", + "label": "spacer_59_0_4", "offset": 0, "slot": "59", "type": "GameType" }, { "bytes": "8", - "label": "respectedGameTypeUpdatedAt", + "label": "spacer_59_4_8", "offset": 4, "slot": "59", "type": "uint64" @@ -124,5 +124,26 @@ "offset": 0, "slot": "61", "type": "uint256" + }, + { + "bytes": "20", + "label": "anchorStateRegistry", + "offset": 0, + "slot": "62", + "type": "contract IAnchorStateRegistry" + }, + { + "bytes": "20", + "label": "ethLockbox", + "offset": 0, + "slot": "63", + "type": "contract IETHLockbox" + }, + { + "bytes": "1", + "label": "superRootsActive", + "offset": 20, + "slot": "63", + "type": "bool" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortalInterop.json b/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortalInterop.json index 654695522e0..a66c4b71b4d 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortalInterop.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/OptimismPortalInterop.json @@ -78,10 +78,10 @@ }, { "bytes": "20", - "label": "disputeGameFactory", + "label": "spacer_56_0_20", "offset": 0, "slot": "56", - "type": "contract IDisputeGameFactory" + "type": "address" }, { "bytes": "32", @@ -92,21 +92,21 @@ }, { "bytes": "32", - "label": "disputeGameBlacklist", + "label": "spacer_58_0_32", "offset": 0, "slot": "58", - "type": "mapping(contract IDisputeGame => bool)" + "type": "bytes32" }, { "bytes": "4", - "label": "respectedGameType", + "label": "spacer_59_0_4", "offset": 0, "slot": "59", "type": "GameType" }, { "bytes": "8", - "label": "respectedGameTypeUpdatedAt", + "label": "spacer_59_4_8", "offset": 4, "slot": "59", "type": "uint64" @@ -124,5 +124,26 @@ "offset": 0, "slot": "61", "type": "uint256" + }, + { + "bytes": "20", + "label": "anchorStateRegistry", + "offset": 0, + "slot": "62", + "type": "contract IAnchorStateRegistry" + }, + { + "bytes": "20", + "label": "ethLockbox", + "offset": 0, + "slot": "63", + "type": "contract IETHLockbox" + }, + { + "bytes": "1", + "label": "superRootsActive", + "offset": 20, + "slot": "63", + "type": "bool" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json index ea0d05feb90..3d1796e5e40 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfig.json @@ -110,5 +110,12 @@ "offset": 12, "slot": "106", "type": "uint64" + }, + { + "bytes": "32", + "label": "l2ChainId", + "offset": 0, + "slot": "107", + "type": "uint256" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigInterop.json b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigInterop.json index ea0d05feb90..3d1796e5e40 100644 --- a/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigInterop.json +++ b/packages/contracts-bedrock/snapshots/storageLayout/SystemConfigInterop.json @@ -110,5 +110,12 @@ "offset": 12, "slot": "106", "type": "uint64" + }, + { + "bytes": "32", + "label": "l2ChainId", + "offset": 0, + "slot": "107", + "type": "uint256" } ] \ No newline at end of file diff --git a/packages/contracts-bedrock/src/L1/ETHLockbox.sol b/packages/contracts-bedrock/src/L1/ETHLockbox.sol new file mode 100644 index 00000000000..3931c90fb3e --- /dev/null +++ b/packages/contracts-bedrock/src/L1/ETHLockbox.sol @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +// Contracts +import { ProxyAdminOwnedBase } from "src/L1/ProxyAdminOwnedBase.sol"; +import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +// Libraries +import { Constants } from "src/libraries/Constants.sol"; + +// Interfaces +import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; + +/// @custom:proxied true +/// @title ETHLockbox +/// @notice Manages ETH liquidity locking and unlocking for authorized OptimismPortals, enabling unified ETH liquidity +/// management across chains in the superchain cluster. +contract ETHLockbox is ProxyAdminOwnedBase, Initializable, ISemver { + /// @notice Thrown when the lockbox is paused. + error ETHLockbox_Paused(); + + /// @notice Thrown when the caller is not authorized. + error ETHLockbox_Unauthorized(); + + /// @notice Thrown when the value to unlock is greater than the balance of the lockbox. + error ETHLockbox_InsufficientBalance(); + + /// @notice Thrown when attempting to unlock ETH from the lockbox through a withdrawal transaction. + error ETHLockbox_NoWithdrawalTransactions(); + + /// @notice Thrown when the admin owner of the lockbox is different from the admin owner of the proxy admin. + error ETHLockbox_DifferentProxyAdminOwner(); + + /// @notice Emitted when ETH is locked in the lockbox by an authorized portal. + /// @param portal The address of the portal that locked the ETH. + /// @param amount The amount of ETH locked. + event ETHLocked(address indexed portal, uint256 amount); + + /// @notice Emitted when ETH is unlocked from the lockbox by an authorized portal. + /// @param portal The address of the portal that unlocked the ETH. + /// @param amount The amount of ETH unlocked. + event ETHUnlocked(address indexed portal, uint256 amount); + + /// @notice Emitted when a portal is authorized to lock and unlock ETH. + /// @param portal The address of the portal that was authorized. + event PortalAuthorized(address indexed portal); + + /// @notice Emitted when an ETH lockbox is authorized to migrate its liquidity to the current ETH lockbox. + /// @param lockbox The address of the ETH lockbox that was authorized. + event LockboxAuthorized(address indexed lockbox); + + /// @notice Emitted when ETH liquidity is migrated from the current ETH lockbox to another. + /// @param lockbox The address of the ETH lockbox that was migrated. + event LiquidityMigrated(address indexed lockbox, uint256 amount); + + /// @notice Emitted when ETH liquidity is received during an authorized lockbox migration. + /// @param lockbox The address of the ETH lockbox that received the liquidity. + /// @param amount The amount of ETH received. + event LiquidityReceived(address indexed lockbox, uint256 amount); + + /// @notice The address of the SuperchainConfig contract. + ISuperchainConfig public superchainConfig; + + /// @notice Mapping of authorized portals. + mapping(address => bool) public authorizedPortals; + + /// @notice Mapping of authorized lockboxes. + mapping(address => bool) public authorizedLockboxes; + + /// @notice Semantic version. + /// @custom:semver 0.0.1 + function version() public view virtual returns (string memory) { + return "0.0.1"; + } + + /// @notice Constructs the ETHLockbox contract. + constructor() { + _disableInitializers(); + } + + /// @notice Initializer. + /// @param _superchainConfig The address of the SuperchainConfig contract. + /// @param _portals The addresses of the portals to authorize. + function initialize( + ISuperchainConfig _superchainConfig, + IOptimismPortal[] calldata _portals + ) + external + initializer + { + superchainConfig = ISuperchainConfig(_superchainConfig); + for (uint256 i; i < _portals.length; i++) { + _authorizePortal(address(_portals[i])); + } + } + + /// @notice Authorizes a portal to lock and unlock ETH. + /// @param _portal The address of the portal to authorize. + function authorizePortal(IOptimismPortal _portal) external { + if (msg.sender != proxyAdminOwner()) revert ETHLockbox_Unauthorized(); + _authorizePortal(address(_portal)); + } + + /// @notice Getter for the current paused status. + function paused() public view returns (bool) { + return superchainConfig.paused(); + } + + /// @notice Receives the ETH liquidity migrated from an authorized lockbox. + function receiveLiquidity() external payable { + if (!authorizedLockboxes[msg.sender]) revert ETHLockbox_Unauthorized(); + emit LiquidityReceived(msg.sender, msg.value); + } + + /// @notice Locks ETH in the lockbox. + /// Called by an authorized portal on a deposit to lock the ETH value. + function lockETH() external payable { + if (!authorizedPortals[msg.sender]) revert ETHLockbox_Unauthorized(); + emit ETHLocked(msg.sender, msg.value); + } + + /// @notice Unlocks ETH from the lockbox. + /// Called by an authorized portal when finalizing a withdrawal that requires ETH. + /// Cannot be called if the lockbox is paused. + /// @param _value The amount of ETH to unlock. + function unlockETH(uint256 _value) external { + if (paused()) revert ETHLockbox_Paused(); + if (!authorizedPortals[msg.sender]) revert ETHLockbox_Unauthorized(); + if (_value > address(this).balance) revert ETHLockbox_InsufficientBalance(); + /// NOTE: Check l2Sender is not set to avoid this function to be called as a target on a withdrawal transaction + if (IOptimismPortal(payable(msg.sender)).l2Sender() != Constants.DEFAULT_L2_SENDER) { + revert ETHLockbox_NoWithdrawalTransactions(); + } + + // Using `donateETH` to avoid triggering a deposit + IOptimismPortal(payable(msg.sender)).donateETH{ value: _value }(); + emit ETHUnlocked(msg.sender, _value); + } + + /// @notice Authorizes an ETH lockbox to migrate its liquidity to the current ETH lockbox. + /// @param _lockbox The address of the ETH lockbox to authorize. + function authorizeLockbox(IETHLockbox _lockbox) external { + if (msg.sender != proxyAdminOwner()) revert ETHLockbox_Unauthorized(); + if (!_sameProxyAdminOwner(address(_lockbox))) revert ETHLockbox_DifferentProxyAdminOwner(); + + authorizedLockboxes[address(_lockbox)] = true; + emit LockboxAuthorized(address(_lockbox)); + } + + /// @notice Migrates liquidity from the current ETH lockbox to another. + /// @dev Must be called atomically with `OptimismPortal.updateLockbox()` in the same + /// transaction batch, or otherwise the OptimismPortal may not be able to unlock ETH + /// from the ETHLockbox on finalized withdrawals. + /// @param _lockbox The address of the ETH lockbox to migrate liquidity to. + function migrateLiquidity(IETHLockbox _lockbox) external { + if (msg.sender != proxyAdminOwner()) revert ETHLockbox_Unauthorized(); + if (!_sameProxyAdminOwner(address(_lockbox))) revert ETHLockbox_DifferentProxyAdminOwner(); + + IETHLockbox(_lockbox).receiveLiquidity{ value: address(this).balance }(); + emit LiquidityMigrated(address(_lockbox), address(this).balance); + } + + /// @notice Authorizes a portal to lock and unlock ETH. + /// @param _portal The address of the portal to authorize. + function _authorizePortal(address _portal) internal { + if (!_sameProxyAdminOwner(_portal)) revert ETHLockbox_DifferentProxyAdminOwner(); + authorizedPortals[_portal] = true; + emit PortalAuthorized(_portal); + } +} diff --git a/packages/contracts-bedrock/src/L1/OPContractsManager.sol b/packages/contracts-bedrock/src/L1/OPContractsManager.sol index 8aaabee8ef2..096b46c5bd4 100644 --- a/packages/contracts-bedrock/src/L1/OPContractsManager.sol +++ b/packages/contracts-bedrock/src/L1/OPContractsManager.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.15; import { Blueprint } from "src/libraries/Blueprint.sol"; import { Constants } from "src/libraries/Constants.sol"; import { Bytes } from "src/libraries/Bytes.sol"; -import { Claim, Duration, GameType, GameTypes, OutputRoot } from "src/dispute/lib/Types.sol"; +import { Claim, Duration, GameType, GameTypes, OutputRoot, Hash } from "src/dispute/lib/Types.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; // Interfaces @@ -17,13 +17,12 @@ import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.so import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IAddressManager } from "interfaces/legacy/IAddressManager.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; -import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IPermissionedDisputeGame } from "interfaces/dispute/IPermissionedDisputeGame.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; @@ -31,6 +30,7 @@ import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; import { IHasSuperchainConfig } from "interfaces/L1/IHasSuperchainConfig.sol"; import { ISystemConfigInterop } from "interfaces/L1/ISystemConfigInterop.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; contract OPContractsManagerContractsContainer { /// @notice Addresses of the Blueprint contracts. @@ -298,6 +298,7 @@ contract OPContractsManagerGameTypeAdder is OPContractsManagerBase { // This conversion is safe because the GameType is a uint32, which will always fit in an int256. int256 gameTypeInt = int256(uint256(gameConfig.disputeGameType.raw())); + // Ensure that the game configs are added in ascending order, and not duplicated. if (lastGameConfig >= gameTypeInt) revert OPContractsManager.InvalidGameConfigs(); lastGameConfig = gameTypeInt; @@ -309,6 +310,7 @@ contract OPContractsManagerGameTypeAdder is OPContractsManagerBase { getGameImplementation(getDisputeGameFactory(gameConfig.systemConfig), GameTypes.PERMISSIONED_CANNON) ) ); + // Pull out the chain ID. uint256 l2ChainId = getL2ChainId(pdg); @@ -526,34 +528,12 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { /// @dev This function is intended to be called via DELEGATECALL from the Upgrade Controller Safe function upgrade(OPContractsManager.OpChainConfig[] memory _opChainConfigs) external virtual { OPContractsManager.Implementations memory impls = getImplementations(); - OPContractsManager.Blueprints memory bps = getBlueprints(); for (uint256 i = 0; i < _opChainConfigs.length; i++) { assertValidOpChainConfig(_opChainConfigs[i]); ISystemConfig.Addresses memory opChainAddrs = _opChainConfigs[i].systemConfigProxy.getAddresses(); - // -------- Upgrade SystemConfig to Isthmus implementation -------- - upgradeTo( - _opChainConfigs[i].proxyAdmin, address(_opChainConfigs[i].systemConfigProxy), impls.systemConfigImpl - ); - - // -------- Upgrade Contracts Stored in SystemConfig -------- - - // OptimismPortal and L1CrossDomainMessenger are being upgraded to include the fixes - // for EIP-7623 (minimum gas limits for L1 -> L2 messages). - upgradeTo(_opChainConfigs[i].proxyAdmin, opChainAddrs.optimismPortal, impls.optimismPortalImpl); - upgradeTo( - _opChainConfigs[i].proxyAdmin, opChainAddrs.l1CrossDomainMessenger, impls.l1CrossDomainMessengerImpl - ); - - // L1ERC721Bridge and L1StandardBridge are being upgraded to include the tweaks to the - // EOA checking code for EIP-7702 (code length == 23). - upgradeTo(_opChainConfigs[i].proxyAdmin, opChainAddrs.l1ERC721Bridge, impls.l1ERC721BridgeImpl); - upgradeTo(_opChainConfigs[i].proxyAdmin, opChainAddrs.l1StandardBridge, impls.l1StandardBridgeImpl); - - // -------- Discover and Upgrade Proofs Contracts -------- - - // All chains have the Permissioned Dispute Game. + // All chains have the PermissionedDisputeGame, grab that. IPermissionedDisputeGame permissionedDisputeGame = IPermissionedDisputeGame( address( getGameImplementation( @@ -562,37 +542,136 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { ) ); - // We're also going to need the l2ChainId below, so we cache it in the outer scope. + // Grab the L2 chain ID from the PermissionedDisputeGame. uint256 l2ChainId = getL2ChainId(IFaultDisputeGame(address(permissionedDisputeGame))); - deployAndSetNewGameImpl({ - _l2ChainId: l2ChainId, - _disputeGame: IDisputeGame(address(permissionedDisputeGame)), - _gameType: GameTypes.PERMISSIONED_CANNON, - _opChainConfig: _opChainConfigs[i], - _implementations: impls, - _blueprints: bps, - _opChainAddrs: opChainAddrs - }); + // Grab the current respectedGameType from the OptimismPortal contract before the upgrade. + GameType respectedGameType = IOptimismPortal(payable(opChainAddrs.optimismPortal)).respectedGameType(); + + // Grab the current SuperchainConfig from the OptimismPortal contract before the upgrade. + ISuperchainConfig superchainConfig = + IOptimismPortal(payable(opChainAddrs.optimismPortal)).superchainConfig(); - // Now retrieve the permissionless game. If it exists, replace its implementation. - IFaultDisputeGame permissionlessDisputeGame = IFaultDisputeGame( - address(getGameImplementation(IDisputeGameFactory(opChainAddrs.disputeGameFactory), GameTypes.CANNON)) + // Start by upgrading the SystemConfig contract to have the l2ChainId. + upgradeToAndCall( + _opChainConfigs[i].proxyAdmin, + address(_opChainConfigs[i].systemConfigProxy), + impls.systemConfigImpl, + abi.encodeCall(ISystemConfig.upgrade, (l2ChainId)) ); - if (address(permissionlessDisputeGame) != address(0)) { - // Deploy and set a new permissionless game to update its prestate + // Separate context to avoid stack too deep. + IAnchorStateRegistry newAnchorStateRegistryProxy; + { + // Deploy a new AnchorStateRegistry contract. + // We use the SOT suffix to avoid CREATE2 conflicts with the existing ASR. + newAnchorStateRegistryProxy = IAnchorStateRegistry( + deployProxy({ + _l2ChainId: l2ChainId, + _proxyAdmin: _opChainConfigs[i].proxyAdmin, + _saltMixer: reusableSaltMixer(_opChainConfigs[i]), + _contractName: "AnchorStateRegistry-SOT" + }) + ); + + // Separate context to avoid stack too deep. + { + // Get the existing anchor root from the old AnchorStateRegistry contract. + // Get the AnchorStateRegistry from the PermissionedDisputeGame. + (Hash root, uint256 l2BlockNumber) = getAnchorStateRegistry( + IFaultDisputeGame(address(permissionedDisputeGame)) + ).anchors(respectedGameType); + + // Upgrade and initialize the AnchorStateRegistry contract. + // Since this is a net-new contract, we need to initialize it. + upgradeToAndCall( + _opChainConfigs[i].proxyAdmin, + address(newAnchorStateRegistryProxy), + impls.anchorStateRegistryImpl, + abi.encodeCall( + IAnchorStateRegistry.initialize, + ( + superchainConfig, + IDisputeGameFactory(opChainAddrs.disputeGameFactory), + OutputRoot({ root: root, l2BlockNumber: l2BlockNumber }), + respectedGameType + ) + ) + ); + } + } + + // Upgrade the OptimismPortal contract implementation. + upgradeTo(_opChainConfigs[i].proxyAdmin, opChainAddrs.optimismPortal, impls.optimismPortalImpl); + + // Separate context to avoid stack too deep. + { + // Deploy the ETHLockbox proxy. + IETHLockbox ethLockbox; + { + ethLockbox = IETHLockbox( + deployProxy({ + _l2ChainId: l2ChainId, + _proxyAdmin: _opChainConfigs[i].proxyAdmin, + _saltMixer: reusableSaltMixer(_opChainConfigs[i]), + _contractName: "ETHLockbox" + }) + ); + + // Initialize the ETHLockbox setting the OptimismPortal as an authorized portal. + IOptimismPortal[] memory portals = new IOptimismPortal[](1); + portals[0] = IOptimismPortal(payable(opChainAddrs.optimismPortal)); + upgradeToAndCall( + _opChainConfigs[i].proxyAdmin, + address(ethLockbox), + impls.ethLockboxImpl, + abi.encodeCall(IETHLockbox.initialize, (superchainConfig, portals)) + ); + } + + // Call `upgrade` on the OptimismPortal contract. + IOptimismPortal(payable(opChainAddrs.optimismPortal)).upgrade( + newAnchorStateRegistryProxy, ethLockbox, _opChainConfigs[i].disputeGameUsesSuperRoots + ); + } + + // We also need to redeploy the dispute games because the AnchorStateRegistry is new. + // Separate context to avoid stack too deep. + { + // Deploy and set a new permissioned game to update its prestate. deployAndSetNewGameImpl({ _l2ChainId: l2ChainId, - _disputeGame: IDisputeGame(address(permissionlessDisputeGame)), - _gameType: GameTypes.CANNON, + _disputeGame: IDisputeGame(address(permissionedDisputeGame)), + _newAnchorStateRegistryProxy: newAnchorStateRegistryProxy, + _gameType: GameTypes.PERMISSIONED_CANNON, _opChainConfig: _opChainConfigs[i], - _implementations: impls, - _blueprints: bps, _opChainAddrs: opChainAddrs }); } + // Separate context to avoid stack too deep. + { + // Now retrieve the permissionless game. + IFaultDisputeGame permissionlessDisputeGame = IFaultDisputeGame( + address( + getGameImplementation(IDisputeGameFactory(opChainAddrs.disputeGameFactory), GameTypes.CANNON) + ) + ); + + // If it exists, replace its implementation. + if (address(permissionlessDisputeGame) != address(0)) { + // Deploy and set a new permissionless game to update its prestate + deployAndSetNewGameImpl({ + _l2ChainId: l2ChainId, + _disputeGame: IDisputeGame(address(permissionlessDisputeGame)), + _newAnchorStateRegistryProxy: newAnchorStateRegistryProxy, + _gameType: GameTypes.CANNON, + _opChainConfig: _opChainConfigs[i], + _opChainAddrs: opChainAddrs + }); + } + } + // Emit the upgraded event with the address of the caller. Since this will be a delegatecall, // the caller will be the value of the ADDRESS opcode. emit Upgraded(l2ChainId, _opChainConfigs[i].systemConfigProxy, address(this)); @@ -621,28 +700,30 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { /// @notice Deploys and sets a new dispute game implementation /// @param _l2ChainId The L2 chain ID /// @param _disputeGame The current dispute game implementation + /// @param _newAnchorStateRegistryProxy The new anchor state registry proxy /// @param _gameType The type of game to deploy /// @param _opChainConfig The OP chain configuration - /// @param _blueprints The blueprint addresses - /// @param _implementations The implementation addresses /// @param _opChainAddrs The OP chain addresses function deployAndSetNewGameImpl( uint256 _l2ChainId, IDisputeGame _disputeGame, + IAnchorStateRegistry _newAnchorStateRegistryProxy, GameType _gameType, OPContractsManager.OpChainConfig memory _opChainConfig, - OPContractsManager.Blueprints memory _blueprints, - OPContractsManager.Implementations memory _implementations, ISystemConfig.Addresses memory _opChainAddrs ) internal { + OPContractsManager.Blueprints memory bps = getBlueprints(); + OPContractsManager.Implementations memory impls = getImplementations(); + // Get the constructor params for the game IFaultDisputeGame.GameConstructorParams memory params = getGameConstructorParams(IFaultDisputeGame(address(_disputeGame))); // Modify the params with the new vm values. - params.vm = IBigStepper(_implementations.mipsImpl); + params.anchorStateRegistry = IAnchorStateRegistry(address(_newAnchorStateRegistryProxy)); + params.vm = IBigStepper(impls.mipsImpl); if (Claim.unwrap(_opChainConfig.absolutePrestate) == bytes32(0)) { revert OPContractsManager.PrestateNotSet(); } @@ -654,8 +735,8 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { address challenger = getChallenger(IPermissionedDisputeGame(address(_disputeGame))); newGame = IDisputeGame( Blueprint.deployFrom( - _blueprints.permissionedDisputeGame1, - _blueprints.permissionedDisputeGame2, + bps.permissionedDisputeGame1, + bps.permissionedDisputeGame2, computeSalt(_l2ChainId, reusableSaltMixer(_opChainConfig), "PermissionedDisputeGame"), encodePermissionedFDGConstructor(params, proposer, challenger) ) @@ -663,8 +744,8 @@ contract OPContractsManagerUpgrader is OPContractsManagerBase { } else { newGame = IDisputeGame( Blueprint.deployFrom( - _blueprints.permissionlessDisputeGame1, - _blueprints.permissionlessDisputeGame2, + bps.permissionlessDisputeGame1, + bps.permissionlessDisputeGame2, computeSalt(_l2ChainId, reusableSaltMixer(_opChainConfig), "PermissionlessDisputeGame"), encodePermissionlessFDGConstructor(params) ) @@ -727,9 +808,11 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { // Deploy ERC-1967 proxied contracts. output.l1ERC721BridgeProxy = IL1ERC721Bridge(deployProxy(_input.l2ChainId, output.opChainProxyAdmin, _input.saltMixer, "L1ERC721Bridge")); - output.optimismPortalProxy = IOptimismPortal2( + output.optimismPortalProxy = IOptimismPortal( payable(deployProxy(_input.l2ChainId, output.opChainProxyAdmin, _input.saltMixer, "OptimismPortal")) ); + output.ethLockboxProxy = + IETHLockbox(deployProxy(_input.l2ChainId, output.opChainProxyAdmin, _input.saltMixer, "ETHLockbox")); output.systemConfigProxy = ISystemConfig(deployProxy(_input.l2ChainId, output.opChainProxyAdmin, _input.saltMixer, "SystemConfig")); output.optimismMintableERC20FactoryProxy = IOptimismMintableERC20Factory( @@ -806,11 +889,17 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { output.opChainProxyAdmin, address(output.l1ERC721BridgeProxy), implementation.l1ERC721BridgeImpl, data ); - data = encodeOptimismPortalInitializer(output, _superchainConfig); + data = encodeOptimismPortalInitializer(_input, output, _superchainConfig); upgradeToAndCall( output.opChainProxyAdmin, address(output.optimismPortalProxy), implementation.optimismPortalImpl, data ); + // Initialize the ETHLockbox. + IOptimismPortal[] memory portals = new IOptimismPortal[](1); + portals[0] = output.optimismPortalProxy; + data = encodeETHLockboxInitializer(_superchainConfig, portals); + upgradeToAndCall(output.opChainProxyAdmin, address(output.ethLockboxProxy), implementation.ethLockboxImpl, data); + data = encodeSystemConfigInitializer(_input, output); upgradeToAndCall( output.opChainProxyAdmin, address(output.systemConfigProxy), implementation.systemConfigImpl, data @@ -955,6 +1044,7 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { /// @notice Helper method for encoding the OptimismPortal initializer data. function encodeOptimismPortalInitializer( + OPContractsManager.DeployInput memory _input, OPContractsManager.DeployOutput memory _output, ISuperchainConfig _superchainConfig ) @@ -964,16 +1054,30 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { returns (bytes memory) { return abi.encodeCall( - IOptimismPortal2.initialize, + IOptimismPortal.initialize, ( - _output.disputeGameFactoryProxy, _output.systemConfigProxy, _superchainConfig, - GameTypes.PERMISSIONED_CANNON + _output.anchorStateRegistryProxy, + _output.ethLockboxProxy, + _input.disputeGameUsesSuperRoots ) ); } + /// @notice Helper method for encoding the ETHLockbox initializer data. + function encodeETHLockboxInitializer( + ISuperchainConfig _superchainConfig, + IOptimismPortal[] memory _portals + ) + internal + view + virtual + returns (bytes memory) + { + return abi.encodeCall(IETHLockbox.initialize, (_superchainConfig, _portals)); + } + /// @notice Helper method for encoding the SystemConfig initializer data. function encodeSystemConfigInitializer( OPContractsManager.DeployInput memory _input, @@ -998,7 +1102,8 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { _input.roles.unsafeBlockSigner, referenceResourceConfig, chainIdToBatchInboxAddress(_input.l2ChainId), - opChainAddrs + opChainAddrs, + _input.l2ChainId ) ); } @@ -1058,7 +1163,7 @@ contract OPContractsManagerDeployer is OPContractsManagerBase { OutputRoot memory startingAnchorRoot = abi.decode(_input.startingAnchorRoot, (OutputRoot)); return abi.encodeCall( IAnchorStateRegistry.initialize, - (_superchainConfig, _output.disputeGameFactoryProxy, _output.optimismPortalProxy, startingAnchorRoot) + (_superchainConfig, _output.disputeGameFactoryProxy, startingAnchorRoot, GameTypes.PERMISSIONED_CANNON) ); } @@ -1114,7 +1219,8 @@ contract OPContractsManagerDeployerInterop is OPContractsManagerDeployer { referenceResourceConfig, chainIdToBatchInboxAddress(_input.l2ChainId), opChainAddrs, - dependencyManager + dependencyManager, + _input.l2ChainId ) ); } @@ -1145,6 +1251,7 @@ contract OPContractsManager is ISemver { string saltMixer; uint64 gasLimit; // Configurable dispute game parameters. + bool disputeGameUsesSuperRoots; GameType disputeGameType; Claim disputeAbsolutePrestate; uint256 disputeMaxGameDepth; @@ -1162,8 +1269,9 @@ contract OPContractsManager is ISemver { IOptimismMintableERC20Factory optimismMintableERC20FactoryProxy; IL1StandardBridge l1StandardBridgeProxy; IL1CrossDomainMessenger l1CrossDomainMessengerProxy; + IETHLockbox ethLockboxProxy; // Fault proof contracts below. - IOptimismPortal2 optimismPortalProxy; + IOptimismPortal optimismPortalProxy; IDisputeGameFactory disputeGameFactoryProxy; IAnchorStateRegistry anchorStateRegistryProxy; IFaultDisputeGame faultDisputeGame; @@ -1195,6 +1303,7 @@ contract OPContractsManager is ISemver { address protocolVersionsImpl; address l1ERC721BridgeImpl; address optimismPortalImpl; + address ethLockboxImpl; address systemConfigImpl; address optimismMintableERC20FactoryImpl; address l1CrossDomainMessengerImpl; @@ -1210,6 +1319,7 @@ contract OPContractsManager is ISemver { ISystemConfig systemConfigProxy; IProxyAdmin proxyAdmin; Claim absolutePrestate; + bool disputeGameUsesSuperRoots; } struct AddGameInput { @@ -1235,9 +1345,9 @@ contract OPContractsManager is ISemver { // -------- Constants and Variables -------- - /// @custom:semver 1.9.0 + /// @custom:semver 1.10.0 function version() public pure virtual returns (string memory) { - return "1.9.0"; + return "1.10.0"; } OPContractsManagerGameTypeAdder public immutable opcmGameTypeAdder; diff --git a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol index 0b43241ea4f..5b8cef6cc83 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortal2.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortal2.sol @@ -2,11 +2,12 @@ pragma solidity 0.8.15; // Contracts +import { ProxyAdminOwnedBase } from "src/L1/ProxyAdminOwnedBase.sol"; import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import { ResourceMetering } from "src/L1/ResourceMetering.sol"; +import { ReinitializableBase } from "src/universal/ReinitializableBase.sol"; // Libraries -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { EOA } from "src/libraries/EOA.sol"; import { SafeCall } from "src/libraries/SafeCall.sol"; import { Constants } from "src/libraries/Constants.sol"; @@ -14,59 +15,35 @@ import { Types } from "src/libraries/Types.sol"; import { Hashing } from "src/libraries/Hashing.sol"; import { SecureMerkleTrie } from "src/libraries/trie/SecureMerkleTrie.sol"; import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; -import { - BadTarget, - LargeCalldata, - SmallGasLimit, - Unauthorized, - CallPaused, - GasEstimation, - NonReentrant, - InvalidProof, - InvalidGameType, - InvalidDisputeGame, - InvalidMerkleProof, - Blacklisted, - Unproven, - ProposalNotValidated, - AlreadyFinalized, - LegacyGame -} from "src/libraries/PortalErrors.sol"; -import { GameStatus, GameType, Claim, Timestamp } from "src/dispute/lib/Types.sol"; +import { GameStatus, GameType } from "src/dispute/lib/Types.sol"; // Interfaces -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ISemver } from "interfaces/universal/ISemver.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; /// @custom:proxied true /// @title OptimismPortal2 /// @notice The OptimismPortal is a low-level contract responsible for passing messages between L1 /// and L2. Messages sent directly to the OptimismPortal have no form of replayability. /// Users are encouraged to use the L1CrossDomainMessenger for a higher-level interface. -contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { - /// @notice Allows for interactions with non standard ERC20 tokens. - using SafeERC20 for IERC20; - +contract OptimismPortal2 is Initializable, ResourceMetering, ReinitializableBase, ProxyAdminOwnedBase, ISemver { /// @notice Represents a proven withdrawal. - /// @custom:field disputeGameProxy The address of the dispute game proxy that the withdrawal was proven against. + /// @custom:field disputeGameProxy Game that the withdrawal was proven against. /// @custom:field timestamp Timestamp at which the withdrawal was proven. struct ProvenWithdrawal { IDisputeGame disputeGameProxy; uint64 timestamp; } - /// @notice The delay between when a withdrawal transaction is proven and when it may be finalized. + /// @notice The delay between when a withdrawal is proven and when it may be finalized. uint256 internal immutable PROOF_MATURITY_DELAY_SECONDS; - /// @notice The delay between when a dispute game is resolved and when a withdrawal proven against it may be - /// finalized. - uint256 internal immutable DISPUTE_GAME_FINALITY_DELAY_SECONDS; - /// @notice Version of the deposit event. uint256 internal constant DEPOSIT_VERSION = 0; @@ -94,7 +71,7 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { /// @notice Spacer for backwards compatibility. bool private spacer_53_0_1; - /// @notice Contract of the Superchain Config. + /// @notice Address of the SuperchainConfig contract. ISuperchainConfig public superchainConfig; /// @custom:legacy @@ -102,25 +79,30 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { /// @notice Spacer taking up the legacy `l2Oracle` address slot. address private spacer_54_0_20; - /// @notice Contract of the SystemConfig. + /// @notice Address of the SystemConfig contract. /// @custom:network-specific ISystemConfig public systemConfig; - /// @notice Address of the DisputeGameFactory. /// @custom:network-specific - IDisputeGameFactory public disputeGameFactory; + /// @custom:legacy + /// @custom:spacer disputeGameFactory + /// @notice Spacer taking up the legacy `disputeGameFactory` address slot. + address private spacer_56_0_20; - /// @notice A mapping of withdrawal hashes to proof submitters to `ProvenWithdrawal` data. + /// @notice A mapping of withdrawal hashes to proof submitters to ProvenWithdrawal data. mapping(bytes32 => mapping(address => ProvenWithdrawal)) public provenWithdrawals; - /// @notice A mapping of dispute game addresses to whether or not they are blacklisted. - mapping(IDisputeGame => bool) public disputeGameBlacklist; + /// @custom:legacy + /// @custom:spacer disputeGameBlacklist + bytes32 private spacer_58_0_32; - /// @notice The game type that the OptimismPortal consults for output proposals. - GameType public respectedGameType; + /// @custom:legacy + /// @custom:spacer respectedGameType + GameType private spacer_59_0_4; - /// @notice The timestamp at which the respected game type was last updated. - uint64 public respectedGameTypeUpdatedAt; + /// @custom:legacy + /// @custom:spacer respectedGameTypeUpdatedAt + uint64 private spacer_59_4_8; /// @notice Mapping of withdrawal hashes to addresses that have submitted a proof for the /// withdrawal. Original OptimismPortal contract only allowed one proof to be submitted @@ -133,12 +115,19 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { /// @custom:legacy /// @custom:spacer _balance - /// @notice Spacer taking up the legacy `_balance` slot. uint256 private spacer_61_0_32; - /// @notice Emitted when a transaction is deposited from L1 to L2. - /// The parameters of this event are read by the rollup node and used to derive deposit - /// transactions on L2. + /// @notice Address of the AnchorStateRegistry contract. + IAnchorStateRegistry public anchorStateRegistry; + + /// @notice Address of the ETHLockbox contract. + IETHLockbox public ethLockbox; + + /// @notice Whether the OptimismPortal is using Super Roots or Output Roots. + bool public superRootsActive; + + /// @notice Emitted when a transaction is deposited from L1 to L2. The parameters of this event + /// are read by the rollup node and used to derive deposit transactions on L2. /// @param from Address that triggered the deposit transaction. /// @param to Address that the deposit transaction is directed to. /// @param version Version of this deposit transaction event. @@ -151,8 +140,9 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { /// @param to Address that the withdrawal transaction is directed to. event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to); - /// @notice Emitted when a withdrawal transaction is proven. Exists as a separate event to allow for backwards - /// compatibility for tooling that observes the `WithdrawalProven` event. + /// @notice Emitted when a withdrawal transaction is proven. Exists as a separate event to + /// allow for backwards compatibility for tooling that observes the WithdrawalProven + /// event. /// @param withdrawalHash Hash of the withdrawal transaction. /// @param proofSubmitter Address of the proof submitter. event WithdrawalProvenExtension1(bytes32 indexed withdrawalHash, address indexed proofSubmitter); @@ -162,74 +152,143 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { /// @param success Whether the withdrawal transaction was successful. event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success); - /// @notice Emitted when a dispute game is blacklisted by the Guardian. - /// @param disputeGame Address of the dispute game that was blacklisted. - event DisputeGameBlacklisted(IDisputeGame indexed disputeGame); + /// @notice Emitted when the total ETH balance is migrated to the ETHLockbox. + /// @param lockbox The address of the ETHLockbox contract. + /// @param ethBalance Amount of ETH migrated. + event ETHMigrated(address indexed lockbox, uint256 ethBalance); + + /// @notice Emitted when the ETHLockbox contract is updated. + /// @param oldLockbox The address of the old ETHLockbox contract. + /// @param newLockbox The address of the new ETHLockbox contract. + event LockboxUpdated(address oldLockbox, address newLockbox); + + /// @notice Thrown when a withdrawal has already been finalized. + error OptimismPortal_AlreadyFinalized(); + + /// @notice Thrown when the target of a withdrawal is unsafe. + error OptimismPortal_BadTarget(); + + /// @notice Thrown when the calldata for a deposit is too large. + error OptimismPortal_CalldataTooLarge(); + + /// @notice Thrown when the portal is paused. + error OptimismPortal_CallPaused(); + + /// @notice Thrown when a gas estimation transaction is being executed. + error OptimismPortal_GasEstimation(); + + /// @notice Thrown when the gas limit for a deposit is too low. + error OptimismPortal_GasLimitTooLow(); + + /// @notice Thrown when the target of a withdrawal is not a proper dispute game. + error OptimismPortal_ImproperDisputeGame(); + + /// @notice Thrown when a withdrawal has not been proven against a valid dispute game. + error OptimismPortal_InvalidDisputeGame(); + + /// @notice Thrown when a withdrawal has not been proven against a valid merkle proof. + error OptimismPortal_InvalidMerkleProof(); + + /// @notice Thrown when a withdrawal has not been proven against a valid output root proof. + error OptimismPortal_InvalidOutputRootProof(); + + /// @notice Thrown when a withdrawal's timestamp is not greater than the dispute game's creation timestamp. + error OptimismPortal_InvalidProofTimestamp(); + + /// @notice Thrown when the root claim of a dispute game is invalid. + error OptimismPortal_InvalidRootClaim(); + + /// @notice Thrown when a withdrawal is being finalized by a reentrant call. + error OptimismPortal_NoReentrancy(); - /// @notice Emitted when the Guardian changes the respected game type in the portal. - /// @param newGameType The new respected game type. - /// @param updatedAt The timestamp at which the respected game type was updated. - event RespectedGameTypeSet(GameType indexed newGameType, Timestamp indexed updatedAt); + /// @notice Thrown when a withdrawal has not been proven for long enough. + error OptimismPortal_ProofNotOldEnough(); + + /// @notice Thrown when a withdrawal has not been proven. + error OptimismPortal_Unproven(); + + /// @notice Thrown when the caller is not authorized to call the function. + error OptimismPortal_Unauthorized(); + + /// @notice Thrown when the wrong proof method is used. + error OptimismPortal_WrongProofMethod(); + + /// @notice Thrown when a super root proof is invalid. + error OptimismPortal_InvalidSuperRootProof(); + + /// @notice Thrown when an output root index is invalid. + error OptimismPortal_InvalidOutputRootIndex(); + + /// @notice Thrown when an output root chain id is invalid. + error OptimismPortal_InvalidOutputRootChainId(); /// @notice Reverts when paused. modifier whenNotPaused() { - if (paused()) revert CallPaused(); + if (paused()) revert OptimismPortal_CallPaused(); _; } /// @notice Semantic version. - /// @custom:semver 3.14.0 + /// @custom:semver 4.0.0 function version() public pure virtual returns (string memory) { - return "3.14.0"; + return "4.0.0"; } - /// @notice Constructs the OptimismPortal contract. - constructor(uint256 _proofMaturityDelaySeconds, uint256 _disputeGameFinalityDelaySeconds) { + /// @param _proofMaturityDelaySeconds The proof maturity delay in seconds. + constructor(uint256 _proofMaturityDelaySeconds) ReinitializableBase(2) { PROOF_MATURITY_DELAY_SECONDS = _proofMaturityDelaySeconds; - DISPUTE_GAME_FINALITY_DELAY_SECONDS = _disputeGameFinalityDelaySeconds; - _disableInitializers(); } /// @notice Initializer. - /// @param _disputeGameFactory Contract of the DisputeGameFactory. - /// @param _systemConfig Contract of the SystemConfig. - /// @param _superchainConfig Contract of the SuperchainConfig. + /// @param _systemConfig Address of the SystemConfig. + /// @param _superchainConfig Address of the SuperchainConfig. + /// @param _anchorStateRegistry Address of the AnchorStateRegistry. + /// @param _ethLockbox Contract of the ETHLockbox. + /// @param _superRootsActive Whether the OptimismPortal is using Super Roots or Output Roots. function initialize( - IDisputeGameFactory _disputeGameFactory, ISystemConfig _systemConfig, ISuperchainConfig _superchainConfig, - GameType _initialRespectedGameType + IAnchorStateRegistry _anchorStateRegistry, + IETHLockbox _ethLockbox, + bool _superRootsActive ) external - initializer + reinitializer(initVersion()) { - disputeGameFactory = _disputeGameFactory; systemConfig = _systemConfig; superchainConfig = _superchainConfig; + anchorStateRegistry = _anchorStateRegistry; + ethLockbox = _ethLockbox; + superRootsActive = _superRootsActive; - // Set the `l2Sender` slot, only if it is currently empty. This signals the first initialization of the - // contract. + // Set the l2Sender slot, only if it is currently empty. This signals the first + // initialization of the contract. if (l2Sender == address(0)) { l2Sender = Constants.DEFAULT_L2_SENDER; - - // Set the `respectedGameTypeUpdatedAt` timestamp, to ignore all games of the respected type prior - // to this operation. - respectedGameTypeUpdatedAt = uint64(block.timestamp); - - // Set the initial respected game type - respectedGameType = _initialRespectedGameType; } __ResourceMetering_init(); } - /// @notice Getter function for the address of the guardian. - /// Public getter is legacy and will be removed in the future. Use `SuperchainConfig.guardian()` instead. - /// @return Address of the guardian. - /// @custom:legacy - function guardian() public view returns (address) { - return superchainConfig.guardian(); + /// @notice Upgrades the OptimismPortal contract to have a reference to the AnchorStateRegistry. + /// @param _anchorStateRegistry AnchorStateRegistry contract. + /// @param _ethLockbox ETHLockbox contract. + /// @param _superRootsActive Whether the OptimismPortal is using Super Roots or Output Roots. + function upgrade( + IAnchorStateRegistry _anchorStateRegistry, + IETHLockbox _ethLockbox, + bool _superRootsActive + ) + external + reinitializer(initVersion()) + { + anchorStateRegistry = _anchorStateRegistry; + ethLockbox = _ethLockbox; + superRootsActive = _superRootsActive; + + // Migrate the whole ETH balance to the ETHLockbox. + _migrateLiquidity(); } /// @notice Getter for the current paused status. @@ -242,9 +301,33 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { return PROOF_MATURITY_DELAY_SECONDS; } + /// @notice Getter for the address of the DisputeGameFactory contract. + function disputeGameFactory() public view returns (IDisputeGameFactory) { + return anchorStateRegistry.disputeGameFactory(); + } + + /// @custom:legacy + /// @notice Getter function for the address of the guardian. + function guardian() public view returns (address) { + return superchainConfig.guardian(); + } + + /// @custom:legacy /// @notice Getter for the dispute game finality delay. - function disputeGameFinalityDelaySeconds() public view returns (uint256) { - return DISPUTE_GAME_FINALITY_DELAY_SECONDS; + function disputeGameFinalityDelaySeconds() external view returns (uint256) { + return anchorStateRegistry.disputeGameFinalityDelaySeconds(); + } + + /// @custom:legacy + /// @notice Getter for the respected game type. + function respectedGameType() external view returns (GameType) { + return anchorStateRegistry.respectedGameType(); + } + + /// @custom:legacy + /// @notice Getter for the timestamp at which the respected game type was updated. + function respectedGameTypeUpdatedAt() external view returns (uint64) { + return anchorStateRegistry.retirementTimestamp(); } /// @notice Computes the minimum gas limit for a deposit. @@ -267,28 +350,60 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { } /// @notice Accepts ETH value without triggering a deposit to L2. - /// This function mainly exists for the sake of the migration between the legacy - /// Optimism system and Bedrock. function donateETH() external payable { // Intentionally empty. } - /// @notice Getter for the resource config. - /// Used internally by the ResourceMetering contract. - /// The SystemConfig is the source of truth for the resource config. - /// @return config_ ResourceMetering ResourceConfig - function _resourceConfig() internal view override returns (ResourceMetering.ResourceConfig memory config_) { - IResourceMetering.ResourceConfig memory config = systemConfig.resourceConfig(); - assembly ("memory-safe") { - config_ := config + /// @notice Updates the ETHLockbox contract. + /// @dev This function MUST be called atomically with `ETHLockbox.migrateLiquidity()` + /// in the same transaction batch, or otherwise the OptimismPortal may not be able to + /// unlock ETH from the ETHLockbox on finalized withdrawals. + /// @param _newLockbox The address of the new ETHLockbox contract. + function updateLockbox(IETHLockbox _newLockbox) external { + if (msg.sender != proxyAdminOwner()) revert OptimismPortal_Unauthorized(); + + address oldLockbox = address(ethLockbox); + ethLockbox = _newLockbox; + + emit LockboxUpdated(oldLockbox, address(_newLockbox)); + } + + /// @notice Proves a withdrawal transaction using a Super Root proof. Only callable when the + /// OptimismPortal is using Super Roots (superRootsActive flag is true). + /// @param _tx Withdrawal transaction to finalize. + /// @param _disputeGameProxy Address of the dispute game to prove the withdrawal against. + /// @param _outputRootIndex Index of the target Output Root within the Super Root. + /// @param _superRootProof Inclusion proof of the Output Root within the Super Root. + /// @param _outputRootProof Inclusion proof of the L2ToL1MessagePasser storage root. + /// @param _withdrawalProof Inclusion proof of the withdrawal within the L2ToL1MessagePasser. + function proveWithdrawalTransaction( + Types.WithdrawalTransaction memory _tx, + IDisputeGame _disputeGameProxy, + uint256 _outputRootIndex, + Types.SuperRootProof calldata _superRootProof, + Types.OutputRootProof calldata _outputRootProof, + bytes[] calldata _withdrawalProof + ) + external + whenNotPaused + { + // Make sure that the OptimismPortal is using Super Roots. + if (!superRootsActive) { + revert OptimismPortal_WrongProofMethod(); } + + // Prove the transaction. + _proveWithdrawalTransaction( + _tx, _disputeGameProxy, _outputRootIndex, _superRootProof, _outputRootProof, _withdrawalProof + ); } - /// @notice Proves a withdrawal transaction. + /// @notice Proves a withdrawal transaction using an Output Root proof. Only callable when the + /// OptimismPortal is using Output Roots (superRootsActive flag is false). /// @param _tx Withdrawal transaction to finalize. /// @param _disputeGameIndex Index of the dispute game to prove the withdrawal against. - /// @param _outputRootProof Inclusion proof of the L2ToL1MessagePasser contract's storage root. - /// @param _withdrawalProof Inclusion proof of the withdrawal in L2ToL1MessagePasser contract. + /// @param _outputRootProof Inclusion proof of the L2ToL1MessagePasser storage root. + /// @param _withdrawalProof Inclusion proof of the withdrawal within the L2ToL1MessagePasser. function proveWithdrawalTransaction( Types.WithdrawalTransaction memory _tx, uint256 _disputeGameIndex, @@ -298,47 +413,102 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { external whenNotPaused { - // Prevent users from creating a deposit transaction where this address is the message - // sender on L2. Because this is checked here, we do not need to check again in - // `finalizeWithdrawalTransaction`. - if (_tx.target == address(this)) revert BadTarget(); + // Make sure that the OptimismPortal is using Output Roots. + if (superRootsActive) { + revert OptimismPortal_WrongProofMethod(); + } // Fetch the dispute game proxy from the `DisputeGameFactory` contract. - (GameType gameType,, IDisputeGame gameProxy) = disputeGameFactory.gameAtIndex(_disputeGameIndex); - Claim outputRoot = gameProxy.rootClaim(); - - // The game type of the dispute game must be the respected game type. - if (gameType.raw() != respectedGameType.raw()) revert InvalidGameType(); - - // The game type of the DisputeGame must have been the respected game type at creation. - // eip150-safe - try gameProxy.wasRespectedGameTypeWhenCreated() returns (bool wasRespected_) { - if (!wasRespected_) revert InvalidGameType(); - } catch { - revert LegacyGame(); + (,, IDisputeGame disputeGameProxy) = disputeGameFactory().gameAtIndex(_disputeGameIndex); + + // Create a dummy super root proof to pass into the internal function. Note that this is + // not a valid Super Root proof but it isn't used anywhere in the internal function when + // using Output Roots. + Types.SuperRootProof memory superRootProof; + + // Prove the transaction. + _proveWithdrawalTransaction(_tx, disputeGameProxy, 0, superRootProof, _outputRootProof, _withdrawalProof); + } + + /// @notice Internal function for proving a withdrawal transaction, used by both the Super Root + /// and Output Root proof functions. Will eventually be replaced with a single function + /// when the Output Root proof method is deprecated. + /// @param _tx Withdrawal transaction to prove. + /// @param _disputeGameProxy Address of the dispute game to prove the withdrawal against. + /// @param _outputRootIndex Index of the target Output Root within the Super Root. + /// @param _superRootProof Inclusion proof of the Output Root within the Super Root. + /// @param _outputRootProof Inclusion proof of the L2ToL1MessagePasser storage root. + /// @param _withdrawalProof Inclusion proof of the withdrawal within the L2ToL1MessagePasser. + function _proveWithdrawalTransaction( + Types.WithdrawalTransaction memory _tx, + IDisputeGame _disputeGameProxy, + uint256 _outputRootIndex, + Types.SuperRootProof memory _superRootProof, + Types.OutputRootProof memory _outputRootProof, + bytes[] memory _withdrawalProof + ) + internal + { + // Make sure that the target address is safe. + if (_isUnsafeTarget(_tx.target)) { + revert OptimismPortal_BadTarget(); } - // Game must have been created after the respected game type was updated. This check is a - // strict inequality because we want to prevent users from being able to prove or finalize - // withdrawals against games that were created in the same block that the retirement - // timestamp was set. If the retirement timestamp and game type are changed in the same - // block, such games could still be considered valid even if they used the old game type - // that we intended to invalidate. - require( - gameProxy.createdAt().raw() > respectedGameTypeUpdatedAt, - "OptimismPortal: dispute game created before respected game type was updated" - ); + // Game must be a Proper Game. + if (!anchorStateRegistry.isGameProper(_disputeGameProxy)) { + revert OptimismPortal_ImproperDisputeGame(); + } + + // Game must have been respected game type when created. + if (!anchorStateRegistry.isGameRespected(_disputeGameProxy)) { + revert OptimismPortal_InvalidDisputeGame(); + } + + // Game must not have resolved in favor of the Challenger (invalid root claim). + if (_disputeGameProxy.status() == GameStatus.CHALLENGER_WINS) { + revert OptimismPortal_InvalidDisputeGame(); + } + + // As a sanity check, we make sure that the current timestamp is not less than or equal to + // the dispute game's creation timestamp. Not strictly necessary but extra layer of + // safety against weird bugs. Note that this blocks withdrawals from being proven in the + // same block that a dispute game is created. + if (block.timestamp <= _disputeGameProxy.createdAt().raw()) { + revert OptimismPortal_InvalidProofTimestamp(); + } - // Verify that the output root can be generated with the elements in the proof. - if (outputRoot.raw() != Hashing.hashOutputRootProof(_outputRootProof)) revert InvalidProof(); + // Validate the provided Output Root and/or Super Root proof depending on proof method. + if (superRootsActive) { + // Verify that the super root can be generated with the elements in the proof. + if (_disputeGameProxy.rootClaim().raw() != Hashing.hashSuperRootProof(_superRootProof)) { + revert OptimismPortal_InvalidSuperRootProof(); + } + + // Check that the index exists in the super root proof. + if (_outputRootIndex >= _superRootProof.outputRoots.length) { + revert OptimismPortal_InvalidOutputRootIndex(); + } + + // Check that the output root has the correct chain id. + Types.OutputRootWithChainId memory outputRoot = _superRootProof.outputRoots[_outputRootIndex]; + if (outputRoot.chainId != systemConfig.l2ChainId()) { + revert OptimismPortal_InvalidOutputRootChainId(); + } + + // Verify that the output root can be generated with the elements in the proof. + if (outputRoot.root != Hashing.hashOutputRootProof(_outputRootProof)) { + revert OptimismPortal_InvalidOutputRootProof(); + } + } else { + // Verify that the output root can be generated with the elements in the proof. + if (_disputeGameProxy.rootClaim().raw() != Hashing.hashOutputRootProof(_outputRootProof)) { + revert OptimismPortal_InvalidOutputRootProof(); + } + } // Load the ProvenWithdrawal into memory, using the withdrawal hash as a unique identifier. bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx); - // We do not allow for proving withdrawals against dispute games that have resolved against the favor - // of the root claim. - if (gameProxy.status() == GameStatus.CHALLENGER_WINS) revert InvalidDisputeGame(); - // Compute the storage slot of the withdrawal hash in the L2ToL1MessagePasser contract. // Refer to the Solidity documentation for more information on how storage layouts are // computed for mappings. @@ -360,21 +530,22 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { _proof: _withdrawalProof, _root: _outputRootProof.messagePasserStorageRoot }) == false - ) revert InvalidMerkleProof(); + ) { + revert OptimismPortal_InvalidMerkleProof(); + } - // Designate the withdrawalHash as proven by storing the `disputeGameProxy` & `timestamp` in the - // `provenWithdrawals` mapping. A `withdrawalHash` can only be proven once unless the dispute game it proved - // against resolves against the favor of the root claim. + // Designate the withdrawalHash as proven by storing the disputeGameProxy and timestamp in + // the provenWithdrawals mapping. A given user may re-prove a withdrawalHash multiple + // times, but each proof will reset the proof timer. provenWithdrawals[withdrawalHash][msg.sender] = - ProvenWithdrawal({ disputeGameProxy: gameProxy, timestamp: uint64(block.timestamp) }); - - // Emit a `WithdrawalProven` event. - emit WithdrawalProven(withdrawalHash, _tx.sender, _tx.target); - // Emit a `WithdrawalProvenExtension1` event. - emit WithdrawalProvenExtension1(withdrawalHash, msg.sender); + ProvenWithdrawal({ disputeGameProxy: _disputeGameProxy, timestamp: uint64(block.timestamp) }); // Add the proof submitter to the list of proof submitters for this withdrawal hash. proofSubmitters[withdrawalHash].push(msg.sender); + + // Emit a WithdrawalProven events. + emit WithdrawalProven(withdrawalHash, _tx.sender, _tx.target); + emit WithdrawalProvenExtension1(withdrawalHash, msg.sender); } /// @notice Finalizes a withdrawal transaction. @@ -396,9 +567,16 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { // Make sure that the l2Sender has not yet been set. The l2Sender is set to a value other // than the default value when a withdrawal transaction is being finalized. This check is // a defacto reentrancy guard. - if (l2Sender != Constants.DEFAULT_L2_SENDER) revert NonReentrant(); + if (l2Sender != Constants.DEFAULT_L2_SENDER) { + revert OptimismPortal_NoReentrancy(); + } + + // Make sure that the target address is safe. + if (_isUnsafeTarget(_tx.target)) { + revert OptimismPortal_BadTarget(); + } - // Compute the withdrawal hash. + // Grab the withdrawal. bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx); // Check that the withdrawal can be finalized. @@ -407,6 +585,9 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { // Mark the withdrawal as finalized so it can't be replayed. finalizedWithdrawals[withdrawalHash] = true; + // Unlock the ETH from the ETHLockbox. + if (_tx.value > 0) ethLockbox.unlockETH(_tx.value); + // Set the l2Sender so contracts know who triggered this withdrawal on L2. l2Sender = _tx.sender; @@ -430,14 +611,61 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { // sub call to the target contract if the minimum gas limit specified by the user would not // be sufficient to execute the sub call. if (!success && tx.origin == Constants.ESTIMATION_ADDRESS) { - revert GasEstimation(); + revert OptimismPortal_GasEstimation(); } } + /// @notice Checks that a withdrawal has been proven and is ready to be finalized. + /// @param _withdrawalHash Hash of the withdrawal. + /// @param _proofSubmitter Address of the proof submitter. + function checkWithdrawal(bytes32 _withdrawalHash, address _proofSubmitter) public view { + // Grab the withdrawal and dispute game proxy. + ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[_withdrawalHash][_proofSubmitter]; + IDisputeGame disputeGameProxy = provenWithdrawal.disputeGameProxy; + + // Check that this withdrawal has not already been finalized, this is replay protection. + if (finalizedWithdrawals[_withdrawalHash]) { + revert OptimismPortal_AlreadyFinalized(); + } + + // A withdrawal can only be finalized if it has been proven. We know that a withdrawal has + // been proven at least once when its timestamp is non-zero. Unproven withdrawals will have + // a timestamp of zero. + if (provenWithdrawal.timestamp == 0) { + revert OptimismPortal_Unproven(); + } + + // As a sanity check, we make sure that the proven withdrawal's timestamp is greater than + // starting timestamp inside the Dispute Game. Not strictly necessary but extra layer of + // safety against weird bugs in the proving step. Note that this blocks withdrawals that + // are proven in the same block that a dispute game is created. + if (provenWithdrawal.timestamp <= disputeGameProxy.createdAt().raw()) { + revert OptimismPortal_InvalidProofTimestamp(); + } + + // A proven withdrawal must wait at least `PROOF_MATURITY_DELAY_SECONDS` before finalizing. + if (block.timestamp - provenWithdrawal.timestamp <= PROOF_MATURITY_DELAY_SECONDS) { + revert OptimismPortal_ProofNotOldEnough(); + } + + // Check that the root claim is valid. + if (!anchorStateRegistry.isGameClaimValid(disputeGameProxy)) { + revert OptimismPortal_InvalidRootClaim(); + } + } + + /// @notice Migrates the total ETH balance to the ETHLockbox. + function migrateLiquidity() public { + if (msg.sender != proxyAdminOwner()) revert OptimismPortal_Unauthorized(); + _migrateLiquidity(); + } + /// @notice Accepts deposits of ETH and data, and emits a TransactionDeposited event for use in /// deriving deposit transactions. Note that if a deposit is made by a contract, its /// address will be aliased when retrieved using `tx.origin` or `msg.sender`. Consider /// using the CrossDomainMessenger contracts for a simpler developer experience. + /// @dev The `msg.value` is locked on the ETHLockbox and minted as ETH when the deposit + /// arrives on L2, while `_value` specifies how much ETH to send to the target. /// @param _to Target address on L2. /// @param _value ETH value to send to the recipient. /// @param _gasLimit Amount of L2 gas to purchase by burning gas on L1. @@ -454,19 +682,28 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { payable metered(_gasLimit) { + // Lock the ETH in the ETHLockbox. + if (msg.value > 0) ethLockbox.lockETH{ value: msg.value }(); + // Just to be safe, make sure that people specify address(0) as the target when doing // contract creations. - if (_isCreation && _to != address(0)) revert BadTarget(); + if (_isCreation && _to != address(0)) { + revert OptimismPortal_BadTarget(); + } // Prevent depositing transactions that have too small of a gas limit. Users should pay // more for more resource usage. - if (_gasLimit < minimumGasLimit(uint64(_data.length))) revert SmallGasLimit(); + if (_gasLimit < minimumGasLimit(uint64(_data.length))) { + revert OptimismPortal_GasLimitTooLow(); + } // Prevent the creation of deposit transactions that have too much calldata. This gives an // upper limit on the size of unsafe blocks over the p2p network. 120kb is chosen to ensure // that the transaction can fit into the p2p network policy of 128kb even though deposit // transactions are not gossipped over the p2p network. - if (_data.length > 120_000) revert LargeCalldata(); + if (_data.length > 120_000) { + revert OptimismPortal_CalldataTooLarge(); + } // Transform the from-address to its alias if the caller is a contract. address from = msg.sender; @@ -484,108 +721,34 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver { emit TransactionDeposited(from, _to, DEPOSIT_VERSION, opaqueData); } - /// @notice Blacklists a dispute game. Should only be used in the event that a dispute game resolves incorrectly. - /// @param _disputeGame Dispute game to blacklist. - function blacklistDisputeGame(IDisputeGame _disputeGame) external { - if (msg.sender != guardian()) revert Unauthorized(); - disputeGameBlacklist[_disputeGame] = true; - emit DisputeGameBlacklisted(_disputeGame); + /// @notice External getter for the number of proof submitters for a withdrawal hash. + /// @param _withdrawalHash Hash of the withdrawal. + /// @return The number of proof submitters for the withdrawal hash. + function numProofSubmitters(bytes32 _withdrawalHash) external view returns (uint256) { + return proofSubmitters[_withdrawalHash].length; } - /// @notice Sets the respected game type. Changing this value can alter the security properties of the system, - /// depending on the new game's behavior. - /// @param _gameType The game type to consult for output proposals. - function setRespectedGameType(GameType _gameType) external { - if (msg.sender != guardian()) revert Unauthorized(); - // respectedGameTypeUpdatedAt is now no longer set by default. We want to avoid modifying - // this function's signature as that would result in changes to the DeputyGuardianModule. - // We use type(uint32).max as a temporary solution to allow us to update the - // respectedGameTypeUpdatedAt timestamp without modifying this function's signature. - if (_gameType.raw() == type(uint32).max) { - respectedGameTypeUpdatedAt = uint64(block.timestamp); - } else { - respectedGameType = _gameType; - } - emit RespectedGameTypeSet(respectedGameType, Timestamp.wrap(respectedGameTypeUpdatedAt)); + /// @notice Checks if a target address is unsafe. + function _isUnsafeTarget(address _target) internal view virtual returns (bool) { + // Prevent users from targetting an unsafe target address on a withdrawal transaction. + return _target == address(this) || _target == address(ethLockbox); } - /// @notice Checks if a withdrawal can be finalized. This function will revert if the withdrawal cannot be - /// finalized, and otherwise has no side-effects. - /// @param _withdrawalHash Hash of the withdrawal to check. - /// @param _proofSubmitter The submitter of the proof for the withdrawal hash - function checkWithdrawal(bytes32 _withdrawalHash, address _proofSubmitter) public view { - ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[_withdrawalHash][_proofSubmitter]; - IDisputeGame disputeGameProxy = provenWithdrawal.disputeGameProxy; - - // The dispute game must not be blacklisted. - if (disputeGameBlacklist[disputeGameProxy]) revert Blacklisted(); - - // A withdrawal can only be finalized if it has been proven. We know that a withdrawal has - // been proven at least once when its timestamp is non-zero. Unproven withdrawals will have - // a timestamp of zero. - if (provenWithdrawal.timestamp == 0) revert Unproven(); - - // Grab the createdAt timestamp once. - uint64 createdAt = disputeGameProxy.createdAt().raw(); - - // As a sanity check, we make sure that the proven withdrawal's timestamp is greater than - // starting timestamp inside the Dispute Game. Not strictly necessary but extra layer of - // safety against weird bugs in the proving step. - require( - provenWithdrawal.timestamp > createdAt, - "OptimismPortal: withdrawal timestamp less than dispute game creation timestamp" - ); - - // A proven withdrawal must wait at least `PROOF_MATURITY_DELAY_SECONDS` before finalizing. - require( - block.timestamp - provenWithdrawal.timestamp > PROOF_MATURITY_DELAY_SECONDS, - "OptimismPortal: proven withdrawal has not matured yet" - ); - - // A proven withdrawal must wait until the dispute game it was proven against has been - // resolved in favor of the root claim (the output proposal). This is to prevent users - // from finalizing withdrawals proven against non-finalized output roots. - if (disputeGameProxy.status() != GameStatus.DEFENDER_WINS) revert ProposalNotValidated(); - - // The game type of the dispute game must have been the respected game type at creation - // time. We check that the game type is the respected game type at proving time, but it's - // possible that the respected game type has since changed. Users can still use this game - // to finalize a withdrawal as long as it has not been otherwise invalidated. - // The game type of the DisputeGame must have been the respected game type at creation. - // eip150-safe - try disputeGameProxy.wasRespectedGameTypeWhenCreated() returns (bool wasRespected_) { - if (!wasRespected_) revert InvalidGameType(); - } catch { - revert LegacyGame(); - } + /// @notice Migrates the total ETH balance to the ETHLockbox. + function _migrateLiquidity() internal { + uint256 ethBalance = address(this).balance; + ethLockbox.lockETH{ value: ethBalance }(); - // Game must have been created after the respected game type was updated. This check is a - // strict inequality because we want to prevent users from being able to prove or finalize - // withdrawals against games that were created in the same block that the retirement - // timestamp was set. If the retirement timestamp and game type are changed in the same - // block, such games could still be considered valid even if they used the old game type - // that we intended to invalidate. - require( - createdAt > respectedGameTypeUpdatedAt, - "OptimismPortal: dispute game created before respected game type was updated" - ); - - // Before a withdrawal can be finalized, the dispute game it was proven against must have been - // resolved for at least `DISPUTE_GAME_FINALITY_DELAY_SECONDS`. This is to allow for manual - // intervention in the event that a dispute game is resolved incorrectly. - require( - block.timestamp - disputeGameProxy.resolvedAt().raw() > DISPUTE_GAME_FINALITY_DELAY_SECONDS, - "OptimismPortal: output proposal in air-gap" - ); - - // Check that this withdrawal has not already been finalized, this is replay protection. - if (finalizedWithdrawals[_withdrawalHash]) revert AlreadyFinalized(); + emit ETHMigrated(address(ethLockbox), ethBalance); } - /// @notice External getter for the number of proof submitters for a withdrawal hash. - /// @param _withdrawalHash Hash of the withdrawal. - /// @return The number of proof submitters for the withdrawal hash. - function numProofSubmitters(bytes32 _withdrawalHash) external view returns (uint256) { - return proofSubmitters[_withdrawalHash].length; + /// @notice Getter for the resource config. Used internally by the ResourceMetering contract. + /// The SystemConfig is the source of truth for the resource config. + /// @return config_ ResourceMetering ResourceConfig + function _resourceConfig() internal view override returns (ResourceMetering.ResourceConfig memory config_) { + IResourceMetering.ResourceConfig memory config = systemConfig.resourceConfig(); + assembly ("memory-safe") { + config_ := config + } } } diff --git a/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol b/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol index 7920a4931b1..da2e9559e64 100644 --- a/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol +++ b/packages/contracts-bedrock/src/L1/OptimismPortalInterop.sol @@ -7,7 +7,6 @@ import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol"; // Libraries import { Predeploys } from "src/libraries/Predeploys.sol"; import { Constants } from "src/libraries/Constants.sol"; -import { Unauthorized } from "src/libraries/PortalErrors.sol"; // Interfaces import { IL1BlockInterop, ConfigType } from "interfaces/L2/IL1BlockInterop.sol"; @@ -18,23 +17,19 @@ import { IL1BlockInterop, ConfigType } from "interfaces/L2/IL1BlockInterop.sol"; /// and L2. Messages sent directly to the OptimismPortal have no form of replayability. /// Users are encouraged to use the L1CrossDomainMessenger for a higher-level interface. contract OptimismPortalInterop is OptimismPortal2 { - constructor( - uint256 _proofMaturityDelaySeconds, - uint256 _disputeGameFinalityDelaySeconds - ) - OptimismPortal2(_proofMaturityDelaySeconds, _disputeGameFinalityDelaySeconds) - { } - - /// @custom:semver +interop.3 + /// @param _proofMaturityDelaySeconds The proof maturity delay in seconds. + constructor(uint256 _proofMaturityDelaySeconds) OptimismPortal2(_proofMaturityDelaySeconds) { } + + /// @custom:semver +interop.4 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop.3"); + return string.concat(super.version(), "+interop.4"); } /// @notice Sets static configuration options for the L2 system. /// @param _type Type of configuration to set. /// @param _value Encoded value of the configuration. function setConfig(ConfigType _type, bytes memory _value) external { - if (msg.sender != address(systemConfig)) revert Unauthorized(); + if (msg.sender != address(systemConfig)) revert OptimismPortal_Unauthorized(); // Set L2 deposit gas as used without paying burning gas. Ensures that deposits cannot use too much L2 gas. // This value must be large enough to cover the cost of calling `L1Block.setConfig`. diff --git a/packages/contracts-bedrock/src/L1/ProxyAdminOwnedBase.sol b/packages/contracts-bedrock/src/L1/ProxyAdminOwnedBase.sol new file mode 100644 index 00000000000..350b91cf10c --- /dev/null +++ b/packages/contracts-bedrock/src/L1/ProxyAdminOwnedBase.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.15; + +import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; +import { Storage } from "src/libraries/Storage.sol"; +import { Constants } from "src/libraries/Constants.sol"; + +/// @notice Base contract for ProxyAdmin-owned contracts. It's main goal is to expose the ProxyAdmin owner address on +/// a function and also to check if the current contract and a given proxy have the same ProxyAdmin owner. +abstract contract ProxyAdminOwnedBase { + /// @notice Getter for the owner of the ProxyAdmin. + /// The ProxyAdmin is the owner of the current proxy contract. + function proxyAdminOwner() public view returns (address) { + // Get the proxy admin address reading for the reserved slot it has on the Proxy contract. + IProxyAdmin proxyAdmin = IProxyAdmin(Storage.getAddress(Constants.PROXY_OWNER_ADDRESS)); + // Return the owner of the proxy admin. + return proxyAdmin.owner(); + } + + /// @notice Checks if the ProxyAdmin owner of the current contract is the same as the ProxyAdmin owner of the given + /// proxy. + /// @param _proxy The address of the proxy to check. + function _sameProxyAdminOwner(address _proxy) internal view returns (bool) { + return proxyAdminOwner() == ProxyAdminOwnedBase(_proxy).proxyAdminOwner(); + } +} diff --git a/packages/contracts-bedrock/src/L1/SystemConfig.sol b/packages/contracts-bedrock/src/L1/SystemConfig.sol index e767bc64a50..092767efb54 100644 --- a/packages/contracts-bedrock/src/L1/SystemConfig.sol +++ b/packages/contracts-bedrock/src/L1/SystemConfig.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.15; // Contracts import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import { ReinitializableBase } from "src/universal/ReinitializableBase.sol"; // Libraries import { Storage } from "src/libraries/Storage.sol"; @@ -16,7 +17,7 @@ import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; /// @notice The SystemConfig contract is used to manage configuration of an Optimism network. /// All configuration is stored on L1 and picked up by L2 as part of the derviation of /// the L2 chain. -contract SystemConfig is OwnableUpgradeable, ISemver { +contract SystemConfig is OwnableUpgradeable, ReinitializableBase, ISemver { /// @notice Enum representing different types of updates. /// @custom:value BATCHER Represents an update to the batcher hash. /// @custom:value FEE_SCALARS Represents an update to l1 data fee scalars. @@ -129,6 +130,9 @@ contract SystemConfig is OwnableUpgradeable, ISemver { /// @notice The operator fee constant. uint64 public operatorFeeConstant; + /// @notice The L2 chain ID that this SystemConfig configures. + uint256 public l2ChainId; + /// @notice Emitted when configuration is updated. /// @param version SystemConfig version. /// @param updateType Type of update. @@ -136,15 +140,15 @@ contract SystemConfig is OwnableUpgradeable, ISemver { event ConfigUpdate(uint256 indexed version, UpdateType indexed updateType, bytes data); /// @notice Semantic version. - /// @custom:semver 2.5.0 + /// @custom:semver 2.6.0 function version() public pure virtual returns (string memory) { - return "2.5.0"; + return "2.6.0"; } /// @notice Constructs the SystemConfig contract. /// @dev START_BLOCK_SLOT is set to type(uint256).max here so that it will be a dead value /// in the singleton. - constructor() { + constructor() ReinitializableBase(2) { Storage.setUint(START_BLOCK_SLOT, type(uint256).max); _disableInitializers(); } @@ -161,6 +165,7 @@ contract SystemConfig is OwnableUpgradeable, ISemver { /// @param _batchInbox Batch inbox address. An identifier for the op-node to find /// canonical data. /// @param _addresses Set of L1 contract addresses. These should be the proxies. + /// @param _l2ChainId The L2 chain ID that this SystemConfig configures. function initialize( address _owner, uint32 _basefeeScalar, @@ -170,10 +175,11 @@ contract SystemConfig is OwnableUpgradeable, ISemver { address _unsafeBlockSigner, IResourceMetering.ResourceConfig memory _config, address _batchInbox, - SystemConfig.Addresses memory _addresses + SystemConfig.Addresses memory _addresses, + uint256 _l2ChainId ) public - initializer + reinitializer(initVersion()) { __Ownable_init(); transferOwnership(_owner); @@ -195,6 +201,14 @@ contract SystemConfig is OwnableUpgradeable, ISemver { _setStartBlock(); _setResourceConfig(_config); + + l2ChainId = _l2ChainId; + } + + /// @notice Upgrades the SystemConfig by setting the L2 chain ID variable. + /// @param _l2ChainId The L2 chain ID that this SystemConfig configures. + function upgrade(uint256 _l2ChainId) external reinitializer(initVersion()) { + l2ChainId = _l2ChainId; } /// @notice Returns the minimum L2 gas limit that can be safely set for the system to diff --git a/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol b/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol index 1d7bd879451..fc4135c0883 100644 --- a/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol +++ b/packages/contracts-bedrock/src/L1/SystemConfigInterop.sol @@ -46,7 +46,8 @@ contract SystemConfigInterop is SystemConfig { IResourceMetering.ResourceConfig memory _config, address _batchInbox, SystemConfig.Addresses memory _addresses, - address _dependencyManager + address _dependencyManager, + uint256 _l2ChainId ) external { @@ -60,14 +61,15 @@ contract SystemConfigInterop is SystemConfig { _unsafeBlockSigner: _unsafeBlockSigner, _config: _config, _batchInbox: _batchInbox, - _addresses: _addresses + _addresses: _addresses, + _l2ChainId: _l2ChainId }); Storage.setAddress(DEPENDENCY_MANAGER_SLOT, _dependencyManager); } - /// @custom:semver +interop.1 + /// @custom:semver +interop.2 function version() public pure override returns (string memory) { - return string.concat(super.version(), "+interop.1"); + return string.concat(super.version(), "+interop.2"); } /// @notice Adds a chain to the interop dependency set. Can only be called by the dependency manager. diff --git a/packages/contracts-bedrock/src/dispute/AnchorStateRegistry.sol b/packages/contracts-bedrock/src/dispute/AnchorStateRegistry.sol index 60cd9ba31e1..35b9d2736fb 100644 --- a/packages/contracts-bedrock/src/dispute/AnchorStateRegistry.sol +++ b/packages/contracts-bedrock/src/dispute/AnchorStateRegistry.sol @@ -13,7 +13,6 @@ import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; /// @custom:proxied true /// @title AnchorStateRegistry @@ -23,8 +22,11 @@ import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; /// be initialized with a more recent starting state which reduces the amount of required offchain computation. contract AnchorStateRegistry is Initializable, ISemver { /// @notice Semantic version. - /// @custom:semver 2.2.2 - string public constant version = "2.2.2"; + /// @custom:semver 3.0.0 + string public constant version = "3.0.0"; + + /// @notice The dispute game finality delay in seconds. + uint256 internal immutable DISPUTE_GAME_FINALITY_DELAY_SECONDS; /// @notice Address of the SuperchainConfig contract. ISuperchainConfig public superchainConfig; @@ -32,61 +34,105 @@ contract AnchorStateRegistry is Initializable, ISemver { /// @notice Address of the DisputeGameFactory contract. IDisputeGameFactory public disputeGameFactory; - /// @notice Address of the OptimismPortal contract. - IOptimismPortal2 public portal; - /// @notice The game whose claim is currently being used as the anchor state. IFaultDisputeGame public anchorGame; /// @notice The starting anchor root. OutputRoot internal startingAnchorRoot; - /// @notice Emitted when an anchor state is not updated. - /// @param game Game that was not used as the new anchor game. - event AnchorNotUpdated(IFaultDisputeGame indexed game); + /// @notice Mapping of blacklisted dispute games. + mapping(IDisputeGame => bool) public disputeGameBlacklist; + + /// @notice The respected game type. + GameType public respectedGameType; + + /// @notice The retirement timestamp. All games created before or at this timestamp are + /// considered retired and are therefore not valid games. Retirement is used as a + /// blanket invalidation mechanism if games resolve incorrectly. + uint64 public retirementTimestamp; /// @notice Emitted when an anchor state is updated. /// @param game Game that was used as the new anchor game. event AnchorUpdated(IFaultDisputeGame indexed game); - /// @notice Thrown when an unauthorized caller attempts to set the anchor state. - error AnchorStateRegistry_Unauthorized(); + /// @notice Emitted when the respected game type is set. + /// @param gameType The new respected game type. + event RespectedGameTypeSet(GameType gameType); - /// @notice Thrown when an invalid anchor game is provided. - error AnchorStateRegistry_InvalidAnchorGame(); + /// @notice Emitted when the retirement timestamp is set. + /// @param timestamp The new retirement timestamp. + event RetirementTimestampSet(uint256 timestamp); + + /// @notice Emitted when a dispute game is blacklisted. + /// @param disputeGame The dispute game that was blacklisted. + event DisputeGameBlacklisted(IDisputeGame indexed disputeGame); /// @notice Thrown when the anchor root is requested, but the anchor game is blacklisted. error AnchorStateRegistry_AnchorGameBlacklisted(); - /// @notice Constructor to disable initializers. - constructor() { + /// @notice Thrown when an invalid anchor game is provided. + error AnchorStateRegistry_InvalidAnchorGame(); + + /// @notice Thrown when an unauthorized caller attempts to set the anchor state. + error AnchorStateRegistry_Unauthorized(); + + /// @param _disputeGameFinalityDelaySeconds The dispute game finality delay in seconds. + constructor(uint256 _disputeGameFinalityDelaySeconds) { + DISPUTE_GAME_FINALITY_DELAY_SECONDS = _disputeGameFinalityDelaySeconds; _disableInitializers(); } /// @notice Initializes the contract. /// @param _superchainConfig The address of the SuperchainConfig contract. /// @param _disputeGameFactory The address of the DisputeGameFactory contract. - /// @param _portal The address of the OptimismPortal contract. /// @param _startingAnchorRoot The starting anchor root. function initialize( ISuperchainConfig _superchainConfig, IDisputeGameFactory _disputeGameFactory, - IOptimismPortal2 _portal, - OutputRoot memory _startingAnchorRoot + OutputRoot memory _startingAnchorRoot, + GameType _startingRespectedGameType ) external initializer { superchainConfig = _superchainConfig; disputeGameFactory = _disputeGameFactory; - portal = _portal; startingAnchorRoot = _startingAnchorRoot; + respectedGameType = _startingRespectedGameType; + retirementTimestamp = uint64(block.timestamp); + } + + /// @notice Returns whether the contract is paused. + function paused() public view returns (bool) { + return superchainConfig.paused(); } - /// @notice Returns the respected game type. - /// @return The respected game type. - function respectedGameType() public view returns (GameType) { - return portal.respectedGameType(); + /// @notice Returns the dispute game finality delay in seconds. + function disputeGameFinalityDelaySeconds() external view returns (uint256) { + return DISPUTE_GAME_FINALITY_DELAY_SECONDS; + } + + /// @notice Allows the Guardian to set the respected game type. + /// @param _gameType The new respected game type. + function setRespectedGameType(GameType _gameType) external { + if (msg.sender != superchainConfig.guardian()) revert AnchorStateRegistry_Unauthorized(); + respectedGameType = _gameType; + emit RespectedGameTypeSet(_gameType); + } + + /// @notice Allows the Guardian to update the retirement timestamp. + function updateRetirementTimestamp() external { + if (msg.sender != superchainConfig.guardian()) revert AnchorStateRegistry_Unauthorized(); + retirementTimestamp = uint64(block.timestamp); + emit RetirementTimestampSet(block.timestamp); + } + + /// @notice Allows the Guardian to blacklist a dispute game. + /// @param _disputeGame Dispute game to blacklist. + function blacklistDisputeGame(IDisputeGame _disputeGame) external { + if (msg.sender != superchainConfig.guardian()) revert AnchorStateRegistry_Unauthorized(); + disputeGameBlacklist[_disputeGame] = true; + emit DisputeGameBlacklisted(_disputeGame); } /// @custom:legacy @@ -129,6 +175,9 @@ contract AnchorStateRegistry is Initializable, ISemver { /// @param _game The game to check. /// @return Whether the game is of a respected game type. function isGameRespected(IDisputeGame _game) public view returns (bool) { + // We don't do a try/catch here for legacy games because by the time this code is live on + // mainnet, users won't be using legacy games anymore. Avoiding the try/catch simplifies + // the logic. return _game.wasRespectedGameTypeWhenCreated(); } @@ -136,7 +185,7 @@ contract AnchorStateRegistry is Initializable, ISemver { /// @param _game The game to check. /// @return Whether the game is blacklisted. function isGameBlacklisted(IDisputeGame _game) public view returns (bool) { - return portal.disputeGameBlacklist(_game); + return disputeGameBlacklist[_game]; } /// @notice Determines whether a game is retired. @@ -146,7 +195,7 @@ contract AnchorStateRegistry is Initializable, ISemver { // Must be created after the respectedGameTypeUpdatedAt timestamp. Note that this means all // games created in the same block as the respectedGameTypeUpdatedAt timestamp are // considered retired. - return _game.createdAt().raw() <= portal.respectedGameTypeUpdatedAt(); + return _game.createdAt().raw() <= retirementTimestamp; } /// @notice Returns whether a game is resolved. @@ -186,6 +235,11 @@ contract AnchorStateRegistry is Initializable, ISemver { return false; } + // Must not be paused, temporarily causes game to be considered improper. + if (paused()) { + return false; + } + return true; } @@ -200,7 +254,7 @@ contract AnchorStateRegistry is Initializable, ISemver { // Game must be beyond the "airgap period" - time since resolution must be at least // "dispute game finality delay" seconds in the past. - if (block.timestamp - _game.resolvedAt().raw() <= portal.disputeGameFinalityDelaySeconds()) { + if (block.timestamp - _game.resolvedAt().raw() <= DISPUTE_GAME_FINALITY_DELAY_SECONDS) { return false; } diff --git a/packages/contracts-bedrock/src/libraries/Encoding.sol b/packages/contracts-bedrock/src/libraries/Encoding.sol index 00c20ea459b..6140b62c9e6 100644 --- a/packages/contracts-bedrock/src/libraries/Encoding.sol +++ b/packages/contracts-bedrock/src/libraries/Encoding.sol @@ -9,6 +9,12 @@ import { RLPWriter } from "src/libraries/rlp/RLPWriter.sol"; /// @title Encoding /// @notice Encoding handles Optimism's various different encoding schemes. library Encoding { + /// @notice Thrown when a provided Super Root proof has an invalid version. + error Encoding_InvalidSuperRootVersion(); + + /// @notice Thrown when a provided Super Root proof has no Output Roots. + error Encoding_EmptySuperRoot(); + /// @notice RLP encodes the L2 transaction that would be generated when a given deposit is sent /// to the L2 system. Useful for searching for a deposit in the L2 system. The /// transaction is prefixed with 0x7e to identify its EIP-2718 type. @@ -262,4 +268,30 @@ library Encoding { _batcherHash ); } + + /// @notice Encodes a super root proof into the preimage of a Super Root. + /// @param _superRootProof Super root proof to encode. + /// @return Encoded super root proof. + function encodeSuperRootProof(Types.SuperRootProof memory _superRootProof) internal pure returns (bytes memory) { + // Version must match the expected version. + if (_superRootProof.version != 0x01) { + revert Encoding_InvalidSuperRootVersion(); + } + + // Output roots must not be empty. + if (_superRootProof.outputRoots.length == 0) { + revert Encoding_EmptySuperRoot(); + } + + // Start with version byte and timestamp. + bytes memory encoded = bytes.concat(bytes1(0x01), bytes8(_superRootProof.timestamp)); + + // Add each output root (chainId + root) + for (uint256 i = 0; i < _superRootProof.outputRoots.length; i++) { + Types.OutputRootWithChainId memory outputRoot = _superRootProof.outputRoots[i]; + encoded = bytes.concat(encoded, bytes32(outputRoot.chainId), outputRoot.root); + } + + return encoded; + } } diff --git a/packages/contracts-bedrock/src/libraries/Hashing.sol b/packages/contracts-bedrock/src/libraries/Hashing.sol index b736ad9e4b7..782bbbe4f99 100644 --- a/packages/contracts-bedrock/src/libraries/Hashing.sol +++ b/packages/contracts-bedrock/src/libraries/Hashing.sol @@ -146,4 +146,11 @@ library Hashing { { return keccak256(abi.encode(_destination, _source, _nonce, _sender, _target, _message)); } + + /// @notice Hashes a Super Root proof into a Super Root. + /// @param _superRootProof Super Root proof to hash. + /// @return Hashed super root proof. + function hashSuperRootProof(Types.SuperRootProof memory _superRootProof) internal pure returns (bytes32) { + return keccak256(Encoding.encodeSuperRootProof(_superRootProof)); + } } diff --git a/packages/contracts-bedrock/src/libraries/PortalErrors.sol b/packages/contracts-bedrock/src/libraries/PortalErrors.sol deleted file mode 100644 index 9096a2938fd..00000000000 --- a/packages/contracts-bedrock/src/libraries/PortalErrors.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/// @notice Error for when a deposit or withdrawal is to a bad target. -error BadTarget(); -/// @notice Error for when a deposit has too much calldata. -error LargeCalldata(); -/// @notice Error for when a deposit has too small of a gas limit. -error SmallGasLimit(); -/// @notice Error for when a withdrawal transfer fails. -error TransferFailed(); -/// @notice Error for when a method cannot be called with non zero CALLVALUE. -error NoValue(); -/// @notice Error for an unauthorized CALLER. -error Unauthorized(); -/// @notice Error for when a method cannot be called when paused. This could be renamed -/// to `Paused` in the future, but it collides with the `Paused` event. -error CallPaused(); -/// @notice Error for special gas estimation. -error GasEstimation(); -/// @notice Error for when a method is being reentered. -error NonReentrant(); -/// @notice Error for invalid proof. -error InvalidProof(); -/// @notice Error for invalid game type. -error InvalidGameType(); -/// @notice Error for an invalid dispute game. -error InvalidDisputeGame(); -/// @notice Error for an invalid merkle proof. -error InvalidMerkleProof(); -/// @notice Error for when a dispute game has been blacklisted. -error Blacklisted(); -/// @notice Error for when trying to withdrawal without first proven. -error Unproven(); -/// @notice Error for when a proposal is not validated. -error ProposalNotValidated(); -/// @notice Error for when a withdrawal has already been finalized. -error AlreadyFinalized(); -/// @notice Error for when a game is a legacy game. -error LegacyGame(); diff --git a/packages/contracts-bedrock/src/libraries/Types.sol b/packages/contracts-bedrock/src/libraries/Types.sol index 7e9a65654bc..3c18c4e1bc1 100644 --- a/packages/contracts-bedrock/src/libraries/Types.sol +++ b/packages/contracts-bedrock/src/libraries/Types.sol @@ -29,6 +29,24 @@ library Types { bytes32 latestBlockhash; } + /// @notice Struct representing an output root with a chain id. + /// @custom:field chainId The chain ID of the L2 chain that the output root commits to. + /// @custom:field root The output root. + struct OutputRootWithChainId { + uint256 chainId; + bytes32 root; + } + + /// @notice Struct representing a super root proof. + /// @custom:field version The version of the super root proof. + /// @custom:field timestamp The timestamp of the super root proof. + /// @custom:field outputRoots The output roots that are included in the super root proof. + struct SuperRootProof { + bytes1 version; + uint64 timestamp; + OutputRootWithChainId[] outputRoots; + } + /// @notice Struct representing a deposit transaction (L1 => L2 transaction) created by an end /// user (as opposed to a system deposit transaction generated by the system). /// @custom:field from Address of the sender of the transaction. diff --git a/packages/contracts-bedrock/src/safe/DeputyGuardianModule.sol b/packages/contracts-bedrock/src/safe/DeputyGuardianModule.sol index a742c452ef0..2a0be015c0f 100644 --- a/packages/contracts-bedrock/src/safe/DeputyGuardianModule.sol +++ b/packages/contracts-bedrock/src/safe/DeputyGuardianModule.sol @@ -6,14 +6,11 @@ import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol"; import { Enum } from "safe-contracts/common/Enum.sol"; // Libraries -import { Unauthorized } from "src/libraries/PortalErrors.sol"; import { GameType, Timestamp } from "src/dispute/lib/Types.sol"; // Interfaces import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; -import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { ISemver } from "interfaces/universal/ISemver.sol"; @@ -24,7 +21,10 @@ import { ISemver } from "interfaces/universal/ISemver.sol"; /// authorization at any time by disabling this module. contract DeputyGuardianModule is ISemver { /// @notice Error message for failed transaction execution - error ExecutionFailed(string); + error DeputyGuardianModule_ExecutionFailed(string); + + /// @notice Thrown when the caller is not the deputy guardian. + error DeputyGuardianModule_Unauthorized(); /// @notice Emitted when the SuperchainConfig is paused event Paused(string identifier); @@ -38,6 +38,9 @@ contract DeputyGuardianModule is ISemver { /// @notice Emitted when the respected game type is set event RespectedGameTypeSet(GameType indexed gameType, Timestamp indexed updatedAt); + /// @notice Emitted when the retirement timestamp is updated + event RetirementTimestampUpdated(Timestamp indexed updatedAt); + /// @notice The Safe contract instance Safe internal immutable SAFE; @@ -48,8 +51,8 @@ contract DeputyGuardianModule is ISemver { address internal immutable DEPUTY_GUARDIAN; /// @notice Semantic version. - /// @custom:semver 2.0.1-beta.5 - string public constant version = "2.0.1-beta.5"; + /// @custom:semver 3.0.0 + string public constant version = "3.0.0"; // Constructor to initialize the Safe and baseModule instances constructor(Safe _safe, ISuperchainConfig _superchainConfig, address _deputyGuardian) { @@ -79,7 +82,7 @@ contract DeputyGuardianModule is ISemver { /// @notice Internal function to ensure that only the deputy guardian can call certain functions. function _onlyDeputyGuardian() internal view { if (msg.sender != DEPUTY_GUARDIAN) { - revert Unauthorized(); + revert DeputyGuardianModule_Unauthorized(); } } @@ -93,7 +96,7 @@ contract DeputyGuardianModule is ISemver { (bool success, bytes memory returnData) = SAFE.execTransactionFromModuleReturnData(address(SUPERCHAIN_CONFIG), 0, data, Enum.Operation.Call); if (!success) { - revert ExecutionFailed(string(returnData)); + revert DeputyGuardianModule_ExecutionFailed(string(returnData)); } emit Paused("Deputy Guardian"); } @@ -108,58 +111,58 @@ contract DeputyGuardianModule is ISemver { (bool success, bytes memory returnData) = SAFE.execTransactionFromModuleReturnData(address(SUPERCHAIN_CONFIG), 0, data, Enum.Operation.Call); if (!success) { - revert ExecutionFailed(string(returnData)); + revert DeputyGuardianModule_ExecutionFailed(string(returnData)); } emit Unpaused(); } /// @notice Calls the Security Council Safe's `execTransactionFromModuleReturnData()`, with the arguments - /// necessary to call `setAnchorState()` on the `AnchorStateRegistry` contract. + /// necessary to call `blacklistDisputeGame()` on the `AnchorStateRegistry` contract. /// Only the deputy guardian can call this function. - /// @param _registry The `IAnchorStateRegistry` contract instance. - /// @param _game The `IFaultDisputeGame` contract instance. - function setAnchorState(IAnchorStateRegistry _registry, IFaultDisputeGame _game) external { + /// @param _anchorStateRegistry The `AnchorStateRegistry` contract instance. + /// @param _game The `IDisputeGame` contract instance. + function blacklistDisputeGame(IAnchorStateRegistry _anchorStateRegistry, IDisputeGame _game) external { _onlyDeputyGuardian(); - bytes memory data = abi.encodeCall(IAnchorStateRegistry.setAnchorState, (_game)); + bytes memory data = abi.encodeCall(IAnchorStateRegistry.blacklistDisputeGame, (_game)); (bool success, bytes memory returnData) = - SAFE.execTransactionFromModuleReturnData(address(_registry), 0, data, Enum.Operation.Call); + SAFE.execTransactionFromModuleReturnData(address(_anchorStateRegistry), 0, data, Enum.Operation.Call); if (!success) { - revert ExecutionFailed(string(returnData)); + revert DeputyGuardianModule_ExecutionFailed(string(returnData)); } + emit DisputeGameBlacklisted(_game); } /// @notice Calls the Security Council Safe's `execTransactionFromModuleReturnData()`, with the arguments - /// necessary to call `blacklistDisputeGame()` on the `OptimismPortal2` contract. + /// necessary to call `setRespectedGameType()` on the `AnchorStateRegistry` contract. /// Only the deputy guardian can call this function. - /// @param _portal The `OptimismPortal2` contract instance. - /// @param _game The `IDisputeGame` contract instance. - function blacklistDisputeGame(IOptimismPortal2 _portal, IDisputeGame _game) external { + /// @param _anchorStateRegistry The `AnchorStateRegistry` contract instance. + /// @param _gameType The `GameType` to set as the respected game type. + function setRespectedGameType(IAnchorStateRegistry _anchorStateRegistry, GameType _gameType) external { _onlyDeputyGuardian(); - bytes memory data = abi.encodeCall(IOptimismPortal2.blacklistDisputeGame, (_game)); + bytes memory data = abi.encodeCall(IAnchorStateRegistry.setRespectedGameType, (_gameType)); (bool success, bytes memory returnData) = - SAFE.execTransactionFromModuleReturnData(address(_portal), 0, data, Enum.Operation.Call); + SAFE.execTransactionFromModuleReturnData(address(_anchorStateRegistry), 0, data, Enum.Operation.Call); if (!success) { - revert ExecutionFailed(string(returnData)); + revert DeputyGuardianModule_ExecutionFailed(string(returnData)); } - emit DisputeGameBlacklisted(_game); + emit RespectedGameTypeSet(_gameType, Timestamp.wrap(uint64(block.timestamp))); } /// @notice Calls the Security Council Safe's `execTransactionFromModuleReturnData()`, with the arguments - /// necessary to call `setRespectedGameType()` on the `OptimismPortal2` contract. + /// necessary to call `updateRetirementTimestamp()` on the `AnchorStateRegistry` contract. /// Only the deputy guardian can call this function. - /// @param _portal The `OptimismPortal2` contract instance. - /// @param _gameType The `GameType` to set as the respected game type. - function setRespectedGameType(IOptimismPortal2 _portal, GameType _gameType) external { + /// @param _anchorStateRegistry The `AnchorStateRegistry` contract instance. + function updateRetirementTimestamp(IAnchorStateRegistry _anchorStateRegistry) external { _onlyDeputyGuardian(); - bytes memory data = abi.encodeCall(IOptimismPortal2.setRespectedGameType, (_gameType)); + bytes memory data = abi.encodeCall(IAnchorStateRegistry.updateRetirementTimestamp, ()); (bool success, bytes memory returnData) = - SAFE.execTransactionFromModuleReturnData(address(_portal), 0, data, Enum.Operation.Call); + SAFE.execTransactionFromModuleReturnData(address(_anchorStateRegistry), 0, data, Enum.Operation.Call); if (!success) { - revert ExecutionFailed(string(returnData)); + revert DeputyGuardianModule_ExecutionFailed(string(returnData)); } - emit RespectedGameTypeSet(_gameType, Timestamp.wrap(uint64(block.timestamp))); + emit RetirementTimestampUpdated(Timestamp.wrap(uint64(block.timestamp))); } } diff --git a/packages/contracts-bedrock/src/universal/ReinitializableBase.sol b/packages/contracts-bedrock/src/universal/ReinitializableBase.sol new file mode 100644 index 00000000000..056a15986e0 --- /dev/null +++ b/packages/contracts-bedrock/src/universal/ReinitializableBase.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +/// @title ReinitializableBase +/// @notice A base contract for reinitializable contracts that exposes a version number. +abstract contract ReinitializableBase { + /// @notice Thrown when the initialization version is zero. + error ReinitializableBase_ZeroInitVersion(); + + /// @notice Current initialization version. + uint8 internal immutable INIT_VERSION; + + /// @param _initVersion Current initialization version. + constructor(uint8 _initVersion) { + // Sanity check, we should never have a zero init version. + if (_initVersion == 0) revert ReinitializableBase_ZeroInitVersion(); + INIT_VERSION = _initVersion; + } + + /// @notice Getter for the current initialization version. + /// @return The current initialization version. + function initVersion() public view returns (uint8) { + return INIT_VERSION; + } +} diff --git a/packages/contracts-bedrock/test/L1/ETHLockbox.t.sol b/packages/contracts-bedrock/test/L1/ETHLockbox.t.sol new file mode 100644 index 00000000000..17ade244554 --- /dev/null +++ b/packages/contracts-bedrock/test/L1/ETHLockbox.t.sol @@ -0,0 +1,484 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Testing utilities +import { Constants } from "src/libraries/Constants.sol"; + +// Interfaces +import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; + +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; + +import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; +import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; + +// Test +import { CommonTest } from "test/setup/CommonTest.sol"; + +import { ProxyAdmin } from "src/universal/ProxyAdmin.sol"; + +contract ETHLockboxTest is CommonTest { + error InvalidInitialization(); + + event ETHLocked(address indexed portal, uint256 amount); + event ETHUnlocked(address indexed portal, uint256 amount); + event PortalAuthorized(address indexed portal); + event LockboxAuthorized(address indexed lockbox); + event LiquidityMigrated(address indexed lockbox, uint256 amount); + event LiquidityReceived(address indexed lockbox, uint256 amount); + + ProxyAdmin public proxyAdmin; + address public proxyAdminOwner; + + function setUp() public virtual override { + super.setUp(); + + // If not on the last upgrade network, we skip the test since the `ETHLockbox` won't be yet deployed + // TODO(#14691): Remove this check once Upgrade 15 is deployed on Mainnet. + if (isForkTest() && !deploy.cfg().useUpgradedFork()) vm.skip(true); + + proxyAdmin = ProxyAdmin(artifacts.mustGetAddress("ProxyAdmin")); + proxyAdminOwner = proxyAdmin.owner(); + } + + /// @notice Tests the superchain config was correctly set during initialization. + function test_initialization_succeeds() public view { + assertEq(address(ethLockbox.superchainConfig()), address(superchainConfig)); + assertEq(ethLockbox.authorizedPortals(address(optimismPortal2)), true); + } + + /// @notice Tests it reverts when the contract is already initialized. + function test_initialize_alreadyInitialized_reverts() public { + vm.expectRevert("Initializable: contract is already initialized"); + IOptimismPortal2[] memory _portals = new IOptimismPortal2[](1); + ethLockbox.initialize(superchainConfig, _portals); + } + + /// @notice Tests the proxy admin owner is correctly returned. + function test_proxyProxyAdminOwner_succeeds() public view { + assertEq(ethLockbox.proxyAdminOwner(), proxyAdminOwner); + } + + /// @notice Tests the paused status is correctly returned. + function test_paused_succeeds() public { + // Assert the paused status is false + assertEq(ethLockbox.paused(), false); + + // Mock the superchain config to return true for the paused status + vm.mockCall(address(superchainConfig), abi.encodeCall(ISuperchainConfig.paused, ()), abi.encode(true)); + + // Assert the paused status is true + assertEq(ethLockbox.paused(), true); + } + + /// @notice Tests the liquidity is correctly received. + function testFuzz_receiveLiquidity_succeeds(address _lockbox, uint256 _value) public { + // Since on the fork the `_lockbox` fuzzed address doesn't exist, we skip the test + if (isForkTest()) vm.skip(true); + assumeNotForgeAddress(_lockbox); + vm.assume(address(_lockbox) != address(ethLockbox)); + + // Deal the value to the lockbox + deal(address(_lockbox), _value); + + // Mock the admin owner of the lockbox to be the same as the current lockbox proxy admin owner + vm.mockCall( + address(_lockbox), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(proxyAdminOwner) + ); + + // Authorize the lockbox if needed + if (!ethLockbox.authorizedLockboxes(_lockbox)) { + vm.prank(proxyAdminOwner); + ethLockbox.authorizeLockbox(IETHLockbox(_lockbox)); + } + + // Get the balance of the lockbox before the receive + uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; + + // Expect the `LiquidityReceived` event to be emitted + vm.expectEmit(address(ethLockbox)); + emit LiquidityReceived(_lockbox, _value); + + // Call the `receiveLiquidity` function + vm.prank(address(_lockbox)); + ethLockbox.receiveLiquidity{ value: _value }(); + + // Assert the lockbox's balance increased by the amount received + assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + _value); + } + + /// @notice Tests it reverts when the caller is not an authorized portal. + function testFuzz_lockETH_unauthorizedPortal_reverts(address _caller) public { + vm.assume(!ethLockbox.authorizedPortals(_caller)); + + // Expect the revert with `Unauthorized` selector + vm.expectRevert(IETHLockbox.ETHLockbox_Unauthorized.selector); + + // Call the `lockETH` function with an unauthorized caller + vm.prank(_caller); + ethLockbox.lockETH(); + } + + /// @notice Tests the ETH is correctly locked when the caller is an authorized portal. + function testFuzz_lockETH_succeeds(uint256 _amount) public { + // Prevent overflow on an upgrade context + _amount = bound(_amount, 0, type(uint256).max - address(ethLockbox).balance); + + // Deal the ETH amount to the portal + vm.deal(address(optimismPortal2), _amount); + + // Get the balance of the portal and lockbox before the lock to compare later on the assertions + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 lockboxBalanceBefore = address(ethLockbox).balance; + + // Look for the emit of the `ETHLocked` event + vm.expectEmit(address(ethLockbox)); + emit ETHLocked(address(optimismPortal2), _amount); + + // Call the `lockETH` function with the portal + vm.prank(address(optimismPortal2)); + ethLockbox.lockETH{ value: _amount }(); + + // Assert the portal's balance decreased and the lockbox's balance increased by the amount locked + assertEq(address(optimismPortal2).balance, portalBalanceBefore - _amount); + assertEq(address(ethLockbox).balance, lockboxBalanceBefore + _amount); + } + + /// @notice Tests the ETH is correctly locked when the caller is an authorized portal with different portals. + function testFuzz_lockETH_multiplePortals_succeeds(IOptimismPortal2 _portal, uint256 _amount) public { + // Since on the fork the `_portal` fuzzed address doesn't exist, we skip the test + if (isForkTest()) vm.skip(true); + assumeNotForgeAddress(address(_portal)); + vm.assume(address(_portal) != address(ethLockbox)); + + // Mock the admin owner of the portal to be the same as the current lockbox proxy admin owner + vm.mockCall( + address(_portal), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(proxyAdminOwner) + ); + + // Set the portal as an authorized portal if needed + if (!ethLockbox.authorizedPortals(address(_portal))) { + vm.prank(proxyAdminOwner); + ethLockbox.authorizePortal(_portal); + } + + // Deal the ETH amount to the portal + vm.deal(address(_portal), _amount); + + // Get the balance of the lockbox before the lock to compare later on the assertions + uint256 lockboxBalanceBefore = address(ethLockbox).balance; + + // Look for the emit of the `ETHLocked` event + vm.expectEmit(address(ethLockbox)); + emit ETHLocked(address(_portal), _amount); + + // Call the `lockETH` function with the portal + vm.prank(address(_portal)); + ethLockbox.lockETH{ value: _amount }(); + + // Assert the portal's balance decreased and the lockbox's balance increased by the amount locked + assertEq(address(ethLockbox).balance, lockboxBalanceBefore + _amount); + } + + /// @notice Tests `unlockETH` reverts when the contract is paused. + function testFuzz_unlockETH_paused_reverts(address _caller, uint256 _value) public { + // Mock the superchain config to return true for the paused status + vm.mockCall(address(superchainConfig), abi.encodeCall(ISuperchainConfig.paused, ()), abi.encode(true)); + + // Expect the revert with `Paused` selector + vm.expectRevert(IETHLockbox.ETHLockbox_Paused.selector); + + // Call the `unlockETH` function with the caller + vm.prank(_caller); + ethLockbox.unlockETH(_value); + } + + /// @notice Tests it reverts when the caller is not an authorized portal. + function testFuzz_unlockETH_unauthorizedPortal_reverts(address _caller, uint256 _value) public { + vm.assume(!ethLockbox.authorizedPortals(_caller)); + + // Expect the revert with `Unauthorized` selector + vm.expectRevert(IETHLockbox.ETHLockbox_Unauthorized.selector); + + // Call the `unlockETH` function with an unauthorized caller + vm.prank(_caller); + ethLockbox.unlockETH(_value); + } + + /// @notice Tests `unlockETH` reverts when the `_value` input is greater than the balance of the lockbox. + function testFuzz_unlockETH_insufficientBalance_reverts(uint256 _value) public { + _value = bound(_value, address(ethLockbox).balance + 1, type(uint256).max); + + // Expect the revert with `InsufficientBalance` selector + vm.expectRevert(IETHLockbox.ETHLockbox_InsufficientBalance.selector); + + // Call the `unlockETH` function with the portal + vm.prank(address(optimismPortal2)); + ethLockbox.unlockETH(_value); + } + + /// @notice Tests `unlockETH` reverts when the portal is not the L2 sender to prevent unlocking ETH from the lockbox + /// through a withdrawal transaction. + function testFuzz_unlockETH_withdrawalTransaction_reverts(uint256 _value, address _l2Sender) public { + _value = bound(_value, 0, address(ethLockbox).balance); + vm.assume(_l2Sender != Constants.DEFAULT_L2_SENDER); + + // Mock the L2 sender + vm.mockCall(address(optimismPortal2), abi.encodeCall(IOptimismPortal.l2Sender, ()), abi.encode(_l2Sender)); + + // Expect the revert with `NoWithdrawalTransactions` selector + vm.expectRevert(IETHLockbox.ETHLockbox_NoWithdrawalTransactions.selector); + + // Call the `unlockETH` function with the portal + vm.prank(address(optimismPortal2)); + ethLockbox.unlockETH(_value); + } + + /// @notice Tests the ETH is correctly unlocked when the caller is an authorized portal. + function testFuzz_unlockETH_succeeds(uint256 _value) public { + // Deal the ETH amount to the lockbox + vm.deal(address(ethLockbox), _value); + + // Get the balance of the portal and lockbox before the unlock to compare later on the assertions + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 lockboxBalanceBefore = address(ethLockbox).balance; + + // Expect `donateETH` function to be called on Portal + vm.expectCall(address(optimismPortal2), abi.encodeCall(IOptimismPortal.donateETH, ())); + + // Look for the emit of the `ETHUnlocked` event + vm.expectEmit(address(ethLockbox)); + emit ETHUnlocked(address(optimismPortal2), _value); + + // Call the `unlockETH` function with the portal + vm.prank(address(optimismPortal2)); + ethLockbox.unlockETH(_value); + + // Assert the portal's balance increased and the lockbox's balance decreased by the amount unlocked + assertEq(address(optimismPortal2).balance, portalBalanceBefore + _value); + assertEq(address(ethLockbox).balance, lockboxBalanceBefore - _value); + } + + /// @notice Tests the ETH is correctly unlocked when the caller is an authorized portal. + function testFuzz_unlockETH_multiplePortals_succeeds(IOptimismPortal2 _portal, uint256 _value) public { + assumeNotForgeAddress(address(_portal)); + + // Mock the admin owner of the portal to be the same as the current lockbox proxy admin owner + vm.mockCall( + address(_portal), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(proxyAdminOwner) + ); + + // Set the portal as an authorized portal if needed + if (!ethLockbox.authorizedPortals(address(_portal))) { + vm.prank(proxyAdminOwner); + ethLockbox.authorizePortal(_portal); + } + + // Deal the ETH amount to the lockbox + vm.deal(address(ethLockbox), _value); + + // Get the balance of the portal and lockbox before the unlock to compare later on the assertions + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 lockboxBalanceBefore = address(ethLockbox).balance; + + // Expect `donateETH` function to be called on Portal + vm.expectCall(address(optimismPortal2), abi.encodeCall(IOptimismPortal.donateETH, ())); + + // Look for the emit of the `ETHUnlocked` event + vm.expectEmit(address(ethLockbox)); + emit ETHUnlocked(address(optimismPortal2), _value); + + // Call the `unlockETH` function with the portal + vm.prank(address(optimismPortal2)); + ethLockbox.unlockETH(_value); + + // Assert the portal's balance increased and the lockbox's balance decreased by the amount unlocked + assertEq(address(optimismPortal2).balance, portalBalanceBefore + _value); + assertEq(address(ethLockbox).balance, lockboxBalanceBefore - _value); + } + + /// @notice Tests the `authorizePortal` function reverts when the caller is not the proxy admin. + function testFuzz_authorizePortal_unauthorized_reverts(address _caller) public { + vm.assume(_caller != proxyAdminOwner); + + // Expect the revert with `Unauthorized` selector + vm.expectRevert(IETHLockbox.ETHLockbox_Unauthorized.selector); + + // Call the `authorizePortal` function with an unauthorized caller + vm.prank(_caller); + ethLockbox.authorizePortal(optimismPortal2); + } + + /// @notice Tests the `authorizePortal` function reverts when the proxy admin owner of the portal is not the same as + /// the one of the lockbox. + function testFuzz_authorizePortal_differentProxyAdminOwner_reverts(IOptimismPortal2 _portal) public { + assumeNotForgeAddress(address(_portal)); + vm.mockCall(address(_portal), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(address(0))); + + // Expect the revert with `DifferentOwner` selector + vm.expectRevert(IETHLockbox.ETHLockbox_DifferentProxyAdminOwner.selector); + + // Call the `authorizePortal` function + vm.prank(proxyAdminOwner); + ethLockbox.authorizePortal(_portal); + } + + /// @notice Tests the `authorizeLockbox` function succeeds using the `optimismPortal2` address as the portal. + function test_authorizePortal_succeeds() public { + // Calculate the correct storage slot for the mapping value + bytes32 mappingSlot = bytes32(uint256(1)); // position on the layout + address key = address(optimismPortal2); + bytes32 slot = keccak256(abi.encode(key, mappingSlot)); + + // Reset the authorization status to false + vm.store(address(ethLockbox), slot, bytes32(0)); + + // Expect the `PortalAuthorized` event to be emitted + vm.expectEmit(address(ethLockbox)); + emit PortalAuthorized(address(optimismPortal2)); + + // Call the `authorizePortal` function with the portal + vm.prank(proxyAdminOwner); + ethLockbox.authorizePortal(optimismPortal2); + + // Assert the portal is authorized + assertTrue(ethLockbox.authorizedPortals(address(optimismPortal2))); + } + + /// @notice Tests the `authorizeLockbox` function succeeds + function testFuzz_authorizePortal_succeeds(IOptimismPortal2 _portal) public { + assumeNotForgeAddress(address(_portal)); + + // Mock the admin owner of the portal to be the same as the current lockbox proxy admin owner + vm.mockCall( + address(_portal), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(proxyAdminOwner) + ); + + // Expect the `PortalAuthorized` event to be emitted + vm.expectEmit(address(ethLockbox)); + emit PortalAuthorized(address(_portal)); + + // Call the `authorizePortal` function with the portal + vm.prank(proxyAdminOwner); + ethLockbox.authorizePortal(_portal); + + // Assert the portal is authorized + assertTrue(ethLockbox.authorizedPortals(address(_portal))); + } + + /// @notice Tests the `authorizeLockbox` function reverts when the caller is not the proxy admin. + function testFuzz_authorizeLockbox_unauthorized_reverts(address _caller) public { + vm.assume(_caller != proxyAdminOwner); + + // Expect the revert with `Unauthorized` selector + vm.expectRevert(IETHLockbox.ETHLockbox_Unauthorized.selector); + + // Call the `authorizeLockbox` function with an unauthorized caller + vm.prank(_caller); + ethLockbox.authorizeLockbox(ethLockbox); + } + + /// @notice Tests the `authorizeLockbox` function reverts when the proxy admin owner of the lockbox is not the same + /// as the proxy admin owner of + /// the proxy admin. + function testFuzz_authorizeLockbox_differentProxyAdminOwner_reverts(address _lockbox) public { + assumeNotForgeAddress(_lockbox); + + vm.mockCall(address(_lockbox), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(address(0))); + + // Expect the revert with `ETHLockbox_DifferentProxyAdminOwner` selector + vm.expectRevert(IETHLockbox.ETHLockbox_DifferentProxyAdminOwner.selector); + + // Call the `authorizeLockbox` function with the lockbox + vm.prank(proxyAdminOwner); + ethLockbox.authorizeLockbox(IETHLockbox(_lockbox)); + } + + /// @notice Tests the `authorizeLockbox` function succeeds + function testFuzz_authorizeLockbox_succeeds(address _lockbox) public { + assumeNotForgeAddress(_lockbox); + + // Mock the admin owner of the lockbox to be the same as the current lockbox proxy admin owner + vm.mockCall( + address(_lockbox), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(proxyAdminOwner) + ); + + // Expect the `LockboxAuthorized` event to be emitted + vm.expectEmit(address(ethLockbox)); + emit LockboxAuthorized(_lockbox); + + // Authorize the lockbox + vm.prank(proxyAdminOwner); + ethLockbox.authorizeLockbox(IETHLockbox(_lockbox)); + + // Assert the lockbox is authorized + assertTrue(ethLockbox.authorizedLockboxes(_lockbox)); + } + + /// @notice Tests the `migrateLiquidity` function reverts when the caller is not the proxy admin. + function testFuzz_migrateLiquidity_unauthorized_reverts(address _caller) public { + vm.assume(_caller != proxyAdminOwner); + + // Expect the revert with `Unauthorized` selector + vm.expectRevert(IETHLockbox.ETHLockbox_Unauthorized.selector); + + // Call the `migrateLiquidity` function with an unauthorized caller + vm.prank(_caller); + ethLockbox.migrateLiquidity(ethLockbox); + } + + /// @notice Tests the `migrateLiquidity` function reverts when the proxy admin owner of the lockbox is not the same + /// as the proxy admin owner of + /// the proxy admin. + function testFuzz_migrateLiquidity_differentProxyAdminOwner_reverts(address _lockbox) public { + assumeNotForgeAddress(_lockbox); + + vm.mockCall(address(_lockbox), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(address(0))); + + // Expect the revert with `ETHLockbox_DifferentProxyAdminOwner` selector + vm.expectRevert(IETHLockbox.ETHLockbox_DifferentProxyAdminOwner.selector); + + // Call the `migrateLiquidity` function with the lockbox + vm.prank(proxyAdminOwner); + ethLockbox.migrateLiquidity(IETHLockbox(_lockbox)); + } + + /// @notice Tests the `migrateLiquidity` function succeeds + function testFuzz_migrateLiquidity_succeeds(uint256 _balance, address _lockbox) public { + _balance = bound(_balance, 0, type(uint256).max - address(ethLockbox).balance); + + // Since on the fork the `_lockbox` fuzzed address doesn't exist, we skip the test + if (isForkTest()) vm.skip(true); + assumeNotForgeAddress(_lockbox); + vm.assume(address(_lockbox) != address(ethLockbox)); + + // Mock on the lockbox that will receive the migration for it to succeed + vm.mockCall( + address(_lockbox), abi.encodeCall(IProxyAdminOwnedBase.proxyAdminOwner, ()), abi.encode(proxyAdminOwner) + ); + vm.mockCall( + address(_lockbox), abi.encodeCall(IETHLockbox.authorizedLockboxes, (address(ethLockbox))), abi.encode(true) + ); + vm.mockCall(address(_lockbox), abi.encodeCall(IETHLockbox.receiveLiquidity, ()), abi.encode(true)); + + // Deal the balance to the lockbox + deal(address(_lockbox), _balance); + + // Get balances before the migration + uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; + uint256 newLockboxBalanceBefore = address(_lockbox).balance; + + // Expect the `LiquidityMigrated` event to be emitted + vm.expectEmit(address(ethLockbox)); + emit LiquidityMigrated(_lockbox, ethLockboxBalanceBefore); + + // Call the `migrateLiquidity` function with the lockbox + vm.prank(proxyAdminOwner); + ethLockbox.migrateLiquidity(IETHLockbox(_lockbox)); + + // Assert the liquidity was migrated + assertEq(address(ethLockbox).balance, 0); + assertEq(address(_lockbox).balance, newLockboxBalanceBefore + ethLockboxBalanceBefore); + } +} diff --git a/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol b/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol index 093c87c5e19..8776c0f7a83 100644 --- a/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol +++ b/packages/contracts-bedrock/test/L1/L1StandardBridge.t.sol @@ -163,7 +163,8 @@ contract L1StandardBridge_Initialize_TestFail is CommonTest { } contract L1StandardBridge_Receive_Test is CommonTest { /// @dev Tests receive bridges ETH successfully. function test_receive_succeeds() external { - uint256 balanceBefore = address(optimismPortal2).balance; + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; // The legacy event must be emitted for backwards compatibility vm.expectEmit(address(l1StandardBridge)); @@ -187,7 +188,8 @@ contract L1StandardBridge_Receive_Test is CommonTest { vm.prank(alice, alice); (bool success,) = address(l1StandardBridge).call{ value: 100 }(hex""); assertEq(success, true); - assertEq(address(optimismPortal2).balance, balanceBefore + 100); + assertEq(address(optimismPortal2).balance, portalBalanceBefore); + assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + 100); } } @@ -196,7 +198,7 @@ contract PreBridgeETH is CommonTest { /// on whether the bridge call is legacy or not. function _preBridgeETH(bool isLegacy, uint256 value) internal { if (!isForkTest()) { - assertEq(address(optimismPortal2).balance, 0); + assertEq(address(optimismPortal2).balance, 0, "OptimismPortal2 balance should be 0"); } uint256 nonce = l1CrossDomainMessenger.messageNonce(); uint256 version = 0; // Internal constant in the OptimismPortal: DEPOSIT_VERSION @@ -266,9 +268,11 @@ contract L1StandardBridge_DepositETH_Test is PreBridgeETH { /// ETH ends up in the optimismPortal. function test_depositETH_fromEOA_succeeds() external { _preBridgeETH({ isLegacy: true, value: 500 }); - uint256 balanceBefore = address(optimismPortal2).balance; + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; l1StandardBridge.depositETH{ value: 500 }(50000, hex"dead"); - assertEq(address(optimismPortal2).balance, balanceBefore + 500); + assertEq(address(optimismPortal2).balance, portalBalanceBefore); + assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + 500); } /// @dev Tests that depositing ETH succeeds for an EOA using 7702 delegation. @@ -277,9 +281,11 @@ contract L1StandardBridge_DepositETH_Test is PreBridgeETH { vm.etch(alice, abi.encodePacked(hex"EF0100", address(0))); _preBridgeETH({ isLegacy: true, value: 500 }); - uint256 balanceBefore = address(optimismPortal2).balance; + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; l1StandardBridge.depositETH{ value: 500 }(50000, hex"dead"); - assertEq(address(optimismPortal2).balance, balanceBefore + 500); + assertEq(address(optimismPortal2).balance, portalBalanceBefore); + assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + 500); } } @@ -301,9 +307,11 @@ contract L1StandardBridge_BridgeETH_Test is PreBridgeETH { /// ETH ends up in the optimismPortal. function test_bridgeETH_succeeds() external { _preBridgeETH({ isLegacy: false, value: 500 }); - uint256 balanceBefore = address(optimismPortal2).balance; + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; l1StandardBridge.bridgeETH{ value: 500 }(50000, hex"dead"); - assertEq(address(optimismPortal2).balance, balanceBefore + 500); + assertEq(address(optimismPortal2).balance, portalBalanceBefore); + assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + 500); } } @@ -381,9 +389,11 @@ contract L1StandardBridge_DepositETHTo_Test is PreBridgeETHTo { /// ETH ends up in the optimismPortal. function test_depositETHTo_succeeds() external { _preBridgeETHTo({ isLegacy: true, value: 600 }); - uint256 balanceBefore = address(optimismPortal2).balance; + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; l1StandardBridge.depositETHTo{ value: 600 }(bob, 60000, hex"dead"); - assertEq(address(optimismPortal2).balance, balanceBefore + 600); + assertEq(address(optimismPortal2).balance, portalBalanceBefore); + assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + 600); } } @@ -395,9 +405,11 @@ contract L1StandardBridge_BridgeETHTo_Test is PreBridgeETHTo { /// ETH ends up in the optimismPortal. function test_bridgeETHTo_succeeds() external { _preBridgeETHTo({ isLegacy: false, value: 600 }); - uint256 balanceBefore = address(optimismPortal2).balance; + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 ethLockboxBalanceBefore = address(ethLockbox).balance; l1StandardBridge.bridgeETHTo{ value: 600 }(bob, 60000, hex"dead"); - assertEq(address(optimismPortal2).balance, balanceBefore + 600); + assertEq(address(optimismPortal2).balance, portalBalanceBefore); + assertEq(address(ethLockbox).balance, ethLockboxBalanceBefore + 600); } } diff --git a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol index 6f742180c67..80880840d8b 100644 --- a/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol +++ b/packages/contracts-bedrock/test/L1/OPContractsManager.t.sol @@ -27,6 +27,7 @@ import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMin import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; import { IMIPS } from "interfaces/cannon/IMIPS.sol"; import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IProxy } from "interfaces/universal/IProxy.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; @@ -39,12 +40,15 @@ import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IOPContractsManager, + IOPCMImplementationsWithoutLockbox, IOPContractsManagerGameTypeAdder, IOPContractsManagerDeployer, IOPContractsManagerUpgrader, IOPContractsManagerContractsContainer } from "interfaces/L1/IOPContractsManager.sol"; +import { IOPContractsManagerLegacyUpgrade } from "interfaces/L1/IOPContractsManagerLegacyUpgrade.sol"; import { ISemver } from "interfaces/universal/ISemver.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; // Contracts import { @@ -144,6 +148,7 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase { startingAnchorRoot: _doi.startingAnchorRoot(), saltMixer: _doi.saltMixer(), gasLimit: _doi.gasLimit(), + disputeGameUsesSuperRoots: _doi.disputeGameUsesSuperRoots(), disputeGameType: _doi.disputeGameType(), disputeAbsolutePrestate: _doi.disputeAbsolutePrestate(), disputeMaxGameDepth: _doi.disputeMaxGameDepth(), @@ -268,7 +273,8 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { IOPContractsManager.OpChainConfig({ systemConfigProxy: systemConfig, proxyAdmin: proxyAdmin, - absolutePrestate: absolutePrestate + absolutePrestate: absolutePrestate, + disputeGameUsesSuperRoots: false }) ); @@ -283,6 +289,29 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { faultDisputeGame = IFaultDisputeGame(address(artifacts.mustGetAddress("FaultDisputeGame"))); } + /// @notice Converts the new OpChainConfig struct to the legacy OpChainConfig struct format. + /// This function is used to test Upgrade 13 and 14 paths and can be safely removed + /// after those upgrades are completed. Only difference in the new struct is the added + /// disputeGameUsesSuperRoots boolean. + /// @param _opChainConfigs The new OpChainConfig structs to convert. + /// @return The legacy OpChainConfig structs. + function convertToLegacyConfigs(IOPContractsManager.OpChainConfig[] memory _opChainConfigs) + public + pure + returns (IOPContractsManagerLegacyUpgrade.OpChainConfig[] memory) + { + IOPContractsManagerLegacyUpgrade.OpChainConfig[] memory legacyConfigs = + new IOPContractsManagerLegacyUpgrade.OpChainConfig[](_opChainConfigs.length); + for (uint256 i = 0; i < _opChainConfigs.length; i++) { + legacyConfigs[i] = IOPContractsManagerLegacyUpgrade.OpChainConfig({ + systemConfigProxy: _opChainConfigs[i].systemConfigProxy, + proxyAdmin: _opChainConfigs[i].proxyAdmin, + absolutePrestate: _opChainConfigs[i].absolutePrestate + }); + } + return legacyConfigs; + } + function expectEmitUpgraded(address impl, address proxy) public { vm.expectEmit(proxy); emit Upgraded(impl); @@ -291,7 +320,8 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { function runV200UpgradeAndChecks(address _delegateCaller) public { // The address below corresponds with the address of the v2.0.0-rc.1 OPCM on mainnet. IOPContractsManager deployedOPCM = IOPContractsManager(address(0x026b2F158255Beac46c1E7c6b8BbF29A4b6A7B76)); - IOPContractsManager.Implementations memory impls = deployedOPCM.implementations(); + IOPCMImplementationsWithoutLockbox.Implementations memory impls = + IOPCMImplementationsWithoutLockbox(address(deployedOPCM)).implementations(); // Cache the old L1xDM address so we can look for it in the AddressManager's event address oldL1CrossDomainMessenger = addressManager.getAddress("OVM_L1CrossDomainMessenger"); @@ -349,7 +379,8 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { vm.etch(_delegateCaller, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); DelegateCaller(_delegateCaller).dcForward( - address(deployedOPCM), abi.encodeCall(IOPContractsManager.upgrade, (opChainConfigs)) + address(deployedOPCM), + abi.encodeCall(IOPContractsManagerLegacyUpgrade.upgrade, (convertToLegacyConfigs(opChainConfigs))) ); VmSafe.Gas memory gas = vm.lastCallGas(); @@ -396,7 +427,10 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { } function runUpgrade14UpgradeAndChecks(address _delegateCaller) public { - IOPContractsManager.Implementations memory impls = opcm.implementations(); + // TODO(#14665): Replace this address with once the final OPCM is deployed for Upgrade 14. + IOPContractsManager deployedOPCM = IOPContractsManager(address(0x3A1f523a4bc09cd344A2745a108Bb0398288094F)); + IOPCMImplementationsWithoutLockbox.Implementations memory impls = + IOPCMImplementationsWithoutLockbox(address(deployedOPCM)).implementations(); // sanity check IPermissionedDisputeGame oldPDG = @@ -427,7 +461,8 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { vm.etch(_delegateCaller, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); DelegateCaller(_delegateCaller).dcForward( - address(opcm), abi.encodeCall(IOPContractsManager.upgrade, (opChainConfigs)) + address(deployedOPCM), + abi.encodeCall(IOPContractsManagerLegacyUpgrade.upgrade, (convertToLegacyConfigs(opChainConfigs))) ); VmSafe.Gas memory gas = vm.lastCallGas(); @@ -458,9 +493,126 @@ contract OPContractsManager_Upgrade_Harness is CommonTest { } } + function runUpgrade15UpgradeAndChecks(address _delegateCaller) public { + IOPContractsManager.Implementations memory impls = opcm.implementations(); + + // Predict the address of the new AnchorStateRegistry proxy. + // Subcontext to avoid stack too deep. + address newAsrProxy; + { + // Compute the salt using the system config address. + bytes32 salt = keccak256( + abi.encode( + l2ChainId, + string.concat(string(bytes.concat(bytes32(uint256(uint160(address(systemConfig))))))), + "AnchorStateRegistry-SOT" + ) + ); + + // Use the actual proxy instead of the local code so we can reuse this test. + address proxyBp = opcm.blueprints().proxy; + Blueprint.Preamble memory preamble = Blueprint.parseBlueprintPreamble(proxyBp.code); + bytes memory initCode = bytes.concat(preamble.initcode, abi.encode(proxyAdmin)); + newAsrProxy = vm.computeCreate2Address(salt, keccak256(initCode), _delegateCaller); + vm.label(newAsrProxy, "NewAnchorStateRegistryProxy"); + } + + // Grab the PermissionedDisputeGame and FaultDisputeGame implementations before upgrade. + address oldPDGImpl = address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)); + address oldFDGImpl = address(disputeGameFactory.gameImpls(GameTypes.CANNON)); + IPermissionedDisputeGame oldPDG = IPermissionedDisputeGame(oldPDGImpl); + IFaultDisputeGame oldFDG = IFaultDisputeGame(oldFDGImpl); + + // Expect the SystemConfig and OptimismPortal to be upgraded. + expectEmitUpgraded(impls.systemConfigImpl, address(systemConfig)); + expectEmitUpgraded(impls.optimismPortalImpl, address(optimismPortal2)); + + // We always expect the PermissionedDisputeGame to be deployed. We don't yet know the + // address of the new permissionedGame which will be deployed by the + // OPContractsManager.upgrade() call, so ignore the first topic. + vm.expectEmit(false, true, true, true, address(disputeGameFactory)); + emit ImplementationSet(address(0), GameTypes.PERMISSIONED_CANNON); + + // If the old FaultDisputeGame exists, we expect it to be upgraded. + if (address(oldFDG) != address(0)) { + // Ignore the first topic for the same reason as the previous comment. + vm.expectEmit(false, true, true, true, address(disputeGameFactory)); + emit ImplementationSet(address(0), GameTypes.CANNON); + } + + vm.expectEmit(address(_delegateCaller)); + emit Upgraded(l2ChainId, systemConfig, address(_delegateCaller)); + + // Temporarily replace the upgrader with a DelegateCaller so we can test the upgrade, + // then reset its code to the original code. + bytes memory delegateCallerCode = address(_delegateCaller).code; + vm.etch(_delegateCaller, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); + + // Execute the upgrade. + // We use the new format here, not the legacy one. + DelegateCaller(_delegateCaller).dcForward( + address(opcm), abi.encodeCall(IOPContractsManager.upgrade, (opChainConfigs)) + ); + + // Less than 90% of the gas target of 20M to account for the gas used by using Safe. + VmSafe.Gas memory gas = vm.lastCallGas(); + assertLt(gas.gasTotalUsed, 0.9 * 20_000_000, "Upgrade exceeds gas target of 15M"); + + // Reset the upgrader's code to the original code. + vm.etch(_delegateCaller, delegateCallerCode); + + // Grab the new implementations. + address newPDGImpl = address(disputeGameFactory.gameImpls(GameTypes.PERMISSIONED_CANNON)); + IPermissionedDisputeGame pdg = IPermissionedDisputeGame(newPDGImpl); + address newFDGImpl = address(disputeGameFactory.gameImpls(GameTypes.CANNON)); + IFaultDisputeGame fdg = IFaultDisputeGame(newFDGImpl); + + // Check that the PermissionedDisputeGame is upgraded to the expected version, references + // the correct anchor state and has the mipsImpl. Although Upgrade 15 doesn't actually + // change any of this, we might as well check it again. + assertEq(ISemver(address(pdg)).version(), "1.4.1"); + assertEq(address(pdg.vm()), impls.mipsImpl); + assertEq(pdg.l2ChainId(), oldPDG.l2ChainId()); + + // If the old FaultDisputeGame exists, we expect it to be upgraded. Check same as above. + if (address(oldFDG) != address(0)) { + assertEq(ISemver(address(fdg)).version(), "1.4.1"); + assertEq(address(fdg.vm()), impls.mipsImpl); + assertEq(fdg.l2ChainId(), oldFDG.l2ChainId()); + } + + // Make sure that the SystemConfig is upgraded to the right version. It must also have the + // right l2ChainId and must be properly initialized. + assertEq(ISemver(address(systemConfig)).version(), "2.6.0"); + assertEq(impls.systemConfigImpl, EIP1967Helper.getImplementation(address(systemConfig))); + assertEq(systemConfig.l2ChainId(), l2ChainId); + DeployUtils.assertInitialized({ _contractAddress: address(systemConfig), _isProxy: true, _slot: 0, _offset: 0 }); + + // Make sure that the OptimismPortal is upgraded to the right version. It must also have a + // reference to the new AnchorStateRegistry. + assertEq(ISemver(address(optimismPortal2)).version(), "4.0.0"); + assertEq(impls.optimismPortalImpl, EIP1967Helper.getImplementation(address(optimismPortal2))); + assertEq(address(optimismPortal2.anchorStateRegistry()), address(newAsrProxy)); + DeployUtils.assertInitialized({ + _contractAddress: address(optimismPortal2), + _isProxy: true, + _slot: 0, + _offset: 0 + }); + + // Make sure the new AnchorStateRegistry has the right version and is initialized. + assertEq(ISemver(address(newAsrProxy)).version(), "3.0.0"); + vm.prank(address(proxyAdmin)); + assertEq(IProxy(payable(newAsrProxy)).admin(), address(proxyAdmin)); + DeployUtils.assertInitialized({ _contractAddress: address(newAsrProxy), _isProxy: true, _slot: 0, _offset: 0 }); + } + function runUpgradeTestAndChecks(address _delegateCaller) public { + // TODO(#14691): Remove this function once Upgrade 15 is deployed on Mainnet. runV200UpgradeAndChecks(_delegateCaller); + // TODO(#14691): Remove this function once Upgrade 15 is deployed on Mainnet. runUpgrade14UpgradeAndChecks(_delegateCaller); + runUpgrade15UpgradeAndChecks(_delegateCaller); } } @@ -646,7 +798,11 @@ contract OPContractsManager_AddGameType_Test is Test { }), optimismPortalImpl: DeployUtils.create1({ _name: "OptimismPortal2", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IOptimismPortal2.__constructor__, (1, 1))) + _args: DeployUtils.encodeConstructor(abi.encodeCall(IOptimismPortal2.__constructor__, (1))) + }), + ethLockboxImpl: DeployUtils.create1({ + _name: "ETHLockbox", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IETHLockbox.__constructor__, ())) }), systemConfigImpl: DeployUtils.create1({ _name: "SystemConfig", @@ -670,7 +826,7 @@ contract OPContractsManager_AddGameType_Test is Test { }), anchorStateRegistryImpl: DeployUtils.create1({ _name: "AnchorStateRegistry", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IAnchorStateRegistry.__constructor__, ())) + _args: DeployUtils.encodeConstructor(abi.encodeCall(IAnchorStateRegistry.__constructor__, (1))) }), delayedWETHImpl: DeployUtils.create1({ _name: "DelayedWETH", @@ -773,6 +929,7 @@ contract OPContractsManager_AddGameType_Test is Test { l2ChainId: 100, saltMixer: "hello", gasLimit: 30_000_000, + disputeGameUsesSuperRoots: false, disputeGameType: GameType.wrap(1), disputeAbsolutePrestate: Claim.wrap( bytes32(hex"038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c") @@ -1009,7 +1166,11 @@ contract OPContractsManager_UpdatePrestate_Test is Test { }), optimismPortalImpl: DeployUtils.create1({ _name: "OptimismPortal2", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IOptimismPortal2.__constructor__, (1, 1))) + _args: DeployUtils.encodeConstructor(abi.encodeCall(IOptimismPortal2.__constructor__, (1))) + }), + ethLockboxImpl: DeployUtils.create1({ + _name: "ETHLockbox", + _args: DeployUtils.encodeConstructor(abi.encodeCall(IETHLockbox.__constructor__, ())) }), systemConfigImpl: DeployUtils.create1({ _name: "SystemConfig", @@ -1033,7 +1194,7 @@ contract OPContractsManager_UpdatePrestate_Test is Test { }), anchorStateRegistryImpl: DeployUtils.create1({ _name: "AnchorStateRegistry", - _args: DeployUtils.encodeConstructor(abi.encodeCall(IAnchorStateRegistry.__constructor__, ())) + _args: DeployUtils.encodeConstructor(abi.encodeCall(IAnchorStateRegistry.__constructor__, (1))) }), delayedWETHImpl: DeployUtils.create1({ _name: "DelayedWETH", @@ -1125,6 +1286,7 @@ contract OPContractsManager_UpdatePrestate_Test is Test { l2ChainId: 100, saltMixer: "hello", gasLimit: 30_000_000, + disputeGameUsesSuperRoots: false, disputeGameType: GameType.wrap(1), disputeAbsolutePrestate: Claim.wrap( bytes32(hex"038512e02c4c3f7bdaec27d00edf55b7155e0905301e1a88083e4e0a6764d54c") @@ -1146,7 +1308,10 @@ contract OPContractsManager_UpdatePrestate_Test is Test { function test_updatePrestate_pdgOnlyWithValidInput_succeeds() public { IOPContractsManager.OpChainConfig[] memory inputs = new IOPContractsManager.OpChainConfig[](1); inputs[0] = IOPContractsManager.OpChainConfig( - chainDeployOutput.systemConfigProxy, chainDeployOutput.opChainProxyAdmin, Claim.wrap(bytes32(hex"ABBA")) + chainDeployOutput.systemConfigProxy, + chainDeployOutput.opChainProxyAdmin, + Claim.wrap(bytes32(hex"ABBA")), + false ); address proxyAdminOwner = chainDeployOutput.opChainProxyAdmin.owner(); @@ -1177,7 +1342,10 @@ contract OPContractsManager_UpdatePrestate_Test is Test { IOPContractsManager.OpChainConfig[] memory inputs = new IOPContractsManager.OpChainConfig[](1); inputs[0] = IOPContractsManager.OpChainConfig( - chainDeployOutput.systemConfigProxy, chainDeployOutput.opChainProxyAdmin, Claim.wrap(bytes32(hex"ABBA")) + chainDeployOutput.systemConfigProxy, + chainDeployOutput.opChainProxyAdmin, + Claim.wrap(bytes32(hex"ABBA")), + false ); address proxyAdminOwner = chainDeployOutput.opChainProxyAdmin.owner(); @@ -1214,7 +1382,8 @@ contract OPContractsManager_UpdatePrestate_Test is Test { inputs[0] = IOPContractsManager.OpChainConfig({ systemConfigProxy: chainDeployOutput.systemConfigProxy, proxyAdmin: chainDeployOutput.opChainProxyAdmin, - absolutePrestate: Claim.wrap(bytes32(0)) + absolutePrestate: Claim.wrap(bytes32(0)), + disputeGameUsesSuperRoots: false }); address proxyAdminOwner = chainDeployOutput.opChainProxyAdmin.owner(); diff --git a/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol index 6b6f60bf778..c0c05e6f327 100644 --- a/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol +++ b/packages/contracts-bedrock/test/L1/OptimismPortal2.t.sol @@ -1,14 +1,21 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -// Testing +// Forge import { VmSafe } from "forge-std/Vm.sol"; +import { console2 as console } from "forge-std/console2.sol"; + +// Testing import { CommonTest } from "test/setup/CommonTest.sol"; import { NextImpl } from "test/mocks/NextImpl.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; +// Scripts +import { ForgeArtifacts, StorageSlot } from "scripts/libraries/ForgeArtifacts.sol"; + // Contracts import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; +import { Predeploys } from "src/libraries/Predeploys.sol"; // Libraries import { Types } from "src/libraries/Types.sol"; @@ -17,14 +24,15 @@ import { Constants } from "src/libraries/Constants.sol"; import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; import "src/dispute/lib/Types.sol"; -import "src/libraries/PortalErrors.sol"; // Interfaces import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IProxy } from "interfaces/universal/IProxy.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; contract OptimismPortal2_Test is CommonTest { address depositor; @@ -44,23 +52,26 @@ contract OptimismPortal2_Test is CommonTest { /// @notice Marked virtual to be overridden in /// test/kontrol/deployment/DeploymentSummary.t.sol function test_constructor_succeeds() external virtual { - IOptimismPortal2 opImpl = IOptimismPortal2(payable(EIP1967Helper.getImplementation(address(optimismPortal2)))); - assertEq(address(opImpl.disputeGameFactory()), address(0)); + IOptimismPortal opImpl = IOptimismPortal(payable(EIP1967Helper.getImplementation(address(optimismPortal2)))); + assertEq(address(opImpl.anchorStateRegistry()), address(0)); assertEq(address(opImpl.systemConfig()), address(0)); assertEq(address(opImpl.superchainConfig()), address(0)); - assertEq(opImpl.respectedGameType().raw(), deploy.cfg().respectedGameType()); assertEq(opImpl.l2Sender(), address(0)); + assertEq(address(opImpl.anchorStateRegistry()), address(0)); + assertEq(address(opImpl.ethLockbox()), address(0)); } /// @dev Tests that the initializer sets the correct values. /// @notice Marked virtual to be overridden in /// test/kontrol/deployment/DeploymentSummary.t.sol function test_initialize_succeeds() external virtual { + assertEq(address(optimismPortal2.anchorStateRegistry()), address(anchorStateRegistry)); assertEq(address(optimismPortal2.disputeGameFactory()), address(disputeGameFactory)); assertEq(address(optimismPortal2.superchainConfig()), address(superchainConfig)); assertEq(optimismPortal2.l2Sender(), Constants.DEFAULT_L2_SENDER); assertEq(optimismPortal2.paused(), false); assertEq(address(optimismPortal2.systemConfig()), address(systemConfig)); + assertEq(address(optimismPortal2.ethLockbox()), address(ethLockbox)); returnIfForkTest( "OptimismPortal2_Initialize_Test: Do not check guardian and respectedGameType on forked networks" @@ -73,6 +84,32 @@ contract OptimismPortal2_Test is CommonTest { assertEq(optimismPortal2.respectedGameType().raw(), deploy.cfg().respectedGameType()); } + /// @dev Tests that the upgrade function succeeds. + function testFuzz_upgrade_succeeds(address _newAnchorStateRegistry, uint256 _balance) external { + // Prevent overflow on an upgrade context + _balance = bound(_balance, 0, type(uint256).max - address(ethLockbox).balance); + + // Set the initialize state of the portal to false. + vm.store(address(optimismPortal2), bytes32(uint256(0)), bytes32(uint256(0))); + + // Set the balance of the portal and get the lockbox balance before the upgrade. + deal(address(optimismPortal2), _balance); + uint256 lockboxBalanceBefore = address(ethLockbox).balance; + + // Expect the ETH to be migrated to the lockbox. + vm.expectCall(address(ethLockbox), _balance, abi.encodeCall(ethLockbox.lockETH, ())); + + // Call the upgrade function. + vm.prank(Predeploys.PROXY_ADMIN); + optimismPortal2.upgrade(IAnchorStateRegistry(_newAnchorStateRegistry), IETHLockbox(ethLockbox), true); + + // Assert the portal is properly upgraded. + assertEq(address(optimismPortal2.ethLockbox()), address(ethLockbox)); + assertEq(address(optimismPortal2.anchorStateRegistry()), _newAnchorStateRegistry); + assertEq(address(optimismPortal2).balance, 0); + assertEq(address(ethLockbox).balance, lockboxBalanceBefore + _balance); + } + /// @dev Tests that `pause` successfully pauses /// when called by the GUARDIAN. function test_pause_succeeds() external { @@ -136,7 +173,10 @@ contract OptimismPortal2_Test is CommonTest { /// @dev Tests that `receive` successdully deposits ETH. function testFuzz_receive_succeeds(uint256 _value) external { + // Prevent overflow on an upgrade context + _value = bound(_value, 0, type(uint256).max - address(ethLockbox).balance); uint256 balanceBefore = address(optimismPortal2).balance; + uint256 lockboxBalanceBefore = address(ethLockbox).balance; _value = bound(_value, 0, type(uint256).max - balanceBefore); vm.expectEmit(address(optimismPortal2)); @@ -150,20 +190,24 @@ contract OptimismPortal2_Test is CommonTest { _data: hex"" }); + // Expect call to the ETHLockbox to lock the funds only if the value is greater than 0. + vm.expectCall(address(ethLockbox), _value, abi.encodeCall(ethLockbox.lockETH, ()), _value > 0 ? 1 : 0); + // give alice money and send as an eoa vm.deal(alice, _value); vm.prank(alice, alice); (bool s,) = address(optimismPortal2).call{ value: _value }(hex""); assertTrue(s); - assertEq(address(optimismPortal2).balance, balanceBefore + _value); + assertEq(address(optimismPortal2).balance, balanceBefore); + assertEq(address(ethLockbox).balance, lockboxBalanceBefore + _value); } /// @dev Tests that `depositTransaction` reverts when the destination address is non-zero /// for a contract creation deposit. function test_depositTransaction_contractCreation_reverts() external { // contract creation must have a target of address(0) - vm.expectRevert(BadTarget.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_BadTarget.selector); optimismPortal2.depositTransaction(address(1), 1, 0, true, hex""); } @@ -172,7 +216,7 @@ contract OptimismPortal2_Test is CommonTest { function test_depositTransaction_largeData_reverts() external { uint256 size = 120_001; uint64 gasLimit = optimismPortal2.minimumGasLimit(uint64(size)); - vm.expectRevert(LargeCalldata.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_CalldataTooLarge.selector); optimismPortal2.depositTransaction({ _to: address(0), _value: 0, @@ -184,7 +228,7 @@ contract OptimismPortal2_Test is CommonTest { /// @dev Tests that `depositTransaction` reverts when the gas limit is too small. function test_depositTransaction_smallGasLimit_reverts() external { - vm.expectRevert(SmallGasLimit.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_GasLimitTooLow.selector); optimismPortal2.depositTransaction({ _to: address(1), _value: 0, _gasLimit: 0, _isCreation: false, _data: hex"" }); } @@ -194,7 +238,7 @@ contract OptimismPortal2_Test is CommonTest { uint64 gasLimit = optimismPortal2.minimumGasLimit(uint64(_data.length)); if (_shouldFail) { gasLimit = uint64(bound(gasLimit, 0, gasLimit - 1)); - vm.expectRevert(SmallGasLimit.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_GasLimitTooLow.selector); } optimismPortal2.depositTransaction({ @@ -226,6 +270,8 @@ contract OptimismPortal2_Test is CommonTest { ) external { + // Prevent overflow on an upgrade context + _mint = bound(_mint, 0, type(uint256).max - address(ethLockbox).balance); _gasLimit = uint64( bound( _gasLimit, @@ -236,6 +282,7 @@ contract OptimismPortal2_Test is CommonTest { if (_isCreation) _to = address(0); uint256 balanceBefore = address(optimismPortal2).balance; + uint256 lockboxBalanceBefore = address(ethLockbox).balance; _mint = bound(_mint, 0, type(uint256).max - balanceBefore); // EOA emulation @@ -250,6 +297,9 @@ contract OptimismPortal2_Test is CommonTest { _data: _data }); + // Expect call to the ETHLockbox to lock the funds only if the value is greater than 0. + vm.expectCall(address(ethLockbox), _mint, abi.encodeCall(ethLockbox.lockETH, ()), _mint > 0 ? 1 : 0); + vm.deal(depositor, _mint); vm.prank(depositor, depositor); optimismPortal2.depositTransaction{ value: _mint }({ @@ -259,7 +309,9 @@ contract OptimismPortal2_Test is CommonTest { _isCreation: _isCreation, _data: _data }); - assertEq(address(optimismPortal2).balance, balanceBefore + _mint); + + assertEq(address(optimismPortal2).balance, balanceBefore); + assertEq(address(ethLockbox).balance, lockboxBalanceBefore + _mint); } /// @dev Tests that `depositTransaction` succeeds for an EOA using 7702 delegation. @@ -274,6 +326,10 @@ contract OptimismPortal2_Test is CommonTest { ) external { + assumeNotForgeAddress(_7702Target); + // Prevent overflow on an upgrade context + _mint = bound(_mint, 0, type(uint256).max - address(ethLockbox).balance); + _gasLimit = uint64( bound( _gasLimit, @@ -283,8 +339,9 @@ contract OptimismPortal2_Test is CommonTest { ); if (_isCreation) _to = address(0); - uint256 balanceBefore = address(optimismPortal2).balance; - _mint = bound(_mint, 0, type(uint256).max - balanceBefore); + uint256 portalBalanceBefore = address(optimismPortal2).balance; + uint256 lockboxBalanceBefore = address(ethLockbox).balance; + _mint = bound(_mint, 0, type(uint256).max - portalBalanceBefore); // EOA emulation vm.expectEmit(address(optimismPortal2)); @@ -310,7 +367,8 @@ contract OptimismPortal2_Test is CommonTest { _isCreation: _isCreation, _data: _data }); - assertEq(address(optimismPortal2).balance, balanceBefore + _mint); + assertEq(address(optimismPortal2).balance, portalBalanceBefore); + assertEq(address(ethLockbox).balance, lockboxBalanceBefore + _mint); } /// @dev Tests that `depositTransaction` succeeds for a contract. @@ -324,6 +382,8 @@ contract OptimismPortal2_Test is CommonTest { ) external { + // Prevent overflow on an upgrade context + _mint = bound(_mint, 0, type(uint256).max - address(ethLockbox).balance); _gasLimit = uint64( bound( _gasLimit, @@ -334,6 +394,7 @@ contract OptimismPortal2_Test is CommonTest { if (_isCreation) _to = address(0); uint256 balanceBefore = address(optimismPortal2).balance; + uint256 lockboxBalanceBefore = address(ethLockbox).balance; _mint = bound(_mint, 0, type(uint256).max - balanceBefore); vm.expectEmit(address(optimismPortal2)); @@ -347,6 +408,9 @@ contract OptimismPortal2_Test is CommonTest { _data: _data }); + // Expect call to the ETHLockbox to lock the funds only if the value is greater than 0. + vm.expectCall(address(ethLockbox), _mint, abi.encodeCall(ethLockbox.lockETH, ()), _mint > 0 ? 1 : 0); + vm.deal(address(this), _mint); vm.prank(address(this)); optimismPortal2.depositTransaction{ value: _mint }({ @@ -356,7 +420,8 @@ contract OptimismPortal2_Test is CommonTest { _isCreation: _isCreation, _data: _data }); - assertEq(address(optimismPortal2).balance, balanceBefore + _mint); + assertEq(address(optimismPortal2).balance, balanceBefore); + assertEq(address(ethLockbox).balance, lockboxBalanceBefore + _mint); } /// @dev Tests that the donateETH function donates ETH and does no state read/write @@ -365,6 +430,7 @@ contract OptimismPortal2_Test is CommonTest { vm.deal(alice, _amount); uint256 preBalance = address(optimismPortal2).balance; + uint256 lockboxBalanceBefore = address(ethLockbox).balance; _amount = bound(_amount, 0, type(uint256).max - preBalance); vm.startStateDiffRecording(); @@ -373,6 +439,8 @@ contract OptimismPortal2_Test is CommonTest { // not necessary since it's checked below assertEq(address(optimismPortal2).balance, preBalance + _amount); + // check that the ETHLockbox balance is unchanged + assertEq(address(ethLockbox).balance, lockboxBalanceBefore); // 0 for extcodesize of proxy before being called by this test, // 1 for the call to the proxy by the pranked address @@ -400,12 +468,34 @@ contract OptimismPortal2_Test is CommonTest { // storage accesses of delegate call of proxy to impl is empty (No storage read or write!) assertEq(accountAccesses[2].storageAccesses.length, 0); } + + /// @dev Tests that `updateLockbox` reverts if the caller is not the proxy admin owner. + function testFuzz_updateLockbox_notProxyAdminOwner_reverts(address _caller) external { + vm.assume(_caller != optimismPortal2.proxyAdminOwner()); + vm.expectRevert(IOptimismPortal.OptimismPortal_Unauthorized.selector); + + vm.prank(_caller); + optimismPortal2.updateLockbox(IETHLockbox(address(1))); + } + + /// @dev Tests that `updateLockbox` updates the ETHLockbox contract. + function testFuzz_updateLockbox_succeeds(address _newLockbox) external { + address oldLockbox = address(optimismPortal2.ethLockbox()); + vm.assume(_newLockbox != oldLockbox); + + vm.expectEmit(address(optimismPortal2)); + emit LockboxUpdated(oldLockbox, _newLockbox); + + vm.prank(optimismPortal2.proxyAdminOwner()); + optimismPortal2.updateLockbox(IETHLockbox(_newLockbox)); + + assertEq(address(optimismPortal2.ethLockbox()), _newLockbox); + } } contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Reusable default values for a test withdrawal Types.WithdrawalTransaction _defaultTx; - IFaultDisputeGame game; uint256 _proposedGameIndex; uint256 _proposedBlockNumber; @@ -428,6 +518,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { gasLimit: 100_000, data: hex"aa" // includes calldata for ERC20 withdrawal test }); + // Get withdrawal proof data we can use for testing. (_stateRoot, _storageRoot, _outputRoot, _withdrawalHash, _withdrawalProof) = ffi.getProveWithdrawalTransactionInputs(_defaultTx); @@ -455,12 +546,13 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { address(disputeGameFactory), keccak256(abi.encode(GameType.wrap(0), uint256(102))), bytes32(uint256(0)) ); } else { - // Warp forward in time to ensure that the game is created after the retirement timestamp. - vm.warp(optimismPortal2.respectedGameTypeUpdatedAt() + 1 seconds); - // Set up the dummy game. _proposedBlockNumber = 0xFF; } + + // Warp forward in time to ensure that the game is created after the retirement timestamp. + vm.warp(anchorStateRegistry.retirementTimestamp() + 1); + GameType respectedGameType = optimismPortal2.respectedGameType(); game = IFaultDisputeGame( payable( @@ -479,12 +571,12 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { vm.warp(block.timestamp + game.maxClockDuration().raw() + 1 seconds); // Fund the portal so that we can withdraw ETH. - vm.deal(address(optimismPortal2), 0xFFFFFFFF); + vm.deal(address(ethLockbox), 0xFFFFFFFF); } /// @dev Asserts that the reentrant call will revert. function callPortalAndExpectRevert() external payable { - vm.expectRevert(NonReentrant.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_NoReentrancy.selector); // Arguments here don't matter, as the require check is the first thing that happens. // We assume that this has already been proven. optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); @@ -492,62 +584,33 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { assertFalse(optimismPortal2.finalizedWithdrawals(Hashing.hashWithdrawal(_defaultTx))); } - /// @dev Tests that `blacklistDisputeGame` reverts when called by a non-guardian. - function testFuzz_blacklist_onlyGuardian_reverts(address _act) external { - vm.assume(_act != address(optimismPortal2.guardian())); - - vm.expectRevert(Unauthorized.selector); - optimismPortal2.blacklistDisputeGame(IDisputeGame(address(0xdead))); - } - - /// @dev Tests that the guardian role can blacklist any dispute game. - function testFuzz_blacklist_guardian_succeeds(IDisputeGame _addr) external { - vm.expectEmit(address(optimismPortal2)); - emit DisputeGameBlacklisted(_addr); - - vm.prank(optimismPortal2.guardian()); - optimismPortal2.blacklistDisputeGame(_addr); + /// @dev Tests that `finalizeWithdrawalTransaction` reverts when the target is the portal contract or the lockbox. + function test_finalizeWithdrawalTransaction_badTarget_reverts() external { + _defaultTx.target = address(optimismPortal2); + vm.expectRevert(IOptimismPortal.OptimismPortal_BadTarget.selector); + optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); - assertTrue(optimismPortal2.disputeGameBlacklist(_addr)); + _defaultTx.target = address(ethLockbox); + vm.expectRevert(IOptimismPortal.OptimismPortal_BadTarget.selector); + optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } - /// @dev Tests that `setRespectedGameType` reverts when called by a non-guardian. - function testFuzz_setRespectedGameType_onlyGuardian_reverts(address _act, GameType _ty) external { - vm.assume(_act != address(optimismPortal2.guardian())); + /// @notice Sets the supeRootsActive variable to the provided value. + /// @param _superRootsActive The value to set the superRootsActive variable to. + function setSuperRootsActive(bool _superRootsActive) public { + // Get the slot for superRootsActive. + StorageSlot memory slot = ForgeArtifacts.getSlot("OptimismPortal2", "superRootsActive"); - vm.prank(_act); - vm.expectRevert(Unauthorized.selector); - optimismPortal2.setRespectedGameType(_ty); - } + // Load the existing storage slot value. + bytes32 existingValue = vm.load(address(optimismPortal2), bytes32(slot.slot)); - /// @dev Tests that the guardian role can set the respected game type to anything they want. - function testFuzz_setRespectedGameType_guardianCanSetRespectedGameType_succeeds(GameType _ty) external { - vm.assume(_ty.raw() != type(uint32).max); - uint64 respectedGameTypeUpdatedAt = optimismPortal2.respectedGameTypeUpdatedAt(); - vm.expectEmit(address(optimismPortal2)); - emit RespectedGameTypeSet(_ty, Timestamp.wrap(respectedGameTypeUpdatedAt)); - vm.prank(optimismPortal2.guardian()); - optimismPortal2.setRespectedGameType(_ty); - // GameType changes, but the timestamp doesn't. - assertEq(optimismPortal2.respectedGameType().raw(), _ty.raw()); - assertEq(optimismPortal2.respectedGameTypeUpdatedAt(), respectedGameTypeUpdatedAt); - } + // Inject the bool into the existing storage slot value with a bitwise OR. + // Shift the bool left by the offset of the storage slot and OR with existing value. + bytes32 newValue = + bytes32(uint256(uint8(_superRootsActive ? 1 : 0)) << slot.offset * 8 | uint256(existingValue)); - /// @dev Tests that the guardian can set the `respectedGameTypeUpdatedAt` timestamp to current timestamp. - function testFuzz_setRespectedGameType_guardianCanSetRespectedGameTypeUpdatedAt_succeeds(uint64 _elapsed) - external - { - _elapsed = uint64(bound(_elapsed, 0, type(uint64).max - uint64(block.timestamp))); - GameType _ty = GameType.wrap(type(uint32).max); - uint64 _newRespectedGameTypeUpdatedAt = uint64(block.timestamp) + _elapsed; - GameType _existingGameType = optimismPortal2.respectedGameType(); - vm.warp(_newRespectedGameTypeUpdatedAt); - emit RespectedGameTypeSet(_existingGameType, Timestamp.wrap(_newRespectedGameTypeUpdatedAt)); - vm.prank(optimismPortal2.guardian()); - optimismPortal2.setRespectedGameType(_ty); - // GameType doesn't change, but the timestamp does. - assertEq(optimismPortal2.respectedGameType().raw(), _existingGameType.raw()); - assertEq(optimismPortal2.respectedGameTypeUpdatedAt(), _newRespectedGameTypeUpdatedAt); + // Store the new value at the correct slot/offset. + vm.store(address(optimismPortal2), bytes32(slot.slot), newValue); } /// @dev Tests that `proveWithdrawalTransaction` reverts when paused. @@ -555,7 +618,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { vm.prank(optimismPortal2.guardian()); superchainConfig.pause("identifier"); - vm.expectRevert(CallPaused.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_CallPaused.selector); optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, _disputeGameIndex: _proposedGameIndex, @@ -567,7 +630,35 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { /// @dev Tests that `proveWithdrawalTransaction` reverts when the target is the portal contract. function test_proveWithdrawalTransaction_onSelfCall_reverts() external { _defaultTx.target = address(optimismPortal2); - vm.expectRevert(BadTarget.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_BadTarget.selector); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameIndex: _proposedGameIndex, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + + _defaultTx.target = address(ethLockbox); + vm.expectRevert(IOptimismPortal.OptimismPortal_BadTarget.selector); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameIndex: _proposedGameIndex, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + } + + /// @dev Tests that `proveWithdrawalTransaction` reverts when the current timestamp is less + /// than or equal to the creation timestamp of the dispute game. + function testFuzz_proveWithdrawalTransaction_timestampLessThanOrEqualToCreation_reverts(uint64 _timestamp) + external + { + // Set the timestamp to be less than or equal to the creation timestamp of the dispute game. + _timestamp = uint64(bound(_timestamp, 0, game.createdAt().raw())); + vm.warp(_timestamp); + + // Should revert. + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidProofTimestamp.selector); optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, _disputeGameIndex: _proposedGameIndex, @@ -580,7 +671,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { function test_proveWithdrawalTransaction_onInvalidOutputRootProof_reverts() external { // Modify the version to invalidate the withdrawal proof. _outputRootProof.version = bytes32(uint256(1)); - vm.expectRevert(InvalidProof.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidOutputRootProof.selector); optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, _disputeGameIndex: _proposedGameIndex, @@ -624,25 +715,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { vm.mockCall(address(game), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); vm.mockCall(address(game2), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); - vm.expectRevert(InvalidDisputeGame.selector); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); - } - - /// @dev Tests that `proveWithdrawalTransaction` reverts if the dispute game being proven against is not of the - /// respected game type. - function test_proveWithdrawalTransaction_badGameType_reverts() external { - vm.mockCall( - address(disputeGameFactory), - abi.encodeCall(disputeGameFactory.gameAtIndex, (_proposedGameIndex)), - abi.encode(GameType.wrap(0xFF), Timestamp.wrap(uint64(block.timestamp)), IDisputeGame(address(game))) - ); - - vm.expectRevert(InvalidGameType.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidDisputeGame.selector); optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, _disputeGameIndex: _proposedGameIndex, @@ -654,7 +727,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { /// @dev Tests that `proveWithdrawalTransaction` reverts if the game was not the respected game type when created. function test_proveWithdrawalTransaction_wasNotRespectedGameTypeWhenCreated_reverts() external { vm.mockCall(address(game), abi.encodeCall(game.wasRespectedGameTypeWhenCreated, ()), abi.encode(false)); - vm.expectRevert(InvalidGameType.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidDisputeGame.selector); optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, _disputeGameIndex: _proposedGameIndex, @@ -667,7 +740,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { /// `wasRespectedGameTypeWhenCreated`. function test_proveWithdrawalTransaction_legacyGame_reverts() external { vm.mockCallRevert(address(game), abi.encodeCall(game.wasRespectedGameTypeWhenCreated, ()), ""); - vm.expectRevert(LegacyGame.selector); + vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, _disputeGameIndex: _proposedGameIndex, @@ -679,7 +752,8 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { /// @dev Tests that `proveWithdrawalTransaction` succeeds if the game was created after the /// game retirement timestamp. function testFuzz_proveWithdrawalTransaction_createdAfterRetirementTimestamp_succeeds(uint64 _createdAt) external { - _createdAt = uint64(bound(_createdAt, optimismPortal2.respectedGameTypeUpdatedAt() + 1, type(uint64).max)); + _createdAt = uint64(bound(_createdAt, optimismPortal2.respectedGameTypeUpdatedAt() + 1, type(uint64).max - 1)); + vm.warp(_createdAt + 1); vm.mockCall(address(game), abi.encodeCall(game.createdAt, ()), abi.encode(uint64(_createdAt))); optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, @@ -696,45 +770,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { { _createdAt = uint64(bound(_createdAt, 0, optimismPortal2.respectedGameTypeUpdatedAt())); vm.mockCall(address(game), abi.encodeCall(game.createdAt, ()), abi.encode(uint64(_createdAt))); - vm.expectRevert("OptimismPortal: dispute game created before respected game type was updated"); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); - } - - /// @dev Tests that `proveWithdrawalTransaction` can be re-executed if the dispute game proven against has been - /// blacklisted. - function test_proveWithdrawalTransaction_replayProveBlacklisted_succeeds() external { - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); - optimismPortal2.proveWithdrawalTransaction({ - _tx: _defaultTx, - _disputeGameIndex: _proposedGameIndex, - _outputRootProof: _outputRootProof, - _withdrawalProof: _withdrawalProof - }); - - // Blacklist the dispute dispute game. - vm.prank(optimismPortal2.guardian()); - optimismPortal2.blacklistDisputeGame(IDisputeGame(address(game))); - - // Mock the status of the dispute game we just proved against to be CHALLENGER_WINS. - vm.mockCall(address(game), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); - // Create a new game to re-prove against - disputeGameFactory.create( - optimismPortal2.respectedGameType(), Claim.wrap(_outputRoot), abi.encode(_proposedBlockNumber + 1) - ); - _proposedGameIndex = disputeGameFactory.gameCount() - 1; - - vm.expectEmit(true, true, true, true); - emit WithdrawalProven(_withdrawalHash, alice, bob); - vm.expectEmit(true, true, true, true); - emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); + vm.expectRevert(IOptimismPortal.OptimismPortal_ImproperDisputeGame.selector); optimismPortal2.proveWithdrawalTransaction({ _tx: _defaultTx, _disputeGameIndex: _proposedGameIndex, @@ -759,12 +795,16 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Mock the status of the dispute game we just proved against to be CHALLENGER_WINS. vm.mockCall(address(game), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); + // Create a new game to re-prove against disputeGameFactory.create( optimismPortal2.respectedGameType(), Claim.wrap(_outputRoot), abi.encode(_proposedBlockNumber + 1) ); _proposedGameIndex = disputeGameFactory.gameCount() - 1; + // Warp 1 second into the future so we're not in the same block as the dispute game. + vm.warp(block.timestamp + 1 seconds); + vm.expectEmit(true, true, true, true); emit WithdrawalProven(_withdrawalHash, alice, bob); vm.expectEmit(true, true, true, true); @@ -798,7 +838,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Update the respected game type to 0xbeef. vm.prank(optimismPortal2.guardian()); - optimismPortal2.setRespectedGameType(GameType.wrap(0xbeef)); + anchorStateRegistry.setRespectedGameType(GameType.wrap(0xbeef)); // Create a new game and mock the game type as 0xbeef in the factory. vm.mockCall( @@ -807,6 +847,9 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { abi.encode(GameType.wrap(0xbeef), Timestamp.wrap(uint64(block.timestamp)), IDisputeGame(address(newGame))) ); + // Warp 1 second into the future so we're not in the same block as the dispute game. + vm.warp(block.timestamp + 1 seconds); + // Re-proving should be successful against the new game. vm.expectEmit(true, true, true, true); emit WithdrawalProven(_withdrawalHash, alice, bob); @@ -820,6 +863,216 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { }); } + /// @dev Tests that `proveWithdrawalTransaction` reverts when using the Output Roots version of + /// `proveWithdrawalTransaction` when `superRootsActive` is true. + function test_proveWithdrawalTransaction_outputRootVersionWhenSuperRootsActive_reverts() external { + // Set superRootsActive to true. + setSuperRootsActive(true); + + // Should revert. + vm.expectRevert(IOptimismPortal.OptimismPortal_WrongProofMethod.selector); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameIndex: _proposedGameIndex, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + } + + /// @dev Tests that `proveWithdrawalTransaction` reverts when using the Super Roots version of + /// `proveWithdrawalTransaction` when `superRootsActive` is false. + function test_proveWithdrawalTransaction_superRootsVersionWhenSuperRootsInactive_reverts() external { + // Set up a dummy super root proof. + Types.OutputRootWithChainId[] memory outputRootWithChainIdArr = new Types.OutputRootWithChainId[](1); + outputRootWithChainIdArr[0] = + Types.OutputRootWithChainId({ root: _outputRoot, chainId: systemConfig.l2ChainId() }); + Types.SuperRootProof memory superRootProof = Types.SuperRootProof({ + version: 0x01, + timestamp: uint64(block.timestamp), + outputRoots: outputRootWithChainIdArr + }); + + // Should revert. + vm.expectRevert(IOptimismPortal.OptimismPortal_WrongProofMethod.selector); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: 0, + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + } + + /// @dev Tests that `proveWithdrawalTransaction` reverts when using the Super Roots version of + /// `proveWithdrawalTransaction` when the provided proof is invalid. + function test_proveWithdrawalTransaction_superRootsVersionBadProof_reverts() external { + // Enable super roots. + setSuperRootsActive(true); + + // Set up a dummy super root proof. + Types.OutputRootWithChainId[] memory outputRootWithChainIdArr = new Types.OutputRootWithChainId[](1); + outputRootWithChainIdArr[0] = + Types.OutputRootWithChainId({ root: _outputRoot, chainId: systemConfig.l2ChainId() }); + Types.SuperRootProof memory superRootProof = Types.SuperRootProof({ + version: 0x01, + timestamp: uint64(block.timestamp), + outputRoots: outputRootWithChainIdArr + }); + + // Should revert because the proof is wrong. + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidSuperRootProof.selector); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: 0, + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + } + + /// @dev Tests that `proveWithdrawalTransaction` reverts when using the Super Roots version of + /// `proveWithdrawalTransaction` when the provided proof is valid but the index is out of + /// bounds. + function test_proveWithdrawalTransaction_superRootsVersionBadIndex_reverts() external { + // Enable super roots. + setSuperRootsActive(true); + + // Set up a dummy super root proof. + Types.OutputRootWithChainId[] memory outputRootWithChainIdArr = new Types.OutputRootWithChainId[](1); + outputRootWithChainIdArr[0] = + Types.OutputRootWithChainId({ root: _outputRoot, chainId: systemConfig.l2ChainId() }); + Types.SuperRootProof memory superRootProof = Types.SuperRootProof({ + version: 0x01, + timestamp: uint64(block.timestamp), + outputRoots: outputRootWithChainIdArr + }); + + // Figure out what the right hash would be. + bytes32 expectedSuperRoot = Hashing.hashSuperRootProof(superRootProof); + + // Mock the game to return the expected super root. + vm.mockCall(address(game), abi.encodeCall(game.rootClaim, ()), abi.encode(expectedSuperRoot)); + + // Should revert because the proof is wrong. + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidOutputRootIndex.selector); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: outputRootWithChainIdArr.length, // out of bounds + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + } + + /// @dev Tests that `proveWithdrawalTransaction` reverts when using the Super Roots version of + /// `proveWithdrawalTransaction` when the provided proof is valid, index is correct, but + /// the output root has the wrong chain id. + function test_proveWithdrawalTransaction_superRootsVersionBadChainId_reverts() external { + // Enable super roots. + setSuperRootsActive(true); + + // Set up a dummy super root proof. + Types.OutputRootWithChainId[] memory outputRootWithChainIdArr = new Types.OutputRootWithChainId[](1); + outputRootWithChainIdArr[0] = Types.OutputRootWithChainId({ + root: _outputRoot, + chainId: systemConfig.l2ChainId() + 1 // wrong chain id + }); + Types.SuperRootProof memory superRootProof = Types.SuperRootProof({ + version: 0x01, + timestamp: uint64(block.timestamp), + outputRoots: outputRootWithChainIdArr + }); + + // Figure out what the right hash would be. + bytes32 expectedSuperRoot = Hashing.hashSuperRootProof(superRootProof); + + // Mock the game to return the expected super root. + vm.mockCall(address(game), abi.encodeCall(game.rootClaim, ()), abi.encode(expectedSuperRoot)); + + // Should revert because the proof is wrong. + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidOutputRootChainId.selector); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: 0, + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + } + + /// @dev Tests that `proveWithdrawalTransaction` reverts when using the Super Roots version of + /// `proveWithdrawalTransaction` when the provided proof is valid, index is correct, chain + /// id is correct, but the output root proof is invalid. + function test_proveWithdrawalTransaction_superRootsVersionBadOutputRootProof_reverts() external { + // Enable super roots. + setSuperRootsActive(true); + + // Set up a dummy super root proof. + Types.OutputRootWithChainId[] memory outputRootWithChainIdArr = new Types.OutputRootWithChainId[](1); + outputRootWithChainIdArr[0] = Types.OutputRootWithChainId({ + root: keccak256(abi.encode(_outputRoot)), // random root so the proof is wrong + chainId: systemConfig.l2ChainId() + }); + Types.SuperRootProof memory superRootProof = Types.SuperRootProof({ + version: 0x01, + timestamp: uint64(block.timestamp), + outputRoots: outputRootWithChainIdArr + }); + + // Figure out what the right hash would be. + bytes32 expectedSuperRoot = Hashing.hashSuperRootProof(superRootProof); + + // Mock the game to return the expected super root. + vm.mockCall(address(game), abi.encodeCall(game.rootClaim, ()), abi.encode(expectedSuperRoot)); + + // Should revert because the proof is wrong. + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidOutputRootProof.selector); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: 0, + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + } + + /// @dev Tests that `proveWithdrawalTransaction` succeeds when all parameters are valid. + function test_proveWithdrawalTransaction_superRootsVersion_succeeds() external { + // Enable super roots. + setSuperRootsActive(true); + + // Set up a dummy super root proof. + Types.OutputRootWithChainId[] memory outputRootWithChainIdArr = new Types.OutputRootWithChainId[](1); + outputRootWithChainIdArr[0] = + Types.OutputRootWithChainId({ root: _outputRoot, chainId: systemConfig.l2ChainId() }); + Types.SuperRootProof memory superRootProof = Types.SuperRootProof({ + version: 0x01, + timestamp: uint64(block.timestamp), + outputRoots: outputRootWithChainIdArr + }); + + // Figure out what the right hash would be. + bytes32 expectedSuperRoot = Hashing.hashSuperRootProof(superRootProof); + + // Mock the game to return the expected super root. + vm.mockCall(address(game), abi.encodeCall(game.rootClaim, ()), abi.encode(expectedSuperRoot)); + + // Should succeed. + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameProxy: game, + _outputRootIndex: 0, + _superRootProof: superRootProof, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + } + /// @dev Tests that `proveWithdrawalTransaction` succeeds. function test_proveWithdrawalTransaction_validWithdrawalProof_succeeds() external { vm.expectEmit(true, true, true, true); @@ -873,7 +1126,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { emit WithdrawalFinalized(_withdrawalHash, true); optimismPortal2.finalizeWithdrawalTransactionExternalProof(_defaultTx, address(0xb0b)); - vm.expectRevert(AlreadyFinalized.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_AlreadyFinalized.selector); optimismPortal2.finalizeWithdrawalTransactionExternalProof(_defaultTx, address(this)); assert(address(bob).balance == bobBalanceBefore + 100); @@ -895,7 +1148,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1 seconds); vm.startPrank(alice, Constants.ESTIMATION_ADDRESS); - vm.expectRevert(GasEstimation.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_GasEstimation.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } @@ -940,7 +1193,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { vm.warp(block.timestamp + game_noData.maxClockDuration().raw() + 1 seconds); // Fund the portal so that we can withdraw ETH. vm.store(address(optimismPortal2), bytes32(uint256(61)), bytes32(uint256(0xFFFFFFFF))); - vm.deal(address(optimismPortal2), 0xFFFFFFFF); + vm.deal(address(ethLockbox), 0xFFFFFFFF); uint256 bobBalanceBefore = bob.balance; @@ -1005,7 +1258,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { ); // Warp 1 second into the future so that the proof is submitted after the timestamp of game creation. - vm.warp(block.timestamp + 1 seconds); + vm.warp(block.timestamp + 1); // Prove the withdrawal transaction against the invalid dispute game, as 0xb0b. vm.expectEmit(true, true, true, true); @@ -1044,7 +1297,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Ensure both proofs are registered successfully. assertEq(optimismPortal2.numProofSubmitters(_withdrawalHash), 2); - vm.expectRevert(ProposalNotValidated.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidRootClaim.selector); vm.prank(address(0xb0b)); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); @@ -1060,7 +1313,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { vm.prank(optimismPortal2.guardian()); superchainConfig.pause("identifier"); - vm.expectRevert(CallPaused.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_CallPaused.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } @@ -1068,7 +1321,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { function test_finalizeWithdrawalTransaction_ifWithdrawalNotProven_reverts() external { uint256 bobBalanceBefore = address(bob).balance; - vm.expectRevert(Unproven.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_Unproven.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); assert(address(bob).balance == bobBalanceBefore); @@ -1090,7 +1343,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { _withdrawalProof: _withdrawalProof }); - vm.expectRevert("OptimismPortal: proven withdrawal has not matured yet"); + vm.expectRevert(IOptimismPortal.OptimismPortal_ProofNotOldEnough.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); assert(address(bob).balance == bobBalanceBefore); @@ -1120,7 +1373,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { vm.mockCall(address(game), abi.encodeCall(game.createdAt, ()), abi.encode(block.timestamp + 1)); // Attempt to finalize the withdrawal - vm.expectRevert("OptimismPortal: withdrawal timestamp less than dispute game creation timestamp"); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidProofTimestamp.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); // Ensure that bob's balance has remained the same @@ -1148,7 +1401,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); // Attempt to finalize the withdrawal - vm.expectRevert(ProposalNotValidated.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidRootClaim.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); // Ensure that bob's balance has remained the same @@ -1206,7 +1459,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { emit WithdrawalFinalized(_withdrawalHash, true); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); - vm.expectRevert(AlreadyFinalized.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_AlreadyFinalized.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } @@ -1324,7 +1577,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Total ETH supply is currently about 120M ETH. uint256 value = bound(_value, 0, 200_000_000 ether); - vm.deal(address(optimismPortal2), value); + vm.deal(address(ethLockbox), value); uint256 gasLimit = bound(_gasLimit, 0, 50_000_000); uint256 nonce = l2ToL1MessagePasser.messageNonce(); @@ -1404,7 +1657,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Total ETH supply is currently about 120M ETH. uint256 value = bound(_value, 0, 200_000_000 ether); - vm.deal(address(optimismPortal2), value); + vm.deal(address(ethLockbox), value); uint256 gasLimit = bound(_gasLimit, 0, 50_000_000); uint256 nonce = l2ToL1MessagePasser.messageNonce(); @@ -1455,7 +1708,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Change the respectedGameType vm.prank(optimismPortal2.guardian()); - optimismPortal2.setRespectedGameType(_newGameType); + anchorStateRegistry.setRespectedGameType(_newGameType); // Withdrawal transaction still finalizable vm.expectCallMinGas(_tx.target, _tx.value, uint64(_tx.gasLimit), _tx.data); @@ -1481,11 +1734,11 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { game.resolve(); vm.prank(optimismPortal2.guardian()); - optimismPortal2.blacklistDisputeGame(IDisputeGame(address(game))); + anchorStateRegistry.blacklistDisputeGame(IDisputeGame(address(game))); vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); - vm.expectRevert(Blacklisted.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidRootClaim.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } @@ -1511,7 +1764,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { game.resolve(); // Attempt to finalize the withdrawal directly after the game resolves. This should fail. - vm.expectRevert("OptimismPortal: output proposal in air-gap"); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidRootClaim.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); // Finalize the withdrawal transaction. This should succeed. @@ -1546,9 +1799,10 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Set respectedGameTypeUpdatedAt. vm.prank(optimismPortal2.guardian()); - optimismPortal2.setRespectedGameType(GameType.wrap(type(uint32).max)); + anchorStateRegistry.updateRetirementTimestamp(); - vm.expectRevert("OptimismPortal: dispute game created before respected game type was updated"); + // Should revert. + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidRootClaim.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } @@ -1578,7 +1832,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { vm.mockCall(address(game), abi.encodeCall(game.wasRespectedGameTypeWhenCreated, ()), abi.encode(false)); - vm.expectRevert(InvalidGameType.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidRootClaim.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } @@ -1607,9 +1861,11 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Warp past the dispute game finality delay. vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds() + 1); + // Mock the wasRespectedGameTypeWhenCreated call to revert. vm.mockCallRevert(address(game), abi.encodeCall(game.wasRespectedGameTypeWhenCreated, ()), ""); - vm.expectRevert(LegacyGame.selector); + // Should revert. + vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } @@ -1629,13 +1885,13 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { // Attempt to finalize the withdrawal transaction 1 second before the proof has matured. This should fail. vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds()); - vm.expectRevert("OptimismPortal: proven withdrawal has not matured yet"); + vm.expectRevert(IOptimismPortal.OptimismPortal_ProofNotOldEnough.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); // Warp 1 second in the future, past the proof maturity delay, and attempt to finalize the withdrawal. // This should also fail, since the dispute game has not resolved yet. vm.warp(block.timestamp + 1 seconds); - vm.expectRevert(ProposalNotValidated.selector); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidRootClaim.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); // Finalize the dispute game and attempt to finalize the withdrawal again. This should also fail, since the @@ -1643,7 +1899,7 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { game.resolveClaim(0, 0); game.resolve(); vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds()); - vm.expectRevert("OptimismPortal: output proposal in air-gap"); + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidRootClaim.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); // Warp 1 second in the future, past the air gap dispute game delay, and attempt to finalize the withdrawal. @@ -1652,6 +1908,140 @@ contract OptimismPortal2_FinalizeWithdrawal_Test is CommonTest { optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); assertTrue(optimismPortal2.finalizedWithdrawals(_withdrawalHash)); } + + /// @notice Tests that checkWithdrawal succeeds if the withdrawal has been proven, the dispute + /// game has been finalized, and the root claim is valid. + function test_checkWithdrawal_succeeds() external { + // Prove the withdrawal transaction. + vm.expectEmit(true, true, true, true); + emit WithdrawalProven(_withdrawalHash, alice, bob); + vm.expectEmit(true, true, true, true); + emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameIndex: _proposedGameIndex, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + + // Warp past the finalization period. + vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); + + // Mark the dispute game as CHALLENGER_WINS. + vm.mockCall(address(game), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); + + // Mock isGameClaimValid to return true. + vm.mockCall( + address(anchorStateRegistry), abi.encodeCall(anchorStateRegistry.isGameClaimValid, (game)), abi.encode(true) + ); + + // Should succeed. + optimismPortal2.checkWithdrawal(_withdrawalHash, address(this)); + } + + /// @notice Tests that checkWithdrawal reverts if the withdrawal has already been finalized. + function test_checkWithdrawal_ifAlreadyFinalized_reverts() external { + // Prove the withdrawal transaction. + vm.expectEmit(true, true, true, true); + emit WithdrawalProven(_withdrawalHash, alice, bob); + vm.expectEmit(true, true, true, true); + emit WithdrawalProvenExtension1(_withdrawalHash, address(this)); + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameIndex: _proposedGameIndex, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + + // Warp and resolve the dispute game. + game.resolveClaim(0, 0); + game.resolve(); + vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); + + // Finalize the withdrawal. + optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); + + // Should revert. + vm.expectRevert(IOptimismPortal.OptimismPortal_AlreadyFinalized.selector); + optimismPortal2.checkWithdrawal(_withdrawalHash, address(this)); + } + + /// @notice Tests that checkWithdrawal reverts if the withdrawal has not been proven. + function test_checkWithdrawal_ifUnproven_reverts() external { + // Don't prove the withdrawal transaction. + // Should revert. + vm.expectRevert(IOptimismPortal.OptimismPortal_Unproven.selector); + optimismPortal2.checkWithdrawal(_withdrawalHash, address(this)); + } + + /// @notice Tests that checkWithdrawal reverts if the proof timestamp is greater than the game + /// creation timestamp. + function testFuzz_checkWithdrawal_ifInvalidProofTimestamp_reverts(uint64 _createdAt) external { + // Prove the withdrawal transaction. + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameIndex: _proposedGameIndex, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + + // Mock the game creation timestamp to be greater than the proof timestamp. + _createdAt = uint64(bound(_createdAt, block.timestamp, type(uint64).max)); + vm.mockCall(address(game), abi.encodeCall(game.createdAt, ()), abi.encode(_createdAt)); + + // Warp beyond the proof maturity delay. + vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); + + // Mark the dispute game as CHALLENGER_WINS. + vm.mockCall(address(game), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); + + // Mock isGameClaimValid to return true. + vm.mockCall( + address(anchorStateRegistry), abi.encodeCall(anchorStateRegistry.isGameClaimValid, (game)), abi.encode(true) + ); + + // Should revert. + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidProofTimestamp.selector); + optimismPortal2.checkWithdrawal(_withdrawalHash, address(this)); + } + + /// @notice Tests that checkWithdrawal reverts if the proof timestamp is less than the proof + /// maturity delay. + function test_checkWithdrawal_ifProofNotOldEnough_reverts() external { + // Prove but don't warp ahead past the proof maturity delay. + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameIndex: _proposedGameIndex, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + + // Should revert. + vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() - 1); + vm.expectRevert(IOptimismPortal.OptimismPortal_ProofNotOldEnough.selector); + optimismPortal2.checkWithdrawal(_withdrawalHash, address(this)); + } + + /// @notice Tests that checkWithdrawal reverts if the root claim is invalid. + function test_checkWithdrawal_ifInvalidRootClaim_reverts() external { + // Prove the withdrawal. + optimismPortal2.proveWithdrawalTransaction({ + _tx: _defaultTx, + _disputeGameIndex: _proposedGameIndex, + _outputRootProof: _outputRootProof, + _withdrawalProof: _withdrawalProof + }); + + // Warp past the proof maturity delay. + vm.warp(block.timestamp + optimismPortal2.proofMaturityDelaySeconds() + 1); + + // Mock the game to have CHALLENGER_WINS status + vm.mockCall(address(game), abi.encodeCall(game.status, ()), abi.encode(GameStatus.CHALLENGER_WINS)); + + // Should revert. + vm.expectRevert(IOptimismPortal.OptimismPortal_InvalidRootClaim.selector); + optimismPortal2.checkWithdrawal(_withdrawalHash, address(this)); + } } contract OptimismPortal2_Upgradeable_Test is CommonTest { @@ -1682,7 +2072,7 @@ contract OptimismPortal2_Upgradeable_Test is CommonTest { // The value passed to the initialize must be larger than the last value // that initialize was called with. IProxy(payable(address(optimismPortal2))).upgradeToAndCall( - address(nextImpl), abi.encodeCall(NextImpl.initialize, (2)) + address(nextImpl), abi.encodeCall(NextImpl.initialize, (3)) ); assertEq(IProxy(payable(address(optimismPortal2))).implementation(), address(nextImpl)); @@ -1693,6 +2083,107 @@ contract OptimismPortal2_Upgradeable_Test is CommonTest { } } +contract OptimismPortal2_LiquidityMigration_Test is CommonTest { + function setUp() public override { + super.setUp(); + } + + /// @notice Tests the liquidity migration from the portal to the lockbox reverts if not called by the admin owner. + function testFuzz_migrateLiquidity_notProxyAdminOwner_reverts(address _caller) external { + vm.assume(_caller != optimismPortal2.proxyAdminOwner()); + vm.expectRevert(IOptimismPortal.OptimismPortal_Unauthorized.selector); + vm.prank(_caller); + optimismPortal2.migrateLiquidity(); + } + + /// @notice Tests that the liquidity migration from the portal to the lockbox succeeds. + function test_migrateLiquidity_succeeds(uint256 _portalBalance) external { + _portalBalance = uint256(bound(_portalBalance, 0, type(uint256).max - address(ethLockbox).balance)); + vm.deal(address(optimismPortal2), _portalBalance); + + uint256 lockboxBalanceBefore = address(ethLockbox).balance; + address proxyAdminOwner = optimismPortal2.proxyAdminOwner(); + + vm.expectCall(address(ethLockbox), _portalBalance, abi.encodeCall(ethLockbox.lockETH, ())); + + vm.expectEmit(address(optimismPortal2)); + emit ETHMigrated(address(ethLockbox), _portalBalance); + + vm.prank(proxyAdminOwner); + optimismPortal2.migrateLiquidity(); + + assertEq(address(optimismPortal2).balance, 0); + assertEq(address(ethLockbox).balance, lockboxBalanceBefore + _portalBalance); + } +} + +/// @title OptimismPortal2_upgrade_Test +/// @notice Reusable test for the current upgrade() function in the OptimismPortal2 contract. If +/// the upgrade() function is changed, tests inside of this contract should be updated to +/// reflect the new function. If the upgrade() function is removed, remove the +/// corresponding tests but leave this contract in place so it's easy to add tests back +/// in the future. +contract OptimismPortal2_upgrade_Test is CommonTest { + function setUp() public override { + super.setUp(); + } + + /// @notice Tests that the upgrade() function succeeds. + function test_upgrade_succeeds() external { + // Get the slot for _initialized. + StorageSlot memory slot = ForgeArtifacts.getSlot("OptimismPortal2", "_initialized"); + + // Set the initialized slot to 0. + vm.store(address(optimismPortal2), bytes32(slot.slot), bytes32(0)); + + // Trigger upgrade(). + optimismPortal2.upgrade(IAnchorStateRegistry(address(0xdeadbeef)), IETHLockbox(ethLockbox), true); + + // Verify that the initialized slot was updated. + bytes32 initializedSlotAfter = vm.load(address(optimismPortal2), bytes32(slot.slot)); + assertEq(initializedSlotAfter, bytes32(uint256(2))); + + // Verify that superRootsActive was set to true. + assertEq(optimismPortal2.superRootsActive(), true); + + // Verify that the AnchorStateRegistry was set. + assertEq(address(optimismPortal2.anchorStateRegistry()), address(0xdeadbeef)); + } + + /// @notice Tests that the upgrade() function reverts if called a second time. + function test_upgrade_upgradeTwice_reverts() external { + // Get the slot for _initialized. + StorageSlot memory slot = ForgeArtifacts.getSlot("OptimismPortal2", "_initialized"); + + // Set the initialized slot to 0. + vm.store(address(optimismPortal2), bytes32(slot.slot), bytes32(0)); + + // Trigger first upgrade. + optimismPortal2.upgrade(IAnchorStateRegistry(address(0xdeadbeef)), IETHLockbox(ethLockbox), true); + + // Try to trigger second upgrade. + vm.expectRevert("Initializable: contract is already initialized"); + optimismPortal2.upgrade(IAnchorStateRegistry(address(0xdeadbeef)), IETHLockbox(ethLockbox), true); + } + + /// @notice Tests that the upgrade() function reverts if called after initialization. + function test_upgrade_afterInitialization_reverts() external { + // Get the slot for _initialized. + StorageSlot memory slot = ForgeArtifacts.getSlot("OptimismPortal2", "_initialized"); + + // Slot value should be set to 2 (already initialized). + bytes32 initializedSlotBefore = vm.load(address(optimismPortal2), bytes32(slot.slot)); + assertEq(initializedSlotBefore, bytes32(uint256(2))); + + // AnchorStateRegistry address should be non-zero. + assertNotEq(address(optimismPortal2.anchorStateRegistry()), address(0)); + + // Try to trigger upgrade(). + vm.expectRevert("Initializable: contract is already initialized"); + optimismPortal2.upgrade(IAnchorStateRegistry(address(0xdeadbeef)), IETHLockbox(ethLockbox), true); + } +} + /// @title OptimismPortal2_ResourceFuzz_Test /// @dev Test various values of the resource metering config to ensure that deposits cannot be /// broken by changing the config. diff --git a/packages/contracts-bedrock/test/L1/OptimismPortalInterop.t.sol b/packages/contracts-bedrock/test/L1/OptimismPortalInterop.t.sol index df9100ce881..3a8c9973e62 100644 --- a/packages/contracts-bedrock/test/L1/OptimismPortalInterop.t.sol +++ b/packages/contracts-bedrock/test/L1/OptimismPortalInterop.t.sol @@ -7,7 +7,6 @@ import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries import { Constants } from "src/libraries/Constants.sol"; import { Predeploys } from "src/libraries/Predeploys.sol"; -import "src/libraries/PortalErrors.sol"; // Interfaces import { IL1BlockInterop, ConfigType } from "interfaces/L2/IL1BlockInterop.sol"; @@ -46,7 +45,7 @@ contract OptimismPortalInterop_Test is CommonTest { /// @dev Tests that setting the add dependency config as not the system config reverts. function testFuzz_setConfig_addDependencyButNotSystemConfig_reverts(bytes calldata _value) public { - vm.expectRevert(Unauthorized.selector); + vm.expectRevert(IOptimismPortalInterop.OptimismPortal_Unauthorized.selector); _optimismPortalInterop().setConfig(ConfigType.ADD_DEPENDENCY, _value); } @@ -69,7 +68,7 @@ contract OptimismPortalInterop_Test is CommonTest { /// @dev Tests that setting the remove dependency config as not the system config reverts. function testFuzz_setConfig_removeDependencyButNotSystemConfig_reverts(bytes calldata _value) public { - vm.expectRevert(Unauthorized.selector); + vm.expectRevert(IOptimismPortalInterop.OptimismPortal_Unauthorized.selector); _optimismPortalInterop().setConfig(ConfigType.REMOVE_DEPENDENCY, _value); } diff --git a/packages/contracts-bedrock/test/L1/SystemConfig.t.sol b/packages/contracts-bedrock/test/L1/SystemConfig.t.sol index fbae6b41524..9fc8dfc9744 100644 --- a/packages/contracts-bedrock/test/L1/SystemConfig.t.sol +++ b/packages/contracts-bedrock/test/L1/SystemConfig.t.sol @@ -4,6 +4,9 @@ pragma solidity 0.8.15; // Testing import { CommonTest } from "test/setup/CommonTest.sol"; +// Scripts +import { ForgeArtifacts, StorageSlot } from "scripts/libraries/ForgeArtifacts.sol"; + // Libraries import { Constants } from "src/libraries/Constants.sol"; import { EIP1967Helper } from "test/mocks/EIP1967Helper.sol"; @@ -114,6 +117,7 @@ contract SystemConfig_Initialize_Test is SystemConfig_Init { assertEq(addrs.optimismPortal, address(optimismPortal2)); assertEq(address(systemConfig.optimismMintableERC20Factory()), address(optimismMintableERC20Factory)); assertEq(addrs.optimismMintableERC20Factory, address(optimismMintableERC20Factory)); + assertNotEq(systemConfig.l2ChainId(), 0); } } @@ -145,7 +149,8 @@ contract SystemConfig_Initialize_TestFail is SystemConfig_Initialize_Test { disputeGameFactory: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0) - }) + }), + _l2ChainId: 1234 }); } @@ -174,7 +179,8 @@ contract SystemConfig_Initialize_TestFail is SystemConfig_Initialize_Test { disputeGameFactory: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0) - }) + }), + _l2ChainId: 1234 }); assertEq(systemConfig.startBlock(), block.number); } @@ -204,7 +210,8 @@ contract SystemConfig_Initialize_TestFail is SystemConfig_Initialize_Test { disputeGameFactory: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0) - }) + }), + _l2ChainId: 1234 }); assertEq(systemConfig.startBlock(), 1); } @@ -318,7 +325,8 @@ contract SystemConfig_Init_ResourceConfig is SystemConfig_Init { disputeGameFactory: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0) - }) + }), + _l2ChainId: 1234 }); } } @@ -480,3 +488,63 @@ contract SystemConfig_Setters_Test is SystemConfig_Init { assertEq(systemConfig.eip1559Elasticity(), _elasticity); } } + +/// @title SystemConfig_upgrade_Test +/// @notice Reusable test for the current upgrade() function in the SystemConfig contract. If +/// the upgrade() function is changed, tests inside of this contract should be updated to +/// reflect the new function. If the upgrade() function is removed, remove the +/// corresponding tests but leave this contract in place so it's easy to add tests back +/// in the future. +contract SystemConfig_upgrade_Test is SystemConfig_Init { + /// @notice Tests that the upgrade() function succeeds. + function test_upgrade_succeeds() external { + // Get the slot for _initialized. + StorageSlot memory slot = ForgeArtifacts.getSlot("SystemConfig", "_initialized"); + + // Set the initialized slot to 0. + vm.store(address(systemConfig), bytes32(slot.slot), bytes32(0)); + + // Trigger upgrade(). + systemConfig.upgrade(1234); + + // Verify that the initialized slot was updated. + bytes32 initializedSlotAfter = vm.load(address(systemConfig), bytes32(slot.slot)); + assertEq(initializedSlotAfter, bytes32(uint256(2))); + + // Verify that the l2ChainId was updated. + assertEq(systemConfig.l2ChainId(), 1234); + } + + /// @notice Tests that the upgrade() function reverts if called a second time. + function test_upgrade_upgradeTwice_reverts() external { + // Get the slot for _initialized. + StorageSlot memory slot = ForgeArtifacts.getSlot("SystemConfig", "_initialized"); + + // Set the initialized slot to 0. + vm.store(address(systemConfig), bytes32(slot.slot), bytes32(0)); + + // Trigger first upgrade. + systemConfig.upgrade(1234); + + // Try to trigger second upgrade. + vm.expectRevert("Initializable: contract is already initialized"); + systemConfig.upgrade(1234); + } + + /// @notice Tests that the upgrade() function reverts if called after initialization. + function test_upgrade_afterInitialization_reverts() external { + // Get the slot for _initialized. + StorageSlot memory slot = ForgeArtifacts.getSlot("SystemConfig", "_initialized"); + + // Slot value should be set to 2 (already initialized). + bytes32 initializedSlotBefore = vm.load(address(systemConfig), bytes32(slot.slot)); + assertEq(initializedSlotBefore, bytes32(uint256(2))); + + // l2ChainId should be non-zero. + assertNotEq(systemConfig.l2ChainId(), 0); + + // Try to trigger upgrade(). + vm.expectRevert("Initializable: contract is already initialized"); + systemConfig.upgrade(1234); + } +} diff --git a/packages/contracts-bedrock/test/dispute/AnchorStateRegistry.t.sol b/packages/contracts-bedrock/test/dispute/AnchorStateRegistry.t.sol index b3c7bf06400..c171d97737e 100644 --- a/packages/contracts-bedrock/test/dispute/AnchorStateRegistry.t.sol +++ b/packages/contracts-bedrock/test/dispute/AnchorStateRegistry.t.sol @@ -8,6 +8,7 @@ import { FaultDisputeGame_Init, _changeClaimStatus } from "test/dispute/FaultDis import { GameType, GameStatus, Hash, Claim, VMStatuses, OutputRoot } from "src/dispute/lib/Types.sol"; // Interfaces +import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; @@ -15,8 +16,9 @@ contract AnchorStateRegistry_Init is FaultDisputeGame_Init { /// @dev A valid l2BlockNumber that comes after the current anchor root block. uint256 validL2BlockNumber; - event AnchorNotUpdated(IFaultDisputeGame indexed game); event AnchorUpdated(IFaultDisputeGame indexed game); + event RespectedGameTypeSet(GameType gameType); + event RetirementTimestampSet(uint256 timestamp); function setUp() public virtual override { // Duplicating the initialization/setup logic of FaultDisputeGame_Test. @@ -46,7 +48,6 @@ contract AnchorStateRegistry_Initialize_Test is AnchorStateRegistry_Init { // Verify contract addresses. assert(anchorStateRegistry.superchainConfig() == superchainConfig); assert(anchorStateRegistry.disputeGameFactory() == disputeGameFactory); - assert(anchorStateRegistry.portal() == optimismPortal2); } } @@ -57,11 +58,11 @@ contract AnchorStateRegistry_Initialize_TestFail is AnchorStateRegistry_Init { anchorStateRegistry.initialize( superchainConfig, disputeGameFactory, - optimismPortal2, OutputRoot({ root: Hash.wrap(0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF), l2BlockNumber: 0 - }) + }), + GameType.wrap(0) ); } } @@ -73,6 +74,25 @@ contract AnchorStateRegistry_Version_Test is AnchorStateRegistry_Init { } } +contract AnchorStateRegistry_Paused_Test is AnchorStateRegistry_Init { + /// @notice Tests that paused() will return the correct value. + function test_paused_succeeds() public { + // Pause the superchain. + vm.prank(superchainConfig.guardian()); + superchainConfig.pause("testing"); + + // Paused should return true. + assertTrue(anchorStateRegistry.paused()); + + // Unpause the superchain. + vm.prank(superchainConfig.guardian()); + superchainConfig.unpause(); + + // Paused should return false. + assertFalse(anchorStateRegistry.paused()); + } +} + contract AnchorStateRegistry_GetAnchorRoot_Test is AnchorStateRegistry_Init { /// @notice Tests that getAnchorRoot will return the value of the starting anchor root when no /// anchor game exists yet. @@ -106,6 +126,29 @@ contract AnchorStateRegistry_GetAnchorRoot_Test is AnchorStateRegistry_Init { assertEq(l2BlockNumber, gameProxy.l2BlockNumber()); } + /// @notice Tests that getAnchorRoot will return the latest anchor root even if the superchain + /// is paused. + function test_getAnchorRoot_superchainPaused_succeeds() public { + // Mock the game to be resolved. + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); + vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds() + 1); + + // Mock the game to be the defender wins. + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.status, ()), abi.encode(GameStatus.DEFENDER_WINS)); + + // Set the anchor game to the game proxy. + anchorStateRegistry.setAnchorState(gameProxy); + + // Pause the superchain. + vm.prank(superchainConfig.guardian()); + superchainConfig.pause("testing"); + + // We should get the anchor root back. + (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); + assertEq(root.raw(), gameProxy.rootClaim().raw()); + assertEq(l2BlockNumber, gameProxy.l2BlockNumber()); + } + /// @notice Tests that getAnchorRoot returns even if the anchor game is blacklisted. function test_getAnchorRoot_blacklistedGame_succeeds() public { // Mock the game to be resolved. @@ -118,12 +161,9 @@ contract AnchorStateRegistry_GetAnchorRoot_Test is AnchorStateRegistry_Init { // Set the anchor game to the game proxy. anchorStateRegistry.setAnchorState(gameProxy); - // Mock the disputeGameBlacklist call to return true. - vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.disputeGameBlacklist, (gameProxy)), - abi.encode(true) - ); + // Blacklist the game. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.blacklistDisputeGame(gameProxy); // Get the anchor root. (Hash root, uint256 l2BlockNumber) = anchorStateRegistry.getAnchorRoot(); @@ -172,12 +212,11 @@ contract AnchorStateRegistry_IsGameRegistered_Test is AnchorStateRegistry_Init { contract AnchorStateRegistry_IsGameBlacklisted_Test is AnchorStateRegistry_Init { /// @notice Tests that isGameBlacklisted will return true if the game is blacklisted. function test_isGameBlacklisted_isActuallyBlacklisted_succeeds() public { - // Mock the disputeGameBlacklist call to return true. - vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.disputeGameBlacklist, (gameProxy)), - abi.encode(true) - ); + // Blacklist the game. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.blacklistDisputeGame(gameProxy); + + // Should return true. assertTrue(anchorStateRegistry.isGameBlacklisted(gameProxy)); } @@ -185,8 +224,8 @@ contract AnchorStateRegistry_IsGameBlacklisted_Test is AnchorStateRegistry_Init function test_isGameBlacklisted_isNotBlacklisted_succeeds() public { // Mock the disputeGameBlacklist call to return false. vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.disputeGameBlacklist, (gameProxy)), + address(anchorStateRegistry), + abi.encodeCall(anchorStateRegistry.disputeGameBlacklist, (gameProxy)), abi.encode(false) ); assertFalse(anchorStateRegistry.isGameBlacklisted(gameProxy)); @@ -214,34 +253,35 @@ contract AnchorStateRegistry_IsGameRespected_Test is AnchorStateRegistry_Init { contract AnchorStateRegistry_IsGameRetired_Test is AnchorStateRegistry_Init { /// @notice Tests that isGameRetired will return true if the game is retired. - /// @param _retirementTimestamp The retirement timestamp to use for the test. - function testFuzz_isGameRetired_isRetired_succeeds(uint64 _retirementTimestamp) public { - // Make sure retirement timestamp is greater than or equal to the game's creation time. - _retirementTimestamp = uint64(bound(_retirementTimestamp, gameProxy.createdAt().raw(), type(uint64).max)); + /// @param _createdAtTimestamp The createdAt timestamp to use for the test. + function testFuzz_isGameRetired_isRetired_succeeds(uint64 _createdAtTimestamp) public { + // Set the retirement timestamp to now. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.updateRetirementTimestamp(); + + // Make sure createdAt timestamp is less than or equal to the retirementTimestamp. + _createdAtTimestamp = uint64(bound(_createdAtTimestamp, 0, anchorStateRegistry.retirementTimestamp())); // Mock the respectedGameTypeUpdatedAt call. - vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.respectedGameTypeUpdatedAt, ()), - abi.encode(_retirementTimestamp) - ); + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.createdAt, ()), abi.encode(_createdAtTimestamp)); // Game should be retired. assertTrue(anchorStateRegistry.isGameRetired(gameProxy)); } /// @notice Tests that isGameRetired will return false if the game is not retired. - /// @param _retirementTimestamp The retirement timestamp to use for the test. - function testFuzz_isGameRetired_isNotRetired_succeeds(uint64 _retirementTimestamp) public { - // Make sure retirement timestamp is earlier than the game's creation time. - _retirementTimestamp = uint64(bound(_retirementTimestamp, 0, gameProxy.createdAt().raw() - 1)); + /// @param _createdAtTimestamp The createdAt timestamp to use for the test. + function testFuzz_isGameRetired_isNotRetired_succeeds(uint64 _createdAtTimestamp) public { + // Set the retirement timestamp to now. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.updateRetirementTimestamp(); - // Mock the respectedGameTypeUpdatedAt call to be earlier than the game's creation time. - vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.respectedGameTypeUpdatedAt, ()), - abi.encode(_retirementTimestamp) - ); + // Make sure createdAt timestamp is greater than the retirementTimestamp. + _createdAtTimestamp = + uint64(bound(_createdAtTimestamp, anchorStateRegistry.retirementTimestamp() + 1, type(uint64).max)); + + // Mock the call to createdAt. + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.createdAt, ()), abi.encode(_createdAtTimestamp)); // Game should not be retired. assertFalse(anchorStateRegistry.isGameRetired(gameProxy)); @@ -287,29 +327,38 @@ contract AnchorStateRegistry_IsGameProper_Test is AnchorStateRegistry_Init { /// @notice Tests that isGameProper will return false if the game is blacklisted. function test_isGameProper_isBlacklisted_succeeds() public { - // Mock the disputeGameBlacklist call to return true. - vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.disputeGameBlacklist, (gameProxy)), - abi.encode(true) - ); + // Blacklist the game. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.blacklistDisputeGame(gameProxy); + + // Should return false. + assertFalse(anchorStateRegistry.isGameProper(gameProxy)); + } + + /// @notice Tests that isGameProper will return false if the superchain is paused. + function test_isGameProper_superchainPaused_succeeds() public { + // Pause the superchain. + vm.prank(superchainConfig.guardian()); + superchainConfig.pause("testing"); + // Game should not be proper. assertFalse(anchorStateRegistry.isGameProper(gameProxy)); } /// @notice Tests that isGameProper will return false if the game is retired. - /// @param _retirementTimestamp The retirement timestamp to use for the test. - function testFuzz_isGameProper_isRetired_succeeds(uint64 _retirementTimestamp) public { - // Make sure retirement timestamp is later than the game's creation time. - _retirementTimestamp = uint64(bound(_retirementTimestamp, gameProxy.createdAt().raw() + 1, type(uint64).max)); + /// @param _createdAtTimestamp The createdAt timestamp to use for the test. + function testFuzz_isGameProper_isRetired_succeeds(uint64 _createdAtTimestamp) public { + // Set the retirement timestamp to now. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.updateRetirementTimestamp(); - // Mock the respectedGameTypeUpdatedAt call to be later than the game's creation time. - vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.respectedGameTypeUpdatedAt, ()), - abi.encode(_retirementTimestamp) - ); + // Make sure createdAt timestamp is less than or equal to the retirementTimestamp. + _createdAtTimestamp = uint64(bound(_createdAtTimestamp, 0, anchorStateRegistry.retirementTimestamp())); + // Mock the call to createdAt. + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.createdAt, ()), abi.encode(_createdAtTimestamp)); + + // Game should not be proper. assertFalse(anchorStateRegistry.isGameProper(gameProxy)); } } @@ -476,8 +525,8 @@ contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_Init { function testFuzz_isGameClaimValid_isBlacklisted_succeeds() public { // Mock the disputeGameBlacklist call to return true. vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.disputeGameBlacklist, (gameProxy)), + address(anchorStateRegistry), + abi.encodeCall(anchorStateRegistry.disputeGameBlacklist, (gameProxy)), abi.encode(true) ); @@ -486,17 +535,17 @@ contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_Init { } /// @notice Tests that isGameClaimValid will return false if the game is retired. - /// @param _resolvedAtTimestamp The resolvedAt timestamp to use for the test. - function testFuzz_isGameClaimValid_isRetired_succeeds(uint256 _resolvedAtTimestamp) public { - // Make sure retirement timestamp is later than the game's creation time. - _resolvedAtTimestamp = uint64(bound(_resolvedAtTimestamp, gameProxy.createdAt().raw() + 1, type(uint64).max)); + /// @param _createdAtTimestamp The createdAt timestamp to use for the test. + function testFuzz_isGameClaimValid_isRetired_succeeds(uint256 _createdAtTimestamp) public { + // Set the retirement timestamp to now. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.updateRetirementTimestamp(); - // Mock the respectedGameTypeUpdatedAt call to be later than the game's creation time. - vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.respectedGameTypeUpdatedAt, ()), - abi.encode(_resolvedAtTimestamp) - ); + // Make sure createdAt timestamp is less than or equal to the retirementTimestamp. + _createdAtTimestamp = uint64(bound(_createdAtTimestamp, 0, anchorStateRegistry.retirementTimestamp())); + + // Mock the call to createdAt. + vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.createdAt, ()), abi.encode(_createdAtTimestamp)); // Claim should not be valid. assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy)); @@ -528,6 +577,16 @@ contract AnchorStateRegistry_IsGameClaimValid_Test is AnchorStateRegistry_Init { // Claim should not be valid. assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy)); } + + /// @notice Tests that isGameClaimValid will return false if the superchain is paused. + function test_isGameClaimValid_superchainPaused_succeeds() public { + // Pause the superchain. + vm.prank(superchainConfig.guardian()); + superchainConfig.pause("testing"); + + // Game should not be valid. + assertFalse(anchorStateRegistry.isGameClaimValid(gameProxy)); + } } contract AnchorStateRegistry_SetAnchorState_Test is AnchorStateRegistry_Init { @@ -768,12 +827,9 @@ contract AnchorStateRegistry_SetAnchorState_TestFail is AnchorStateRegistry_Init vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.resolvedAt, ()), abi.encode(block.timestamp)); vm.warp(block.timestamp + optimismPortal2.disputeGameFinalityDelaySeconds() + 1); - // Mock the disputeGameBlacklist call to return true. - vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.disputeGameBlacklist, (gameProxy)), - abi.encode(true) - ); + // Blacklist the game. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.blacklistDisputeGame(gameProxy); // Update the anchor state. vm.prank(address(gameProxy)); @@ -786,8 +842,7 @@ contract AnchorStateRegistry_SetAnchorState_TestFail is AnchorStateRegistry_Init assertEq(updatedRoot.raw(), root.raw()); } - /// @notice Tests that setAnchorState will revert if the game is valid and the game is - /// retired. + /// @notice Tests that setAnchorState will revert if the game is retired. /// @param _l2BlockNumber The L2 block number to use for the game. function testFuzz_setAnchorState_retiredGame_fails(uint256 _l2BlockNumber) public { // Grab block number of the existing anchor root. @@ -802,11 +857,15 @@ contract AnchorStateRegistry_SetAnchorState_TestFail is AnchorStateRegistry_Init // Mock that the game was respected. vm.mockCall(address(gameProxy), abi.encodeCall(gameProxy.wasRespectedGameTypeWhenCreated, ()), abi.encode(true)); - // Mock the respectedGameTypeUpdatedAt call to be later than the game's creation time. + // Set the retirement timestamp. + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.updateRetirementTimestamp(); + + // Mock the call to createdAt. vm.mockCall( - address(optimismPortal2), - abi.encodeCall(optimismPortal2.respectedGameTypeUpdatedAt, ()), - abi.encode(gameProxy.createdAt().raw() + 1) + address(gameProxy), + abi.encodeCall(gameProxy.createdAt, ()), + abi.encode(anchorStateRegistry.retirementTimestamp() - 1) ); // Update the anchor state. @@ -819,4 +878,153 @@ contract AnchorStateRegistry_SetAnchorState_TestFail is AnchorStateRegistry_Init assertEq(updatedL2BlockNumber, l2BlockNumber); assertEq(updatedRoot.raw(), root.raw()); } + + /// @notice Tests that setAnchorState will revert if the superchain is paused. + function test_setAnchorState_superchainPaused_fails() public { + // Pause the superchain. + vm.prank(superchainConfig.guardian()); + superchainConfig.pause("testing"); + + // Update the anchor state. + vm.prank(address(gameProxy)); + vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_InvalidAnchorGame.selector); + anchorStateRegistry.setAnchorState(gameProxy); + } +} + +contract AnchorStateRegistry_setRespectedGameType_Test is AnchorStateRegistry_Init { + /// @notice Tests that setRespectedGameType succeeds when called by the guardian + /// @param _gameType The game type to set as respected + function testFuzz_setRespectedGameType_succeeds(GameType _gameType) public { + // Call as guardian + vm.prank(superchainConfig.guardian()); + vm.expectEmit(address(anchorStateRegistry)); + emit RespectedGameTypeSet(_gameType); + anchorStateRegistry.setRespectedGameType(_gameType); + + // Verify the game type was set + assertEq(anchorStateRegistry.respectedGameType().raw(), _gameType.raw()); + } +} + +contract AnchorStateRegistry_setRespectedGameType_TestFail is AnchorStateRegistry_Init { + /// @notice Tests that setRespectedGameType reverts when not called by the guardian + /// @param _gameType The game type to attempt to set + /// @param _caller The address attempting to call the function + function testFuzz_setRespectedGameType_notGuardian_reverts(GameType _gameType, address _caller) public { + // Ensure caller is not the guardian + vm.assume(_caller != superchainConfig.guardian()); + + // Attempt to call as non-guardian + vm.prank(_caller); + vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_Unauthorized.selector); + anchorStateRegistry.setRespectedGameType(_gameType); + } +} + +contract AnchorStateRegistry_updateRetirementTimestamp_Test is AnchorStateRegistry_Init { + /// @notice Tests that updateRetirementTimestamp succeeds when called by the guardian + function test_updateRetirementTimestamp_succeeds() public { + // Call as guardian + vm.prank(superchainConfig.guardian()); + vm.expectEmit(address(anchorStateRegistry)); + emit RetirementTimestampSet(block.timestamp); + anchorStateRegistry.updateRetirementTimestamp(); + + // Verify the timestamp was set + assertEq(anchorStateRegistry.retirementTimestamp(), block.timestamp); + } + + /// @notice Tests that updateRetirementTimestamp can be called multiple times by the guardian + function test_updateRetirementTimestamp_multipleUpdates_succeeds() public { + // First update + vm.prank(superchainConfig.guardian()); + anchorStateRegistry.updateRetirementTimestamp(); + uint64 firstTimestamp = anchorStateRegistry.retirementTimestamp(); + + // Warp forward and update again + vm.warp(block.timestamp + 1000); + vm.prank(superchainConfig.guardian()); + vm.expectEmit(address(anchorStateRegistry)); + emit RetirementTimestampSet(block.timestamp); + anchorStateRegistry.updateRetirementTimestamp(); + + // Verify the timestamp was updated + assertEq(anchorStateRegistry.retirementTimestamp(), block.timestamp); + assertGt(anchorStateRegistry.retirementTimestamp(), firstTimestamp); + } +} + +contract AnchorStateRegistry_updateRetirementTimestamp_TestFail is AnchorStateRegistry_Init { + /// @notice Tests that updateRetirementTimestamp reverts when not called by the guardian + /// @param _caller The address attempting to call the function + function testFuzz_updateRetirementTimestamp_notGuardian_reverts(address _caller) public { + // Ensure caller is not the guardian + vm.assume(_caller != superchainConfig.guardian()); + + // Attempt to call as non-guardian + vm.prank(_caller); + vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_Unauthorized.selector); + anchorStateRegistry.updateRetirementTimestamp(); + } +} + +contract AnchorStateRegistry_blacklistDisputeGame_Test is AnchorStateRegistry_Init { + /// @notice Tests that blacklistDisputeGame succeeds when called by the guardian + function test_blacklistDisputeGame_succeeds() public { + // Call as guardian + vm.prank(superchainConfig.guardian()); + vm.expectEmit(address(anchorStateRegistry)); + emit DisputeGameBlacklisted(gameProxy); + anchorStateRegistry.blacklistDisputeGame(gameProxy); + + // Verify the game was blacklisted + assertTrue(anchorStateRegistry.disputeGameBlacklist(gameProxy)); + } + + /// @notice Tests that multiple games can be blacklisted + function test_blacklistDisputeGame_multipleGames_succeeds() public { + // Create a second game proxy + IDisputeGame secondGame = IDisputeGame(address(0x123)); + + // Blacklist both games + vm.startPrank(superchainConfig.guardian()); + anchorStateRegistry.blacklistDisputeGame(gameProxy); + anchorStateRegistry.blacklistDisputeGame(secondGame); + vm.stopPrank(); + + // Verify both games are blacklisted + assertTrue(anchorStateRegistry.disputeGameBlacklist(gameProxy)); + assertTrue(anchorStateRegistry.disputeGameBlacklist(secondGame)); + } +} + +contract AnchorStateRegistry_blacklistDisputeGame_TestFail is AnchorStateRegistry_Init { + /// @notice Tests that blacklistDisputeGame reverts when not called by the guardian + /// @param _caller The address attempting to call the function + function testFuzz_blacklistDisputeGame_notGuardian_reverts(address _caller) public { + // Ensure caller is not the guardian + vm.assume(_caller != superchainConfig.guardian()); + + // Attempt to call as non-guardian + vm.prank(_caller); + vm.expectRevert(IAnchorStateRegistry.AnchorStateRegistry_Unauthorized.selector); + anchorStateRegistry.blacklistDisputeGame(gameProxy); + } + + /// @notice Tests that blacklisting a game twice succeeds but doesn't change state + function test_blacklistDisputeGame_twice_succeeds() public { + // Blacklist the game + vm.startPrank(superchainConfig.guardian()); + anchorStateRegistry.blacklistDisputeGame(gameProxy); + + // Blacklist again - should emit event but not change state + vm.expectEmit(address(anchorStateRegistry)); + emit DisputeGameBlacklisted(gameProxy); + anchorStateRegistry.blacklistDisputeGame(gameProxy); + vm.stopPrank(); + + // Verify the game is still blacklisted + assertTrue(anchorStateRegistry.disputeGameBlacklist(gameProxy)); + } } diff --git a/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol b/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol index a90f62ea68f..a655fcf12f3 100644 --- a/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol +++ b/packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol @@ -96,6 +96,12 @@ contract FaultDisputeGame_Init is DisputeGameFactory_Init { // Register the game implementation with the factory. disputeGameFactory.setImplementation(GAME_TYPE, gameImpl); uint256 bondAmount = disputeGameFactory.initBonds(GAME_TYPE); + + // Warp ahead of the game retirement timestamp if needed. + if (block.timestamp <= anchorStateRegistry.retirementTimestamp()) { + vm.warp(anchorStateRegistry.retirementTimestamp() + 1); + } + // Create a new game. gameProxy = IFaultDisputeGame( payable(address(disputeGameFactory.create{ value: bondAmount }(GAME_TYPE, rootClaim, extraData))) diff --git a/packages/contracts-bedrock/test/dispute/SuperFaultDisputeGame.t.sol b/packages/contracts-bedrock/test/dispute/SuperFaultDisputeGame.t.sol index 099a85e56c0..919deaa2c96 100644 --- a/packages/contracts-bedrock/test/dispute/SuperFaultDisputeGame.t.sol +++ b/packages/contracts-bedrock/test/dispute/SuperFaultDisputeGame.t.sol @@ -98,7 +98,12 @@ contract SuperFaultDisputeGame_Init is DisputeGameFactory_Init { uint256 bondAmount = disputeGameFactory.initBonds(GAME_TYPE); vm.prank(superchainConfig.guardian()); - optimismPortal2.setRespectedGameType(GAME_TYPE); + anchorStateRegistry.setRespectedGameType(GAME_TYPE); + + // Warp ahead of the game retirement timestamp if needed. + if (block.timestamp <= anchorStateRegistry.retirementTimestamp()) { + vm.warp(anchorStateRegistry.retirementTimestamp() + 1); + } // Create a new game. gameProxy = IFaultDisputeGame( diff --git a/packages/contracts-bedrock/test/invariants/OptimismPortal2.t.sol b/packages/contracts-bedrock/test/invariants/OptimismPortal2.t.sol index 15ce33c252b..8ad36653893 100644 --- a/packages/contracts-bedrock/test/invariants/OptimismPortal2.t.sol +++ b/packages/contracts-bedrock/test/invariants/OptimismPortal2.t.sol @@ -14,7 +14,6 @@ import { ResourceMetering } from "src/L1/ResourceMetering.sol"; import { Constants } from "src/libraries/Constants.sol"; import { Types } from "src/libraries/Types.sol"; import "src/dispute/lib/Types.sol"; -import "src/libraries/PortalErrors.sol"; // Interfaces import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; @@ -119,7 +118,7 @@ contract OptimismPortal2_Invariant_Harness is CommonTest { }); // Warp forward in time to ensure that the game is created after the retirement timestamp. - vm.warp(optimismPortal2.respectedGameTypeUpdatedAt() + 1 seconds); + vm.warp(anchorStateRegistry.retirementTimestamp() + 1); // Create a dispute game with the output root we've proposed. _proposedBlockNumber = 0xFF; @@ -140,7 +139,7 @@ contract OptimismPortal2_Invariant_Harness is CommonTest { game.resolve(); // Fund the portal so that we can withdraw ETH. - vm.deal(address(optimismPortal2), 0xFFFFFFFF); + vm.deal(address(ethLockbox), 0xFFFFFFFF); } } @@ -188,7 +187,7 @@ contract OptimismPortal2_CannotTimeTravel is OptimismPortal2_Invariant_Harness { /// A withdrawal that has been proven should not be able to be finalized /// until after the proof maturity period has elapsed. function invariant_cannotFinalizeBeforePeriodHasPassed() external { - vm.expectRevert("OptimismPortal: proven withdrawal has not matured yet"); + vm.expectRevert(IOptimismPortal2.OptimismPortal_ProofNotOldEnough.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } } @@ -217,7 +216,7 @@ contract OptimismPortal2_CannotFinalizeTwice is OptimismPortal2_Invariant_Harnes /// Ensures that there is no chain of calls that can be made that allows a withdrawal to be /// finalized twice. function invariant_cannotFinalizeTwice() external { - vm.expectRevert(AlreadyFinalized.selector); + vm.expectRevert(IOptimismPortal2.OptimismPortal_AlreadyFinalized.selector); optimismPortal2.finalizeWithdrawalTransaction(_defaultTx); } } diff --git a/packages/contracts-bedrock/test/invariants/SystemConfig.t.sol b/packages/contracts-bedrock/test/invariants/SystemConfig.t.sol index 4ca84d96b02..2107e15dc50 100644 --- a/packages/contracts-bedrock/test/invariants/SystemConfig.t.sol +++ b/packages/contracts-bedrock/test/invariants/SystemConfig.t.sol @@ -45,7 +45,8 @@ contract SystemConfig_GasLimitBoundaries_Invariant is Test { disputeGameFactory: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0) - }) + }), + 1234 // _l2ChainId ) ) ); diff --git a/packages/contracts-bedrock/test/kontrol/proofs/OptimismPortal2.k.sol b/packages/contracts-bedrock/test/kontrol/proofs/OptimismPortal2.k.sol index 7d7b49e3a0c..756b225751e 100644 --- a/packages/contracts-bedrock/test/kontrol/proofs/OptimismPortal2.k.sol +++ b/packages/contracts-bedrock/test/kontrol/proofs/OptimismPortal2.k.sol @@ -6,7 +6,6 @@ import { KontrolUtils } from "./utils/KontrolUtils.sol"; import { Types } from "src/libraries/Types.sol"; import { IOptimismPortal2 as OptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; import { ISuperchainConfig as SuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; -import "src/libraries/PortalErrors.sol"; contract OptimismPortal2Kontrol is DeploymentSummaryFaultProofs, KontrolUtils { OptimismPortal optimismPortal; @@ -26,7 +25,7 @@ contract OptimismPortal2Kontrol is DeploymentSummaryFaultProofs, KontrolUtils { vm.prank(optimismPortal.guardian()); superchainConfig.pause("identifier"); - vm.expectRevert(CallPaused.selector); + vm.expectRevert(OptimismPortal.OptimismPortal_CallPaused.selector); optimismPortal.finalizeWithdrawalTransaction(_tx); } @@ -47,7 +46,7 @@ contract OptimismPortal2Kontrol is DeploymentSummaryFaultProofs, KontrolUtils { vm.prank(optimismPortal.guardian()); superchainConfig.pause("identifier"); - vm.expectRevert(CallPaused.selector); + vm.expectRevert(OptimismPortal.OptimismPortal_CallPaused.selector); optimismPortal.proveWithdrawalTransaction(_tx, _l2OutputIndex, _outputRootProof, _withdrawalProof); } diff --git a/packages/contracts-bedrock/test/libraries/Encoding.t.sol b/packages/contracts-bedrock/test/libraries/Encoding.t.sol index 277cce328dc..76ab3343d74 100644 --- a/packages/contracts-bedrock/test/libraries/Encoding.t.sol +++ b/packages/contracts-bedrock/test/libraries/Encoding.t.sol @@ -1,17 +1,24 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.15; -// Testing utilities +// Testing import { CommonTest } from "test/setup/CommonTest.sol"; // Libraries +import { Encoding } from "src/libraries/Encoding.sol"; import { Types } from "src/libraries/Types.sol"; import { LegacyCrossDomainUtils } from "src/libraries/LegacyCrossDomainUtils.sol"; -// Target contract -import { Encoding } from "src/libraries/Encoding.sol"; +contract Encoding_TestInit is CommonTest { + Encoding_Harness encoding; -contract Encoding_Test is CommonTest { + function setUp() public override { + super.setUp(); + encoding = new Encoding_Harness(); + } +} + +contract Encoding_Test is Encoding_TestInit { /// @dev Tests encoding and decoding a nonce and version. function testFuzz_nonceVersioning_succeeds(uint240 _nonce, uint16 _version) external pure { (uint240 nonce, uint16 version) = Encoding.decodeVersionedNonce(Encoding.encodeVersionedNonce(_nonce, _version)); @@ -77,8 +84,6 @@ contract Encoding_Test is CommonTest { uint256 minInvalidNonce = (uint256(type(uint240).max) + 1) * 2; nonce = bound(nonce, minInvalidNonce, type(uint256).max); - EncodingContract encoding = new EncodingContract(); - vm.expectRevert(bytes("Encoding: unknown cross domain message version")); encoding.encodeCrossDomainMessage(nonce, address(this), address(this), 1, 100, hex""); } @@ -107,7 +112,182 @@ contract Encoding_Test is CommonTest { } } -contract EncodingContract { +contract Encoding_encodeSuperRootProof_Test is Encoding_TestInit { + /// @notice Tests successful encoding of a valid super root proof + /// @param _timestamp The timestamp of the super root proof + /// @param _length The number of output roots in the super root proof + /// @param _seed The seed used to generate the output roots + function testFuzz_encodeSuperRootProof_succeeds(uint64 _timestamp, uint256 _length, uint256 _seed) external pure { + // Ensure at least 1 element and cap at a reasonable maximum to avoid gas issues + _length = uint256(bound(_length, 1, 50)); + + // Create output roots array + Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](_length); + + // Generate deterministic chain IDs and roots based on the seed + for (uint256 i = 0; i < _length; i++) { + // Use different derivations of the seed for each value + uint256 chainId = uint256(keccak256(abi.encode(_seed, "chainId", i))); + bytes32 root = keccak256(abi.encode(_seed, "root", i)); + + outputRoots[i] = Types.OutputRootWithChainId({ chainId: chainId, root: root }); + } + + // Create the super root proof + Types.SuperRootProof memory proof = + Types.SuperRootProof({ version: 0x01, timestamp: _timestamp, outputRoots: outputRoots }); + + // Encode the proof + bytes memory encoded = Encoding.encodeSuperRootProof(proof); + + // Verify encoding structure + assertEq(encoded[0], bytes1(0x01), "Version byte should be 0x01"); + + // Verify timestamp (bytes 1-8) + bytes8 encodedTimestamp; + for (uint256 i = 0; i < 8; i++) { + encodedTimestamp |= bytes8(encoded[i + 1]) >> (i * 8); + } + assertEq(uint64(encodedTimestamp), _timestamp, "Timestamp should match"); + + // Verify each chain ID and root is encoded correctly + uint256 offset = 9; // 1 byte version + 8 bytes timestamp + for (uint256 i = 0; i < _length; i++) { + // Extract chain ID (32 bytes) + uint256 encodedChainId; + assembly { + // Load 32 bytes from encoded at position offset + encodedChainId := mload(add(add(encoded, 32), offset)) + } + assertEq(encodedChainId, outputRoots[i].chainId, "Chain ID should match"); + offset += 32; + + // Extract root (32 bytes) + bytes32 encodedRoot; + assembly { + // Load 32 bytes from encoded at position offset + encodedRoot := mload(add(add(encoded, 32), offset)) + } + assertEq(encodedRoot, outputRoots[i].root, "Root should match"); + offset += 32; + } + + // Verify total length + assertEq(encoded.length, 9 + (_length * 64), "Encoded length should match expected"); + } + + /// @notice Tests encoding with a single output root + function test_encodeSuperRootProof_singleOutputRoot_succeeds() external pure { + // Create a single output root + Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](1); + outputRoots[0] = Types.OutputRootWithChainId({ chainId: 10, root: bytes32(uint256(0xdeadbeef)) }); + + // Create the super root proof + Types.SuperRootProof memory proof = + Types.SuperRootProof({ version: 0x01, timestamp: 1234567890, outputRoots: outputRoots }); + + // Encode the proof + bytes memory encoded = Encoding.encodeSuperRootProof(proof); + + // Expected: 1 byte version + 8 bytes timestamp + (32 bytes chainId + 32 bytes root) + assertEq(encoded.length, 1 + 8 + 64, "Encoded length should be 73 bytes"); + assertEq(encoded[0], bytes1(0x01), "First byte should be version 0x01"); + } + + /// @notice Tests encoding with multiple output roots + function test_encodeSuperRootProof_multipleOutputRoots_succeeds() external pure { + // Create multiple output roots + Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](3); + outputRoots[0] = Types.OutputRootWithChainId({ chainId: 10, root: bytes32(uint256(0xdeadbeef)) }); + outputRoots[1] = Types.OutputRootWithChainId({ chainId: 20, root: bytes32(uint256(0xbeefcafe)) }); + outputRoots[2] = Types.OutputRootWithChainId({ chainId: 30, root: bytes32(uint256(0xcafebabe)) }); + + // Create the super root proof + Types.SuperRootProof memory proof = + Types.SuperRootProof({ version: 0x01, timestamp: 1234567890, outputRoots: outputRoots }); + + // Encode the proof + bytes memory encoded = Encoding.encodeSuperRootProof(proof); + + // Expected: 1 byte version + 8 bytes timestamp + 3 * (32 bytes chainId + 32 bytes root) + assertEq(encoded.length, 1 + 8 + (3 * 64), "Encoded length should be 201 bytes"); + } + + /// @notice Tests that the Solidity impl of encodeSuperRootProof matches the FFI impl + /// @param _timestamp The timestamp of the super root proof + /// @param _length The number of output roots in the super root proof + /// @param _seed The seed used to generate the output roots + function testDiff_encodeSuperRootProof_succeeds(uint64 _timestamp, uint256 _length, uint256 _seed) external { + // Ensure at least 1 element and cap at a reasonable maximum to avoid gas issues + _length = uint256(bound(_length, 1, 50)); + + // Create output roots array + Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](_length); + + // Generate deterministic chain IDs and roots based on the seed + for (uint256 i = 0; i < _length; i++) { + // Use different derivations of the seed for each value + uint256 chainId = uint256(keccak256(abi.encode(_seed, "chainId", i))); + bytes32 root = keccak256(abi.encode(_seed, "root", i)); + + outputRoots[i] = Types.OutputRootWithChainId({ chainId: chainId, root: root }); + } + + // Create the super root proof + Types.SuperRootProof memory proof = + Types.SuperRootProof({ version: 0x01, timestamp: _timestamp, outputRoots: outputRoots }); + + // Encode using the Solidity implementation + bytes memory encoding1 = Encoding.encodeSuperRootProof(proof); + + // Encode using the FFI implementation + bytes memory encoding2 = ffi.encodeSuperRootProof(proof); + + // Compare the results + assertEq(encoding1, encoding2, "Solidity and FFI implementations should match"); + } +} + +contract Encoding_encodeSuperRootProof_TestFail is Encoding_TestInit { + /// @notice Tests that encoding fails when version is not 0x01 + /// @param _version The version to use for the super root proof + /// @param _timestamp The timestamp of the super root proof + function testFuzz_encodeSuperRootProof_invalidVersion_reverts(bytes1 _version, uint64 _timestamp) external { + // Ensure version is not 0x01 + if (_version == 0x01) { + _version = 0x02; + } + + // Create a minimal valid output roots array + Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](1); + outputRoots[0] = Types.OutputRootWithChainId({ chainId: 1, root: bytes32(uint256(1)) }); + + // Create the super root proof with invalid version + Types.SuperRootProof memory proof = + Types.SuperRootProof({ version: _version, timestamp: _timestamp, outputRoots: outputRoots }); + + // Expect revert when encoding + vm.expectRevert(Encoding.Encoding_InvalidSuperRootVersion.selector); + encoding.encodeSuperRootProof(proof); + } + + /// @notice Tests that encoding fails when output roots array is empty + /// @param _timestamp The timestamp of the super root proof + function testFuzz_encodeSuperRootProof_emptyOutputRoots_reverts(uint64 _timestamp) external { + // Create an empty output roots array + Types.OutputRootWithChainId[] memory outputRoots = new Types.OutputRootWithChainId[](0); + + // Create the super root proof with empty output roots + Types.SuperRootProof memory proof = + Types.SuperRootProof({ version: 0x01, timestamp: _timestamp, outputRoots: outputRoots }); + + // Expect revert when encoding + vm.expectRevert(Encoding.Encoding_EmptySuperRoot.selector); + encoding.encodeSuperRootProof(proof); + } +} + +contract Encoding_Harness { function encodeCrossDomainMessage( uint256 nonce, address sender, @@ -122,4 +302,8 @@ contract EncodingContract { { return Encoding.encodeCrossDomainMessage(nonce, sender, target, value, gasLimit, data); } + + function encodeSuperRootProof(Types.SuperRootProof memory proof) external pure returns (bytes memory) { + return Encoding.encodeSuperRootProof(proof); + } } diff --git a/packages/contracts-bedrock/test/libraries/Hashing.t.sol b/packages/contracts-bedrock/test/libraries/Hashing.t.sol index 45242acb088..73aef7cb220 100644 --- a/packages/contracts-bedrock/test/libraries/Hashing.t.sol +++ b/packages/contracts-bedrock/test/libraries/Hashing.t.sol @@ -12,6 +12,12 @@ import { LegacyCrossDomainUtils } from "src/libraries/LegacyCrossDomainUtils.sol // Target contract import { Hashing } from "src/libraries/Hashing.sol"; +contract Hashing_Harness { + function hashSuperRootProof(Types.SuperRootProof memory _proof) external pure returns (bytes32) { + return Hashing.hashSuperRootProof(_proof); + } +} + contract Hashing_hashDepositSource_Test is CommonTest { /// @notice Tests that hashDepositSource returns the correct hash in a simple case. function test_hashDepositSource_succeeds() external pure { @@ -136,3 +142,51 @@ contract Hashing_hashDepositTransaction_Test is CommonTest { ); } } + +contract Hashing_hashSuperRootProof_Test is CommonTest { + Hashing_Harness internal harness; + + /// @notice Sets up the test. + function setUp() public override { + super.setUp(); + harness = new Hashing_Harness(); + } + + /// @notice Tests that the Solidity impl of hashSuperRootProof matches the FFI impl + /// @param _proof The super root proof to test. + function testDiff_hashSuperRootProof_succeeds(Types.SuperRootProof memory _proof) external { + // Make sure the proof has the right version. + _proof.version = 0x01; + + // Make sure the proof has at least one output root. + if (_proof.outputRoots.length == 0) { + _proof.outputRoots = new Types.OutputRootWithChainId[](1); + _proof.outputRoots[0] = Types.OutputRootWithChainId({ + chainId: vm.randomUint(0, type(uint64).max), + root: bytes32(vm.randomUint()) + }); + } + + // Encode using the Solidity implementation + bytes32 hash1 = harness.hashSuperRootProof(_proof); + + // Encode using the FFI implementation + bytes32 hash2 = ffi.hashSuperRootProof(_proof); + + // Compare the results + assertEq(hash1, hash2, "Solidity and FFI implementations should match"); + } + + /// @notice Tests that hashSuperRootProof reverts when the version is incorrect. + /// @param _proof The super root proof to test. + function testFuzz_hashSuperRootProof_wrongVersion_reverts(Types.SuperRootProof memory _proof) external { + // 0x01 is the correct version, so we need any other version. + if (_proof.version == 0x01) { + _proof.version = 0x00; + } + + // Should always revert when the version is incorrect. + vm.expectRevert(Encoding.Encoding_InvalidSuperRootVersion.selector); + harness.hashSuperRootProof(_proof); + } +} diff --git a/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol b/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol index 85680343ccc..07d577e6422 100644 --- a/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol +++ b/packages/contracts-bedrock/test/opcm/DeployImplementations.t.sol @@ -17,7 +17,7 @@ import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.so import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol"; import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; import { IL1ERC721Bridge } from "interfaces/L1/IL1ERC721Bridge.sol"; @@ -25,6 +25,7 @@ import { IL1StandardBridge } from "interfaces/L1/IL1StandardBridge.sol"; import { IOptimismMintableERC20Factory } from "interfaces/universal/IOptimismMintableERC20Factory.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { IProxy } from "interfaces/universal/IProxy.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { DeployImplementationsInput, @@ -88,7 +89,8 @@ contract DeployImplementationsOutput_Test is Test { function test_set_succeeds() public { IOPContractsManager opcm = IOPContractsManager(address(makeAddr("opcm"))); - IOptimismPortal2 optimismPortalImpl = IOptimismPortal2(payable(makeAddr("optimismPortalImpl"))); + IOptimismPortal optimismPortalImpl = IOptimismPortal(payable(makeAddr("optimismPortalImpl"))); + IETHLockbox ethLockboxImpl = IETHLockbox(payable(makeAddr("ethLockboxImpl"))); IDelayedWETH delayedWETHImpl = IDelayedWETH(payable(makeAddr("delayedWETHImpl"))); IPreimageOracle preimageOracleSingleton = IPreimageOracle(makeAddr("preimageOracleSingleton")); IMIPS mipsSingleton = IMIPS(makeAddr("mipsSingleton")); @@ -104,6 +106,7 @@ contract DeployImplementationsOutput_Test is Test { vm.etch(address(opcm), hex"01"); vm.etch(address(optimismPortalImpl), hex"01"); + vm.etch(address(ethLockboxImpl), hex"01"); vm.etch(address(delayedWETHImpl), hex"01"); vm.etch(address(preimageOracleSingleton), hex"01"); vm.etch(address(mipsSingleton), hex"01"); @@ -116,6 +119,7 @@ contract DeployImplementationsOutput_Test is Test { vm.etch(address(anchorStateRegistryImpl), hex"01"); dio.set(dio.opcm.selector, address(opcm)); dio.set(dio.optimismPortalImpl.selector, address(optimismPortalImpl)); + dio.set(dio.ethLockboxImpl.selector, address(ethLockboxImpl)); dio.set(dio.delayedWETHImpl.selector, address(delayedWETHImpl)); dio.set(dio.preimageOracleSingleton.selector, address(preimageOracleSingleton)); dio.set(dio.mipsSingleton.selector, address(mipsSingleton)); @@ -139,6 +143,7 @@ contract DeployImplementationsOutput_Test is Test { assertEq(address(optimismMintableERC20FactoryImpl), address(dio.optimismMintableERC20FactoryImpl()), "900"); assertEq(address(disputeGameFactoryImpl), address(dio.disputeGameFactoryImpl()), "950"); assertEq(address(anchorStateRegistryImpl), address(dio.anchorStateRegistryImpl()), "960"); + assertEq(address(ethLockboxImpl), address(dio.ethLockboxImpl()), "1000"); } function test_getters_whenNotSet_reverts() public { @@ -147,6 +152,9 @@ contract DeployImplementationsOutput_Test is Test { vm.expectRevert(expectedErr); dio.optimismPortalImpl(); + vm.expectRevert(expectedErr); + dio.ethLockboxImpl(); + vm.expectRevert(expectedErr); dio.delayedWETHImpl(); @@ -186,6 +194,10 @@ contract DeployImplementationsOutput_Test is Test { vm.expectRevert(expectedErr); dio.optimismPortalImpl(); + dio.set(dio.ethLockboxImpl.selector, emptyAddr); + vm.expectRevert(expectedErr); + dio.ethLockboxImpl(); + dio.set(dio.delayedWETHImpl.selector, emptyAddr); vm.expectRevert(expectedErr); dio.delayedWETHImpl(); @@ -284,11 +296,12 @@ contract DeployImplementations_Test is Test { deployImplementations.deployL1StandardBridgeImpl(dio); deployImplementations.deployOptimismMintableERC20FactoryImpl(dio); deployImplementations.deployOptimismPortalImpl(dii, dio); + deployImplementations.deployETHLockboxImpl(dio); deployImplementations.deployDelayedWETHImpl(dii, dio); deployImplementations.deployPreimageOracleSingleton(dii, dio); deployImplementations.deployMipsSingleton(dii, dio); deployImplementations.deployDisputeGameFactoryImpl(dio); - deployImplementations.deployAnchorStateRegistryImpl(dio); + deployImplementations.deployAnchorStateRegistryImpl(dii, dio); deployImplementations.deployOPContractsManager(dii, dio); // Store the original addresses. @@ -298,6 +311,7 @@ contract DeployImplementations_Test is Test { address l1StandardBridgeImpl = address(dio.l1StandardBridgeImpl()); address optimismMintableERC20FactoryImpl = address(dio.optimismMintableERC20FactoryImpl()); address optimismPortalImpl = address(dio.optimismPortalImpl()); + address ethLockboxImpl = address(dio.ethLockboxImpl()); address delayedWETHImpl = address(dio.delayedWETHImpl()); address preimageOracleSingleton = address(dio.preimageOracleSingleton()); address mipsSingleton = address(dio.mipsSingleton()); @@ -312,11 +326,12 @@ contract DeployImplementations_Test is Test { deployImplementations.deployL1StandardBridgeImpl(dio); deployImplementations.deployOptimismMintableERC20FactoryImpl(dio); deployImplementations.deployOptimismPortalImpl(dii, dio); + deployImplementations.deployETHLockboxImpl(dio); deployImplementations.deployDelayedWETHImpl(dii, dio); deployImplementations.deployPreimageOracleSingleton(dii, dio); deployImplementations.deployMipsSingleton(dii, dio); deployImplementations.deployDisputeGameFactoryImpl(dio); - deployImplementations.deployAnchorStateRegistryImpl(dio); + deployImplementations.deployAnchorStateRegistryImpl(dii, dio); deployImplementations.deployOPContractsManager(dii, dio); // Assert that the addresses did not change. @@ -332,6 +347,7 @@ contract DeployImplementations_Test is Test { assertEq(disputeGameFactoryImpl, address(dio.disputeGameFactoryImpl()), "1000"); assertEq(anchorStateRegistryImpl, address(dio.anchorStateRegistryImpl()), "1100"); assertEq(opcm, address(dio.opcm()), "1200"); + assertEq(ethLockboxImpl, address(dio.ethLockboxImpl()), "1300"); } function testFuzz_run_memory_succeeds(bytes32 _seed) public { diff --git a/packages/contracts-bedrock/test/opcm/SetDisputeGameImpl.t.sol b/packages/contracts-bedrock/test/opcm/SetDisputeGameImpl.t.sol index 2dca24b486b..1ece4e242bb 100644 --- a/packages/contracts-bedrock/test/opcm/SetDisputeGameImpl.t.sol +++ b/packages/contracts-bedrock/test/opcm/SetDisputeGameImpl.t.sol @@ -4,15 +4,14 @@ pragma solidity ^0.8.0; import { Test } from "forge-std/Test.sol"; import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; -import { GameType } from "src/dispute/lib/Types.sol"; +import { GameType, OutputRoot, Hash } from "src/dispute/lib/Types.sol"; import { SetDisputeGameImpl, SetDisputeGameImplInput } from "scripts/deploy/SetDisputeGameImpl.s.sol"; import { DisputeGameFactory } from "src/dispute/DisputeGameFactory.sol"; import { Proxy } from "src/universal/Proxy.sol"; -import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; -import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; +import { AnchorStateRegistry } from "src/dispute/AnchorStateRegistry.sol"; +import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; contract SetDisputeGameImplInput_Test is Test { SetDisputeGameImplInput input; @@ -70,7 +69,7 @@ contract SetDisputeGameImpl_Test is Test { SetDisputeGameImpl script; SetDisputeGameImplInput input; IDisputeGameFactory factory; - IOptimismPortal2 portal; + IAnchorStateRegistry anchorStateRegistry; address mockImpl; uint32 gameType; @@ -78,8 +77,8 @@ contract SetDisputeGameImpl_Test is Test { script = new SetDisputeGameImpl(); input = new SetDisputeGameImplInput(); DisputeGameFactory dgfImpl = new DisputeGameFactory(); - OptimismPortal2 portalImpl = new OptimismPortal2(0, 0); SuperchainConfig supConfigImpl = new SuperchainConfig(); + AnchorStateRegistry anchorStateRegistryImpl = new AnchorStateRegistry(0); Proxy supConfigProxy = new Proxy(address(1)); vm.prank(address(1)); @@ -92,21 +91,21 @@ contract SetDisputeGameImpl_Test is Test { factoryProxy.upgradeToAndCall(address(dgfImpl), abi.encodeCall(dgfImpl.initialize, (address(this)))); factory = IDisputeGameFactory(address(factoryProxy)); - Proxy portalProxy = new Proxy(address(1)); + Proxy anchorStateRegistryProxy = new Proxy(address(1)); vm.prank(address(1)); - portalProxy.upgradeToAndCall( - address(portalImpl), + anchorStateRegistryProxy.upgradeToAndCall( + address(anchorStateRegistryImpl), abi.encodeCall( - portalImpl.initialize, + anchorStateRegistryImpl.initialize, ( - factory, - ISystemConfig(makeAddr("sysConfig")), ISuperchainConfig(address(supConfigProxy)), + factory, + OutputRoot({ root: Hash.wrap(0), l2BlockNumber: 0 }), GameType.wrap(100) ) ) ); - portal = IOptimismPortal2(payable(address(portalProxy))); + anchorStateRegistry = IAnchorStateRegistry(address(anchorStateRegistryProxy)); mockImpl = makeAddr("impl"); gameType = 999; @@ -115,7 +114,7 @@ contract SetDisputeGameImpl_Test is Test { function test_run_succeeds() public { input.set(input.factory.selector, address(factory)); input.set(input.impl.selector, mockImpl); - input.set(input.portal.selector, address(portal)); + input.set(input.anchorStateRegistry.selector, address(anchorStateRegistry)); input.set(input.gameType.selector, gameType); script.run(input); @@ -124,7 +123,7 @@ contract SetDisputeGameImpl_Test is Test { function test_run_whenImplAlreadySet_reverts() public { input.set(input.factory.selector, address(factory)); input.set(input.impl.selector, mockImpl); - input.set(input.portal.selector, address(portal)); + input.set(input.anchorStateRegistry.selector, address(anchorStateRegistry)); input.set(input.gameType.selector, gameType); // First run should succeed diff --git a/packages/contracts-bedrock/test/opcm/UpgradeOPChain.t.sol b/packages/contracts-bedrock/test/opcm/UpgradeOPChain.t.sol index 06e20c078c1..8b4944be707 100644 --- a/packages/contracts-bedrock/test/opcm/UpgradeOPChain.t.sol +++ b/packages/contracts-bedrock/test/opcm/UpgradeOPChain.t.sol @@ -55,7 +55,8 @@ contract UpgradeOPChainInput_Test is Test { configs[0] = OPContractsManager.OpChainConfig({ systemConfigProxy: ISystemConfig(systemConfig1), proxyAdmin: IProxyAdmin(proxyAdmin1), - absolutePrestate: Claim.wrap(bytes32(uint256(1))) + absolutePrestate: Claim.wrap(bytes32(uint256(1))), + disputeGameUsesSuperRoots: false }); // Setup mock addresses and contracts for second config @@ -67,7 +68,8 @@ contract UpgradeOPChainInput_Test is Test { configs[1] = OPContractsManager.OpChainConfig({ systemConfigProxy: ISystemConfig(systemConfig2), proxyAdmin: IProxyAdmin(proxyAdmin2), - absolutePrestate: Claim.wrap(bytes32(uint256(2))) + absolutePrestate: Claim.wrap(bytes32(uint256(2))), + disputeGameUsesSuperRoots: false }); input.set(input.opChainConfigs.selector, configs); @@ -111,7 +113,8 @@ contract UpgradeOPChainInput_Test is Test { configs[0] = OPContractsManager.OpChainConfig({ systemConfigProxy: ISystemConfig(mockSystemConfig), proxyAdmin: IProxyAdmin(mockProxyAdmin), - absolutePrestate: Claim.wrap(bytes32(uint256(1))) + absolutePrestate: Claim.wrap(bytes32(uint256(1))), + disputeGameUsesSuperRoots: false }); vm.expectRevert("UpgradeOPCMInput: unknown selector"); @@ -147,7 +150,8 @@ contract UpgradeOPChain_Test is Test { config = OPContractsManager.OpChainConfig({ systemConfigProxy: ISystemConfig(makeAddr("systemConfigProxy")), proxyAdmin: IProxyAdmin(makeAddr("proxyAdmin")), - absolutePrestate: Claim.wrap(keccak256("absolutePrestate")) + absolutePrestate: Claim.wrap(keccak256("absolutePrestate")), + disputeGameUsesSuperRoots: false }); OPContractsManager.OpChainConfig[] memory configs = new OPContractsManager.OpChainConfig[](1); configs[0] = config; diff --git a/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol b/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol index f17ec2204f7..cdecf1110fa 100644 --- a/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol +++ b/packages/contracts-bedrock/test/safe/DeputyGuardianModule.t.sol @@ -14,17 +14,14 @@ import "src/dispute/lib/Types.sol"; // Interfaces import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; -import { IFaultDisputeGame } from "interfaces/dispute/IFaultDisputeGame.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; import { DeployUtils } from "scripts/libraries/DeployUtils.sol"; contract DeputyGuardianModule_TestInit is CommonTest, SafeTestTools { using SafeTestLib for SafeInstance; - error Unauthorized(); - error ExecutionFailed(string); - event ExecutionFromModuleSuccess(address indexed); + event RetirementTimestampUpdated(Timestamp indexed); IDeputyGuardianModule deputyGuardianModule; SafeInstance safeInstance; @@ -91,7 +88,7 @@ contract DeputyGuardianModule_Pause_Test is DeputyGuardianModule_TestInit { contract DeputyGuardianModule_Pause_TestFail is DeputyGuardianModule_TestInit { /// @dev Tests that `pause` reverts when called by a non deputy guardian. function test_pause_notDeputyGuardian_reverts() external { - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector)); + vm.expectRevert(abi.encodeWithSelector(IDeputyGuardianModule.DeputyGuardianModule_Unauthorized.selector)); deputyGuardianModule.pause(); } @@ -104,7 +101,12 @@ contract DeputyGuardianModule_Pause_TestFail is DeputyGuardianModule_TestInit { ); vm.prank(address(deputyGuardian)); - vm.expectRevert(abi.encodeWithSelector(ExecutionFailed.selector, "SuperchainConfig: pause() reverted")); + vm.expectRevert( + abi.encodeWithSelector( + IDeputyGuardianModule.DeputyGuardianModule_ExecutionFailed.selector, + "SuperchainConfig: pause() reverted" + ) + ); deputyGuardianModule.pause(); } } @@ -140,7 +142,7 @@ contract DeputyGuardianModule_Unpause_Test is DeputyGuardianModule_TestInit { contract DeputyGuardianModule_Unpause_TestFail is DeputyGuardianModule_Unpause_Test { /// @dev Tests that `unpause` reverts when called by a non deputy guardian. function test_unpause_notDeputyGuardian_reverts() external { - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector)); + vm.expectRevert(abi.encodeWithSelector(IDeputyGuardianModule.DeputyGuardianModule_Unauthorized.selector)); deputyGuardianModule.unpause(); assertTrue(superchainConfig.paused()); } @@ -153,42 +155,14 @@ contract DeputyGuardianModule_Unpause_TestFail is DeputyGuardianModule_Unpause_T "SuperchainConfig: unpause reverted" ); - vm.prank(address(deputyGuardian)); - vm.expectRevert(abi.encodeWithSelector(ExecutionFailed.selector, "SuperchainConfig: unpause reverted")); - deputyGuardianModule.unpause(); - } -} - -contract DeputyGuardianModule_SetAnchorState_TestFail is DeputyGuardianModule_TestInit { - function test_setAnchorState_notDeputyGuardian_reverts() external { - IAnchorStateRegistry asr = IAnchorStateRegistry(makeAddr("asr")); - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector)); - deputyGuardianModule.setAnchorState(asr, IFaultDisputeGame(address(0))); - } - - function test_setAnchorState_targetReverts_reverts() external { - IAnchorStateRegistry asr = IAnchorStateRegistry(makeAddr("asr")); - vm.mockCallRevert( - address(asr), abi.encodePacked(asr.setAnchorState.selector), "AnchorStateRegistry: setAnchorState reverted" - ); vm.prank(address(deputyGuardian)); vm.expectRevert( - abi.encodeWithSelector(ExecutionFailed.selector, "AnchorStateRegistry: setAnchorState reverted") - ); - deputyGuardianModule.setAnchorState(asr, IFaultDisputeGame(address(0))); - } -} - -contract DeputyGuardianModule_SetAnchorState_Test is DeputyGuardianModule_TestInit { - function test_setAnchorState_succeeds() external { - IAnchorStateRegistry asr = IAnchorStateRegistry(makeAddr("asr")); - vm.mockCall( - address(asr), abi.encodeCall(IAnchorStateRegistry.setAnchorState, (IFaultDisputeGame(address(0)))), "" + abi.encodeWithSelector( + IDeputyGuardianModule.DeputyGuardianModule_ExecutionFailed.selector, + "SuperchainConfig: unpause reverted" + ) ); - vm.expectEmit(address(safeInstance.safe)); - emit ExecutionFromModuleSuccess(address(deputyGuardianModule)); - vm.prank(address(deputyGuardian)); - deputyGuardianModule.setAnchorState(asr, IFaultDisputeGame(address(0))); + deputyGuardianModule.unpause(); } } @@ -205,8 +179,8 @@ contract DeputyGuardianModule_BlacklistDisputeGame_Test is DeputyGuardianModule_ emit DisputeGameBlacklisted(game); vm.prank(address(deputyGuardian)); - deputyGuardianModule.blacklistDisputeGame(optimismPortal2, game); - assertTrue(optimismPortal2.disputeGameBlacklist(game)); + deputyGuardianModule.blacklistDisputeGame(anchorStateRegistry, game); + assertTrue(anchorStateRegistry.disputeGameBlacklist(game)); } } @@ -214,25 +188,28 @@ contract DeputyGuardianModule_BlacklistDisputeGame_TestFail is DeputyGuardianMod /// @dev Tests that `blacklistDisputeGame` reverts when called by a non deputy guardian. function test_blacklistDisputeGame_notDeputyGuardian_reverts() external { IDisputeGame game = IDisputeGame(makeAddr("game")); - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector)); - deputyGuardianModule.blacklistDisputeGame(optimismPortal2, game); - assertFalse(optimismPortal2.disputeGameBlacklist(game)); + vm.expectRevert(abi.encodeWithSelector(IDeputyGuardianModule.DeputyGuardianModule_Unauthorized.selector)); + deputyGuardianModule.blacklistDisputeGame(anchorStateRegistry, game); + assertFalse(anchorStateRegistry.disputeGameBlacklist(game)); } /// @dev Tests that when the call from the Safe reverts, the error message is returned. function test_blacklistDisputeGame_targetReverts_reverts() external { vm.mockCallRevert( - address(optimismPortal2), - abi.encodePacked(optimismPortal2.blacklistDisputeGame.selector), - "OptimismPortal2: blacklistDisputeGame reverted" + address(anchorStateRegistry), + abi.encodePacked(anchorStateRegistry.blacklistDisputeGame.selector), + "AnchorStateRegistry: blacklistDisputeGame reverted" ); IDisputeGame game = IDisputeGame(makeAddr("game")); vm.prank(address(deputyGuardian)); vm.expectRevert( - abi.encodeWithSelector(ExecutionFailed.selector, "OptimismPortal2: blacklistDisputeGame reverted") + abi.encodeWithSelector( + IDeputyGuardianModule.DeputyGuardianModule_ExecutionFailed.selector, + "AnchorStateRegistry: blacklistDisputeGame reverted" + ) ); - deputyGuardianModule.blacklistDisputeGame(optimismPortal2, game); + deputyGuardianModule.blacklistDisputeGame(anchorStateRegistry, game); } } @@ -240,11 +217,6 @@ contract DeputyGuardianModule_setRespectedGameType_Test is DeputyGuardianModule_ /// @dev Tests that `setRespectedGameType` successfully updates the respected game type when called by the deputy /// guardian. function testFuzz_setRespectedGameType_succeeds(GameType _gameType) external { - // Game type(uint32).max is reserved for setting the respectedGameTypeUpdatedAt timestamp. - // TODO(#14638): Remove this once we've removed the hack. - uint32 boundedGameType = uint32(bound(_gameType.raw(), 0, type(uint32).max - 1)); - _gameType = GameType.wrap(boundedGameType); - vm.expectEmit(address(safeInstance.safe)); emit ExecutionFromModuleSuccess(address(deputyGuardianModule)); @@ -252,9 +224,8 @@ contract DeputyGuardianModule_setRespectedGameType_Test is DeputyGuardianModule_ emit RespectedGameTypeSet(_gameType, Timestamp.wrap(uint64(block.timestamp))); vm.prank(address(deputyGuardian)); - deputyGuardianModule.setRespectedGameType(optimismPortal2, _gameType); - assertEq(GameType.unwrap(optimismPortal2.respectedGameType()), GameType.unwrap(_gameType)); - assertEq(optimismPortal2.respectedGameTypeUpdatedAt(), uint64(block.timestamp)); + deputyGuardianModule.setRespectedGameType(anchorStateRegistry, _gameType); + assertEq(GameType.unwrap(anchorStateRegistry.respectedGameType()), GameType.unwrap(_gameType)); } } @@ -262,31 +233,81 @@ contract DeputyGuardianModule_setRespectedGameType_TestFail is DeputyGuardianMod /// @dev Tests that `setRespectedGameType` when called by a non deputy guardian. function testFuzz_setRespectedGameType_notDeputyGuardian_reverts(GameType _gameType) external { // Change the game type if it's the same to avoid test rejections. - if (GameType.unwrap(optimismPortal2.respectedGameType()) == GameType.unwrap(_gameType)) { + if (GameType.unwrap(anchorStateRegistry.respectedGameType()) == GameType.unwrap(_gameType)) { unchecked { _gameType = GameType.wrap(GameType.unwrap(_gameType) + 1); } } - vm.expectRevert(abi.encodeWithSelector(Unauthorized.selector)); - deputyGuardianModule.setRespectedGameType(optimismPortal2, _gameType); - assertNotEq(GameType.unwrap(optimismPortal2.respectedGameType()), GameType.unwrap(_gameType)); + vm.expectRevert(abi.encodeWithSelector(IDeputyGuardianModule.DeputyGuardianModule_Unauthorized.selector)); + deputyGuardianModule.setRespectedGameType(anchorStateRegistry, _gameType); + assertNotEq(GameType.unwrap(anchorStateRegistry.respectedGameType()), GameType.unwrap(_gameType)); } /// @dev Tests that when the call from the Safe reverts, the error message is returned. function test_setRespectedGameType_targetReverts_reverts() external { vm.mockCallRevert( - address(optimismPortal2), - abi.encodePacked(optimismPortal2.setRespectedGameType.selector), - "OptimismPortal2: setRespectedGameType reverted" + address(anchorStateRegistry), + abi.encodePacked(anchorStateRegistry.setRespectedGameType.selector), + "AnchorStateRegistry: setRespectedGameType reverted" ); GameType gameType = GameType.wrap(1); vm.prank(address(deputyGuardian)); vm.expectRevert( - abi.encodeWithSelector(ExecutionFailed.selector, "OptimismPortal2: setRespectedGameType reverted") + abi.encodeWithSelector( + IDeputyGuardianModule.DeputyGuardianModule_ExecutionFailed.selector, + "AnchorStateRegistry: setRespectedGameType reverted" + ) + ); + deputyGuardianModule.setRespectedGameType(anchorStateRegistry, gameType); + } +} + +contract DeputyGuardianModule_updateRetirementTimestamp_Test is DeputyGuardianModule_TestInit { + /// @notice Tests that updateRetirementTimestamp() successfully updates the retirement timestamp + /// when called by the deputy guardian. + function test_updateRetirementTimestamp_succeeds() external { + vm.expectEmit(address(safeInstance.safe)); + emit ExecutionFromModuleSuccess(address(deputyGuardianModule)); + + vm.expectEmit(address(deputyGuardianModule)); + emit RetirementTimestampUpdated(Timestamp.wrap(uint64(block.timestamp))); + + vm.prank(address(deputyGuardian)); + deputyGuardianModule.updateRetirementTimestamp(anchorStateRegistry); + assertEq(anchorStateRegistry.retirementTimestamp(), block.timestamp); + } +} + +contract DeputyGuardianModule_updateRetirementTimestamp_TestFail is DeputyGuardianModule_TestInit { + /// @notice Tests that updateRetirementTimestamp() reverts when called by an address other than + /// the deputy guardian. + function testFuzz_updateRetirementTimestamp_notDeputyGuardian_reverts(address _caller) external { + vm.assume(_caller != address(deputyGuardian)); + vm.prank(_caller); + vm.expectRevert(abi.encodeWithSelector(IDeputyGuardianModule.DeputyGuardianModule_Unauthorized.selector)); + deputyGuardianModule.updateRetirementTimestamp(anchorStateRegistry); + } + + /// @notice Tests that when the call from the Safe reverts, the error message is returned. + function test_updateRetirementTimestamp_targetReverts_reverts() external { + // Mock a revert from the ASR. + vm.mockCallRevert( + address(anchorStateRegistry), + abi.encodePacked(anchorStateRegistry.updateRetirementTimestamp.selector), + "AnchorStateRegistry: updateRetirementTimestamp reverted" + ); + + // Call the function and expect a revert. + vm.prank(address(deputyGuardian)); + vm.expectRevert( + abi.encodeWithSelector( + IDeputyGuardianModule.DeputyGuardianModule_ExecutionFailed.selector, + "AnchorStateRegistry: updateRetirementTimestamp reverted" + ) ); - deputyGuardianModule.setRespectedGameType(optimismPortal2, gameType); + deputyGuardianModule.updateRetirementTimestamp(anchorStateRegistry); } } @@ -296,16 +317,14 @@ contract DeputyGuardianModule_NoPortalCollisions_Test is DeputyGuardianModule_Te function test_noPortalCollisions_succeeds() external { string[] memory excludes = new string[](5); excludes[0] = "src/dispute/lib/*"; - excludes[1] = "src/L1/OptimismPortal2.sol"; - excludes[2] = "src/L1/OptimismPortalInterop.sol"; - excludes[3] = "interfaces/L1/IOptimismPortal2.sol"; - excludes[4] = "interfaces/L1/IOptimismPortalInterop.sol"; + excludes[1] = "src/dispute/AnchorStateRegistry.sol"; + excludes[2] = "interfaces/dispute/IAnchorStateRegistry.sol"; Abi[] memory abis = ForgeArtifacts.getContractFunctionAbis("src/{L1,dispute,universal}", excludes); for (uint256 i; i < abis.length; i++) { for (uint256 j; j < abis[i].entries.length; j++) { bytes4 sel = abis[i].entries[j].sel; - assertNotEq(sel, optimismPortal2.blacklistDisputeGame.selector); - assertNotEq(sel, optimismPortal2.setRespectedGameType.selector); + assertNotEq(sel, anchorStateRegistry.blacklistDisputeGame.selector); + assertNotEq(sel, anchorStateRegistry.setRespectedGameType.selector); } } } diff --git a/packages/contracts-bedrock/test/safe/DeputyPauseModule.t.sol b/packages/contracts-bedrock/test/safe/DeputyPauseModule.t.sol index 25c54a7d3d3..ca87a54662a 100644 --- a/packages/contracts-bedrock/test/safe/DeputyPauseModule.t.sol +++ b/packages/contracts-bedrock/test/safe/DeputyPauseModule.t.sol @@ -568,7 +568,8 @@ contract DeputyPauseModule_Pause_TestFail is DeputyPauseModule_TestInit { IDeputyPauseModule.DeputyPauseModule_ExecutionFailed.selector, string( abi.encodeWithSelector( - IDeputyGuardianModule.ExecutionFailed.selector, "SuperchainConfig: pause() reverted" + IDeputyGuardianModule.DeputyGuardianModule_ExecutionFailed.selector, + "SuperchainConfig: pause() reverted" ) ) ) diff --git a/packages/contracts-bedrock/test/setup/Events.sol b/packages/contracts-bedrock/test/setup/Events.sol index 7056f0cbdd6..022258e02ba 100644 --- a/packages/contracts-bedrock/test/setup/Events.sol +++ b/packages/contracts-bedrock/test/setup/Events.sol @@ -106,4 +106,8 @@ contract Events { event Unpaused(); event BalanceChanged(address account, uint256 balance); + + event ETHMigrated(address indexed lockbox, uint256 ethBalance); + + event LockboxUpdated(address oldLockbox, address newLockbox); } diff --git a/packages/contracts-bedrock/test/setup/FFIInterface.sol b/packages/contracts-bedrock/test/setup/FFIInterface.sol index c1e1612da8e..97ed21cbc7f 100644 --- a/packages/contracts-bedrock/test/setup/FFIInterface.sol +++ b/packages/contracts-bedrock/test/setup/FFIInterface.sol @@ -188,6 +188,28 @@ contract FFIInterface { return abi.decode(result, (bytes)); } + function encodeSuperRootProof(Types.SuperRootProof calldata proof) external returns (bytes memory) { + string[] memory cmds = new string[](4); + cmds[0] = "scripts/go-ffi/go-ffi"; + cmds[1] = "diff"; + cmds[2] = "encodeSuperRootProof"; + cmds[3] = vm.toString(abi.encode(proof)); + + bytes memory result = Process.run(cmds); + return abi.decode(result, (bytes)); + } + + function hashSuperRootProof(Types.SuperRootProof calldata proof) external returns (bytes32) { + string[] memory cmds = new string[](4); + cmds[0] = "scripts/go-ffi/go-ffi"; + cmds[1] = "diff"; + cmds[2] = "hashSuperRootProof"; + cmds[3] = vm.toString(abi.encode(proof)); + + bytes memory result = Process.run(cmds); + return abi.decode(result, (bytes32)); + } + function decodeVersionedNonce(uint256 nonce) external returns (uint256, uint256) { string[] memory cmds = new string[](4); cmds[0] = "scripts/go-ffi/go-ffi"; diff --git a/packages/contracts-bedrock/test/setup/ForkLive.s.sol b/packages/contracts-bedrock/test/setup/ForkLive.s.sol index 3de0214c8ea..6f3d663000e 100644 --- a/packages/contracts-bedrock/test/setup/ForkLive.s.sol +++ b/packages/contracts-bedrock/test/setup/ForkLive.s.sol @@ -24,6 +24,9 @@ import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IProxyAdmin } from "interfaces/universal/IProxyAdmin.sol"; import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IOPContractsManagerLegacyUpgrade } from "interfaces/L1/IOPContractsManagerLegacyUpgrade.sol"; /// @title ForkLive /// @notice This script is called by Setup.sol as a preparation step for the foundry test suite, and is run as an @@ -34,6 +37,7 @@ import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.so /// Therefore this script can only be run against a fork of a production network which is listed in the /// superchain-registry. /// This contract must not have constructor logic because it is set into state using `etch`. + contract ForkLive is Deployer { using stdToml for string; @@ -116,6 +120,15 @@ contract ForkLive is Deployer { artifacts.save("OptimismPortalProxy", optimismPortal); artifacts.save("OptimismPortal2Impl", EIP1967Helper.getImplementation(optimismPortal)); + // Get the lockbox address from the portal, and save it + /// NOTE: Using try catch because this function could be called before or after the upgrade. + try IOptimismPortal2(payable(optimismPortal)).ethLockbox() returns (IETHLockbox ethLockbox_) { + console.log("ForkLive: ETHLockboxProxy found: %s", address(ethLockbox_)); + artifacts.save("ETHLockboxProxy", address(ethLockbox_)); + } catch { + console.log("ForkLive: ETHLockboxProxy not found"); + } + address addressManager = vm.parseTomlAddress(opToml, ".addresses.AddressManager"); artifacts.save("AddressManager", addressManager); artifacts.save( @@ -177,17 +190,42 @@ contract ForkLive is Deployer { opChains[0] = IOPContractsManager.OpChainConfig({ systemConfigProxy: systemConfig, proxyAdmin: proxyAdmin, - absolutePrestate: Claim.wrap(bytes32(keccak256("absolutePrestate"))) + absolutePrestate: Claim.wrap(bytes32(keccak256("absolutePrestate"))), + disputeGameUsesSuperRoots: false }); // Temporarily replace the upgrader with a DelegateCaller so we can test the upgrade, // then reset its code to the original code. bytes memory upgraderCode = address(upgrader).code; vm.etch(upgrader, vm.getDeployedCode("test/mocks/Callers.sol:DelegateCaller")); + + // Some upgrades require the legacy format. + IOPContractsManagerLegacyUpgrade.OpChainConfig[] memory legacyConfigs = + new IOPContractsManagerLegacyUpgrade.OpChainConfig[](opChains.length); + for (uint256 i = 0; i < opChains.length; i++) { + legacyConfigs[i] = IOPContractsManagerLegacyUpgrade.OpChainConfig({ + systemConfigProxy: opChains[i].systemConfigProxy, + proxyAdmin: opChains[i].proxyAdmin, + absolutePrestate: opChains[i].absolutePrestate + }); + } + + // Start by doing Upgrade 13. DelegateCaller(upgrader).dcForward( - address(0x026b2F158255Beac46c1E7c6b8BbF29A4b6A7B76), abi.encodeCall(IOPContractsManager.upgrade, (opChains)) + address(0x026b2F158255Beac46c1E7c6b8BbF29A4b6A7B76), + abi.encodeCall(IOPContractsManagerLegacyUpgrade.upgrade, (legacyConfigs)) ); + + // Then do Upgrade 14. + DelegateCaller(upgrader).dcForward( + address(0x3A1f523a4bc09cd344A2745a108Bb0398288094F), + abi.encodeCall(IOPContractsManagerLegacyUpgrade.upgrade, (legacyConfigs)) + ); + + // Then do the final upgrade. DelegateCaller(upgrader).dcForward(address(opcm), abi.encodeCall(IOPContractsManager.upgrade, (opChains))); + + // Reset the upgrader to the original code. vm.etch(upgrader, upgraderCode); console.log("ForkLive: Saving newly deployed contracts"); @@ -205,6 +243,11 @@ contract ForkLive is Deployer { IAnchorStateRegistry newAnchorStateRegistry = IPermissionedDisputeGame(permissionedDisputeGame).anchorStateRegistry(); artifacts.save("AnchorStateRegistryProxy", address(newAnchorStateRegistry)); + + // Get the lockbox address from the portal, and save it + IOptimismPortal2 portal = IOptimismPortal2(artifacts.mustGetAddress("OptimismPortalProxy")); + address lockboxAddress = address(portal.ethLockbox()); + artifacts.save("ETHLockboxProxy", lockboxAddress); } /// @notice Saves the proxy and implementation addresses for a contract name diff --git a/packages/contracts-bedrock/test/setup/Setup.sol b/packages/contracts-bedrock/test/setup/Setup.sol index 62c7118ac73..a650d23ee34 100644 --- a/packages/contracts-bedrock/test/setup/Setup.sol +++ b/packages/contracts-bedrock/test/setup/Setup.sol @@ -22,7 +22,8 @@ import { Chains } from "scripts/libraries/Chains.sol"; // Interfaces import { IOPContractsManager } from "interfaces/L1/IOPContractsManager.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; +import { IOptimismPortal2 as IOptimismPortal } from "interfaces/L1/IOptimismPortal2.sol"; +import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol"; import { IL1CrossDomainMessenger } from "interfaces/L1/IL1CrossDomainMessenger.sol"; import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; @@ -96,7 +97,8 @@ contract Setup { IDelayedWETH delayedWETHPermissionedGameProxy; // L1 contracts - core - IOptimismPortal2 optimismPortal2; + IOptimismPortal optimismPortal2; + IETHLockbox ethLockbox; ISystemConfig systemConfig; IL1StandardBridge l1StandardBridge; IL1CrossDomainMessenger l1CrossDomainMessenger; @@ -229,7 +231,14 @@ contract Setup { console.log("Setup: completed L1 deployment, registering addresses now"); - optimismPortal2 = IOptimismPortal2(artifacts.mustGetAddress("OptimismPortalProxy")); + optimismPortal2 = IOptimismPortal(artifacts.mustGetAddress("OptimismPortalProxy")); + + // Only skip ETHLockbox assignment if we're in a fork test with non-upgraded fork + // TODO(#14691): Remove this check once Upgrade 15 is deployed on Mainnet. + if (!isForkTest() || deploy.cfg().useUpgradedFork()) { + ethLockbox = IETHLockbox(artifacts.mustGetAddress("ETHLockboxProxy")); + } + systemConfig = ISystemConfig(artifacts.mustGetAddress("SystemConfigProxy")); l1StandardBridge = IL1StandardBridge(artifacts.mustGetAddress("L1StandardBridgeProxy")); l1CrossDomainMessenger = IL1CrossDomainMessenger(artifacts.mustGetAddress("L1CrossDomainMessengerProxy")); diff --git a/packages/contracts-bedrock/test/universal/ReinitializableBase.t.sol b/packages/contracts-bedrock/test/universal/ReinitializableBase.t.sol new file mode 100644 index 00000000000..1c3f7d93eb0 --- /dev/null +++ b/packages/contracts-bedrock/test/universal/ReinitializableBase.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +// Testing +import { Test } from "forge-std/Test.sol"; + +// Contracts +import { ReinitializableBase } from "src/universal/ReinitializableBase.sol"; + +contract ReinitializableBase_Test is Test { + /// @notice Tests that the contract is created correctly and initVersion returns the right + /// value when the provided init version is non-zero. + /// @param _initVersion The init version to use when creating the contract. + function testFuzz_initVersion_validVersion_succeeds(uint8 _initVersion) public { + // Zero version not allowed. + _initVersion = uint8(bound(_initVersion, 1, type(uint8).max)); + + // Deploy the reinitializable contract. + ReinitializableBase_Harness harness = new ReinitializableBase_Harness(_initVersion); + + // Check the init version. + assertEq(harness.initVersion(), _initVersion); + } +} + +contract ReinitializableBase_TestFail is Test { + /// @notice Tests that the contract creation reverts when the init version is zero. + function test_initVersion_zeroVersion_reverts() public { + vm.expectRevert(ReinitializableBase.ReinitializableBase_ZeroInitVersion.selector); + new ReinitializableBase_Harness(0); + } +} + +contract ReinitializableBase_Harness is ReinitializableBase { + constructor(uint8 _initVersion) ReinitializableBase(_initVersion) { } +} diff --git a/packages/contracts-bedrock/test/universal/Specs.t.sol b/packages/contracts-bedrock/test/universal/Specs.t.sol index abd2ebfc8d2..236294d0b0f 100644 --- a/packages/contracts-bedrock/test/universal/Specs.t.sol +++ b/packages/contracts-bedrock/test/universal/Specs.t.sol @@ -209,31 +209,45 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "L1StandardBridge", _sel: _getSel("version()") }); // OptimismPortalInterop + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("anchorStateRegistry()") }); + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("checkWithdrawal(bytes32,address)") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("depositTransaction(address,uint256,uint64,bool,bytes)") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("donateETH()") }); + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("updateLockbox(address)") }); _addSpec({ _name: "OptimismPortalInterop", - _sel: IOptimismPortal2.finalizeWithdrawalTransaction.selector, + _sel: IOptimismPortalInterop.finalizeWithdrawalTransaction.selector, _pausable: true }); _addSpec({ _name: "OptimismPortalInterop", - _sel: IOptimismPortal2.finalizeWithdrawalTransactionExternalProof.selector, + _sel: IOptimismPortalInterop.finalizeWithdrawalTransactionExternalProof.selector, _pausable: true }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("finalizedWithdrawals(bytes32)") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("guardian()") }); - _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("initialize(address,address,address,uint32)") }); + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("initialize(address,address,address,address,bool)") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("l2Sender()") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("minimumGasLimit(uint64)") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("params()") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("paused()") }); + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("initVersion()") }); + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("superRootsActive()") }); + _addSpec({ + _name: "OptimismPortalInterop", + _sel: _getSel( + "proveWithdrawalTransaction((uint256,address,address,uint256,uint256,bytes),uint256,(bytes32,bytes32,bytes32,bytes32),bytes[])" + ), + _pausable: true + }); _addSpec({ _name: "OptimismPortalInterop", - _sel: IOptimismPortal2.proveWithdrawalTransaction.selector, + _sel: _getSel( + "proveWithdrawalTransaction((uint256,address,address,uint256,uint256,bytes),address,uint256,(bytes1,uint64,(uint256,bytes32)[]),(bytes32,bytes32,bytes32,bytes32),bytes[])" + ), _pausable: true }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("provenWithdrawals(bytes32,address)") }); @@ -241,25 +255,16 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("systemConfig()") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("version()") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("disputeGameFactory()") }); - _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("disputeGameBlacklist(address)") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("respectedGameType()") }); - // Comment out the auth to not disturb the testDeputyGuardianAuth test. This code is not meant to run in - // production, - // and will be merged into the OptimismPortal2 contract itself in the future. - _addSpec({ - _name: "OptimismPortalInterop", - _sel: _getSel("blacklistDisputeGame(address)") /*, _auth: Role.GUARDIAN*/ - }); - _addSpec({ - _name: "OptimismPortalInterop", - _sel: _getSel("setRespectedGameType(uint32)") /*, _auth: Role.GUARDIAN*/ - }); - _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("checkWithdrawal(bytes32,address)") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("proofMaturityDelaySeconds()") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("disputeGameFinalityDelaySeconds()") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("respectedGameTypeUpdatedAt()") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("proofSubmitters(bytes32,uint256)") }); _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("numProofSubmitters(bytes32)") }); + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("ethLockbox()") }); + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("migrateLiquidity()") }); + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("proxyAdminOwner()") }); + _addSpec({ _name: "OptimismPortalInterop", _sel: _getSel("upgrade(address,address,bool)") }); _addSpec({ _name: "OptimismPortalInterop", _sel: IOptimismPortalInterop.setConfig.selector, @@ -267,8 +272,11 @@ contract Specification_Test is CommonTest { }); // OptimismPortal2 + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("anchorStateRegistry()") }); + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("checkWithdrawal(bytes32,address)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("depositTransaction(address,uint256,uint64,bool,bytes)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("donateETH()") }); + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("updateLockbox(address)") }); _addSpec({ _name: "OptimismPortal2", _sel: IOptimismPortal2.finalizeWithdrawalTransaction.selector, @@ -281,14 +289,25 @@ contract Specification_Test is CommonTest { }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("finalizedWithdrawals(bytes32)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("guardian()") }); - _addSpec({ _name: "OptimismPortal2", _sel: _getSel("initialize(address,address,address,uint32)") }); + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("initialize(address,address,address,address,bool)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("l2Sender()") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("minimumGasLimit(uint64)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("params()") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("paused()") }); + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("initVersion()") }); + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("superRootsActive()") }); _addSpec({ _name: "OptimismPortal2", - _sel: IOptimismPortal2.proveWithdrawalTransaction.selector, + _sel: _getSel( + "proveWithdrawalTransaction((uint256,address,address,uint256,uint256,bytes),uint256,(bytes32,bytes32,bytes32,bytes32),bytes[])" + ), + _pausable: true + }); + _addSpec({ + _name: "OptimismPortal2", + _sel: _getSel( + "proveWithdrawalTransaction((uint256,address,address,uint256,uint256,bytes),address,uint256,(bytes1,uint64,(uint256,bytes32)[]),(bytes32,bytes32,bytes32,bytes32),bytes[])" + ), _pausable: true }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("provenWithdrawals(bytes32,address)") }); @@ -296,16 +315,19 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "OptimismPortal2", _sel: _getSel("systemConfig()") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("version()") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("disputeGameFactory()") }); - _addSpec({ _name: "OptimismPortal2", _sel: _getSel("disputeGameBlacklist(address)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("respectedGameType()") }); - _addSpec({ _name: "OptimismPortal2", _sel: _getSel("blacklistDisputeGame(address)"), _auth: Role.GUARDIAN }); - _addSpec({ _name: "OptimismPortal2", _sel: _getSel("setRespectedGameType(uint32)"), _auth: Role.GUARDIAN }); - _addSpec({ _name: "OptimismPortal2", _sel: _getSel("checkWithdrawal(bytes32,address)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("proofMaturityDelaySeconds()") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("disputeGameFinalityDelaySeconds()") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("respectedGameTypeUpdatedAt()") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("proofSubmitters(bytes32,uint256)") }); _addSpec({ _name: "OptimismPortal2", _sel: _getSel("numProofSubmitters(bytes32)") }); + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("upgrade(address,address,bool)") }); + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("ethLockbox()") }); + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("migrateLiquidity()") }); + _addSpec({ _name: "OptimismPortal2", _sel: _getSel("proxyAdminOwner()") }); + + // ProxyAdminOwnedBase + _addSpec({ _name: "ProxyAdminOwnedBase", _sel: _getSel("proxyAdminOwner()") }); // ProtocolVersions _addSpec({ _name: "ProtocolVersions", _sel: _getSel("RECOMMENDED_SLOT()") }); @@ -329,6 +351,21 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "ProtocolVersions", _sel: _getSel("transferOwnership(address)") }); _addSpec({ _name: "ProtocolVersions", _sel: _getSel("version()") }); + // ETHLockbox + _addSpec({ _name: "ETHLockbox", _sel: _getSel("version()") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("initialize(address,address[])") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("superchainConfig()") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("paused()") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("authorizedPortals(address)") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("authorizedLockboxes(address)") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("receiveLiquidity()") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("lockETH()") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("unlockETH(uint256)") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("authorizePortal(address)") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("authorizeLockbox(address)") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("migrateLiquidity(address)") }); + _addSpec({ _name: "ETHLockbox", _sel: _getSel("proxyAdminOwner()") }); + // ResourceMetering _addSpec({ _name: "ResourceMetering", _sel: _getSel("params()") }); @@ -400,6 +437,9 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "SystemConfig", _sel: _getSel("blobbasefeeScalar()") }); _addSpec({ _name: "SystemConfig", _sel: _getSel("maximumGasLimit()") }); _addSpec({ _name: "SystemConfig", _sel: _getSel("getAddresses()") }); + _addSpec({ _name: "SystemConfig", _sel: _getSel("l2ChainId()") }); + _addSpec({ _name: "SystemConfig", _sel: _getSel("upgrade(uint256)") }); + _addSpec({ _name: "SystemConfig", _sel: _getSel("initVersion()") }); // SystemConfigInterop _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("UNSAFE_BLOCK_SIGNER_SLOT()") }); @@ -487,7 +527,9 @@ contract Specification_Test is CommonTest { }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("dependencyManager()") }); _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("getAddresses()") }); - + _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("l2ChainId()") }); + _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("upgrade(uint256)") }); + _addSpec({ _name: "SystemConfigInterop", _sel: _getSel("initVersion()") }); // ProxyAdmin _addSpec({ _name: "ProxyAdmin", _sel: _getSel("addressManager()") }); _addSpec({ @@ -572,7 +614,7 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("anchors(uint32)") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("getAnchorRoot()") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("disputeGameFactory()") }); - _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("initialize(address,address,address,(bytes32,uint256))") }); + _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("initialize(address,address,(bytes32,uint256),uint32)") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("isGameBlacklisted(address)") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("isGameClaimValid(address)") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("isGameFinalized(address)") }); @@ -581,11 +623,17 @@ contract Specification_Test is CommonTest { _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("isGameResolved(address)") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("isGameRespected(address)") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("isGameRetired(address)") }); - _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("portal()") }); + _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("paused()") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("respectedGameType()") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("setAnchorState(address)") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("superchainConfig()") }); _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("version()") }); + _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("disputeGameFinalityDelaySeconds()") }); + _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("disputeGameBlacklist(address)") }); + _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("retirementTimestamp()") }); + _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("blacklistDisputeGame(address)"), _auth: Role.GUARDIAN }); + _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("setRespectedGameType(uint32)"), _auth: Role.GUARDIAN }); + _addSpec({ _name: "AnchorStateRegistry", _sel: _getSel("updateRetirementTimestamp()"), _auth: Role.GUARDIAN }); // PermissionedDisputeGame _addSpec({ _name: "PermissionedDisputeGame", _sel: _getSel("absolutePrestate()") }); @@ -939,7 +987,7 @@ contract Specification_Test is CommonTest { }); _addSpec({ _name: "DeputyGuardianModule", - _sel: _getSel("setAnchorState(address,address)"), + _sel: _getSel("updateRetirementTimestamp(address)"), _auth: Role.DEPUTYGUARDIAN }); _addSpec({ _name: "DeputyGuardianModule", _sel: _getSel("pause()"), _auth: Role.DEPUTYGUARDIAN }); @@ -1104,13 +1152,12 @@ contract Specification_Test is CommonTest { /// @notice Ensures that the DeputyGuardian is authorized to take all Guardian actions. function test_deputyGuardianAuth_works() public view { // Additional 2 roles for the DeputyPauseModule - // Additional role for `setAnchorState` which is in DGM but no longer role-restricted. - assertEq(specsByRole[Role.GUARDIAN].length, 4); - assertEq(specsByRole[Role.DEPUTYGUARDIAN].length, specsByRole[Role.GUARDIAN].length + 3); + assertEq(specsByRole[Role.GUARDIAN].length, 5); + assertEq(specsByRole[Role.DEPUTYGUARDIAN].length, specsByRole[Role.GUARDIAN].length + 2); mapping(bytes4 => Spec) storage dgmFuncSpecs = specs["DeputyGuardianModule"]; mapping(bytes4 => Spec) storage superchainConfigFuncSpecs = specs["SuperchainConfig"]; - mapping(bytes4 => Spec) storage portal2FuncSpecs = specs["OptimismPortal2"]; + mapping(bytes4 => Spec) storage asrSpecs = specs["AnchorStateRegistry"]; // Ensure that for each of the DeputyGuardianModule's methods there is a corresponding method on another // system contract authed to the Guardian role. @@ -1121,11 +1168,12 @@ contract Specification_Test is CommonTest { _assertRolesEq(superchainConfigFuncSpecs[_getSel("unpause()")].auth, Role.GUARDIAN); _assertRolesEq(dgmFuncSpecs[_getSel("blacklistDisputeGame(address,address)")].auth, Role.DEPUTYGUARDIAN); - _assertRolesEq(portal2FuncSpecs[_getSel("blacklistDisputeGame(address)")].auth, Role.GUARDIAN); + _assertRolesEq(asrSpecs[_getSel("blacklistDisputeGame(address)")].auth, Role.GUARDIAN); _assertRolesEq(dgmFuncSpecs[_getSel("setRespectedGameType(address,uint32)")].auth, Role.DEPUTYGUARDIAN); - _assertRolesEq(portal2FuncSpecs[_getSel("setRespectedGameType(uint32)")].auth, Role.GUARDIAN); + _assertRolesEq(asrSpecs[_getSel("setRespectedGameType(uint32)")].auth, Role.GUARDIAN); - _assertRolesEq(dgmFuncSpecs[_getSel("setAnchorState(address,address)")].auth, Role.DEPUTYGUARDIAN); + _assertRolesEq(dgmFuncSpecs[_getSel("updateRetirementTimestamp(address)")].auth, Role.DEPUTYGUARDIAN); + _assertRolesEq(asrSpecs[_getSel("updateRetirementTimestamp()")].auth, Role.GUARDIAN); } } diff --git a/packages/contracts-bedrock/test/vendor/Initializable.t.sol b/packages/contracts-bedrock/test/vendor/Initializable.t.sol index 9835e4e73a0..3eb79187e65 100644 --- a/packages/contracts-bedrock/test/vendor/Initializable.t.sol +++ b/packages/contracts-bedrock/test/vendor/Initializable.t.sol @@ -18,8 +18,8 @@ import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol"; import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol"; import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; -import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; import { ProtocolVersion } from "interfaces/L1/IProtocolVersions.sol"; +import { IOptimismPortal2 } from "interfaces/L1/IOptimismPortal2.sol"; /// @title Initializer_Test /// @dev Ensures that the `initialize()` function on contracts cannot be called more than @@ -124,13 +124,7 @@ contract Initializer_Test is CommonTest { name: "OptimismPortal2Impl", target: EIP1967Helper.getImplementation(address(optimismPortal2)), initCalldata: abi.encodeCall( - optimismPortal2.initialize, - ( - disputeGameFactory, - systemConfig, - superchainConfig, - GameType.wrap(uint32(deploy.cfg().respectedGameType())) - ) + optimismPortal2.initialize, (systemConfig, superchainConfig, anchorStateRegistry, ethLockbox, false) ) }) ); @@ -140,16 +134,11 @@ contract Initializer_Test is CommonTest { name: "OptimismPortal2Proxy", target: address(optimismPortal2), initCalldata: abi.encodeCall( - optimismPortal2.initialize, - ( - disputeGameFactory, - systemConfig, - superchainConfig, - GameType.wrap(uint32(deploy.cfg().respectedGameType())) - ) + optimismPortal2.initialize, (systemConfig, superchainConfig, anchorStateRegistry, ethLockbox, false) ) }) ); + // SystemConfigImpl contracts.push( InitializeableContract({ @@ -180,7 +169,8 @@ contract Initializer_Test is CommonTest { disputeGameFactory: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0) - }) + }), + 0 ) ) }) @@ -215,7 +205,8 @@ contract Initializer_Test is CommonTest { disputeGameFactory: address(0), optimismPortal: address(0), optimismMintableERC20Factory: address(0) - }) + }), + 0 ) ) }) @@ -314,8 +305,8 @@ contract Initializer_Test is CommonTest { ( ISuperchainConfig(address(0)), IDisputeGameFactory(address(0)), - IOptimismPortal2(payable(0)), - OutputRoot({ root: Hash.wrap(bytes32(0)), l2BlockNumber: 0 }) + OutputRoot({ root: Hash.wrap(bytes32(0)), l2BlockNumber: 0 }), + GameType.wrap(uint32(deploy.cfg().respectedGameType())) ) ) }) @@ -330,12 +321,34 @@ contract Initializer_Test is CommonTest { ( ISuperchainConfig(address(0)), IDisputeGameFactory(address(0)), - IOptimismPortal2(payable(0)), - OutputRoot({ root: Hash.wrap(bytes32(0)), l2BlockNumber: 0 }) + OutputRoot({ root: Hash.wrap(bytes32(0)), l2BlockNumber: 0 }), + GameType.wrap(uint32(deploy.cfg().respectedGameType())) ) ) }) ); + + // ETHLockboxImpl + contracts.push( + InitializeableContract({ + name: "ETHLockboxImpl", + target: EIP1967Helper.getImplementation(address(ethLockbox)), + initCalldata: abi.encodeCall( + ethLockbox.initialize, (ISuperchainConfig(address(0)), new IOptimismPortal2[](0)) + ) + }) + ); + + // ETHLockboxProxy + contracts.push( + InitializeableContract({ + name: "ETHLockboxProxy", + target: address(ethLockbox), + initCalldata: abi.encodeCall( + ethLockbox.initialize, (ISuperchainConfig(address(0)), new IOptimismPortal2[](0)) + ) + }) + ); } /// @notice Tests that: From 9ff945ad364eb0fb0ca09332781a4e2cd72397f0 Mon Sep 17 00:00:00 2001 From: Axel Kingsley Date: Mon, 10 Mar 2025 17:51:36 -0500 Subject: [PATCH 127/130] Add consistency checks in ChainsDB Queries (#14769) --- op-program/client/interop/consolidate.go | 4 +- .../backend/cross/unsafe_frontier.go | 4 +- .../backend/cross/unsafe_frontier_test.go | 12 +++--- .../backend/cross/unsafe_update_test.go | 2 +- op-supervisor/supervisor/backend/db/query.go | 38 ++++++++++--------- 5 files changed, 31 insertions(+), 29 deletions(-) diff --git a/op-program/client/interop/consolidate.go b/op-program/client/interop/consolidate.go index 459012be07e..e946aae7b37 100644 --- a/op-program/client/interop/consolidate.go +++ b/op-program/client/interop/consolidate.go @@ -309,8 +309,8 @@ func (d *consolidateCheckDeps) IsLocalUnsafe(chainID eth.ChainID, block eth.Bloc return nil } -func (d *consolidateCheckDeps) ParentBlock(chainID eth.ChainID, parentOf eth.BlockID) (parent eth.BlockID, err error) { - block, err := d.CanonBlockByNumber(d.oracle, parentOf.Number-1, chainID) +func (d *consolidateCheckDeps) FindBlockID(chainID eth.ChainID, num uint64) (blockID eth.BlockID, err error) { + block, err := d.CanonBlockByNumber(d.oracle, num, chainID) if err != nil { return eth.BlockID{}, err } diff --git a/op-supervisor/supervisor/backend/cross/unsafe_frontier.go b/op-supervisor/supervisor/backend/cross/unsafe_frontier.go index f7eaecf4c2c..8746e13455a 100644 --- a/op-supervisor/supervisor/backend/cross/unsafe_frontier.go +++ b/op-supervisor/supervisor/backend/cross/unsafe_frontier.go @@ -10,7 +10,7 @@ import ( ) type UnsafeFrontierCheckDeps interface { - ParentBlock(chainID eth.ChainID, parentOf eth.BlockID) (parent eth.BlockID, err error) + FindBlockID(chainID eth.ChainID, blockNum uint64) (eth.BlockID, error) IsCrossUnsafe(chainID eth.ChainID, block eth.BlockID) error IsLocalUnsafe(chainID eth.ChainID, block eth.BlockID) error @@ -46,7 +46,7 @@ func HazardUnsafeFrontierChecks(d UnsafeFrontierCheckDeps, hazards *HazardSet) e // If it doesn't have a parent block, then there is no prior block required to be cross-safe if hazardBlock.Number > 0 { // Check that parent of hazardBlockID is cross-safe within view - parent, err := d.ParentBlock(hazardChainID, hazardBlock.ID()) + parent, err := d.FindBlockID(hazardChainID, hazardBlock.Number-1) if err != nil { return fmt.Errorf("failed to retrieve parent-block of hazard block %s (chain %s): %w", hazardBlock, hazardChainID, err) } diff --git a/op-supervisor/supervisor/backend/cross/unsafe_frontier_test.go b/op-supervisor/supervisor/backend/cross/unsafe_frontier_test.go index df4080f7018..110aafb2531 100644 --- a/op-supervisor/supervisor/backend/cross/unsafe_frontier_test.go +++ b/op-supervisor/supervisor/backend/cross/unsafe_frontier_test.go @@ -68,7 +68,7 @@ func TestHazardUnsafeFrontierChecks(t *testing.T) { ufcd := &mockUnsafeFrontierCheckDeps{} hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {Number: 3}} ufcd.isCrossUnsafe = types.ErrFuture - ufcd.parentBlockFn = func() (parent eth.BlockID, err error) { + ufcd.findBlockIDFn = func() (parent eth.BlockID, err error) { return eth.BlockID{}, errors.New("some error") } // when there is one hazard, and IsCrossUnsafe returns an ErrFuture, @@ -81,7 +81,7 @@ func TestHazardUnsafeFrontierChecks(t *testing.T) { ufcd := &mockUnsafeFrontierCheckDeps{} hazards := map[types.ChainIndex]types.BlockSeal{types.ChainIndex(0): {Number: 3}} ufcd.isCrossUnsafe = types.ErrFuture - ufcd.parentBlockFn = func() (parent eth.BlockID, err error) { + ufcd.findBlockIDFn = func() (parent eth.BlockID, err error) { // when getting the parent block, prep isCrossSafe to be err ufcd.isCrossUnsafe = errors.New("not cross unsafe!") return eth.BlockID{}, nil @@ -105,7 +105,7 @@ func TestHazardUnsafeFrontierChecks(t *testing.T) { type mockUnsafeFrontierCheckDeps struct { deps mockDependencySet - parentBlockFn func() (parent eth.BlockID, err error) + findBlockIDFn func() (parent eth.BlockID, err error) isCrossUnsafe error isLocalUnsafe error } @@ -114,9 +114,9 @@ func (m *mockUnsafeFrontierCheckDeps) DependencySet() depset.DependencySet { return m.deps } -func (m *mockUnsafeFrontierCheckDeps) ParentBlock(chainID eth.ChainID, block eth.BlockID) (parent eth.BlockID, err error) { - if m.parentBlockFn != nil { - return m.parentBlockFn() +func (m *mockUnsafeFrontierCheckDeps) FindBlockID(chainID eth.ChainID, num uint64) (parent eth.BlockID, err error) { + if m.findBlockIDFn != nil { + return m.findBlockIDFn() } return eth.BlockID{}, nil } diff --git a/op-supervisor/supervisor/backend/cross/unsafe_update_test.go b/op-supervisor/supervisor/backend/cross/unsafe_update_test.go index 5c774357367..3f291a27cd6 100644 --- a/op-supervisor/supervisor/backend/cross/unsafe_update_test.go +++ b/op-supervisor/supervisor/backend/cross/unsafe_update_test.go @@ -240,6 +240,6 @@ func (m *mockCrossUnsafeDeps) IsLocalUnsafe(chainID eth.ChainID, blockNum eth.Bl return nil } -func (m *mockCrossUnsafeDeps) ParentBlock(chainID eth.ChainID, blockNum eth.BlockID) (eth.BlockID, error) { +func (m *mockCrossUnsafeDeps) FindBlockID(chainID eth.ChainID, num uint64) (eth.BlockID, error) { return eth.BlockID{}, nil } diff --git a/op-supervisor/supervisor/backend/db/query.go b/op-supervisor/supervisor/backend/db/query.go index 3fac6911104..8d4e7a95e9a 100644 --- a/op-supervisor/supervisor/backend/db/query.go +++ b/op-supervisor/supervisor/backend/db/query.go @@ -17,6 +17,14 @@ func (db *ChainsDB) FindSealedBlock(chain eth.ChainID, number uint64) (seal type return logDB.FindSealedBlock(number) } +func (db *ChainsDB) FindBlockID(chain eth.ChainID, number uint64) (id eth.BlockID, err error) { + sealed, err := db.FindSealedBlock(chain, number) + if err != nil { + return eth.BlockID{}, err + } + return sealed.ID(), nil +} + // LatestBlockNum returns the latest fully-sealed block number that has been recorded to the logs db // for the given chain. It does not contain safety guarantees. // The block number might not be available (empty database, or non-existent chain). @@ -31,38 +39,32 @@ func (db *ChainsDB) LatestBlockNum(chain eth.ChainID) (num uint64, ok bool) { // IsCrossUnsafe checks if the given block is less than the cross-unsafe block number. // It does not check if the block is actually cross-unsafe (ie, known in the database). -// TODO(#14765): Check Consistency of IsCrossUnsafe and return error if inconsistent func (db *ChainsDB) IsCrossUnsafe(chainID eth.ChainID, block eth.BlockID) error { - v, ok := db.crossUnsafe.Get(chainID) + xU, ok := db.crossUnsafe.Get(chainID) if !ok { return types.ErrUnknownChain } - crossUnsafe := v.Get() + crossUnsafe := xU.Get() if crossUnsafe == (types.BlockSeal{}) { return types.ErrFuture } if block.Number > crossUnsafe.Number { return types.ErrFuture } - return nil -} - -// ParentBlock returns a block with a number one less than the given block number. -// It does not check if the block obtained is truly the parent of the given block. -// TODO(#14765): Check Consistency of ParentBlock (or replace with FindSealedBlock) -func (db *ChainsDB) ParentBlock(chainID eth.ChainID, parentOf eth.BlockID) (parent eth.BlockID, err error) { - logDB, ok := db.logDBs.Get(chainID) + // now we know it's within the cross-unsafe range + // check if it's consistent with unsafe data + lU, ok := db.logDBs.Get(chainID) if !ok { - return eth.BlockID{}, types.ErrUnknownChain - } - if parentOf.Number == 0 { - return eth.BlockID{}, nil + return types.ErrUnknownChain } - got, err := logDB.FindSealedBlock(parentOf.Number - 1) + unsafeBlock, err := lU.FindSealedBlock(block.Number) if err != nil { - return eth.BlockID{}, err + return fmt.Errorf("failed to find sealed block %d: %w", block.Number, err) + } + if unsafeBlock.ID() != block { + return fmt.Errorf("found %s but was looking for unsafe block %s: %w", unsafeBlock.ID(), block, types.ErrConflict) } - return got.ID(), nil + return nil } func (db *ChainsDB) IsLocalUnsafe(chainID eth.ChainID, block eth.BlockID) error { From 3d5540cbd5e95d65f1472763de7a847fe174358d Mon Sep 17 00:00:00 2001 From: Stefano Charissis Date: Tue, 11 Mar 2025 10:41:38 +1100 Subject: [PATCH 128/130] feat(op-acceptance-tests): introduce op-acceptance-tests. (#14706) This introduces a new directory for acceptance tests. Within it we can store our tests, the op-acceptor configuration and a justfile to simplify running the acceptance tests. --- op-acceptance-tests/README.md | 56 +++++++++++++++++++++++ op-acceptance-tests/acceptance-tests.yaml | 17 +++++++ op-acceptance-tests/justfile | 29 ++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 op-acceptance-tests/README.md create mode 100644 op-acceptance-tests/acceptance-tests.yaml create mode 100644 op-acceptance-tests/justfile diff --git a/op-acceptance-tests/README.md b/op-acceptance-tests/README.md new file mode 100644 index 00000000000..f11b4beaf1c --- /dev/null +++ b/op-acceptance-tests/README.md @@ -0,0 +1,56 @@ +# OP Stack Acceptance Tests + +## Overview + +This directory contains the acceptance tests and configuration for the OP Stack. These tests are executed by `op-acceptor`, which serves as an automated gatekeeper for OP Stack network promotions. + +Think of acceptance testing as Gandalf 🧙, standing at the gates and shouting, "You shall not pass!" to networks that don't meet our standards. It enforces the "Don't trust, verify" principle by: + +- Running automated acceptance tests +- Providing clear pass/fail results (and tracking these over time) +- Gating network promotions based on test results +- Providing insight into test feature/functional coverage + +The `op-acceptor` ensures network quality and readiness by running a comprehensive suite of acceptance tests before features can advance through the promotion pipeline: + +Localnet -> Alphanet → Betanet → Testnet + +This process helps maintain high-quality standards across all networks in the OP Stack ecosystem. + +## Usage + +The tests can be run using the `just` command runner: + +```bash +# Run the default acceptance tests against a simple devnet +just + +# Run the acceptance tests against a specific devnet and gate +just acceptance-test + +# Run the acceptance tests using a specific version of op-acceptor +ACCEPTOR_IMAGE=op-acceptor:latest just acceptance-test +``` + +### Configuration + +- `acceptance-tests.yaml`: Defines the validation gates and the suites and tests that should be run for each gate. +- `justfile`: Contains the commands for running the acceptance tests. + +## Adding New Tests + +To add new acceptance tests: + +1. Create your test in the appropriate Go package (as a regular Go test) +2. Register the test in `acceptance-tests.yaml` under the appropriate gate +3. Follow the existing pattern for test registration: + ```yaml + - name: YourTestName + package: github.com/ethereum-optimism/optimism/your/package/path + ``` + +## Further Information + +For more details about `op-acceptor` and the acceptance testing process, refer to the main documentation or ask the team for guidance. + +The source code for `op-acceptor` is available at [github.com/ethereum-optimism/infra/op-acceptor](https://github.com/ethereum-optimism/infra/tree/main/op-acceptor). If you discover any bugs or have feature requests, please open an issue in that repository. \ No newline at end of file diff --git a/op-acceptance-tests/acceptance-tests.yaml b/op-acceptance-tests/acceptance-tests.yaml new file mode 100644 index 00000000000..36fe719a7ae --- /dev/null +++ b/op-acceptance-tests/acceptance-tests.yaml @@ -0,0 +1,17 @@ +# Configuration file for acceptance tests (op-acceptor) +# +# All acceptance tests need to be registered here for op-acceptor to run them. +# +# Note: The current acceptance tests are placeholders; real ones to come soon. + +gates: + - id: localnet + description: "Localnet validation gate" + tests: + - name: TestFindRPCEndpoints + package: github.com/ethereum-optimism/optimism/kurtosis-devnet/pkg/kurtosis/api/run + - name: TestSystemWrapETH + package: github.com/ethereum-optimism/optimism/kurtosis-devnet/tests/interop + - name: TestInteropSystemNoop + package: github.com/ethereum-optimism/optimism/kurtosis-devnet/tests/interop + diff --git a/op-acceptance-tests/justfile b/op-acceptance-tests/justfile new file mode 100644 index 00000000000..4967b2efc41 --- /dev/null +++ b/op-acceptance-tests/justfile @@ -0,0 +1,29 @@ +REPO_ROOT := `realpath ..` +KURTOSIS_DIR := REPO_ROOT + "/kurtosis-devnet" +ACCEPTOR_IMAGE := env_var_or_default("ACCEPTOR_IMAGE", "us-docker.pkg.dev/oplabs-tools-artifacts/images/op-acceptor:v0.1.1") + +# Default recipe - runs the acceptance tests with default parameters +default: + @just acceptance-test + +# Run acceptance tests against a devnet +acceptance-test devnet="simple" gate="localnet": + #!/usr/bin/env bash + set -euo pipefail + + # First run the appropriate devnet from the kurtosis-devnet directory if needed. + # We ignore failures here because if the devnet is already running then this command will fail. + just {{KURTOSIS_DIR}}/{{ devnet }}-devnet || true + + # Print which image is being used (for debugging) + echo "Using acceptor image: {{ACCEPTOR_IMAGE}}" + + # Run op-acceptor with the repository mounted at the correct Go module path + docker run \ + -v "$(pwd)/acceptance-tests.yaml:/acceptance-tests.yaml" \ + -v "{{REPO_ROOT}}:/go/src/github.com/ethereum-optimism/optimism" \ + {{ACCEPTOR_IMAGE}} \ + --testdir "/go/src/github.com/ethereum-optimism/optimism" \ + --gate {{gate}} \ + --validators /acceptance-tests.yaml \ + --log.level info From 78879307f0bad51fa4f3a5c5a13836ba89a61ed4 Mon Sep 17 00:00:00 2001 From: Adrian Sutton Date: Tue, 11 Mar 2025 11:12:04 +1000 Subject: [PATCH 129/130] op-program: Update issue number on TODO (#14776) --- op-program/host/config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/op-program/host/config/config.go b/op-program/host/config/config.go index a1a768cc418..0ab01a9aa09 100644 --- a/op-program/host/config/config.go +++ b/op-program/host/config/config.go @@ -336,7 +336,7 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) { if interopEnabled { depsetConfigPath := ctx.Path(flags.DepsetConfig.Name) if depsetConfigPath == "" { - // TODO(#13887): Load static config dependency from embed if no path is provided + // TODO(#14771): Load static config dependency from embed if no path is provided return nil, fmt.Errorf("empty depset config path") } dependencySet, err = loadDepsetConfig(depsetConfigPath) From ac1f32d28862ca101cfdd74b07b6b07fd9a4184c Mon Sep 17 00:00:00 2001 From: George Knee Date: Tue, 11 Mar 2025 10:44:14 +0000 Subject: [PATCH 130/130] op-batcher: introduce `ClearAllStateMetrics()` and call from `channelManager.Clear()` (#14780) * op-batcher: introduce ClearAllStateMetrics() and call from channelManager.Clear() * fix test metrics * use real metrics in test * add godoc --- op-batcher/batcher/channel_manager.go | 5 ++++- op-batcher/batcher/channel_manager_test.go | 9 +++++++-- op-batcher/metrics/metrics.go | 15 +++++++++++++++ op-batcher/metrics/noop.go | 2 ++ op-batcher/metrics/test.go | 16 +++++++++++++--- 5 files changed, 41 insertions(+), 6 deletions(-) diff --git a/op-batcher/batcher/channel_manager.go b/op-batcher/batcher/channel_manager.go index 8083cc4c82d..acb63b74c50 100644 --- a/op-batcher/batcher/channel_manager.go +++ b/op-batcher/batcher/channel_manager.go @@ -83,7 +83,10 @@ func (s *channelManager) Clear(l1OriginLastSubmittedChannel eth.BlockID) { s.tip = common.Hash{} s.currentChannel = nil s.channelQueue = nil - s.metr.RecordChannelQueueLength(0) + + // This is particularly important because pendingDABytes metric controls throttling: + s.metr.ClearAllStateMetrics() + s.txChannels = make(map[string]*channel) } diff --git a/op-batcher/batcher/channel_manager_test.go b/op-batcher/batcher/channel_manager_test.go index 8dbab2f9240..2685c935954 100644 --- a/op-batcher/batcher/channel_manager_test.go +++ b/op-batcher/batcher/channel_manager_test.go @@ -122,7 +122,7 @@ func ChannelManager_Clear(t *testing.T, batchType uint) { // clearing confirmed transactions, and resetting the pendingChannels map cfg.ChannelTimeout = 10 cfg.InitRatioCompressor(1, derive.Zlib) - m := NewChannelManager(log, metrics.NoopMetrics, cfg, defaultTestRollupConfig) + m := NewChannelManager(log, metrics.NewMetrics("test"), cfg, defaultTestRollupConfig) // Channel Manager state should be empty by default require.Empty(m.blocks) @@ -150,7 +150,6 @@ func ChannelManager_Clear(t *testing.T, batchType uint) { // Process the blocks // We should have a pending channel with 1 frame - require.NoError(m.processBlocks()) require.NoError(m.currentChannel.channelBuilder.co.Flush()) require.NoError(m.outputFrames()) @@ -174,6 +173,11 @@ func ChannelManager_Clear(t *testing.T, batchType uint) { safeL1Origin := eth.BlockID{ Number: 123, } + + // Artificially pump up some metrics which need to be cleared + m.metr.RecordL2BlockInPendingQueue(a) + require.NotZero(m.metr.PendingDABytes()) + // Clear the channel manager m.Clear(safeL1Origin) @@ -184,6 +188,7 @@ func ChannelManager_Clear(t *testing.T, batchType uint) { require.Nil(m.currentChannel) require.Empty(m.channelQueue) require.Empty(m.txChannels) + require.Zero(m.metr.PendingDABytes()) } func ChannelManager_TxResend(t *testing.T, batchType uint) { diff --git a/op-batcher/metrics/metrics.go b/op-batcher/metrics/metrics.go index 68033f376bc..4eee89e5186 100644 --- a/op-batcher/metrics/metrics.go +++ b/op-batcher/metrics/metrics.go @@ -44,6 +44,10 @@ type Metricer interface { RecordChannelTimedOut(id derive.ChannelID) RecordChannelQueueLength(len int) + // ClearAllStateMetrics resets any metrics that track current ChannelManager state + // It should be called when clearing the ChannelManager state. + ClearAllStateMetrics() + RecordBatchTxSubmitted() RecordBatchTxSuccess() RecordBatchTxFailed() @@ -349,6 +353,17 @@ func (m *Metrics) RecordChannelQueueLength(len int) { m.channelQueueLength.Set(float64(len)) } +// ClearAllStateMetrics clears all state metrics. +// +// This should cover any metric which is a Gauge and is incremented / decremented rather than "set". +// Counter Metrics only ever go up, so they can't be reset and shouldn't be. +// Gauge Metrics which are "set" will get the right value the next time they are updated and don't need to be reset. +func (m *Metrics) ClearAllStateMetrics() { + m.RecordChannelQueueLength(0) + atomic.StoreInt64(&m.pendingDABytes, 0) + m.pendingBlocksBytesCurrent.Set(0) +} + // estimateBatchSize returns the estimated size of the block in a batch both with compression ('daSize') and without // ('rawSize'). func estimateBatchSize(block *types.Block) (daSize, rawSize uint64) { diff --git a/op-batcher/metrics/noop.go b/op-batcher/metrics/noop.go index 9fb7b0342a5..fc6795058f8 100644 --- a/op-batcher/metrics/noop.go +++ b/op-batcher/metrics/noop.go @@ -61,3 +61,5 @@ type ThrottlingMetrics struct { func (nm *ThrottlingMetrics) PendingDABytes() float64 { return math.MaxFloat64 } + +func (*noopMetrics) ClearAllStateMetrics() {} diff --git a/op-batcher/metrics/test.go b/op-batcher/metrics/test.go index 7e2ce295976..dc4c759ab5b 100644 --- a/op-batcher/metrics/test.go +++ b/op-batcher/metrics/test.go @@ -8,19 +8,29 @@ type TestMetrics struct { noopMetrics PendingBlocksBytesCurrent float64 ChannelQueueLength int + pendingDABytes float64 } var _ Metricer = new(TestMetrics) func (m *TestMetrics) RecordL2BlockInPendingQueue(block *types.Block) { - _, rawSize := estimateBatchSize(block) + daSize, rawSize := estimateBatchSize(block) m.PendingBlocksBytesCurrent += float64(rawSize) - + m.pendingDABytes += float64(daSize) } func (m *TestMetrics) RecordL2BlockInChannel(block *types.Block) { - _, rawSize := estimateBatchSize(block) + daSize, rawSize := estimateBatchSize(block) m.PendingBlocksBytesCurrent -= float64(rawSize) + m.pendingDABytes -= float64(daSize) } func (m *TestMetrics) RecordChannelQueueLength(l int) { m.ChannelQueueLength = l } +func (m *TestMetrics) PendingDABytes() float64 { + return m.pendingDABytes +} +func (m *TestMetrics) ClearAllStateMetrics() { + m.PendingBlocksBytesCurrent = 0 + m.ChannelQueueLength = 0 + m.pendingDABytes = 0 +}