ETH Price: $2,553.36 (-7.61%)

Transaction Decoder

Block:
18971530 at Jan-09-2024 07:36:47 PM +UTC
Transaction Fee:
0.10258533265927698 ETH $261.94
Gas Used:
4,946,220 Gas / 20.740147559 Gwei

Emitted Events:

260 CurveTricryptoOptimizedWETH.Transfer( sender=0x0000000000000000000000000000000000000000, receiver=CurveTricryptoOptimizedWETH, value=0 )
261 CurveTricryptoFactory.TricryptoPoolDeployed( pool=CurveTricryptoOptimizedWETH, name=TricryptoGHO, symbol=GHOBTCwstE, weth=0xC02aaA39...83C756Cc2, coins=[0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f, 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0], math=0xcBFf3004...26e0fD6eE, salt=3A847C6ECFFBFA1208CCDC9B7C0A9E9065E382C9604D48DCEBBD57031D1418D5, packed_precisions=340282367105405904200470123591768211457, packed_A_gamma=183752478137306770270222288013235334186240000, packed_fee_params=340282366920938466045918777751505437696000000, packed_rebalancing_params=34028236692093848191011868114131982745600000867, packed_prices=902650008195656700496122197659313174573420334391326473002646, deployer=[Sender] 0x818c277dbe886b934e60aa047250a73529e26a99 )

Account State Difference:

  Address   Before After State Difference Code
0x0c0e5f2f...6dC4B4963
(Curve: Tricrypto Factory NG)
0x818C277d...529E26A99
0.494125730288600461 Eth
Nonce: 2
0.391540397629323481 Eth
Nonce: 3
0.10258533265927698
0x8Cd52ee2...504aB3eE9
0 Eth
Nonce: 0
0 Eth
Nonce: 1
From: 0 To: 518432465918041559291463351244838653539200995389593506077045618018102544327055121505464241185349109659594012825548655725636171104580279610821919345953238158695601280028873896381398163867291841931693389252746938764457778415781109816251899351200165026076240584809931034961064488892454403534840402773466557636128391431314541703647444495114875254686981518126645920187736885854133275151943534929234208796615936420516791419505796139862452729376976233285658899456589253333124188965572194356608646002770580202283304072385688091100589248862524409308029694396204195335162663339121634105059011122062891695331691050492117884191524732466012467686352037088328571766997955633710673627844540960495662104807404632061865599829842514527492364621549342050892080308053896859637201210782049583596760293107655479265150932081931494717978689118561561198957239757454835651508547160587488330913544081658059316505745318250055235665308581827017574612955412432077149404654353988033263597756099917459588126069486531963424481338969406378583356447436725667448061661608808121898509678358686933379274403547191866158418653015183232539906203591323969347357012990826258884407596837581658868479491604705064245263276712315410949623067030964817703972193970254107538671818407377432177737322396596052586095546781448507722773772998159224933864208339166038636825345512196632793075655366200248736714104054908640107258652626887585683188485518699124444769045942412307869036226830839444365081888832848903610484873113632507980004295280245654516901650674951743819946829460535478996022372639356594799858823819598709557297576664632045475332122954073812892881105211953297607407362007129074256531264691174321791237938873855646081091050790097808169476096981067248989289127552238799802829415003981454982290948883128988966900387445489208958368004401559662232661844988666563790946132177099301800199208352348322893429861787454758891386824198526365499184287419419987373201841763015063893719051812000388829027432646225482564995770691129264897862975949874636626138820654851562629401853878706242922283478431006558533026905717735564389561467935858626880521382897020509829183587388045227731878208392079946184007697576877656737703910010522650365821565941024222963834299187864343860537900267285385652549466678018315638769636025570836227497850951886158065781504020424257365037870456702059592548195402701973384184968139110342133869484977186167120942916612644458741608660699590909132081387518711731612216736131925371738738903045672898700780126903524895297124579490260351766662760471747279181896805561159131601873523757415666793117141828790482168619258586888106628321901884464000543163939548648791628438283085419259057594813068865951722620370349357993722501314100059432774892023934570007941534500967312203798109845622831053548343969229549405663618359236487535000450871086616242081346295560444472931737212266303007903825855571097381377808900616969113682531148080522242162749178582731463543649513074067944575347114647876571557534654482614865997398520988939679192359676893762067114218668409056636271772361855660530625841019234318725040204806765555903277425232407860242736993321001084566341863869491162949536628188909918621730452674725772442392933321834934297172880846787973489937204813208964154462424513033878692654932980337285741292192948324494200833687259417136999539486921557740252060608853671834407523764733923613768751515379642060875251849641747047479094992752386056782529741183199096039771107947168345606220568481688021910377621557892704981069084155830124168189330644688421694483028179228209000817358192976969743082225935636825709737258777976869464989335683911382288773581393582985466498463857357716876635363339770099957645792858216230575051284423192992147621481352078486201802312431334593350079690271113013708951464163338141230057910986175399730706697900439810879696448236148615973203019496566576912348941623045077651752111982703357979326665331039705471866872556734557203942766676455246966840879180526161017298321161466362166547915067177322173464674619490272069578967070592146716224734660029372841338312417452266315265937267919940813203039628742774661511144307907024195735340246838953181599521360901524254609773240201478276404660450436553099923634630292143310432114177455425714281685875287103008590602233220036305255955124081825768355687486852083947788036674974323627475086356395108925655647007194501182714255936732028719126091030310592504652461894858617735048355529204404236708059749803896634956337996293378048103004465059017244027449430802862012302557052053563442458052567483081553851244305004782110429120334430017402363478659423341199133775394589528097712922938926316356847743330491239433799539714500549862603441372633898636745202216404275477353725023647815769941768540331402481217844250669727940023708408003381937260051951748881381964330989121816503114272852468684116601520707061425166578082493783217615700806717469536346942019651978856588982633479339497614496271805337208597519068781349387161691581208610815612172039003711838259125930423153641709321344917419761830742509633275146033659641971523353292013909655392140197687208483801584249502678027181242020286025776770640273215272151109375219589019613382118688837850082391852876013903232608659529596981504138657750464427172077187945194105907403022515806635309779647829902028386504562916417936526609147450512207977637439210516982484656120059906298031997999935955209245010384342978476900575238981295585788595944104216229671241787690002134097507881687468368265025997455482398717969324638707187711493159165777065663561727378398736686188449519194693056913243942057210785931985363943703688063179126782466681587043876560455320058009554939146435902910046295695494468529141589393558817653665817929893864788959896755757450837085720980171232585820521693867426003716323405236567723324274983440135542437602892103002181546390654007013930200379038367044926460579829045587426574686004067753042241715017167404306091088600443629234898355253605041909580641770558550181806982483465010969072257188723530127486742538946890442930302196156171186486226810018137393705053649478588338855159977448324354706636869921111368017887072742614868834745463461923740068421592190291559072034646613537305193084142570048992592757107856587473962596105442749414344291999758197204003744690136263416387531737878779593806473067873623035185777919308335985081869640999755312599725689095602603180398742622087282083861337142543570116781190581533607034116037929497577692423523380486482905807518850461597503761093985509106624730681084379476095803535849119211191857004893691240932445944231147376826859126457588967934009215238599497473219827994147665801527831402874598433629735161890117540432130827377139243367150450694528107964694508059759311961609307887851681352577624276512287568726552268468547771581064456272526287880945453130585791069729893990169235668145361095151170780765434545833766931657238635612552677602832131306094180902383329165399599762618921730129210253162256761493274737807309451219261424596128237493150070005416182593668645297865152617606229193328038853176957323944929227458392536866228029489974181305948062704194727285965506618402211591400053488369666362826150389120117557180670205854718263328621827042354485197242790279926632316457227808819574457695889459569229395778067563014820842214535165266822997874686481780171226799983812469518022186962701688344854860985434945206118914014765577364081011915077816566723105753287325255957093581850518393499096667416127268956988381394382750079484621886389725835614594112792364227529279879826941765015812962824788903459309662186535352229865742683559091208004427690211900362412705402805802989258557677187093332240081844033082459865217854351721040511130816683757040960492353928708071244389330726809688778884194910002406308681012051812325962032350525406843148671681272493202881811973459298507373103844009266020445443502048234391532813062551485748462458141463550902079485553300698265658874645115478078627291721636685422807163366621334934014561554702023221948042695477679528953592605859300752355553152215899625574619815731600063034702111440093041736584578908136803558916046374767322667198484786589213887249909724851025112399672635525434944108488832138626599834296860361767838350105182143778242211153862409572929225354594397235937849610401848838776352311794236789571406506271035854883962617326526677119412865275604954475035737490312944243574255760741751779851122887570155854659313777383353694396603103949641040575070545932697477253819451438906606910935191271908988456704378609964769404057550357881386620923864246886723205661162001223714573769228113907004909201783709110191285399966971566251319878988094416065581188950169884035999069184171261023625952466275501812195892612198252176512860754095425259071509847178802216940309067850076126056597238234665220568434198589938755426804851849988588639070384747797969443465530481375543241789518928527892545742630126901599123659753743697961387068293709877637788471957169033680428298194615666487395241076416964174677565791594946075611718607450514173276152702189270040307163367809737714073978041182559274798164560786094137169280965290644433220377487767739520745399623174374093484011926969926525868090228024520924121395457121768247662407823781139470597651275193588651710901694195354411788738097438903419368051643850920298210125989647140912161469439391200518495411230838752151819514663793919896740876316697696877495594603654065333165450086107788837627989643192333579434689334129764262789384259410424126679454350540787375762766068742721263582793467163535458934244733819245894175718541946267595543177724145996765737569377081731924273110125850476543927932207091828463115090038140208921426839536638296332291163038969476994506660835445520113861666432168978682183531146363525688384328772020986604841963601590808247297910198359976756718281317806847535806114472317094244450303498130169487017032219449114204054517926041145355949755206432781321785181815389930063477919106096463887839882289303678798322100917255025425862941818586248269057946476945299819532754609710596420151879638653732019331157019565564435420604946551797527325165849081668445468696206094739616804820041413395490588929073084238306618881788279708515889198014016998553514916684252413748009930485788272655118833014044480159843244938256835755820791659892107292665228797433457870529410159159907078556485531151811996801208203347052593533041465687757793337689300100871126351860850662034513389814489602387921590426947661354186905821869750182382181705803482557686343689721918780090851962872271108925781555488959340138944804608813529250699150579446846493272252417643480624320503705252676428738776439631566491347635366862307627705749822198283010522617452471822995653080570691495934882656575255871501149995917666450050561195852656583903014276926428090847499635646969237925505628795354413492333361205982087286118632342506821370830159164751707611418929583925589027924065301727330645638560530767413554802584885602524324929626368101853226967426050692988796601749928145964685070928681166722080311548855057035358561245593640083551643774838071031149426287820005191800085049395387825286297057974062406413058475641150187760466528990899191949533420558684588330675594714710836147479621417162931147770822795210314319843531359716385961831477977085321592599594641453680175196742588101406871042235709236075339874279655468965821519107260789730499953771094232771660896505006706774816974879839927963556710214684101396696777761598061308594799108465619422517017831313967985434170921122344057471814691112017484005239501911666763834336473504480973506437069212873662714718539007927080277245242289328311513107691323724020774823097307792804547421428096622511389855776533036098562192616413306315352898810103818838087403261280506600772873419773160359790460179451296637954163127881698909274981413049374985324007371687405502907794555092906270354205642116020116453655079302355281937553476038283359704387364054039351810422093961823293762002994759021185662852952600326693810518877322376889645708375673124601257619022769698621204851432967911431144053625047058695481096107099057683093852725609008943468151931869331371019792220070552977591957805200735014188925125778906774186530309307470994707370226796429720326008664527814271936492266273680311175756924311070976478802229301971901143990834628601135153393202267961516146845645616211063688096000129351983476109396657631005336026226945170153024690344896121810141829621761346844788083605541819757137010645059552614373842093785381082880914053109376437826176611982111217188223312555631778912613291738953916083236213260586499778728691341919817453258810252388056678207052972358860395246026717693534223528184448705181841818166681427312297471541972195858583534428539160427086017094902694630536002983505902764888418618253805300155774498402685521395734091799830912171838401439260108272711601151916792261917503469371794031762731965083918686410610523831556976586346045823305716785382479953738727544221858581751196311613823472450908973933896130720593722259669565382618138660905168251228297487625589926923847961270779765251501733906865541867314848974500786287160174134508941314942537651020518090303419750520422606102043177352892911817465924807947024882613395961464942210607234825335204723041049664056003144325395846565461137651069849637648244043125183626239110841198833207123386875319088470569802409543642301139079685283146077900141466575811701031995634541432953743736149883189081285328965361763481183218452987333953615313997780189675139172861641956439135622808561219132974415773992426378239719213879537105453116103819010590046504507913178336417336858497859678139546041366401394776382016085843916837872400355416008864329408215986583177445363090771810278581004515209526462908710455700977243225636831215366588161713938103481491249135010807146046695447008895969866479743424578443404263370274374575413534261722926840487389690401304454774126595634902257424278507088883956084691125979273130260337883126384081942613120843638689887990926230515138740407766836998631280081853949660611686928978841766250253553025035783759714606584985905171768224456175987192670553623153880983432551999657761011325767557592664001135795225260344841869704541812540041798435146583634857807229940731492139259346261263693495766116251834793651379067213026627591120566066762376748446181025302612264258392455526156817467773634611164329161265430702189208568160708129322319092613001536661630391083945928740924111960197085042501925616255268091428066421587790444386605557716872881539723386303173575717472254492475007520157536909061085815526910391190258437904551681122859578910493441579177921105520350605020508911513908067448058244252853435271358634316264582040089495973590078337929363258510157972825928319051848892826894982119301426167600346928975060063683456213320788850003437056172758189595869641882725289882937895458678918174306540681419644061281757726432435477411715658978876244458570881195224101117354955087861427002238741823940970769492290843294287360181630865696793738809078668186426226182922697305282094988289964205002688726681324639335312816239233704877621970660510526466373910777978177818726995542036378002854753786639819587620751064040020214418581510460401631001157566314041720009841022747914406781284147822541591506791283111099487110429960921084278828245059600641945019617818519333601626012374914174385558560005226392677819910379967787464165697313782764492718885585290859590557564918442285701766142504762683351649881984257461981222857062556487785896097304985911598591273410030169521190797990217916330264636733726054397614595901804318724230650340384766514021997286156148414116816565266255846300204775289381067885967121276410065993648418912325881388752379378901793960930303545719321318410351203505214983900363319339090354097735758045673222179262650833382498724444398124890176634261876634112266480671669767557782193642857171357560322095722796587361110297403894517315956964457312963126624511230231821303764725825698477314355553041061872348635322857105549496670849774763306354297492101463470265445217792134684629840563396137559013852584473905237810645806518627382077023190437078559932136672399012068858828279941629289600671070324989479922549744661145720690275669284454602525946666162377472800863788506962398308909240310508521689801013842974320419140737930825096343321559898204341023351102499530123259383089024130253598338605932143747515414144279574749121982143535703554502933732574441238567995547123045281698653296548189550540500664940389466957068940632194822237896759602630908737094350842808764059058481431467457955518594733169210909735829144831613946776700372140957970587549030685010858526909200511574025868160692572911697146069382955911330676778000404769762306689680327821952385217443143418536126462516007161618194651404730812048873156485333522873990640444737741304461906475315051790632494512289410275378296796930002905684202733761178294631129556538430946238115745834878635267894217221239775110175607875034269130285686749979051794059045542629383698069882916721156062548430761430366181493650146661293098907896043110329222640733522157868107420769420624260750623534206609252318649418481532792234953077871184576161865586944974836827512251471149062395772002082501795675968166508511876445151432242948155343080542263622443688279138892886441649153826379528068555128650419550767522508765490850585268600903712437898091827735026259188755462130281448941706193430438392309464255377862006958289889878401568237490038766846178215434499461589325948377404056253924280396691849541037717296225959425833624504690385766293336054264827507333918337579088853176653761522148483753101392106000294007031282703657096904279245873719913157065901001602624722465726266338563013776507431739503748357322851862581625897528887781266842170625588829397123719131135986701274895607496350175824425483894792830236706497107558809599331757877975518381562919015394412452368409067175193569707659617427558954328119638118626467457566800787840739311426988419048750363840104069541449057840954881916429015373609713574659584291622405121194457659813344183436851049797343363379816562817763775782005262912774354667385105107936169696249741790239622112521825306750199177185102029142486953254373905216612772980914049115584263346659267596293183681930167466788776353788204989926081897676248192595569577672301323030972382252239713838846685945864318747927210001273677108874473008205214258709680300077896676846967459865411127655060380860970098606673993911450271878021300479172224432784476060855702807660875224822779579533173173130350598984390923484528399857196104741088134313844842765087777164184816566629780035540436240483424658727531322176645109614925770397248318802855096373863621929373962428773065194038664974727832297862501078277335477583787717203943293372715364473445550402470795335927297331405158000096880385626331167993828683722429636722435167650578715415647468371075671515564264990017654488371829929469051171060013452630829231816608431447078675508221160191295058222401756718744965073936494060232017292005340999722305525863189648925294683361537738985990819657998984845970878741759493434563741827697773349923394362147323396516339641288404685383968027475242975632822056649166559163775814269123939165997435874972906954806692009297637764091901015690898797388597270579067847289395488749556134662811908752504216464838957784840470897720621118259713882628082157435157287658154623148162161931137260006302018295878878475038164930641259316386586833235703778262718212855695945225011003284752421283797818307759495438367111354006206261458085143870098749346400939823909374271028236868852557678671564823511106521650919118193725236400003898772180042446347128605957138337893782763654123200016242080989228487621004684102383209164747501264986005321945967758255977678545426255926476096570711312988131446046901769049034220182797478692189669904418561499799317940481189871898128116653328837374758509870169585444577650026852712794141824641838980488299441029893565041737106705054036867359979724955377269474136651549654332862723657724978572144852652739365965929118028070132089130085878667347883612539210305786814742058322524891702239369110030856178071813252196720870220664455304269217515869065164642923834633050538208746863716867891443911344986679835248272884156956475103569403427392528500026275967975516511014430331975373594823573606559814844508243703082898769042732051321888850830940914402041864804089937154486701383390063375138267816339256404370652860512545716710107848777635713374920577962590956841554270630810309905023222831517794714908778279592404896388987951798260108009187656438094592216899027540022797146375143554663868754262784514576654334695828718355520944479844693538730208967795688534728313120469259091905981732096121655398277805847718602256025941956129426744956593083558803375665376808700219705001373996600107642283023172575492571956064699454746423916948581207878117302124919752471445983132531177743693642270697122960028764756394087611150369174132668574783660672734976385345064307121371680862069695794001710227131798369934864815780875684820436161627567036122213140616035735758605134929679906025310429198037563103989636562328257764603578564179052268465200305531032928020773151269826586886396244931158762757002487685009793506735200588314631306500043334580158946385027740281282698490046150767542717001313338438114308384945550794126135378355520039095290070431741206359447947794646056910666981495206461364026583748158195340290491682084252833482114187641594914139302731216106437133898812863591347318269996809930268632232341949028751442366132424005838137518409279883855761456400299715281234060743957342412049906460840880391140058532765811190343711969053649386466426410050737806968205924192217868633442535958769595678817575836237896908929145638693957364250888616231533755396112420573705723933595920644018586463024761640771337479108614881974038392087808937358654445707358884196503900643509682328950199014749235282751745663335644302869260096856931981204799866535121955524528364754244321101187648341077596006798741086067150631914776068815269995603034663742534221297956939046655514197024730337085497141207122202589057748904944639338024889152123830678935147809445068665264460795996574541519560432376568669440145068527543431816180415538622611499210584000841270955583488701915029962130845554003029926910908115459593824110173332150220980542136944660344713295289910038554319345919542658591927305362714058158730246030720485912026367761066904172843288360998730595034885085048841648034733362756719783623411353338372115972755636966384280754200277369319534058502278271601497256934484488278350342968541488230056898408704613953583029618251344082260994906647515422894871572190335020907686242327026484598196117956705875280505450878949560172382903352897532819056144680931614612943171734881174248276415871424779958777173505387822617220413457027253659608040891043064314745062960052451894118668882468982851522524990452515363115578823395591779861454315821031433277655923280806900072647002181888489037847614700788763381641975510388668811500835154561353416145750023034815102179468642561627456173519013542717448599415484693315109361190560551751142385610465909159161469285311080990822447248983336671101148383594069800110733844430222109400784138298349777529481432276474669786992714434023245384677141087314425875197084046369035226109183383341354472306843235487104277402029902666543785006766967016803107831327903291708563810093225637814932450622039306317869667159179816213260191833436326632506528604841209694948768142536348624640143297732058876556823147330716676451130253810925877972188347681952487351812745849248928993318257324459724025855747164597677584346312386617654903786359879842795049437363511373953312219342467168638612499204145721760924939660714041467098282475232763873588057888411941766525721553491032354642720640013077881325694060662578581911486605493128451622098159784897737090129313928875693604467502043643822897719773666097641474115702061348420298766966289221474721700256370500641697104067083707438421167154893367667161831342061763533429742935358237948801966220919222097601688333076484170650282410659511684092875726523829195029238133933442513058960691881761189420337009615827419083778234995105765903368482372027580604866675973088079201214762833295904340827815095081818521815235303515073779646683568753038151234021442121904550993630655130815037002677254585229819384763850489716164710482940110572144879393791628080331675463626417631097520852946754333836960389257917861197000949002647870335712854014407113546183558291202904886689594413022951681075190461313276423184143382463582206911932240870043361582909344823254102243094602599172585510173694086789565194107472132494713616550232734008304313122691536260124840935510229649981864612215163944005301482241975889451091834186182167248488235553504541623489938640511841575715859916446841809916171135164214069788005062613017101045896948059766815533048953777945196811768049748911089635331240373388202335715154498772075097423793871220433878700283306284842606685869601478861974251165053471167940448188616648704091887239868238084778873669848178108560246943715550535632512212485115809268684979759482490752562855102590119846621592980154104591583000447074678689491377929313707480487955788518572670601210609285254411697812738809058511699340587798510075330805724348599396712122591175780366105165182956532637015790292738497648526153776063006198684185581953838949770539055831501316184481014628078849517727574125097160177400050469219988100200145349194387670418181115830764548075862818699067262536171946914537698174900199017611145598775322207180378874166553821512261484266064714780581633504883705539861027525669545965770086317686608584827641469097989949147391822939018586937097287355753482032767220179308911276190467865956220440155712076186239075823280228319141271460424482258213412768956113395978736320633951014912955354627378738220196642764443686907020824914323546210100287378192734429121335758696218462196132823901469849261488911188774230370334906311730995233260009370221782408168095637438143126727667502585065687028847707295219852312524734251245995749912604621785221347070416807832473059001418546650322037458974577162637938657062001693468877685372541630878738580807086174162190125197052606000112781297318419549977109037197512387859453046995089212778115039653482421239359800127221082541356448934013348516402781889431838298356325385110108478217338620164338206914808345527286924674563720885866885116217717838677024888659920672946127125027489673081625530432067963624110223121528292424943572645289113382041677631850268322102361749458058163730136893319720220267019935799287304522622494612240985628973175132242535741168796661919460743556049996607241515965652882233062733727371663817674716454899499153255638114246815966864333433721795107821408974439949223485882289705371457137494639990411040218245358318550922289508484187726057790118774720479505443094186296032963689090751691820635001744109928698565979816302995442387112824245461730494243628418420289062278807297948970708654288712829605961816725116095804337101369920520664222464846269195199751531703060845633635611538229311352586003805746261812302272684983106298204322510474902504116892137964548082488279163092969693418960772164874405592600635734949126359438665875565177356839446385288045301424306376227391576795004135373104007271520477353374882762091740524705616826602748077159370627929942161228579489960624122362428863089868352633719796608276396528636908025369569439997877507337473988850266374377954504216240663699425150962124896699761770236373995659004528983185292020994476088099472470436355087729758559683059002746766694520722376485431159273878573197405677069934739066828424572437595246278599344160640580675456431904812904429685460885532915248421617037316545088487377929676731035134860485251122374821548607400786209989772783556630416035669416497643255129421962807105875075107156029598935153587955169258422456358197620623732972355424141423501488801037055223374732911291831531538319636791280189075956722926990622635589440847195850830672692874053530636269580875251057900090023769105346066130208973208264528557908654187384268217716711637889006881916820866094386582096350548779018108734071607671711515869662093886377750032315716913993199880707649821067946850183037218787554358682600413261708960308533021686497228757824185409688425260290158131099047571673378307458361903063731063425094971122139788095623206167036419842294119510828350767356540904105362267202189637120766731066779202764671233926565302318033947126462255084938637611985755620013327742921604242622080842252439628848896282004916254059763838840685718934945543303082172621730963758296648495391077184330899727459700375656245928161521201907013929424269111262539284238023627508028644489181769389005925260525098762827679187993682601744904113436919247357010346110684108152687329577990660013990694310379525177896040053775545072336897841625957671881658850136413528956265171853544716855182680563448401619796266324788961196276424137238495917598102993112181862543736869504145935193895933281724718644364997143212443213891689043231111553960341827986243967185591222623044922170731932920321504990671223017139464595119142113904024095842197448717265304467915003657239407980829806933967783230265218427798186722290994251522282919701641090653038120790579050225172192466214974018832935886687697386300329291780237839586057688898883684104678598348263088731098315261081983192638263110902788557644893805890959198687968935834963314745568067596426351127597321957807953553610210137351160349760422630446111757460031918347649217698229082921153192982387316207217695302004074463906692173059382794588510281773402357814478524474118101771191530595036659341717701167372162845422819549922774450302592898873029776806055751439452822226857622586390473179766715017012353716303055112247932208487503177057197214855443766805000165152723037031323371832267892366865728013807720722214536469170817178593934913825677799212890377004668346858444594834336836097647933944099792236192237750136946305750116890571132464747666965202249065515681902351654221074365522255439692103480686053685218658540537823566133578333352621797185226357600326130511708698186884909591193986001777974228776248367320859056107388398970752018955595890199272349708763527293125285152065432279167907820479635719210778524661442838169521379225177146843573988252947338922560234151597177800966243240849750067233964135608216471969644077644887449672846400110525035603262089789502461394633006076288552399952706182309548959763989145713874508560914549226065779962527880324389198595107096681198911129833698867292794543528107689106760362527531646268112928801618973431842878464364251874807888962341947845688699633975801290003957710869882601952565702628667039551033928125022655071327162118439457568995487735150560109264669177378837627130934015246775684387512913861749873026340280838036863500850692558417027041172628929474628983656951454237432538036001755261693398083074272855575847471463456058206918324725553149047626891491108203450570399470314362085808288396271075369886343570290232168118408757565203384448075882356096129450712681475674526472475959550454413023803534096935695006507435679394607383819684300841568496150902690609428604292448060997104841478270319159793337337813418343913116074546008526088516319816041555482714800436074816173477467319096951710125607474895145472754930856330092699904923161228445176288742056454053708024090432819090895014380647327559125296225562524997719077422961999569813572625657199135928115710668674731405042392641759537248673397016458600358464283364746187172932151322154164851067344406580130496973181977012706747353458848738729275943667409766445492495094069020741243050496200225718998375644248090019677860771427852249948757421227930660093187678783342645620882628888771595550355332992688684696833392570938418642666952447551180718397948048465371157237863098672854842986178824562709169910355842940142792679861504715821838793430475929170219105654827060755571703546592091405958091759284545615782132536458826152601840685585465303701025053787901832767208616355557408828616519632330420303951465003416000805551535828279959814795863603450335599995114143509191976534126998533126732723597515545987393275554337223329742816425368283853716626092250095679182569040094356567040190081088082644222855928838305981849582557413754139046537730104587804978950107866151079229169832391831529189396264985669244108264812217634515707749907972360252964334076354880184216118519134490872983083080092689001383146831456852857779495360202716168079927510432134298070336090143542609785302574226767059618361538116854042106697468923919796803866144179926675315500100417717628974590142061575908957324274414189913514583448920166549797333356174798358617795989601195126215960465055036314418550805901626483449646613839581604338206704866783427308058241227940977193222355908726597600385167632489307705968934451794157614330707450495248133567402180761414696957810372273638122109041994563069569312697993616275121575476666241255071090875996915219139967024375442716836041799479641182869266006727121526138831733277429233663886577030331495494428225271262860221189487555291338909947727485911444392174456515913778411145309325742253292938358335075863699220563587557674164649043549036136431133569226871928445685625633606915121117564844404694871626579412278303848301565088912267559663752464075624227676695482184817812802338633836412402555615128087656189343011451321933727699155072057733231471517175826317029219395355481039330121126527893923983858275184230973252182994221632212310459732930940721233394533024861508133396678331205929743196955086453280088798133771116731853746824853953526527393955173703564708392472000919540294640628868073709756479386820584881213609546457507980916185697154404654488241571823789173913760715169251619378985467256798234836378169995999058886412672821235109135711407379974413603070540599708638234908430284481300422562392361998097997534291538636049037256850072663955001952076635746506554766563813828291079582710268830163265905126241353723730797272453788047220911756201906775732820618667880031670202534188386589081389562254786329449094560492191234069187883252467159505067376663375583213878097840287951867961120656584559788829066473448440883958727671924489969774255101755714133104606891381296248131434363866993650386221101194778903378203755421106069601530121228502789221524665345334337970997673963361590390296234756529017316646140070525076808308264713692871679385388256327229494615589619083538296437875710782773089061883854618124489510083861392482647936777401612867061433330122097459717926086298496086646195709007279731895245637214951153919221049210547355071655066147850859913072372674783064931918190944642138887197706609816651363717971749802723621277570995551672057623983527426724067317816269318596154802554262469234793329216818912386984925043549518057225288217549932578279231241872071962010220454965409121927708444006370070017783919208404319010977303718258299255842134912496317933784557573567808005718951728258010049705745281174738753166763920760791445037784446732785121369919793755665775775430284447063007321713337549603685265133783602423713024480616580955952698960173979168336411789813874833958518488473731404202143870908185945947548067919104296452408437198296291593212228504167455051180942495360084158148578994127957061587699389456796596445260392413708066756021285838547708814944812663101189417135258369302357141702896736198309464853366783318172431109607166565377495127101570187021698414054660816349702860587386655353902384116475568564849581649482678850540452651516076876356856279957171417693639300338507826128580471489228521175542153456208758769174180530071985294685777302072846171445375051234545204475120043993219375303962990873948318529757791471634615629444374891521428736485602033370975594786104714477829550721539293328966498198067783937888333623581371797862838465255635521580563944377957694719089770500116912434958515938457555114886875810658080190597694513634947071731238925908193561270335149131086899807880072658986813445817057906633665458051086402586087541459925363728092775948681260764962912458771163178231660406057728318192759714241378909681156585944336316376108110120715952389410629263429734412856766300294281655598667422791831951266487278062455459875789423777587012496563144544354890852653072538866757079482120055059479450221812452801694252062909386962041075375456639290550623061278735925293379359244147115301848310337352404785860105232268952579901031669542356829179775688888161260976273909677409061069575948940591112922376592830691433153370741535720673491468226567550107654395148722309686396961670812916768390829004159175736896002296240134109869325295207475732999296615057717346241154021008977806479840736086650744590723331111242680136138292170866820447504815950909010339459915855983759432405444988946176916323004788693679790545638298262987681216154781741019467865056748304781407435740952701473152623704333446025965081739082688443769201976905189983823411004138154882110270739828677674292905392982211492005562714946871183331114354194849505033497749123476578940752047798216599843516225527776156388885647694840995850985241868740614166997994459232461126865035635014645386908553314453593926586505077987634040225805184634813571621458890699581381972197725066775591474115634984735489588350874415698825827806031966612801436524999209644312329398103870242324797456338831382702909052243193942439551638997283887558877600451754412605681386648366335967280084307403544464784652111003425472546725427899046249496150324487279973406450774702158780778941231278976353301946998012769319703360632710321394383012361266453396559897902007348626839686414719449864884260710034177085107068473711973196939437314575180589327495082914036891217247015115655288644248797696074629489611577280894234114638844583814212178003522559262552292611671658785907148177777789210359156518619856813871030827661475903371035752207469495558654072709031949810289403053578722200562064017838668751480202902411076252239359139703491136558066855517982358431162723682947931568588714409757005044885298931828390397024670151983370534375202172559277686162150888699049866304914550878627346800313119685111314515617957549306084612875441344870703143516549482444024994857665547543090748478357875695579222461509938378796799173755670494564360288423375291169929921931708655724194817730702097422228854040669938961315207784846968785788455384853016460585485514041874897403625703061429300364377466387869130139377080638372043896499731741177924469597481408757403992898878116407201929642780523010977450267894325419293642637389969043048633907504657350648002151537334049932082233709687266409795657669215141306782683389258753685576181279159715476022514253421329362829403104171866344216457745603601395076211707753574096682421328399471220949427535188839960067078211148821444163035378241443868840008001777095666719053977034999117056473372813443455037471154070463728064663782642347291378878851315515834794145662515044659758033916646147493758414301700829903551628683938223393190605236540612258900522983177078231829383838065088921615267409994702127597536729489766591416556701717395104595953056840949750838081434664163487911934956621552064186806123740087156108630444547424592563336176828757021682973408394516021586988737781546675541793993601165237973230425278300676421117664997164911718857240097689180647248884794854904506522449055935878428662173784405113411641309310983561718741845974722313013622868967797841010879676089090571791333142937510735097630218257673849011783702295637432984513510718568048335615385022722879206538654907399923083825959200940174883448899967520606388637610327250050682707748531349748918743281611781321290904631997166212559863026148820436754627908470239503684381200533722446561549333029818931717736417465479894450990696469912317224806461210957982458182895367245774728094586615887549341160887361653256337621452593402091479633519452517633190327146246624516550415006335505670187717559548768395260737284054823787196002636852888333176566388058057891370722781747872462346527077426856013104994699299482029004250991897680715506729666885719892399744322847773994755200377669204118276013007393741388454798163295409781188113677293906727505161906384664276388948337069785708131565165061808972191661073009034149465266280516017668539554223754335729541270075574993027511104416963068173228942714853007346996322468449238675006641393265281916909943921623563894794297682482681573602926008331893009374622553172354020274473208743323722163064849523325084885642407709230033989459656993450295231786523901391498332861307107184224848330086835817071375870770533209448838939594146144948383051848038843386036090327390450605679615356547859089578675361402691920512605223269397258986142443174079444526676839308563374087636574458373835559674433667399790556012950714026040432929907350619836048008097796429186926665422372449125677395209347972564215886074621947946409170592810615733738169143577869997596359612835265873393993007274655343907820526230954335593228967447015860390581583845386452048678944511055745695813937320253445485825177216028169161798549727627796249903390540389155965142345999984903822784098524875548321341316164035429026791702320679650436258404561181890189350816675816432128831555795906701602538146399582974022589416863171998309576702789433952544673961638745114228287977891790914077101088715501694154329316279048002517800668789481108020304995346656236801411898055853348520042200670913704498556902566221456990928604787051850491946754735787621474545707291450529418724608178377902215591645733185786410709511200983353987419727105912922875327919133796477999684145760498410989162769603581525770271917178501412280191645610193245458485001728383969015441608219055410324275597106071627858694763642115370358706716683726851598393999847211289931029889650204645401742387938409611190855146190689303209496980385987118667738615514381677958640399387604836360487133446228584721781675365571535105116332251212956450727560955057453932515430378186769007698687100508477992203986731321021214040191429167758058566740275685441672569316943019377868409158690175087690752411517737223940509367017352873556975153122667579957686377574807002248182480548499715407437753532236775692241394272443953689875486009231496056378364753859500186337128456644733868455674856205172105730942965232925176228971806527948985311880221196289260598946278693471481193525969434575857131084102174292094800657279527919438521127364179019834554139810000768148784934473644647822390579754472771413137414274844212789440331217470321233321831274654977752896744619807024172060168379175333397942852783965736214102929181322767907825183449351468890342922988817792420061369342128691828190219544496326558787101259156470397078833999380286145287874347894479332507418555658520423934930957672109186552190386394805235651103242117223548144963932065790700004052236431869904564365830409314727299703973976323688719624159745003973364565831368286452157248896132903006892536110482491170521161231345116443283751660249156068948359379767720686546689351428749134396180736265418933349707478777699813392030903244263785406882446116792527320064595149878006638180293564382790951163822938697199810145214488517634172775265350872793637140804327670097428523612534531679036152220205118163329709746448460309072686631041682889512844782571647898927438719266044845271039636646236401290089308048728775179009458337536166268845021048396680592166808740945884771256227780500222298725185017216473415705943012064700377712808635913636268562229575920618884150422183176151524188028151208764549465655658597588877006774552424850238458039871735462985094339913229387704869143268134105732067212711507861247811088041049140323732487268970177561085392418669337651357249811179103582924056285651510964949427117273398630321611443123558295435987255259140803446947319192372446252196055876735694996833717813332373157478254469409959571270172889301701335926999924149608257147613237308532894985656634149722617186436833127654817370311769699966903141738843237519429463452863084136935060054439775660583445658413036652178714254225658157398273747010214828058332723851549610287926696033713511688948967218793146898808406460651973819165220153655155898398361918940622554635448947443339646847081826768514032932475020039314384722742186272226868927219369350732531911279114405821714026561580670875158579128493222260687581429259737603747866742757994163046595159543146968866337022845421892251872677671311549794038206508616388072115557453111889539232763685736301986181218477646915963491720571160779254755457130953387321974564560695640698336052314956929259744494840578981302727111701952832425227066444415613970604343994768493612979920357658324925122483480808742113562776758893895384573122008589121480097714519565400653401688551621107840428451493001289262186117670205426024199088543378199160461681572971517809226670258434661314712210905853667621061894408368154357626826687924098574128017218758549732601502645285130187474384922510181048569672376788944243499754442674518023479243641717273481143083592387203540354635037595670736696686273979915827161523747715005480279450791724249088466704555556518081883189790040208316380623423288050341994355809966913699643224251907355996318803396652161546541736234061869056023860249997999985330715371630275140217389283534733228353183872148266003599567448641867627287627485507223326890894622700977218122868989845010138323790896736391005234093342942228201984181596606808380177920593446171737660832271891866941872230325112033475327257646488006034698380219531906388473155222309581851183100837333126756946950665056565728602304096792537288887409056815148050534638339762145986437252151437435249645985037866328257948559265630371394342896244309557916811836335458329073807941007087726113027481092533016107475460655620812068279226547940153462990051701675182272840916085165280692806008594847727537892645933056979748714719789005832462525235540712549242750237362309077642079650953679005275793586348688908666497122939119106258186456986718968586738826704591168787429493971905261932857017691041334592284548162220414292303915918851599211173466734225020566762472624355953707775928163227110258860664819531846304035251244180095088925476082437700073962698853041926599605009604107112196840236655990569887373936843994267265330295386828267231043402233732792650579254932162568928027858434550873254328394566577841374818600536003240189211280870891400145654525712751522174367655321961382498655209557981899085873469008062009924458933747809433753040326031372850931822215109701408656985125671672428001373332803826741903244015686622544106601692754194250791252436818099606825054264787088048939795237104736404244483594111980902432794205527840583013893716096307410208732260235522345650114023967689508254553959994841683130888863663238848619653206273388886393749048801589139184872967072678100155288839586640350577480413626515511591911091395428353457108453046266347962149748585475008203543218892220765693026815883310360157058562835362896140362273049044070130471710894996312064595779909752024947171509604516455780279697426986427437188284558906312542975744304069223169142192183252029322331604574313033828806425929028170807131724954574983242492297084708275293134054976483323638575431989188387043325414371484336253682215173273704228547063871035033360288715915396828065040772484978825007784544670987624389853636703598351993618137180970846728939828782809115710987083632834791120924007177754985953371316822746841861012761745621303977495550491853867924443285868932508707689034343661614923200277186719024637408592575782501459075329712119050291398813984185987156836959841500714085394963466839996221164332626204667439552890241458895285342830915401930159110205279182888752202723749862447559212404326964841083401871733267664881704238174830518513108571784347416875924255167832563601080853842331360461042971827941225144683324686466732863784389749498684605920385727995664270481393515009327609340585041177028950669421129588565597825724677381275084742967236315746008388598032368847899320903831282528988512368829763396900557706186092749559443027358980473352452120518229018607666615827491056724544388701838207655021748407623597806122264894067792860511336301702821828291322017616228584972581666850551730632075314917955133825634995611818403901698544568900635699939139392340683910140793927748193884211784159426258085833261910893840851796734400387481375891158748466471513392871063995990708826489805398922538711901775872486565224234331793818876120283945374978401842553862469541118920112700793825628172123921585777983782989390232706393796263101817905206893937135416521186663092132601965348703271506884437270938767183325628336191185422141471576191601763139793335567192592155601683168474056278899175081963344409561880875134813087677127397355552860320277656761068846291804766579384846926507085158267524434450045524079370452755424212885814697366116890247297225728183221763038453296113123671394474307046592269853061820042956954599198856473108275831279622135835928664067781571773706715064371735871137987902649233629030997678711362445237998087710519684426108999289516741066060599451064554104461461354659059489170359338878577419100703165970355370698400311982807241967737612610119591415317576726061339799026607018391853381655220798154591282338047288428191376181796537269408634096530723743929107223425516171828231453746073043669821256964963720328919599426321567466775645575797081367020465292185667912931197448165582836410917186134613759513309829270247477828542268454281058233295925225673147777998258613333897054759473336103798855652485633224337091747238529795258951289900113320669956410674169895226489188412581362047599481888351337526513995501196668142620502259958945131372146950070929104359605527887105153280124054662639803222617716476691572247896168327261554275789904948488170579994682647874266546089367210949906452348897550676931394028971244824512960103046834520951537859494433586320793297136345929562837355112046916869461399501081942323314753303087051232203872082225953992340994397244681499848664695123234410630093343510647298874002570032977391956490468473450409536236418309594975449359376396927243078895365056169446051944750574082503396529717979684745460738542619654926498144994409357244608725548519635493355229135591484634914890436859911015826307983167997783544851770852074735962398140948575539810776360949883617419446586336647246756740721178109281861762964528036399758496826816463374890900063280234306730155526224756677468127589998181859989541333719308841436710397246374063271913726110499532096147197159993892481710678269322457464242165793290323311684395185533726509048151089458057346859965280769838222016928049917800608510205229724351557865866379437881403409653329619466057051090890881489378075531198868816772626465997842261401984196911357856579993427184218042481280720939563284169800237117075756452394217300643913109724964522207911424252146917528085143126010152319307318973821087188233021851978486069078372433332730069308371173905974288793743316169210237406172857037935324725673847593643653410134593222325830517190542266388715162062116313436726123208597717123688964240676661637697120006216418414142631228564482082156227661315697845742500644196802692830337841263075278327405762927875668776214466216487885527548846865715495543686349041858742975246352778603629346982156763545685674686452321965125215589638021498925784949836648152442191166278396922055094408015935068592141462839361225013580138232215310341947503114696065862288667630392051159652743457798562884698721039639508240575111552516324125986842492169936772877810148446240298647376460900775920270488705949569637693561580362246645899367551297499448695835127476793671771264929947584931746972436226605937176303526272707825054617162671767374377942445154810825255509355734350217454389979703104698071903813135945255776045399313735344195317064822926202507996713606719456207839054551717548632847795667278878252078907563379156804980162698509677928993306295537829700446408949413970594096143086949372797166277684611833631949661828624649399404511227081067512649270780999623535498136369226016486214689366946026814508008016302154182885727086262523519242601232859211094748063510194615043842240765434768676070571467058329586196443268245685453600671151787942553707329413602606934149522632348967669415542336525037129490620591184551959261005588069468349694548519662063660493178017473239815410555602630524363660535949270232444889495947578876461116499172223274977981729766003868991274821860540555693083107541729179085906463348043429012218527369231808694751626657559741689556621813024739396608838185204217963864770506375060660791798354005898555068568215038163256428979888951736279011527127152499180648687148250013706282597179373372460134901873262795155851492414255849085741108365476975694483879544945460375794003856966044527843575499
(beaverbuild)
7.969191366955624779 Eth7.969438677955624779 Eth0.000247311

Execution Trace

CurveTricryptoFactory.deploy_pool( _name=TricryptoGHO, _symbol=GHOBTCwstE, _coins=[0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f, 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0], _weth=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, implementation_id=0, A=540000, gamma=80500000000000, mid_fee=1000000, out_fee=140000000, fee_gamma=400000000000000, allowed_extra_profit=100000000, adjustment_step=100000000000, ma_exp_time=867, initial_prices=[47796462778006751223446, 2652649963509214929450] ) => ( 0x8Cd52ee292313C4D851e71A7064F096504aB3eE9 )
  • GhoToken.STATICCALL( )
  • WBTC.STATICCALL( )
  • WstETH.STATICCALL( )
  • Null: 0x000...004.54726963( )
  • Null: 0x000...004.00000000( )
  • CurveTricryptoOptimizedWETH.6154fd51( )
  • Null: 0x000...004.54726963( )
    File 1 of 5: CurveTricryptoFactory
    # @version 0.3.9
    
    """
    @title CurveTricryptoFactory
    @author Curve.Fi
    @license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved
    @notice Permissionless 3-coin cryptoswap pool deployer and registry
    """
    
    interface TricryptoPool:
        def balances(i: uint256) -> uint256: view
    
    interface ERC20:
        def decimals() -> uint256: view
    
    
    event TricryptoPoolDeployed:
        pool: address
        name: String[64]
        symbol: String[32]
        weth: address
        coins: address[N_COINS]
        math: address
        salt: bytes32
        packed_precisions: uint256
        packed_A_gamma: uint256
        packed_fee_params: uint256
        packed_rebalancing_params: uint256
        packed_prices: uint256
        deployer: address
    
    
    event LiquidityGaugeDeployed:
        pool: address
        gauge: address
    
    event UpdateFeeReceiver:
        _old_fee_receiver: address
        _new_fee_receiver: address
    
    event UpdatePoolImplementation:
        _implemention_id: uint256
        _old_pool_implementation: address
        _new_pool_implementation: address
    
    event UpdateGaugeImplementation:
        _old_gauge_implementation: address
        _new_gauge_implementation: address
    
    event UpdateMathImplementation:
        _old_math_implementation: address
        _new_math_implementation: address
    
    event UpdateViewsImplementation:
        _old_views_implementation: address
        _new_views_implementation: address
    
    event TransferOwnership:
        _old_owner: address
        _new_owner: address
    
    
    struct PoolArray:
        liquidity_gauge: address
        coins: address[N_COINS]
        decimals: uint256[N_COINS]
    
    
    N_COINS: constant(uint256) = 3
    A_MULTIPLIER: constant(uint256) = 10000
    
    # Limits
    MAX_FEE: constant(uint256) = 10 * 10 ** 9
    
    MIN_GAMMA: constant(uint256) = 10 ** 10
    MAX_GAMMA: constant(uint256) = 5 * 10**16
    
    MIN_A: constant(uint256) = N_COINS ** N_COINS * A_MULTIPLIER / 100
    MAX_A: constant(uint256) = 1000 * A_MULTIPLIER * N_COINS**N_COINS
    
    PRICE_SIZE: constant(uint128) = 256 / (N_COINS - 1)
    PRICE_MASK: constant(uint256) = 2**PRICE_SIZE - 1
    
    admin: public(address)
    future_admin: public(address)
    
    # fee receiver for all pools:
    fee_receiver: public(address)
    
    pool_implementations: public(HashMap[uint256, address])
    gauge_implementation: public(address)
    views_implementation: public(address)
    math_implementation: public(address)
    
    # mapping of coins -> pools for trading
    # a mapping key is generated for each pair of addresses via
    # `bitwise_xor(convert(a, uint256), convert(b, uint256))`
    markets: HashMap[uint256, address[4294967296]]
    market_counts: HashMap[uint256, uint256]
    
    pool_count: public(uint256)              # actual length of pool_list
    pool_data: HashMap[address, PoolArray]
    pool_list: public(address[4294967296])   # master list of pools
    
    
    @external
    def __init__(_fee_receiver: address, _admin: address):
    
        self.fee_receiver = _fee_receiver
        self.admin = _admin
    
        log UpdateFeeReceiver(empty(address), _fee_receiver)
        log TransferOwnership(empty(address), _admin)
    
    
    @internal
    @view
    def _pack(x: uint256[3]) -> uint256:
        """
        @notice Packs 3 integers with values <= 10**18 into a uint256
        @param x The uint256[3] to pack
        @return The packed uint256
        """
        return (x[0] << 128) | (x[1] << 64) | x[2]
    
    
    
    # <--- Pool Deployers --->
    
    @external
    def deploy_pool(
        _name: String[64],
        _symbol: String[32],
        _coins: address[N_COINS],
        _weth: address,
        implementation_id: uint256,
        A: uint256,
        gamma: uint256,
        mid_fee: uint256,
        out_fee: uint256,
        fee_gamma: uint256,
        allowed_extra_profit: uint256,
        adjustment_step: uint256,
        ma_exp_time: uint256,
        initial_prices: uint256[N_COINS-1],
    ) -> address:
        """
        @notice Deploy a new pool
        @param _name Name of the new plain pool
        @param _symbol Symbol for the new plain pool - will be concatenated with factory symbol
    
        @return Address of the deployed pool
        """
        pool_implementation: address = self.pool_implementations[implementation_id]
        assert pool_implementation != empty(address), "Pool implementation not set"
    
        # Validate parameters
        assert A > MIN_A-1
        assert A < MAX_A+1
    
        assert gamma > MIN_GAMMA-1
        assert gamma < MAX_GAMMA+1
    
        assert mid_fee < MAX_FEE-1  # mid_fee can be zero
        assert out_fee >= mid_fee
        assert out_fee < MAX_FEE-1
        assert fee_gamma < 10**18+1
        assert fee_gamma > 0
    
        assert allowed_extra_profit < 10**18+1
    
        assert adjustment_step < 10**18+1
        assert adjustment_step > 0
    
        assert ma_exp_time < 872542  # 7 * 24 * 60 * 60 / ln(2)
        assert ma_exp_time > 86  # 60 / ln(2)
    
        assert min(initial_prices[0], initial_prices[1]) > 10**6
        assert max(initial_prices[0], initial_prices[1]) < 10**30
    
        assert _coins[0] != _coins[1] and _coins[1] != _coins[2] and _coins[0] != _coins[2], "Duplicate coins"
    
        decimals: uint256[N_COINS] = empty(uint256[N_COINS])
        precisions: uint256[N_COINS] = empty(uint256[N_COINS])
        for i in range(N_COINS):
            d: uint256 = ERC20(_coins[i]).decimals()
            assert d < 19, "Max 18 decimals for coins"
            decimals[i] = d
            precisions[i] = 10** (18 - d)
    
        # pack precisions
        packed_precisions: uint256 = self._pack(precisions)
    
        # pack fees
        packed_fee_params: uint256 = self._pack(
            [mid_fee, out_fee, fee_gamma]
        )
    
        # pack liquidity rebalancing params
        packed_rebalancing_params: uint256 = self._pack(
            [allowed_extra_profit, adjustment_step, ma_exp_time]
        )
    
        # pack A_gamma
        packed_A_gamma: uint256 = A << 128
        packed_A_gamma = packed_A_gamma | gamma
    
        # pack initial prices
        packed_prices: uint256 = 0
        for k in range(N_COINS - 1):
            packed_prices = packed_prices << PRICE_SIZE
            p: uint256 = initial_prices[N_COINS - 2 - k]
            assert p < PRICE_MASK
            packed_prices = p | packed_prices
    
        # pool is an ERC20 implementation
        _salt: bytes32 = block.prevhash
        _math_implementation: address = self.math_implementation
        pool: address = create_from_blueprint(
            pool_implementation,
            _name,
            _symbol,
            _coins,
            _math_implementation,
            _weth,
            _salt,
            packed_precisions,
            packed_A_gamma,
            packed_fee_params,
            packed_rebalancing_params,
            packed_prices,
            code_offset=3
        )
    
        # populate pool data
        length: uint256 = self.pool_count
        self.pool_list[length] = pool
        self.pool_count = length + 1
        self.pool_data[pool].decimals = decimals
        self.pool_data[pool].coins = _coins
    
        # add coins to market:
        self._add_coins_to_market(_coins[0], _coins[1], pool)
        self._add_coins_to_market(_coins[0], _coins[2], pool)
        self._add_coins_to_market(_coins[1], _coins[2], pool)
    
        log TricryptoPoolDeployed(
            pool,
            _name,
            _symbol,
            _weth,
            _coins,
            _math_implementation,
            _salt,
            packed_precisions,
            packed_A_gamma,
            packed_fee_params,
            packed_rebalancing_params,
            packed_prices,
            msg.sender,
        )
    
        return pool
    
    
    @internal
    def _add_coins_to_market(coin_a: address, coin_b: address, pool: address):
    
        key: uint256 = (
            convert(coin_a, uint256) ^ convert(coin_b, uint256)
        )
    
        length: uint256 = self.market_counts[key]
        self.markets[key][length] = pool
        self.market_counts[key] = length + 1
    
    
    @external
    def deploy_gauge(_pool: address) -> address:
        """
        @notice Deploy a liquidity gauge for a factory pool
        @param _pool Factory pool address to deploy a gauge for
        @return Address of the deployed gauge
        """
        assert self.pool_data[_pool].coins[0] != empty(address), "Unknown pool"
        assert self.pool_data[_pool].liquidity_gauge == empty(address), "Gauge already deployed"
        assert self.gauge_implementation != empty(address), "Gauge implementation not set"
    
        gauge: address = create_from_blueprint(self.gauge_implementation, _pool, code_offset=3)
        self.pool_data[_pool].liquidity_gauge = gauge
    
        log LiquidityGaugeDeployed(_pool, gauge)
        return gauge
    
    
    # <--- Admin / Guarded Functionality --->
    
    
    @external
    def set_fee_receiver(_fee_receiver: address):
        """
        @notice Set fee receiver
        @param _fee_receiver Address that fees are sent to
        """
        assert msg.sender == self.admin, "dev: admin only"
    
        log UpdateFeeReceiver(self.fee_receiver, _fee_receiver)
        self.fee_receiver = _fee_receiver
    
    
    @external
    def set_pool_implementation(
        _pool_implementation: address, _implementation_index: uint256
    ):
        """
        @notice Set pool implementation
        @dev Set to empty(address) to prevent deployment of new pools
        @param _pool_implementation Address of the new pool implementation
        @param _implementation_index Index of the pool implementation
        """
        assert msg.sender == self.admin, "dev: admin only"
    
        log UpdatePoolImplementation(
            _implementation_index,
            self.pool_implementations[_implementation_index],
            _pool_implementation
        )
    
        self.pool_implementations[_implementation_index] = _pool_implementation
    
    
    @external
    def set_gauge_implementation(_gauge_implementation: address):
        """
        @notice Set gauge implementation
        @dev Set to empty(address) to prevent deployment of new gauges
        @param _gauge_implementation Address of the new token implementation
        """
        assert msg.sender == self.admin, "dev: admin only"
    
        log UpdateGaugeImplementation(self.gauge_implementation, _gauge_implementation)
        self.gauge_implementation = _gauge_implementation
    
    
    @external
    def set_views_implementation(_views_implementation: address):
        """
        @notice Set views contract implementation
        @param _views_implementation Address of the new views contract
        """
        assert msg.sender == self.admin,  "dev: admin only"
    
        log UpdateViewsImplementation(self.views_implementation, _views_implementation)
        self.views_implementation = _views_implementation
    
    
    @external
    def set_math_implementation(_math_implementation: address):
        """
        @notice Set math implementation
        @param _math_implementation Address of the new math contract
        """
        assert msg.sender == self.admin, "dev: admin only"
    
        log UpdateMathImplementation(self.math_implementation, _math_implementation)
        self.math_implementation = _math_implementation
    
    
    @external
    def commit_transfer_ownership(_addr: address):
        """
        @notice Transfer ownership of this contract to `addr`
        @param _addr Address of the new owner
        """
        assert msg.sender == self.admin, "dev: admin only"
    
        self.future_admin = _addr
    
    
    @external
    def accept_transfer_ownership():
        """
        @notice Accept a pending ownership transfer
        @dev Only callable by the new owner
        """
        assert msg.sender == self.future_admin, "dev: future admin only"
    
        log TransferOwnership(self.admin, msg.sender)
        self.admin = msg.sender
    
    
    # <--- Factory Getters --->
    
    
    @view
    @external
    def find_pool_for_coins(_from: address, _to: address, i: uint256 = 0) -> address:
        """
        @notice Find an available pool for exchanging two coins
        @param _from Address of coin to be sent
        @param _to Address of coin to be received
        @param i Index value. When multiple pools are available
                this value is used to return the n'th address.
        @return Pool address
        """
        key: uint256 = convert(_from, uint256) ^ convert(_to, uint256)
        return self.markets[key][i]
    
    
    # <--- Pool Getters --->
    
    
    @view
    @external
    def get_coins(_pool: address) -> address[N_COINS]:
        """
        @notice Get the coins within a pool
        @param _pool Pool address
        @return List of coin addresses
        """
        return self.pool_data[_pool].coins
    
    
    @view
    @external
    def get_decimals(_pool: address) -> uint256[N_COINS]:
        """
        @notice Get decimal places for each coin within a pool
        @param _pool Pool address
        @return uint256 list of decimals
        """
        return self.pool_data[_pool].decimals
    
    
    @view
    @external
    def get_balances(_pool: address) -> uint256[N_COINS]:
        """
        @notice Get balances for each coin within a pool
        @dev For pools using lending, these are the wrapped coin balances
        @param _pool Pool address
        @return uint256 list of balances
        """
        return [
            TricryptoPool(_pool).balances(0),
            TricryptoPool(_pool).balances(1),
            TricryptoPool(_pool).balances(2),
        ]
    
    
    @view
    @external
    def get_coin_indices(
        _pool: address,
        _from: address,
        _to: address
    ) -> (uint256, uint256):
        """
        @notice Convert coin addresses to indices for use with pool methods
        @param _pool Pool address
        @param _from Coin address to be used as `i` within a pool
        @param _to Coin address to be used as `j` within a pool
        @return uint256 `i`, uint256 `j`
        """
        coins: address[N_COINS] = self.pool_data[_pool].coins
    
        for i in range(N_COINS):
            for j in range(N_COINS):
                if i == j:
                    continue
    
                if coins[i] == _from and coins[j] == _to:
                    return i, j
    
        raise "Coins not found"
    
    
    @view
    @external
    def get_gauge(_pool: address) -> address:
        """
        @notice Get the address of the liquidity gauge contract for a factory pool
        @dev Returns `empty(address)` if a gauge has not been deployed
        @param _pool Pool address
        @return Implementation contract address
        """
        return self.pool_data[_pool].liquidity_gauge
    
    
    @view
    @external
    def get_market_counts(coin_a: address, coin_b: address) -> uint256:
        """
        @notice Gets the number of markets with the specified coins.
        @return Number of pools with the input coins
        """
    
        key: uint256 = (
            convert(coin_a, uint256) ^ convert(coin_b, uint256)
        )
    
        return self.market_counts[key]

    File 2 of 5: CurveTricryptoOptimizedWETH
    # @version 0.3.9
    
    """
    @title CurveTricryptoOptimizedWETH
    @author Curve.Fi
    @license Copyright (c) Curve.Fi, 2020-2023 - all rights reserved
    @notice A Curve AMM pool for 3 unpegged assets (e.g. ETH, BTC, USD).
    @dev All prices in the AMM are with respect to the first token in the pool.
    """
    
    from vyper.interfaces import ERC20
    implements: ERC20  # <--------------------- AMM contract is also the LP token.
    
    # --------------------------------- Interfaces -------------------------------
    
    interface Math:
        def geometric_mean(_x: uint256[N_COINS]) -> uint256: view
        def wad_exp(_power: int256) -> uint256: view
        def cbrt(x: uint256) -> uint256: view
        def reduction_coefficient(
            x: uint256[N_COINS], fee_gamma: uint256
        ) -> uint256: view
        def newton_D(
            ANN: uint256,
            gamma: uint256,
            x_unsorted: uint256[N_COINS],
            K0_prev: uint256
        ) -> uint256: view
        def get_y(
            ANN: uint256,
            gamma: uint256,
            x: uint256[N_COINS],
            D: uint256,
            i: uint256,
        ) -> uint256[2]: view
        def get_p(
            _xp: uint256[N_COINS], _D: uint256, _A_gamma: uint256[2],
        ) -> uint256[N_COINS-1]: view
    
    interface WETH:
        def deposit(): payable
        def withdraw(_amount: uint256): nonpayable
    
    interface Factory:
        def admin() -> address: view
        def fee_receiver() -> address: view
        def views_implementation() -> address: view
    
    interface Views:
        def calc_token_amount(
            amounts: uint256[N_COINS], deposit: bool, swap: address
        ) -> uint256: view
        def get_dy(
            i: uint256, j: uint256, dx: uint256, swap: address
        ) -> uint256: view
        def get_dx(
            i: uint256, j: uint256, dy: uint256, swap: address
        ) -> uint256: view
    
    
    # ------------------------------- Events -------------------------------------
    
    event Transfer:
        sender: indexed(address)
        receiver: indexed(address)
        value: uint256
    
    event Approval:
        owner: indexed(address)
        spender: indexed(address)
        value: uint256
    
    event TokenExchange:
        buyer: indexed(address)
        sold_id: uint256
        tokens_sold: uint256
        bought_id: uint256
        tokens_bought: uint256
        fee: uint256
        packed_price_scale: uint256
    
    event AddLiquidity:
        provider: indexed(address)
        token_amounts: uint256[N_COINS]
        fee: uint256
        token_supply: uint256
        packed_price_scale: uint256
    
    event RemoveLiquidity:
        provider: indexed(address)
        token_amounts: uint256[N_COINS]
        token_supply: uint256
    
    event RemoveLiquidityOne:
        provider: indexed(address)
        token_amount: uint256
        coin_index: uint256
        coin_amount: uint256
        approx_fee: uint256
        packed_price_scale: uint256
    
    event CommitNewParameters:
        deadline: indexed(uint256)
        mid_fee: uint256
        out_fee: uint256
        fee_gamma: uint256
        allowed_extra_profit: uint256
        adjustment_step: uint256
        ma_time: uint256
    
    event NewParameters:
        mid_fee: uint256
        out_fee: uint256
        fee_gamma: uint256
        allowed_extra_profit: uint256
        adjustment_step: uint256
        ma_time: uint256
    
    event RampAgamma:
        initial_A: uint256
        future_A: uint256
        initial_gamma: uint256
        future_gamma: uint256
        initial_time: uint256
        future_time: uint256
    
    event StopRampA:
        current_A: uint256
        current_gamma: uint256
        time: uint256
    
    event ClaimAdminFee:
        admin: indexed(address)
        tokens: uint256
    
    
    # ----------------------- Storage/State Variables ----------------------------
    
    WETH20: public(immutable(address))
    
    N_COINS: constant(uint256) = 3
    PRECISION: constant(uint256) = 10**18  # <------- The precision to convert to.
    A_MULTIPLIER: constant(uint256) = 10000
    packed_precisions: uint256
    
    MATH: public(immutable(Math))
    coins: public(immutable(address[N_COINS]))
    factory: public(address)
    
    price_scale_packed: uint256  # <------------------------ Internal price scale.
    price_oracle_packed: uint256  # <------- Price target given by moving average.
    
    last_prices_packed: uint256
    last_prices_timestamp: public(uint256)
    
    initial_A_gamma: public(uint256)
    initial_A_gamma_time: public(uint256)
    
    future_A_gamma: public(uint256)
    future_A_gamma_time: public(uint256)  # <------ Time when ramping is finished.
    #         This value is 0 (default) when pool is first deployed, and only gets
    #        populated by block.timestamp + future_time in `ramp_A_gamma` when the
    #                      ramping process is initiated. After ramping is finished
    #      (i.e. self.future_A_gamma_time < block.timestamp), the variable is left
    #                                                            and not set to 0.
    
    balances: public(uint256[N_COINS])
    D: public(uint256)
    xcp_profit: public(uint256)
    xcp_profit_a: public(uint256)  # <--- Full profit at last claim of admin fees.
    
    virtual_price: public(uint256)  # <------ Cached (fast to read) virtual price.
    #                          The cached `virtual_price` is also used internally.
    
    # -------------- Params that affect how price_scale get adjusted -------------
    
    packed_rebalancing_params: public(uint256)  # <---------- Contains rebalancing
    #               parameters allowed_extra_profit, adjustment_step, and ma_time.
    
    future_packed_rebalancing_params: uint256
    
    # ---------------- Fee params that determine dynamic fees --------------------
    
    packed_fee_params: public(uint256)  # <---- Packs mid_fee, out_fee, fee_gamma.
    future_packed_fee_params: uint256
    
    ADMIN_FEE: public(constant(uint256)) = 5 * 10**9  # <----- 50% of earned fees.
    MIN_FEE: constant(uint256) = 5 * 10**5  # <-------------------------- 0.5 BPS.
    MAX_FEE: constant(uint256) = 10 * 10**9
    NOISE_FEE: constant(uint256) = 10**5  # <---------------------------- 0.1 BPS.
    
    # ----------------------- Admin params ---------------------------------------
    
    admin_actions_deadline: public(uint256)
    
    ADMIN_ACTIONS_DELAY: constant(uint256) = 3 * 86400
    MIN_RAMP_TIME: constant(uint256) = 86400
    
    MIN_A: constant(uint256) = N_COINS**N_COINS * A_MULTIPLIER / 100
    MAX_A: constant(uint256) = 1000 * A_MULTIPLIER * N_COINS**N_COINS
    MAX_A_CHANGE: constant(uint256) = 10
    MIN_GAMMA: constant(uint256) = 10**10
    MAX_GAMMA: constant(uint256) = 5 * 10**16
    
    PRICE_SIZE: constant(uint128) = 256 / (N_COINS - 1)
    PRICE_MASK: constant(uint256) = 2**PRICE_SIZE - 1
    
    # ----------------------- ERC20 Specific vars --------------------------------
    
    name: public(immutable(String[64]))
    symbol: public(immutable(String[32]))
    decimals: public(constant(uint8)) = 18
    version: public(constant(String[8])) = "v2.0.0"
    
    balanceOf: public(HashMap[address, uint256])
    allowance: public(HashMap[address, HashMap[address, uint256]])
    totalSupply: public(uint256)
    nonces: public(HashMap[address, uint256])
    
    EIP712_TYPEHASH: constant(bytes32) = keccak256(
        "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)"
    )
    EIP2612_TYPEHASH: constant(bytes32) = keccak256(
        "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
    )
    VERSION_HASH: constant(bytes32) = keccak256(version)
    NAME_HASH: immutable(bytes32)
    CACHED_CHAIN_ID: immutable(uint256)
    salt: public(immutable(bytes32))
    CACHED_DOMAIN_SEPARATOR: immutable(bytes32)
    
    
    # ----------------------- Contract -------------------------------------------
    
    @external
    def __init__(
        _name: String[64],
        _symbol: String[32],
        _coins: address[N_COINS],
        _math: address,
        _weth: address,
        _salt: bytes32,
        packed_precisions: uint256,
        packed_A_gamma: uint256,
        packed_fee_params: uint256,
        packed_rebalancing_params: uint256,
        packed_prices: uint256,
    ):
    
        WETH20 = _weth
        MATH = Math(_math)
    
        self.factory = msg.sender
    
        name = _name
        symbol = _symbol
        coins = _coins
    
        self.packed_precisions = packed_precisions  # <------- Precisions of coins
        #                            are calculated as 10**(18 - coin.decimals()).
    
        self.initial_A_gamma = packed_A_gamma  # <------------------- A and gamma.
        self.future_A_gamma = packed_A_gamma
    
        self.packed_rebalancing_params = packed_rebalancing_params  # <-- Contains
        #               rebalancing params: allowed_extra_profit, adjustment_step,
        #                                                         and ma_exp_time.
    
        self.packed_fee_params = packed_fee_params  # <-------------- Contains Fee
        #                                  params: mid_fee, out_fee and fee_gamma.
    
        self.price_scale_packed = packed_prices
        self.price_oracle_packed = packed_prices
        self.last_prices_packed = packed_prices
        self.last_prices_timestamp = block.timestamp
        self.xcp_profit_a = 10**18
    
        #         Cache DOMAIN_SEPARATOR. If chain.id is not CACHED_CHAIN_ID, then
        #     DOMAIN_SEPARATOR will be re-calculated each time `permit` is called.
        #                   Otherwise, it will always use CACHED_DOMAIN_SEPARATOR.
        #                       see: `_domain_separator()` for its implementation.
        NAME_HASH = keccak256(name)
        salt = _salt
        CACHED_CHAIN_ID = chain.id
        CACHED_DOMAIN_SEPARATOR = keccak256(
            _abi_encode(
                EIP712_TYPEHASH,
                NAME_HASH,
                VERSION_HASH,
                chain.id,
                self,
                salt,
            )
        )
    
        log Transfer(empty(address), self, 0)  # <------- Fire empty transfer from
        #                                       0x0 to self for indexers to catch.
    
    
    # ------------------- Token transfers in and out of the AMM ------------------
    
    
    @payable
    @external
    def __default__():
        if msg.value > 0:
            assert WETH20 in coins
    
    
    @internal
    def _transfer_in(
        _coin: address,
        dx: uint256,
        dy: uint256,
        mvalue: uint256,
        callbacker: address,
        callback_sig: bytes32,
        sender: address,
        receiver: address,
        use_eth: bool
    ):
        """
        @notice Transfers `_coin` from `sender` to `self` and calls `callback_sig`
                if it is not empty.
        @dev The callback sig must have the following args:
             sender: address
             receiver: address
             coin: address
             dx: uint256
             dy: uint256
        @params _coin address of the coin to transfer in.
        @params dx amount of `_coin` to transfer into the pool.
        @params dy amount of `_coin` to transfer out of the pool.
        @params mvalue msg.value if the transfer is ETH, 0 otherwise.
        @params callbacker address to call `callback_sig` on.
        @params callback_sig signature of the callback function.
        @params sender address to transfer `_coin` from.
        @params receiver address to transfer `_coin` to.
        @params use_eth True if the transfer is ETH, False otherwise.
        """
    
        if use_eth and _coin == WETH20:
            assert mvalue == dx  # dev: incorrect eth amount
        else:
            assert mvalue == 0  # dev: nonzero eth amount
    
            if callback_sig == empty(bytes32):
    
                assert ERC20(_coin).transferFrom(
                    sender, self, dx, default_return_value=True
                )
    
            else:
    
                # --------- This part of the _transfer_in logic is only accessible
                #                                                    by _exchange.
    
                #                 First call callback logic and then check if pool
                #                  gets dx amounts of _coins[i], revert otherwise.
                b: uint256 = ERC20(_coin).balanceOf(self)
                raw_call(
                    callbacker,
                    concat(
                        slice(callback_sig, 0, 4),
                        _abi_encode(sender, receiver, _coin, dx, dy)
                    )
                )
                assert ERC20(_coin).balanceOf(self) - b == dx  # dev: callback didn't give us coins
                #                                          ^------ note: dx cannot
                #                   be 0, so the contract MUST receive some _coin.
    
            if _coin == WETH20:
                WETH(WETH20).withdraw(dx)  # <--------- if WETH was transferred in
                #           previous step and `not use_eth`, withdraw WETH to ETH.
    
    
    @internal
    def _transfer_out(
        _coin: address, _amount: uint256, use_eth: bool, receiver: address
    ):
        """
        @notice Transfer a single token from the pool to receiver.
        @dev This function is called by `remove_liquidity` and
             `remove_liquidity_one` and `_exchange` methods.
        @params _coin Address of the token to transfer out
        @params _amount Amount of token to transfer out
        @params use_eth Whether to transfer ETH or not
        @params receiver Address to send the tokens to
        """
    
        if use_eth and _coin == WETH20:
            raw_call(receiver, b"", value=_amount)
        else:
            if _coin == WETH20:
                WETH(WETH20).deposit(value=_amount)
    
            assert ERC20(_coin).transfer(
                receiver, _amount, default_return_value=True
            )
    
    
    # -------------------------- AMM Main Functions ------------------------------
    
    
    @payable
    @external
    @nonreentrant("lock")
    def exchange(
        i: uint256,
        j: uint256,
        dx: uint256,
        min_dy: uint256,
        use_eth: bool = False,
        receiver: address = msg.sender
    ) -> uint256:
        """
        @notice Exchange using wrapped native token by default
        @param i Index value for the input coin
        @param j Index value for the output coin
        @param dx Amount of input coin being swapped in
        @param min_dy Minimum amount of output coin to receive
        @param use_eth True if the input coin is native token, False otherwise
        @param receiver Address to send the output coin to. Default is msg.sender
        @return uint256 Amount of tokens at index j received by the `receiver
        """
        return self._exchange(
            msg.sender,
            msg.value,
            i,
            j,
            dx,
            min_dy,
            use_eth,
            receiver,
            empty(address),
            empty(bytes32)
        )
    
    
    @payable
    @external
    @nonreentrant('lock')
    def exchange_underlying(
        i: uint256,
        j: uint256,
        dx: uint256,
        min_dy: uint256,
        receiver: address = msg.sender
    ) -> uint256:
        """
        @notice Exchange using native token transfers.
        @param i Index value for the input coin
        @param j Index value for the output coin
        @param dx Amount of input coin being swapped in
        @param min_dy Minimum amount of output coin to receive
        @param receiver Address to send the output coin to. Default is msg.sender
        @return uint256 Amount of tokens at index j received by the `receiver
        """
        return self._exchange(
            msg.sender,
            msg.value,
            i,
            j,
            dx,
            min_dy,
            True,
            receiver,
            empty(address),
            empty(bytes32)
        )
    
    
    @external
    @nonreentrant('lock')
    def exchange_extended(
        i: uint256,
        j: uint256,
        dx: uint256,
        min_dy: uint256,
        use_eth: bool,
        sender: address,
        receiver: address,
        cb: bytes32
    ) -> uint256:
        """
        @notice Exchange with callback method.
        @dev This method does not allow swapping in native token, but does allow
             swaps that transfer out native token from the pool.
        @dev Does not allow flashloans
        @dev One use-case is to reduce the number of redundant ERC20 token
             transfers in zaps.
        @param i Index value for the input coin
        @param j Index value for the output coin
        @param dx Amount of input coin being swapped in
        @param min_dy Minimum amount of output coin to receive
        @param use_eth True if output is native token, False otherwise
        @param sender Address to transfer input coin from
        @param receiver Address to send the output coin to
        @param cb Callback signature
        @return uint256 Amount of tokens at index j received by the `receiver`
        """
    
        assert cb != empty(bytes32)  # dev: No callback specified
        return self._exchange(
            sender, 0, i, j, dx, min_dy, use_eth, receiver, msg.sender, cb
        )  # callbacker should never be self ------------------^
    
    
    @payable
    @external
    @nonreentrant("lock")
    def add_liquidity(
        amounts: uint256[N_COINS],
        min_mint_amount: uint256,
        use_eth: bool = False,
        receiver: address = msg.sender
    ) -> uint256:
        """
        @notice Adds liquidity into the pool.
        @param amounts Amounts of each coin to add.
        @param min_mint_amount Minimum amount of LP to mint.
        @param use_eth True if native token is being added to the pool.
        @param receiver Address to send the LP tokens to. Default is msg.sender
        @return uint256 Amount of LP tokens received by the `receiver
        """
    
        A_gamma: uint256[2] = self._A_gamma()
        xp: uint256[N_COINS] = self.balances
        amountsp: uint256[N_COINS] = empty(uint256[N_COINS])
        xx: uint256[N_COINS] = empty(uint256[N_COINS])
        d_token: uint256 = 0
        d_token_fee: uint256 = 0
        old_D: uint256 = 0
    
        assert amounts[0] + amounts[1] + amounts[2] > 0  # dev: no coins to add
    
        # --------------------- Get prices, balances -----------------------------
    
        precisions: uint256[N_COINS] = self._unpack(self.packed_precisions)
        packed_price_scale: uint256 = self.price_scale_packed
        price_scale: uint256[N_COINS-1] = self._unpack_prices(packed_price_scale)
    
        # -------------------------------------- Update balances and calculate xp.
        xp_old: uint256[N_COINS] = xp
        for i in range(N_COINS):
            bal: uint256 = xp[i] + amounts[i]
            xp[i] = bal
            self.balances[i] = bal
        xx = xp
    
        xp[0] *= precisions[0]
        xp_old[0] *= precisions[0]
        for i in range(1, N_COINS):
            xp[i] = unsafe_div(xp[i] * price_scale[i-1] * precisions[i], PRECISION)
            xp_old[i] = unsafe_div(
                xp_old[i] * unsafe_mul(price_scale[i-1], precisions[i]),
                PRECISION
            )
    
        # ---------------- transferFrom token into the pool ----------------------
    
        for i in range(N_COINS):
    
            if amounts[i] > 0:
    
                if coins[i] == WETH20:
    
                    self._transfer_in(
                        coins[i],
                        amounts[i],
                        0,  # <-----------------------------------
                        msg.value,  #                             | No callbacks
                        empty(address),  # <----------------------| for
                        empty(bytes32),  # <----------------------| add_liquidity.
                        msg.sender,  #                            |
                        empty(address),  # <-----------------------
                        use_eth
                    )
    
                else:
    
                    self._transfer_in(
                        coins[i],
                        amounts[i],
                        0,
                        0,  # <----------------- mvalue = 0 if coin is not WETH20.
                        empty(address),
                        empty(bytes32),
                        msg.sender,
                        empty(address),
                        False  # <-------- use_eth is False if coin is not WETH20.
                    )
    
                amountsp[i] = xp[i] - xp_old[i]
    
        # -------------------- Calculate LP tokens to mint -----------------------
    
        if self.future_A_gamma_time > block.timestamp:  # <--- A_gamma is ramping.
    
            # ----- Recalculate the invariant if A or gamma are undergoing a ramp.
            old_D = MATH.newton_D(A_gamma[0], A_gamma[1], xp_old, 0)
    
        else:
    
            old_D = self.D
    
        D: uint256 = MATH.newton_D(A_gamma[0], A_gamma[1], xp, 0)
    
        token_supply: uint256 = self.totalSupply
        if old_D > 0:
            d_token = token_supply * D / old_D - token_supply
        else:
            d_token = self.get_xcp(D)  # <------------------------- Making initial
            #                                            virtual price equal to 1.
    
        assert d_token > 0  # dev: nothing minted
    
        if old_D > 0:
    
            d_token_fee = (
                self._calc_token_fee(amountsp, xp) * d_token / 10**10 + 1
            )
    
            d_token -= d_token_fee
            token_supply += d_token
            self.mint(receiver, d_token)
    
            packed_price_scale = self.tweak_price(A_gamma, xp, D, 0)
    
        else:
    
            self.D = D
            self.virtual_price = 10**18
            self.xcp_profit = 10**18
            self.xcp_profit_a = 10**18
            self.mint(receiver, d_token)
    
        assert d_token >= min_mint_amount, "Slippage"
    
        log AddLiquidity(
            receiver, amounts, d_token_fee, token_supply, packed_price_scale
        )
    
        self._claim_admin_fees()  # <--------------------------- Claim admin fees.
    
        return d_token
    
    
    @external
    @nonreentrant("lock")
    def remove_liquidity(
        _amount: uint256,
        min_amounts: uint256[N_COINS],
        use_eth: bool = False,
        receiver: address = msg.sender,
        claim_admin_fees: bool = True,
    ) -> uint256[N_COINS]:
        """
        @notice This withdrawal method is very safe, does no complex math since
                tokens are withdrawn in balanced proportions. No fees are charged.
        @param _amount Amount of LP tokens to burn
        @param min_amounts Minimum amounts of tokens to withdraw
        @param use_eth Whether to withdraw ETH or not
        @param receiver Address to send the withdrawn tokens to
        @param claim_admin_fees If True, call self._claim_admin_fees(). Default is True.
        @return uint256[3] Amount of pool tokens received by the `receiver`
        """
        amount: uint256 = _amount
        balances: uint256[N_COINS] = self.balances
        d_balances: uint256[N_COINS] = empty(uint256[N_COINS])
    
        if claim_admin_fees:
            self._claim_admin_fees()  # <------ We claim fees so that the DAO gets
            #         paid before withdrawal. In emergency cases, set it to False.
    
        # -------------------------------------------------------- Burn LP tokens.
    
        total_supply: uint256 = self.totalSupply  # <------ Get totalSupply before
        self.burnFrom(msg.sender, _amount)  # ---- reducing it with self.burnFrom.
    
        # There are two cases for withdrawing tokens from the pool.
        #   Case 1. Withdrawal does not empty the pool.
        #           In this situation, D is adjusted proportional to the amount of
        #           LP tokens burnt. ERC20 tokens transferred is proportional
        #           to : (AMM balance * LP tokens in) / LP token total supply
        #   Case 2. Withdrawal empties the pool.
        #           In this situation, all tokens are withdrawn and the invariant
        #           is reset.
    
        if amount == total_supply:  # <----------------------------------- Case 2.
    
            for i in range(N_COINS):
    
                d_balances[i] = balances[i]
                self.balances[i] = 0  # <------------------------- Empty the pool.
    
        else:  # <-------------------------------------------------------- Case 1.
    
            amount -= 1  # <---- To prevent rounding errors, favor LPs a tiny bit.
    
            for i in range(N_COINS):
                d_balances[i] = balances[i] * amount / total_supply
                assert d_balances[i] >= min_amounts[i]
                self.balances[i] = balances[i] - d_balances[i]
                balances[i] = d_balances[i]  # <-- Now it's the amounts going out.
    
        D: uint256 = self.D
        self.D = D - unsafe_div(D * amount, total_supply)  # <----------- Reduce D
        #      proportional to the amount of tokens leaving. Since withdrawals are
        #       balanced, this is a simple subtraction. If amount == total_supply,
        #                                                             D will be 0.
    
        # ---------------------------------- Transfers ---------------------------
    
        for i in range(N_COINS):
            self._transfer_out(coins[i], d_balances[i], use_eth, receiver)
    
        log RemoveLiquidity(msg.sender, balances, total_supply - _amount)
    
        return d_balances
    
    
    @external
    @nonreentrant("lock")
    def remove_liquidity_one_coin(
        token_amount: uint256,
        i: uint256,
        min_amount: uint256,
        use_eth: bool = False,
        receiver: address = msg.sender
    ) -> uint256:
        """
        @notice Withdraw liquidity in a single token.
                Involves fees (lower than swap fees).
        @dev This operation also involves an admin fee claim.
        @param token_amount Amount of LP tokens to burn
        @param i Index of the token to withdraw
        @param min_amount Minimum amount of token to withdraw.
        @param use_eth Whether to withdraw ETH or not
        @param receiver Address to send the withdrawn tokens to
        @return Amount of tokens at index i received by the `receiver`
        """
    
        A_gamma: uint256[2] = self._A_gamma()
    
        dy: uint256 = 0
        D: uint256 = 0
        p: uint256 = 0
        xp: uint256[N_COINS] = empty(uint256[N_COINS])
        approx_fee: uint256 = 0
    
        # ---------------------------- Claim admin fees before removing liquidity.
        self._claim_admin_fees()
    
        # ------------------------------------------------------------------------
    
        dy, D, xp, approx_fee = self._calc_withdraw_one_coin(
            A_gamma,
            token_amount,
            i,
            (self.future_A_gamma_time > block.timestamp),  # <------- During ramps
        )  #                                                  we need to update D.
    
        assert dy >= min_amount, "Slippage"
    
        # ------------------------- Transfers ------------------------------------
    
        self.balances[i] -= dy
        self.burnFrom(msg.sender, token_amount)
        self._transfer_out(coins[i], dy, use_eth, receiver)
    
        packed_price_scale: uint256 = self.tweak_price(A_gamma, xp, D, 0)
        #        Safe to use D from _calc_withdraw_one_coin here ---^
    
        log RemoveLiquidityOne(
            msg.sender, token_amount, i, dy, approx_fee, packed_price_scale
        )
    
        return dy
    
    
    @external
    @nonreentrant("lock")
    def claim_admin_fees():
        """
        @notice Claim admin fees. Callable by anyone.
        """
        self._claim_admin_fees()
    
    
    # -------------------------- Packing functions -------------------------------
    
    
    @internal
    @view
    def _pack(x: uint256[3]) -> uint256:
        """
        @notice Packs 3 integers with values <= 10**18 into a uint256
        @param x The uint256[3] to pack
        @return uint256 Integer with packed values
        """
        return (x[0] << 128) | (x[1] << 64) | x[2]
    
    
    @internal
    @view
    def _unpack(_packed: uint256) -> uint256[3]:
        """
        @notice Unpacks a uint256 into 3 integers (values must be <= 10**18)
        @param val The uint256 to unpack
        @return uint256[3] A list of length 3 with unpacked integers
        """
        return [
            (_packed >> 128) & 18446744073709551615,
            (_packed >> 64) & 18446744073709551615,
            _packed & 18446744073709551615,
        ]
    
    
    @internal
    @view
    def _pack_prices(prices_to_pack: uint256[N_COINS-1]) -> uint256:
        """
        @notice Packs N_COINS-1 prices into a uint256.
        @param prices_to_pack The prices to pack
        @return uint256 An integer that packs prices
        """
        packed_prices: uint256 = 0
        p: uint256 = 0
        for k in range(N_COINS - 1):
            packed_prices = packed_prices << PRICE_SIZE
            p = prices_to_pack[N_COINS - 2 - k]
            assert p < PRICE_MASK
            packed_prices = p | packed_prices
        return packed_prices
    
    
    @internal
    @view
    def _unpack_prices(_packed_prices: uint256) -> uint256[2]:
        """
        @notice Unpacks N_COINS-1 prices from a uint256.
        @param _packed_prices The packed prices
        @return uint256[2] Unpacked prices
        """
        unpacked_prices: uint256[N_COINS-1] = empty(uint256[N_COINS-1])
        packed_prices: uint256 = _packed_prices
        for k in range(N_COINS - 1):
            unpacked_prices[k] = packed_prices & PRICE_MASK
            packed_prices = packed_prices >> PRICE_SIZE
    
        return unpacked_prices
    
    
    # ---------------------- AMM Internal Functions -------------------------------
    
    
    @internal
    def _exchange(
        sender: address,
        mvalue: uint256,
        i: uint256,
        j: uint256,
        dx: uint256,
        min_dy: uint256,
        use_eth: bool,
        receiver: address,
        callbacker: address,
        callback_sig: bytes32
    ) -> uint256:
    
        assert i != j  # dev: coin index out of range
        assert dx > 0  # dev: do not exchange 0 coins
    
        A_gamma: uint256[2] = self._A_gamma()
        xp: uint256[N_COINS] = self.balances
        precisions: uint256[N_COINS] = self._unpack(self.packed_precisions)
        dy: uint256 = 0
    
        y: uint256 = xp[j]  # <----------------- if j > N_COINS, this will revert.
        x0: uint256 = xp[i]  # <--------------- if i > N_COINS, this will  revert.
        xp[i] = x0 + dx
        self.balances[i] = xp[i]
    
        packed_price_scale: uint256 = self.price_scale_packed
        price_scale: uint256[N_COINS - 1] = self._unpack_prices(
            packed_price_scale
        )
    
        xp[0] *= precisions[0]
        for k in range(1, N_COINS):
            xp[k] = unsafe_div(
                xp[k] * price_scale[k - 1] * precisions[k],
                PRECISION
            )  # <-------- Safu to do unsafe_div here since PRECISION is not zero.
    
        prec_i: uint256 = precisions[i]
    
        # ----------- Update invariant if A, gamma are undergoing ramps ---------
    
        t: uint256 = self.future_A_gamma_time
        if t > block.timestamp:
    
            x0 *= prec_i
    
            if i > 0:
                x0 = unsafe_div(x0 * price_scale[i - 1], PRECISION)
    
            x1: uint256 = xp[i]  # <------------------ Back up old value in xp ...
            xp[i] = x0                                                         # |
            self.D = MATH.newton_D(A_gamma[0], A_gamma[1], xp, 0)              # |
            xp[i] = x1  # <-------------------------------------- ... and restore.
    
        # ----------------------- Calculate dy and fees --------------------------
    
        D: uint256 = self.D
        prec_j: uint256 = precisions[j]
        y_out: uint256[2] = MATH.get_y(A_gamma[0], A_gamma[1], xp, D, j)
        dy = xp[j] - y_out[0]
        xp[j] -= dy
        dy -= 1
    
        if j > 0:
            dy = dy * PRECISION / price_scale[j - 1]
        dy /= prec_j
    
        fee: uint256 = unsafe_div(self._fee(xp) * dy, 10**10)
    
        dy -= fee  # <--------------------- Subtract fee from the outgoing amount.
        assert dy >= min_dy, "Slippage"
    
        y -= dy
        self.balances[j] = y  # <----------- Update pool balance of outgoing coin.
    
        y *= prec_j
        if j > 0:
            y = unsafe_div(y * price_scale[j - 1], PRECISION)
        xp[j] = y  # <------------------------------------------------- Update xp.
    
        # ---------------------- Do Transfers in and out -------------------------
    
        ########################## TRANSFER IN <-------
        self._transfer_in(
            coins[i], dx, dy, mvalue,
            callbacker, callback_sig,  # <-------- Callback method is called here.
            sender, receiver, use_eth,
        )
    
        ########################## -------> TRANSFER OUT
        self._transfer_out(coins[j], dy, use_eth, receiver)
    
        # ------ Tweak price_scale with good initial guess for newton_D ----------
    
        packed_price_scale = self.tweak_price(A_gamma, xp, 0, y_out[1])
    
        log TokenExchange(sender, i, dx, j, dy, fee, packed_price_scale)
    
        return dy
    
    
    @internal
    def tweak_price(
        A_gamma: uint256[2],
        _xp: uint256[N_COINS],
        new_D: uint256,
        K0_prev: uint256 = 0,
    ) -> uint256:
        """
        @notice Tweaks price_oracle, last_price and conditionally adjusts
                price_scale. This is called whenever there is an unbalanced
                liquidity operation: _exchange, add_liquidity, or
                remove_liquidity_one_coin.
        @dev Contains main liquidity rebalancing logic, by tweaking `price_scale`.
        @param A_gamma Array of A and gamma parameters.
        @param _xp Array of current balances.
        @param new_D New D value.
        @param K0_prev Initial guess for `newton_D`.
        """
    
        # ---------------------------- Read storage ------------------------------
    
        rebalancing_params: uint256[3] = self._unpack(
            self.packed_rebalancing_params
        )  # <---------- Contains: allowed_extra_profit, adjustment_step, ma_time.
        price_oracle: uint256[N_COINS - 1] = self._unpack_prices(
            self.price_oracle_packed
        )
        last_prices: uint256[N_COINS - 1] = self._unpack_prices(
            self.last_prices_packed
        )
        packed_price_scale: uint256 = self.price_scale_packed
        price_scale: uint256[N_COINS - 1] = self._unpack_prices(
            packed_price_scale
        )
    
        total_supply: uint256 = self.totalSupply
        old_xcp_profit: uint256 = self.xcp_profit
        old_virtual_price: uint256 = self.virtual_price
        last_prices_timestamp: uint256 = self.last_prices_timestamp
    
        # ----------------------- Update MA if needed ----------------------------
    
        if last_prices_timestamp < block.timestamp:
    
            #   The moving average price oracle is calculated using the last_price
            #      of the trade at the previous block, and the price oracle logged
            #              before that trade. This can happen only once per block.
    
            # ------------------ Calculate moving average params -----------------
    
            alpha: uint256 = MATH.wad_exp(
                -convert(
                    unsafe_div(
                        (block.timestamp - last_prices_timestamp) * 10**18,
                        rebalancing_params[2]  # <----------------------- ma_time.
                    ),
                    int256,
                )
            )
    
            for k in range(N_COINS - 1):
    
                # ----------------- We cap state price that goes into the EMA with
                #                                                 2 x price_scale.
                price_oracle[k] = unsafe_div(
                    min(last_prices[k], 2 * price_scale[k]) * (10**18 - alpha) +
                    price_oracle[k] * alpha,  # ^-------- Cap spot price into EMA.
                    10**18
                )
    
            self.price_oracle_packed = self._pack_prices(price_oracle)
            self.last_prices_timestamp = block.timestamp  # <---- Store timestamp.
    
        #                  price_oracle is used further on to calculate its vector
        #            distance from price_scale. This distance is used to calculate
        #                  the amount of adjustment to be done to the price_scale.
    
        # ------------------ If new_D is set to 0, calculate it ------------------
    
        D_unadjusted: uint256 = new_D
        if new_D == 0:  #  <--------------------------- _exchange sets new_D to 0.
            D_unadjusted = MATH.newton_D(A_gamma[0], A_gamma[1], _xp, K0_prev)
    
        # ----------------------- Calculate last_prices --------------------------
    
        last_prices = MATH.get_p(_xp, D_unadjusted, A_gamma)
        for k in range(N_COINS - 1):
            last_prices[k] = unsafe_div(last_prices[k] * price_scale[k], 10**18)
        self.last_prices_packed = self._pack_prices(last_prices)
    
        # ---------- Update profit numbers without price adjustment first --------
    
        xp: uint256[N_COINS] = empty(uint256[N_COINS])
        xp[0] = unsafe_div(D_unadjusted, N_COINS)
        for k in range(N_COINS - 1):
            xp[k + 1] = D_unadjusted * 10**18 / (N_COINS * price_scale[k])
    
        # ------------------------- Update xcp_profit ----------------------------
    
        xcp_profit: uint256 = 10**18
        virtual_price: uint256 = 10**18
    
        if old_virtual_price > 0:
    
            xcp: uint256 = MATH.geometric_mean(xp)
            virtual_price = 10**18 * xcp / total_supply
    
            xcp_profit = unsafe_div(
                old_xcp_profit * virtual_price,
                old_virtual_price
            )  # <---------------- Safu to do unsafe_div as old_virtual_price > 0.
    
            #       If A and gamma are not undergoing ramps (t < block.timestamp),
            #         ensure new virtual_price is not less than old virtual_price,
            #                                        else the pool suffers a loss.
            if self.future_A_gamma_time < block.timestamp:
                assert virtual_price > old_virtual_price, "Loss"
    
        self.xcp_profit = xcp_profit
    
        # ------------ Rebalance liquidity if there's enough profits to adjust it:
        if virtual_price * 2 - 10**18 > xcp_profit + 2 * rebalancing_params[0]:
            #                          allowed_extra_profit --------^
    
            # ------------------- Get adjustment step ----------------------------
    
            #                Calculate the vector distance between price_scale and
            #                                                        price_oracle.
            norm: uint256 = 0
            ratio: uint256 = 0
            for k in range(N_COINS - 1):
    
                ratio = unsafe_div(price_oracle[k] * 10**18, price_scale[k])
                # unsafe_div because we did safediv before ----^
    
                if ratio > 10**18:
                    ratio = unsafe_sub(ratio, 10**18)
                else:
                    ratio = unsafe_sub(10**18, ratio)
                norm = unsafe_add(norm, ratio**2)
    
            norm = isqrt(norm)  # <-------------------- isqrt is not in base 1e18.
            adjustment_step: uint256 = max(
                rebalancing_params[1], unsafe_div(norm, 5)
            )  #           ^------------------------------------- adjustment_step.
    
            if norm > adjustment_step:  # <---------- We only adjust prices if the
                #          vector distance between price_oracle and price_scale is
                #             large enough. This check ensures that no rebalancing
                #           occurs if the distance is low i.e. the pool prices are
                #                                     pegged to the oracle prices.
    
                # ------------------------------------- Calculate new price scale.
    
                p_new: uint256[N_COINS - 1] = empty(uint256[N_COINS - 1])
                for k in range(N_COINS - 1):
                    p_new[k] = unsafe_div(
                        price_scale[k] * unsafe_sub(norm, adjustment_step)
                        + adjustment_step * price_oracle[k],
                        norm
                    )  # <- norm is non-zero and gt adjustment_step; unsafe = safe
    
                # ---------------- Update stale xp (using price_scale) with p_new.
                xp = _xp
                for k in range(N_COINS - 1):
                    xp[k + 1] = unsafe_div(_xp[k + 1] * p_new[k], price_scale[k])
                    # unsafe_div because we did safediv before ----^
    
                # ------------------------------------------ Update D with new xp.
                D: uint256 = MATH.newton_D(A_gamma[0], A_gamma[1], xp, 0)
    
                for k in range(N_COINS):
                    frac: uint256 = xp[k] * 10**18 / D  # <----- Check validity of
                    assert (frac > 10**16 - 1) and (frac < 10**20 + 1)  #   p_new.
    
                xp[0] = D / N_COINS
                for k in range(N_COINS - 1):
                    xp[k + 1] = D * 10**18 / (N_COINS * p_new[k])  # <---- Convert
                    #                                           xp to real prices.
    
                # ---------- Calculate new virtual_price using new xp and D. Reuse
                #              `old_virtual_price` (but it has new virtual_price).
                old_virtual_price = unsafe_div(
                    10**18 * MATH.geometric_mean(xp), total_supply
                )  # <----- unsafe_div because we did safediv before (if vp>1e18)
    
                # ---------------------------- Proceed if we've got enough profit.
                if (
                    old_virtual_price > 10**18 and
                    2 * old_virtual_price - 10**18 > xcp_profit
                ):
    
                    packed_price_scale = self._pack_prices(p_new)
    
                    self.D = D
                    self.virtual_price = old_virtual_price
                    self.price_scale_packed = packed_price_scale
    
                    return packed_price_scale
    
        # --------- price_scale was not adjusted. Update the profit counter and D.
        self.D = D_unadjusted
        self.virtual_price = virtual_price
    
        return packed_price_scale
    
    
    @internal
    def _claim_admin_fees():
        """
        @notice Claims admin fees and sends it to fee_receiver set in the factory.
        """
        A_gamma: uint256[2] = self._A_gamma()
    
        xcp_profit: uint256 = self.xcp_profit  # <---------- Current pool profits.
        xcp_profit_a: uint256 = self.xcp_profit_a  # <- Profits at previous claim.
        total_supply: uint256 = self.totalSupply
    
        # Do not claim admin fees if:
        # 1. insufficient profits accrued since last claim, and
        # 2. there are less than 10**18 (or 1 unit of) lp tokens, else it can lead
        #    to manipulated virtual prices.
        if xcp_profit <= xcp_profit_a or total_supply < 10**18:
            return
    
        #      Claim tokens belonging to the admin here. This is done by 'gulping'
        #       pool tokens that have accrued as fees, but not accounted in pool's
        #         `self.balances` yet: pool balances only account for incoming and
        #                  outgoing tokens excluding fees. Following 'gulps' fees:
    
        for i in range(N_COINS):
            if coins[i] == WETH20:
                self.balances[i] = self.balance
            else:
                self.balances[i] = ERC20(coins[i]).balanceOf(self)
    
        #            If the pool has made no profits, `xcp_profit == xcp_profit_a`
        #                         and the pool gulps nothing in the previous step.
    
        vprice: uint256 = self.virtual_price
    
        #  Admin fees are calculated as follows.
        #      1. Calculate accrued profit since last claim. `xcp_profit`
        #         is the current profits. `xcp_profit_a` is the profits
        #         at the previous claim.
        #      2. Take out admin's share, which is hardcoded at 5 * 10**9.
        #         (50% => half of 100% => 10**10 / 2 => 5 * 10**9).
        #      3. Since half of the profits go to rebalancing the pool, we
        #         are left with half; so divide by 2.
    
        fees: uint256 = unsafe_div(
            unsafe_sub(xcp_profit, xcp_profit_a) * ADMIN_FEE, 2 * 10**10
        )
    
        # ------------------------------ Claim admin fees by minting admin's share
        #                                                of the pool in LP tokens.
        receiver: address = Factory(self.factory).fee_receiver()
        if receiver != empty(address) and fees > 0:
    
            frac: uint256 = vprice * 10**18 / (vprice - fees) - 10**18
            claimed: uint256 = self.mint_relative(receiver, frac)
    
            xcp_profit -= fees * 2
    
            self.xcp_profit = xcp_profit
    
            log ClaimAdminFee(receiver, claimed)
    
        # ------------------------------------------- Recalculate D b/c we gulped.
        D: uint256 = MATH.newton_D(A_gamma[0], A_gamma[1], self.xp(), 0)
        self.D = D
    
        # ------------------- Recalculate virtual_price following admin fee claim.
        #     In this instance we do not check if current virtual price is greater
        #               than old virtual price, since the claim process can result
        #                                     in a small decrease in pool's value.
    
        self.virtual_price = 10**18 * self.get_xcp(D) / self.totalSupply
        self.xcp_profit_a = xcp_profit  # <------------ Cache last claimed profit.
    
    
    @internal
    @view
    def xp() -> uint256[N_COINS]:
    
        result: uint256[N_COINS] = self.balances
        packed_prices: uint256 = self.price_scale_packed
        precisions: uint256[N_COINS] = self._unpack(self.packed_precisions)
    
        result[0] *= precisions[0]
        for i in range(1, N_COINS):
            p: uint256 = (packed_prices & PRICE_MASK) * precisions[i]
            result[i] = result[i] * p / PRECISION
            packed_prices = packed_prices >> PRICE_SIZE
    
        return result
    
    
    @view
    @internal
    def _A_gamma() -> uint256[2]:
        t1: uint256 = self.future_A_gamma_time
    
        A_gamma_1: uint256 = self.future_A_gamma
        gamma1: uint256 = A_gamma_1 & 2**128 - 1
        A1: uint256 = A_gamma_1 >> 128
    
        if block.timestamp < t1:
    
            # --------------- Handle ramping up and down of A --------------------
    
            A_gamma_0: uint256 = self.initial_A_gamma
            t0: uint256 = self.initial_A_gamma_time
    
            t1 -= t0
            t0 = block.timestamp - t0
            t2: uint256 = t1 - t0
    
            A1 = ((A_gamma_0 >> 128) * t2 + A1 * t0) / t1
            gamma1 = ((A_gamma_0 & 2**128 - 1) * t2 + gamma1 * t0) / t1
    
        return [A1, gamma1]
    
    
    @internal
    @view
    def _fee(xp: uint256[N_COINS]) -> uint256:
        fee_params: uint256[3] = self._unpack(self.packed_fee_params)
        f: uint256 = MATH.reduction_coefficient(xp, fee_params[2])
        return unsafe_div(
            fee_params[0] * f + fee_params[1] * (10**18 - f),
            10**18
        )
    
    
    @internal
    @view
    def get_xcp(D: uint256) -> uint256:
    
        x: uint256[N_COINS] = empty(uint256[N_COINS])
        x[0] = D / N_COINS
        packed_prices: uint256 = self.price_scale_packed  # <-- No precisions here
        #                                 because we don't switch to "real" units.
    
        for i in range(1, N_COINS):
            x[i] = D * 10**18 / (N_COINS * (packed_prices & PRICE_MASK))
            packed_prices = packed_prices >> PRICE_SIZE
    
        return MATH.geometric_mean(x)
    
    
    @view
    @internal
    def _calc_token_fee(amounts: uint256[N_COINS], xp: uint256[N_COINS]) -> uint256:
        # fee = sum(amounts_i - avg(amounts)) * fee' / sum(amounts)
        fee: uint256 = unsafe_div(
            unsafe_mul(self._fee(xp), N_COINS),
            unsafe_mul(4, unsafe_sub(N_COINS, 1))
        )
    
        S: uint256 = 0
        for _x in amounts:
            S += _x
    
        avg: uint256 = unsafe_div(S, N_COINS)
        Sdiff: uint256 = 0
    
        for _x in amounts:
            if _x > avg:
                Sdiff += unsafe_sub(_x, avg)
            else:
                Sdiff += unsafe_sub(avg, _x)
    
        return fee * Sdiff / S + NOISE_FEE
    
    
    @internal
    @view
    def _calc_withdraw_one_coin(
        A_gamma: uint256[2],
        token_amount: uint256,
        i: uint256,
        update_D: bool,
    ) -> (uint256, uint256, uint256[N_COINS], uint256):
    
        token_supply: uint256 = self.totalSupply
        assert token_amount <= token_supply  # dev: token amount more than supply
        assert i < N_COINS  # dev: coin out of range
    
        xx: uint256[N_COINS] = self.balances
        precisions: uint256[N_COINS] = self._unpack(self.packed_precisions)
        xp: uint256[N_COINS] = precisions
        D0: uint256 = 0
    
        # -------------------------- Calculate D0 and xp -------------------------
    
        price_scale_i: uint256 = PRECISION * precisions[0]
        packed_prices: uint256 = self.price_scale_packed
        xp[0] *= xx[0]
        for k in range(1, N_COINS):
            p: uint256 = (packed_prices & PRICE_MASK)
            if i == k:
                price_scale_i = p * xp[i]
            xp[k] = unsafe_div(xp[k] * xx[k] * p, PRECISION)
            packed_prices = packed_prices >> PRICE_SIZE
    
        if update_D:  # <-------------- D is updated if pool is undergoing a ramp.
            D0 = MATH.newton_D(A_gamma[0], A_gamma[1], xp, 0)
        else:
            D0 = self.D
    
        D: uint256 = D0
    
        # -------------------------------- Fee Calc ------------------------------
    
        # Charge fees on D. Roughly calculate xp[i] after withdrawal and use that
        # to calculate fee. Precision is not paramount here: we just want a
        # behavior where the higher the imbalance caused the more fee the AMM
        # charges.
    
        # xp is adjusted assuming xp[0] ~= xp[1] ~= x[2], which is usually not the
        #  case. We charge self._fee(xp), where xp is an imprecise adjustment post
        #  withdrawal in one coin. If the withdraw is too large: charge max fee by
        #   default. This is because the fee calculation will otherwise underflow.
    
        xp_imprecise: uint256[N_COINS] = xp
        xp_correction: uint256 = xp[i] * N_COINS * token_amount / token_supply
        fee: uint256 = self._unpack(self.packed_fee_params)[1]  # <- self.out_fee.
    
        if xp_correction < xp_imprecise[i]:
            xp_imprecise[i] -= xp_correction
            fee = self._fee(xp_imprecise)
    
        dD: uint256 = unsafe_div(token_amount * D, token_supply)
        D_fee: uint256 = fee * dD / (2 * 10**10) + 1  # <------- Actual fee on D.
    
        # --------- Calculate `approx_fee` (assuming balanced state) in ith token.
        # -------------------------------- We only need this for fee in the event.
        approx_fee: uint256 = N_COINS * D_fee * xx[i] / D
    
        # ------------------------------------------------------------------------
        D -= (dD - D_fee)  # <----------------------------------- Charge fee on D.
        # --------------------------------- Calculate `y_out`` with `(D - D_fee)`.
        y: uint256 = MATH.get_y(A_gamma[0], A_gamma[1], xp, D, i)[0]
        dy: uint256 = (xp[i] - y) * PRECISION / price_scale_i
        xp[i] = y
    
        return dy, D, xp, approx_fee
    
    
    # ------------------------ ERC20 functions -----------------------------------
    
    
    @internal
    def _approve(_owner: address, _spender: address, _value: uint256):
        self.allowance[_owner][_spender] = _value
    
        log Approval(_owner, _spender, _value)
    
    
    @internal
    def _transfer(_from: address, _to: address, _value: uint256):
        assert _to not in [self, empty(address)]
    
        self.balanceOf[_from] -= _value
        self.balanceOf[_to] += _value
    
        log Transfer(_from, _to, _value)
    
    
    @view
    @internal
    def _domain_separator() -> bytes32:
        if chain.id != CACHED_CHAIN_ID:
            return keccak256(
                _abi_encode(
                    EIP712_TYPEHASH,
                    NAME_HASH,
                    VERSION_HASH,
                    chain.id,
                    self,
                    salt,
                )
            )
        return CACHED_DOMAIN_SEPARATOR
    
    
    @external
    def transferFrom(_from: address, _to: address, _value: uint256) -> bool:
        """
        @dev Transfer tokens from one address to another.
        @param _from address The address which you want to send tokens from
        @param _to address The address which you want to transfer to
        @param _value uint256 the amount of tokens to be transferred
        @return bool True on successul transfer. Reverts otherwise.
        """
        _allowance: uint256 = self.allowance[_from][msg.sender]
        if _allowance != max_value(uint256):
            self._approve(_from, msg.sender, _allowance - _value)
    
        self._transfer(_from, _to, _value)
        return True
    
    
    @external
    def transfer(_to: address, _value: uint256) -> bool:
        """
        @dev Transfer token for a specified address
        @param _to The address to transfer to.
        @param _value The amount to be transferred.
        @return bool True on successful transfer. Reverts otherwise.
        """
        self._transfer(msg.sender, _to, _value)
        return True
    
    
    @external
    def approve(_spender: address, _value: uint256) -> bool:
        """
        @notice Allow `_spender` to transfer up to `_value` amount
                of tokens from the caller's account.
        @dev Non-zero to non-zero approvals are allowed, but should
             be used cautiously. The methods increaseAllowance + decreaseAllowance
             are available to prevent any front-running that may occur.
        @param _spender The account permitted to spend up to `_value` amount of
                        caller's funds.
        @param _value The amount of tokens `_spender` is allowed to spend.
        @return bool Success
        """
        self._approve(msg.sender, _spender, _value)
        return True
    
    
    @external
    def increaseAllowance(_spender: address, _add_value: uint256) -> bool:
        """
        @notice Increase the allowance granted to `_spender`.
        @dev This function will never overflow, and instead will bound
             allowance to max_value(uint256). This has the potential to grant an
             infinite approval.
        @param _spender The account to increase the allowance of.
        @param _add_value The amount to increase the allowance by.
        @return bool Success
        """
        cached_allowance: uint256 = self.allowance[msg.sender][_spender]
        allowance: uint256 = unsafe_add(cached_allowance, _add_value)
    
        if allowance < cached_allowance:  # <-------------- Check for an overflow.
            allowance = max_value(uint256)
    
        if allowance != cached_allowance:
            self._approve(msg.sender, _spender, allowance)
    
        return True
    
    
    @external
    def decreaseAllowance(_spender: address, _sub_value: uint256) -> bool:
        """
        @notice Decrease the allowance granted to `_spender`.
        @dev This function will never underflow, and instead will bound
            allowance to 0.
        @param _spender The account to decrease the allowance of.
        @param _sub_value The amount to decrease the allowance by.
        @return bool Success.
        """
        cached_allowance: uint256 = self.allowance[msg.sender][_spender]
        allowance: uint256 = unsafe_sub(cached_allowance, _sub_value)
    
        if cached_allowance < allowance:  # <------------- Check for an underflow.
            allowance = 0
    
        if allowance != cached_allowance:
            self._approve(msg.sender, _spender, allowance)
    
        return True
    
    
    @external
    def permit(
        _owner: address,
        _spender: address,
        _value: uint256,
        _deadline: uint256,
        _v: uint8,
        _r: bytes32,
        _s: bytes32,
    ) -> bool:
        """
        @notice Permit `_spender` to spend up to `_value` amount of `_owner`'s
                tokens via a signature.
        @dev In the event of a chain fork, replay attacks are prevented as
             domain separator is recalculated. However, this is only if the
             resulting chains update their chainId.
        @param _owner The account which generated the signature and is granting an
                      allowance.
        @param _spender The account which will be granted an allowance.
        @param _value The approval amount.
        @param _deadline The deadline by which the signature must be submitted.
        @param _v The last byte of the ECDSA signature.
        @param _r The first 32 bytes of the ECDSA signature.
        @param _s The second 32 bytes of the ECDSA signature.
        @return bool Success.
        """
        assert _owner != empty(address)  # dev: invalid owner
        assert block.timestamp <= _deadline  # dev: permit expired
    
        nonce: uint256 = self.nonces[_owner]
        digest: bytes32 = keccak256(
            concat(
                b"\x19\x01",
                self._domain_separator(),
                keccak256(
                    _abi_encode(
                        EIP2612_TYPEHASH, _owner, _spender, _value, nonce, _deadline
                    )
                ),
            )
        )
        assert ecrecover(digest, _v, _r, _s) == _owner  # dev: invalid signature
    
        self.nonces[_owner] = unsafe_add(nonce, 1)  # <-- Unsafe add is safe here.
        self._approve(_owner, _spender, _value)
        return True
    
    
    @internal
    def mint(_to: address, _value: uint256) -> bool:
        """
        @dev Mint an amount of the token and assigns it to an account.
             This encapsulates the modification of balances such that the
             proper events are emitted.
        @param _to The account that will receive the created tokens.
        @param _value The amount that will be created.
        @return bool Success.
        """
        self.totalSupply += _value
        self.balanceOf[_to] += _value
    
        log Transfer(empty(address), _to, _value)
        return True
    
    
    @internal
    def mint_relative(_to: address, frac: uint256) -> uint256:
        """
        @dev Increases supply by factor of (1 + frac/1e18) and mints it for _to
        @param _to The account that will receive the created tokens.
        @param frac The fraction of the current supply to mint.
        @return uint256 Amount of tokens minted.
        """
        supply: uint256 = self.totalSupply
        d_supply: uint256 = supply * frac / 10**18
        if d_supply > 0:
            self.totalSupply = supply + d_supply
            self.balanceOf[_to] += d_supply
            log Transfer(empty(address), _to, d_supply)
    
        return d_supply
    
    
    @internal
    def burnFrom(_to: address, _value: uint256) -> bool:
        """
        @dev Burn an amount of the token from a given account.
        @param _to The account whose tokens will be burned.
        @param _value The amount that will be burned.
        @return bool Success.
        """
        self.totalSupply -= _value
        self.balanceOf[_to] -= _value
    
        log Transfer(_to, empty(address), _value)
        return True
    
    
    # ------------------------- AMM View Functions -------------------------------
    
    
    @external
    @view
    def fee_receiver() -> address:
        """
        @notice Returns the address of the admin fee receiver.
        @return address Fee receiver.
        """
        return Factory(self.factory).fee_receiver()
    
    
    @external
    @view
    def calc_token_amount(amounts: uint256[N_COINS], deposit: bool) -> uint256:
        """
        @notice Calculate LP tokens minted or to be burned for depositing or
                removing `amounts` of coins
        @dev Includes fee.
        @param amounts Amounts of tokens being deposited or withdrawn
        @param deposit True if it is a deposit action, False if withdrawn.
        @return uint256 Amount of LP tokens deposited or withdrawn.
        """
        view_contract: address = Factory(self.factory).views_implementation()
        return Views(view_contract).calc_token_amount(amounts, deposit, self)
    
    
    @external
    @view
    def get_dy(i: uint256, j: uint256, dx: uint256) -> uint256:
        """
        @notice Get amount of coin[j] tokens received for swapping in dx amount of coin[i]
        @dev Includes fee.
        @param i index of input token. Check pool.coins(i) to get coin address at ith index
        @param j index of output token
        @param dx amount of input coin[i] tokens
        @return uint256 Exact amount of output j tokens for dx amount of i input tokens.
        """
        view_contract: address = Factory(self.factory).views_implementation()
        return Views(view_contract).get_dy(i, j, dx, self)
    
    
    @external
    @view
    def get_dx(i: uint256, j: uint256, dy: uint256) -> uint256:
        """
        @notice Get amount of coin[i] tokens to input for swapping out dy amount
                of coin[j]
        @dev This is an approximate method, and returns estimates close to the input
             amount. Expensive to call on-chain.
        @param i index of input token. Check pool.coins(i) to get coin address at
               ith index
        @param j index of output token
        @param dy amount of input coin[j] tokens received
        @return uint256 Approximate amount of input i tokens to get dy amount of j tokens.
        """
        view_contract: address = Factory(self.factory).views_implementation()
        return Views(view_contract).get_dx(i, j, dy, self)
    
    
    @external
    @view
    @nonreentrant("lock")
    def lp_price() -> uint256:
        """
        @notice Calculates the current price of the LP token w.r.t coin at the
                0th index
        @return uint256 LP price.
        """
    
        price_oracle: uint256[N_COINS-1] = self._unpack_prices(
            self.price_oracle_packed
        )
        return (
            3 * self.virtual_price * MATH.cbrt(price_oracle[0] * price_oracle[1])
        ) / 10**24
    
    
    @external
    @view
    @nonreentrant("lock")
    def get_virtual_price() -> uint256:
        """
        @notice Calculates the current virtual price of the pool LP token.
        @dev Not to be confused with `self.virtual_price` which is a cached
             virtual price.
        @return uint256 Virtual Price.
        """
        return 10**18 * self.get_xcp(self.D) / self.totalSupply
    
    
    @external
    @view
    @nonreentrant("lock")
    def price_oracle(k: uint256) -> uint256:
        """
        @notice Returns the oracle price of the coin at index `k` w.r.t the coin
                at index 0.
        @dev The oracle is an exponential moving average, with a periodicity
             determined by `self.ma_time`. The aggregated prices are cached state
             prices (dy/dx) calculated AFTER the latest trade.
        @param k The index of the coin.
        @return uint256 Price oracle value of kth coin.
        """
        price_oracle: uint256 = self._unpack_prices(self.price_oracle_packed)[k]
        price_scale: uint256 = self._unpack_prices(self.price_scale_packed)[k]
        last_prices_timestamp: uint256 = self.last_prices_timestamp
    
        if last_prices_timestamp < block.timestamp:  # <------------ Update moving
            #                                                   average if needed.
    
            last_prices: uint256 = self._unpack_prices(self.last_prices_packed)[k]
            ma_time: uint256 = self._unpack(self.packed_rebalancing_params)[2]
            alpha: uint256 = MATH.wad_exp(
                -convert(
                    (block.timestamp - last_prices_timestamp) * 10**18 / ma_time,
                    int256,
                )
            )
    
            # ---- We cap state price that goes into the EMA with 2 x price_scale.
            return (
                min(last_prices, 2 * price_scale) * (10**18 - alpha) +
                price_oracle * alpha
            ) / 10**18
    
        return price_oracle
    
    
    @external
    @view
    def last_prices(k: uint256) -> uint256:
        """
        @notice Returns last price of the coin at index `k` w.r.t the coin
                at index 0.
        @dev last_prices returns the quote by the AMM for an infinitesimally small swap
             after the last trade. It is not equivalent to the last traded price, and
             is computed by taking the partial differential of `x` w.r.t `y`. The
             derivative is calculated in `get_p` and then multiplied with price_scale
             to give last_prices.
        @param k The index of the coin.
        @return uint256 Last logged price of coin.
        """
        return self._unpack_prices(self.last_prices_packed)[k]
    
    
    @external
    @view
    def price_scale(k: uint256) -> uint256:
        """
        @notice Returns the price scale of the coin at index `k` w.r.t the coin
                at index 0.
        @dev Price scale determines the price band around which liquidity is
             concentrated.
        @param k The index of the coin.
        @return uint256 Price scale of coin.
        """
        return self._unpack_prices(self.price_scale_packed)[k]
    
    
    @external
    @view
    def fee() -> uint256:
        """
        @notice Returns the fee charged by the pool at current state.
        @dev Not to be confused with the fee charged at liquidity action, since
             there the fee is calculated on `xp` AFTER liquidity is added or
             removed.
        @return uint256 fee bps.
        """
        return self._fee(self.xp())
    
    
    @view
    @external
    def calc_withdraw_one_coin(token_amount: uint256, i: uint256) -> uint256:
        """
        @notice Calculates output tokens with fee
        @param token_amount LP Token amount to burn
        @param i token in which liquidity is withdrawn
        @return uint256 Amount of ith tokens received for burning token_amount LP tokens.
        """
    
        return self._calc_withdraw_one_coin(
            self._A_gamma(),
            token_amount,
            i,
            (self.future_A_gamma_time > block.timestamp)
        )[0]
    
    
    @external
    @view
    def calc_token_fee(
        amounts: uint256[N_COINS], xp: uint256[N_COINS]
    ) -> uint256:
        """
        @notice Returns the fee charged on the given amounts for add_liquidity.
        @param amounts The amounts of coins being added to the pool.
        @param xp The current balances of the pool multiplied by coin precisions.
        @return uint256 Fee charged.
        """
        return self._calc_token_fee(amounts, xp)
    
    
    @view
    @external
    def A() -> uint256:
        """
        @notice Returns the current pool amplification parameter.
        @return uint256 A param.
        """
        return self._A_gamma()[0]
    
    
    @view
    @external
    def gamma() -> uint256:
        """
        @notice Returns the current pool gamma parameter.
        @return uint256 gamma param.
        """
        return self._A_gamma()[1]
    
    
    @view
    @external
    def mid_fee() -> uint256:
        """
        @notice Returns the current mid fee
        @return uint256 mid_fee value.
        """
        return self._unpack(self.packed_fee_params)[0]
    
    
    @view
    @external
    def out_fee() -> uint256:
        """
        @notice Returns the current out fee
        @return uint256 out_fee value.
        """
        return self._unpack(self.packed_fee_params)[1]
    
    
    @view
    @external
    def fee_gamma() -> uint256:
        """
        @notice Returns the current fee gamma
        @return uint256 fee_gamma value.
        """
        return self._unpack(self.packed_fee_params)[2]
    
    
    @view
    @external
    def allowed_extra_profit() -> uint256:
        """
        @notice Returns the current allowed extra profit
        @return uint256 allowed_extra_profit value.
        """
        return self._unpack(self.packed_rebalancing_params)[0]
    
    
    @view
    @external
    def adjustment_step() -> uint256:
        """
        @notice Returns the current adjustment step
        @return uint256 adjustment_step value.
        """
        return self._unpack(self.packed_rebalancing_params)[1]
    
    
    @view
    @external
    def ma_time() -> uint256:
        """
        @notice Returns the current moving average time in seconds
        @dev To get time in seconds, the parameter is multipled by ln(2)
             One can expect off-by-one errors here.
        @return uint256 ma_time value.
        """
        return self._unpack(self.packed_rebalancing_params)[2] * 694 / 1000
    
    
    @view
    @external
    def precisions() -> uint256[N_COINS]:  # <-------------- For by view contract.
        """
        @notice Returns the precisions of each coin in the pool.
        @return uint256[3] precisions of coins.
        """
        return self._unpack(self.packed_precisions)
    
    
    @external
    @view
    def fee_calc(xp: uint256[N_COINS]) -> uint256:  # <----- For by view contract.
        """
        @notice Returns the fee charged by the pool at current state.
        @param xp The current balances of the pool multiplied by coin precisions.
        @return uint256 Fee value.
        """
        return self._fee(xp)
    
    
    @view
    @external
    def DOMAIN_SEPARATOR() -> bytes32:
        """
        @notice EIP712 domain separator.
        @return bytes32 Domain Separator set for the current chain.
        """
        return self._domain_separator()
    
    
    # ------------------------- AMM Admin Functions ------------------------------
    
    
    @external
    def ramp_A_gamma(
        future_A: uint256, future_gamma: uint256, future_time: uint256
    ):
        """
        @notice Initialise Ramping A and gamma parameter values linearly.
        @dev Only accessible by factory admin, and only
        @param future_A The future A value.
        @param future_gamma The future gamma value.
        @param future_time The timestamp at which the ramping will end.
        """
        assert msg.sender == Factory(self.factory).admin()  # dev: only owner
        assert block.timestamp > self.initial_A_gamma_time + (MIN_RAMP_TIME - 1)  # dev: ramp undergoing
        assert future_time > block.timestamp + MIN_RAMP_TIME - 1  # dev: insufficient time
    
        A_gamma: uint256[2] = self._A_gamma()
        initial_A_gamma: uint256 = A_gamma[0] << 128
        initial_A_gamma = initial_A_gamma | A_gamma[1]
    
        assert future_A > MIN_A - 1
        assert future_A < MAX_A + 1
        assert future_gamma > MIN_GAMMA - 1
        assert future_gamma < MAX_GAMMA + 1
    
        ratio: uint256 = 10**18 * future_A / A_gamma[0]
        assert ratio < 10**18 * MAX_A_CHANGE + 1
        assert ratio > 10**18 / MAX_A_CHANGE - 1
    
        ratio = 10**18 * future_gamma / A_gamma[1]
        assert ratio < 10**18 * MAX_A_CHANGE + 1
        assert ratio > 10**18 / MAX_A_CHANGE - 1
    
        self.initial_A_gamma = initial_A_gamma
        self.initial_A_gamma_time = block.timestamp
    
        future_A_gamma: uint256 = future_A << 128
        future_A_gamma = future_A_gamma | future_gamma
        self.future_A_gamma_time = future_time
        self.future_A_gamma = future_A_gamma
    
        log RampAgamma(
            A_gamma[0],
            future_A,
            A_gamma[1],
            future_gamma,
            block.timestamp,
            future_time,
        )
    
    
    @external
    def stop_ramp_A_gamma():
        """
        @notice Stop Ramping A and gamma parameters immediately.
        @dev Only accessible by factory admin.
        """
        assert msg.sender == Factory(self.factory).admin()  # dev: only owner
    
        A_gamma: uint256[2] = self._A_gamma()
        current_A_gamma: uint256 = A_gamma[0] << 128
        current_A_gamma = current_A_gamma | A_gamma[1]
        self.initial_A_gamma = current_A_gamma
        self.future_A_gamma = current_A_gamma
        self.initial_A_gamma_time = block.timestamp
        self.future_A_gamma_time = block.timestamp
    
        # ------ Now (block.timestamp < t1) is always False, so we return saved A.
    
        log StopRampA(A_gamma[0], A_gamma[1], block.timestamp)
    
    
    @external
    def commit_new_parameters(
        _new_mid_fee: uint256,
        _new_out_fee: uint256,
        _new_fee_gamma: uint256,
        _new_allowed_extra_profit: uint256,
        _new_adjustment_step: uint256,
        _new_ma_time: uint256,
    ):
        """
        @notice Commit new parameters.
        @dev Only accessible by factory admin.
        @param _new_mid_fee The new mid fee.
        @param _new_out_fee The new out fee.
        @param _new_fee_gamma The new fee gamma.
        @param _new_allowed_extra_profit The new allowed extra profit.
        @param _new_adjustment_step The new adjustment step.
        @param _new_ma_time The new ma time. ma_time is time_in_seconds/ln(2).
        """
        assert msg.sender == Factory(self.factory).admin()  # dev: only owner
        assert self.admin_actions_deadline == 0  # dev: active action
    
        _deadline: uint256 = block.timestamp + ADMIN_ACTIONS_DELAY
        self.admin_actions_deadline = _deadline
    
        # ----------------------------- Set fee params ---------------------------
    
        new_mid_fee: uint256 = _new_mid_fee
        new_out_fee: uint256 = _new_out_fee
        new_fee_gamma: uint256 = _new_fee_gamma
    
        current_fee_params: uint256[3] = self._unpack(self.packed_fee_params)
    
        if new_out_fee < MAX_FEE + 1:
            assert new_out_fee > MIN_FEE - 1  # dev: fee is out of range
        else:
            new_out_fee = current_fee_params[1]
    
        if new_mid_fee > MAX_FEE:
            new_mid_fee = current_fee_params[0]
        assert new_mid_fee <= new_out_fee  # dev: mid-fee is too high
    
        if new_fee_gamma < 10**18:
            assert new_fee_gamma > 0  # dev: fee_gamma out of range [1 .. 10**18]
        else:
            new_fee_gamma = current_fee_params[2]
    
        self.future_packed_fee_params = self._pack(
            [new_mid_fee, new_out_fee, new_fee_gamma]
        )
    
        # ----------------- Set liquidity rebalancing parameters -----------------
    
        new_allowed_extra_profit: uint256 = _new_allowed_extra_profit
        new_adjustment_step: uint256 = _new_adjustment_step
        new_ma_time: uint256 = _new_ma_time
    
        current_rebalancing_params: uint256[3] = self._unpack(self.packed_rebalancing_params)
    
        if new_allowed_extra_profit > 10**18:
            new_allowed_extra_profit = current_rebalancing_params[0]
    
        if new_adjustment_step > 10**18:
            new_adjustment_step = current_rebalancing_params[1]
    
        if new_ma_time < 872542:  # <----- Calculated as: 7 * 24 * 60 * 60 / ln(2)
            assert new_ma_time > 86  # dev: MA time should be longer than 60/ln(2)
        else:
            new_ma_time = current_rebalancing_params[2]
    
        self.future_packed_rebalancing_params = self._pack(
            [new_allowed_extra_profit, new_adjustment_step, new_ma_time]
        )
    
        # ---------------------------------- LOG ---------------------------------
    
        log CommitNewParameters(
            _deadline,
            new_mid_fee,
            new_out_fee,
            new_fee_gamma,
            new_allowed_extra_profit,
            new_adjustment_step,
            new_ma_time,
        )
    
    
    @external
    @nonreentrant("lock")
    def apply_new_parameters():
        """
        @notice Apply committed parameters.
        @dev Only callable after admin_actions_deadline.
        """
        assert block.timestamp >= self.admin_actions_deadline  # dev: insufficient time
        assert self.admin_actions_deadline != 0  # dev: no active action
    
        self.admin_actions_deadline = 0
    
        packed_fee_params: uint256 = self.future_packed_fee_params
        self.packed_fee_params = packed_fee_params
    
        packed_rebalancing_params: uint256 = self.future_packed_rebalancing_params
        self.packed_rebalancing_params = packed_rebalancing_params
    
        rebalancing_params: uint256[3] = self._unpack(packed_rebalancing_params)
        fee_params: uint256[3] = self._unpack(packed_fee_params)
    
        log NewParameters(
            fee_params[0],
            fee_params[1],
            fee_params[2],
            rebalancing_params[0],
            rebalancing_params[1],
            rebalancing_params[2],
        )
    
    
    @external
    def revert_new_parameters():
        """
        @notice Revert committed parameters
        @dev Only accessible by factory admin. Setting admin_actions_deadline to 0
             ensures a revert in apply_new_parameters.
        """
        assert msg.sender == Factory(self.factory).admin()  # dev: only owner
        self.admin_actions_deadline = 0

    File 3 of 5: GhoToken
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    import {EnumerableSet} from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';
    import {AccessControl} from '@openzeppelin/contracts/access/AccessControl.sol';
    import {ERC20} from './ERC20.sol';
    import {IGhoToken} from './interfaces/IGhoToken.sol';
    /**
     * @title GHO Token
     * @author Aave
     */
    contract GhoToken is ERC20, AccessControl, IGhoToken {
      using EnumerableSet for EnumerableSet.AddressSet;
      mapping(address => Facilitator) internal _facilitators;
      EnumerableSet.AddressSet internal _facilitatorsList;
      /// @inheritdoc IGhoToken
      bytes32 public constant FACILITATOR_MANAGER_ROLE = keccak256('FACILITATOR_MANAGER_ROLE');
      /// @inheritdoc IGhoToken
      bytes32 public constant BUCKET_MANAGER_ROLE = keccak256('BUCKET_MANAGER_ROLE');
      /**
       * @dev Constructor
       * @param admin This is the initial holder of the default admin role
       */
      constructor(address admin) ERC20('Gho Token', 'GHO', 18) {
        _setupRole(DEFAULT_ADMIN_ROLE, admin);
      }
      /// @inheritdoc IGhoToken
      function mint(address account, uint256 amount) external {
        require(amount > 0, 'INVALID_MINT_AMOUNT');
        Facilitator storage f = _facilitators[msg.sender];
        uint256 currentBucketLevel = f.bucketLevel;
        uint256 newBucketLevel = currentBucketLevel + amount;
        require(f.bucketCapacity >= newBucketLevel, 'FACILITATOR_BUCKET_CAPACITY_EXCEEDED');
        f.bucketLevel = uint128(newBucketLevel);
        _mint(account, amount);
        emit FacilitatorBucketLevelUpdated(msg.sender, currentBucketLevel, newBucketLevel);
      }
      /// @inheritdoc IGhoToken
      function burn(uint256 amount) external {
        require(amount > 0, 'INVALID_BURN_AMOUNT');
        Facilitator storage f = _facilitators[msg.sender];
        uint256 currentBucketLevel = f.bucketLevel;
        uint256 newBucketLevel = currentBucketLevel - amount;
        f.bucketLevel = uint128(newBucketLevel);
        _burn(msg.sender, amount);
        emit FacilitatorBucketLevelUpdated(msg.sender, currentBucketLevel, newBucketLevel);
      }
      /// @inheritdoc IGhoToken
      function addFacilitator(
        address facilitatorAddress,
        string calldata facilitatorLabel,
        uint128 bucketCapacity
      ) external onlyRole(FACILITATOR_MANAGER_ROLE) {
        Facilitator storage facilitator = _facilitators[facilitatorAddress];
        require(bytes(facilitator.label).length == 0, 'FACILITATOR_ALREADY_EXISTS');
        require(bytes(facilitatorLabel).length > 0, 'INVALID_LABEL');
        facilitator.label = facilitatorLabel;
        facilitator.bucketCapacity = bucketCapacity;
        _facilitatorsList.add(facilitatorAddress);
        emit FacilitatorAdded(
          facilitatorAddress,
          keccak256(abi.encodePacked(facilitatorLabel)),
          bucketCapacity
        );
      }
      /// @inheritdoc IGhoToken
      function removeFacilitator(
        address facilitatorAddress
      ) external onlyRole(FACILITATOR_MANAGER_ROLE) {
        require(
          bytes(_facilitators[facilitatorAddress].label).length > 0,
          'FACILITATOR_DOES_NOT_EXIST'
        );
        require(
          _facilitators[facilitatorAddress].bucketLevel == 0,
          'FACILITATOR_BUCKET_LEVEL_NOT_ZERO'
        );
        delete _facilitators[facilitatorAddress];
        _facilitatorsList.remove(facilitatorAddress);
        emit FacilitatorRemoved(facilitatorAddress);
      }
      /// @inheritdoc IGhoToken
      function setFacilitatorBucketCapacity(
        address facilitator,
        uint128 newCapacity
      ) external onlyRole(BUCKET_MANAGER_ROLE) {
        require(bytes(_facilitators[facilitator].label).length > 0, 'FACILITATOR_DOES_NOT_EXIST');
        uint256 oldCapacity = _facilitators[facilitator].bucketCapacity;
        _facilitators[facilitator].bucketCapacity = newCapacity;
        emit FacilitatorBucketCapacityUpdated(facilitator, oldCapacity, newCapacity);
      }
      /// @inheritdoc IGhoToken
      function getFacilitator(address facilitator) external view returns (Facilitator memory) {
        return _facilitators[facilitator];
      }
      /// @inheritdoc IGhoToken
      function getFacilitatorBucket(address facilitator) external view returns (uint256, uint256) {
        return (_facilitators[facilitator].bucketCapacity, _facilitators[facilitator].bucketLevel);
      }
      /// @inheritdoc IGhoToken
      function getFacilitatorsList() external view returns (address[] memory) {
        return _facilitatorsList.values();
      }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.8.0) (utils/structs/EnumerableSet.sol)
    // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
    pragma solidity ^0.8.0;
    /**
     * @dev Library for managing
     * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
     * types.
     *
     * Sets have the following properties:
     *
     * - Elements are added, removed, and checked for existence in constant time
     * (O(1)).
     * - Elements are enumerated in O(n). No guarantees are made on the ordering.
     *
     * ```
     * contract Example {
     *     // Add the library methods
     *     using EnumerableSet for EnumerableSet.AddressSet;
     *
     *     // Declare a set state variable
     *     EnumerableSet.AddressSet private mySet;
     * }
     * ```
     *
     * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
     * and `uint256` (`UintSet`) are supported.
     *
     * [WARNING]
     * ====
     * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
     * unusable.
     * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
     *
     * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
     * array of EnumerableSet.
     * ====
     */
    library EnumerableSet {
        // To implement this library for multiple types with as little code
        // repetition as possible, we write it in terms of a generic Set type with
        // bytes32 values.
        // The Set implementation uses private functions, and user-facing
        // implementations (such as AddressSet) are just wrappers around the
        // underlying Set.
        // This means that we can only create new EnumerableSets for types that fit
        // in bytes32.
        struct Set {
            // Storage of set values
            bytes32[] _values;
            // Position of the value in the `values` array, plus 1 because index 0
            // means a value is not in the set.
            mapping(bytes32 => uint256) _indexes;
        }
        /**
         * @dev Add a value to a set. O(1).
         *
         * Returns true if the value was added to the set, that is if it was not
         * already present.
         */
        function _add(Set storage set, bytes32 value) private returns (bool) {
            if (!_contains(set, value)) {
                set._values.push(value);
                // The value is stored at length-1, but we add 1 to all indexes
                // and use 0 as a sentinel value
                set._indexes[value] = set._values.length;
                return true;
            } else {
                return false;
            }
        }
        /**
         * @dev Removes a value from a set. O(1).
         *
         * Returns true if the value was removed from the set, that is if it was
         * present.
         */
        function _remove(Set storage set, bytes32 value) private returns (bool) {
            // We read and store the value's index to prevent multiple reads from the same storage slot
            uint256 valueIndex = set._indexes[value];
            if (valueIndex != 0) {
                // Equivalent to contains(set, value)
                // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
                // the array, and then remove the last element (sometimes called as 'swap and pop').
                // This modifies the order of the array, as noted in {at}.
                uint256 toDeleteIndex = valueIndex - 1;
                uint256 lastIndex = set._values.length - 1;
                if (lastIndex != toDeleteIndex) {
                    bytes32 lastValue = set._values[lastIndex];
                    // Move the last value to the index where the value to delete is
                    set._values[toDeleteIndex] = lastValue;
                    // Update the index for the moved value
                    set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
                }
                // Delete the slot where the moved value was stored
                set._values.pop();
                // Delete the index for the deleted slot
                delete set._indexes[value];
                return true;
            } else {
                return false;
            }
        }
        /**
         * @dev Returns true if the value is in the set. O(1).
         */
        function _contains(Set storage set, bytes32 value) private view returns (bool) {
            return set._indexes[value] != 0;
        }
        /**
         * @dev Returns the number of values on the set. O(1).
         */
        function _length(Set storage set) private view returns (uint256) {
            return set._values.length;
        }
        /**
         * @dev Returns the value stored at position `index` in the set. O(1).
         *
         * Note that there are no guarantees on the ordering of values inside the
         * array, and it may change when more values are added or removed.
         *
         * Requirements:
         *
         * - `index` must be strictly less than {length}.
         */
        function _at(Set storage set, uint256 index) private view returns (bytes32) {
            return set._values[index];
        }
        /**
         * @dev Return the entire set in an array
         *
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
         */
        function _values(Set storage set) private view returns (bytes32[] memory) {
            return set._values;
        }
        // Bytes32Set
        struct Bytes32Set {
            Set _inner;
        }
        /**
         * @dev Add a value to a set. O(1).
         *
         * Returns true if the value was added to the set, that is if it was not
         * already present.
         */
        function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
            return _add(set._inner, value);
        }
        /**
         * @dev Removes a value from a set. O(1).
         *
         * Returns true if the value was removed from the set, that is if it was
         * present.
         */
        function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
            return _remove(set._inner, value);
        }
        /**
         * @dev Returns true if the value is in the set. O(1).
         */
        function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
            return _contains(set._inner, value);
        }
        /**
         * @dev Returns the number of values in the set. O(1).
         */
        function length(Bytes32Set storage set) internal view returns (uint256) {
            return _length(set._inner);
        }
        /**
         * @dev Returns the value stored at position `index` in the set. O(1).
         *
         * Note that there are no guarantees on the ordering of values inside the
         * array, and it may change when more values are added or removed.
         *
         * Requirements:
         *
         * - `index` must be strictly less than {length}.
         */
        function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
            return _at(set._inner, index);
        }
        /**
         * @dev Return the entire set in an array
         *
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
         */
        function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
            bytes32[] memory store = _values(set._inner);
            bytes32[] memory result;
            /// @solidity memory-safe-assembly
            assembly {
                result := store
            }
            return result;
        }
        // AddressSet
        struct AddressSet {
            Set _inner;
        }
        /**
         * @dev Add a value to a set. O(1).
         *
         * Returns true if the value was added to the set, that is if it was not
         * already present.
         */
        function add(AddressSet storage set, address value) internal returns (bool) {
            return _add(set._inner, bytes32(uint256(uint160(value))));
        }
        /**
         * @dev Removes a value from a set. O(1).
         *
         * Returns true if the value was removed from the set, that is if it was
         * present.
         */
        function remove(AddressSet storage set, address value) internal returns (bool) {
            return _remove(set._inner, bytes32(uint256(uint160(value))));
        }
        /**
         * @dev Returns true if the value is in the set. O(1).
         */
        function contains(AddressSet storage set, address value) internal view returns (bool) {
            return _contains(set._inner, bytes32(uint256(uint160(value))));
        }
        /**
         * @dev Returns the number of values in the set. O(1).
         */
        function length(AddressSet storage set) internal view returns (uint256) {
            return _length(set._inner);
        }
        /**
         * @dev Returns the value stored at position `index` in the set. O(1).
         *
         * Note that there are no guarantees on the ordering of values inside the
         * array, and it may change when more values are added or removed.
         *
         * Requirements:
         *
         * - `index` must be strictly less than {length}.
         */
        function at(AddressSet storage set, uint256 index) internal view returns (address) {
            return address(uint160(uint256(_at(set._inner, index))));
        }
        /**
         * @dev Return the entire set in an array
         *
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
         */
        function values(AddressSet storage set) internal view returns (address[] memory) {
            bytes32[] memory store = _values(set._inner);
            address[] memory result;
            /// @solidity memory-safe-assembly
            assembly {
                result := store
            }
            return result;
        }
        // UintSet
        struct UintSet {
            Set _inner;
        }
        /**
         * @dev Add a value to a set. O(1).
         *
         * Returns true if the value was added to the set, that is if it was not
         * already present.
         */
        function add(UintSet storage set, uint256 value) internal returns (bool) {
            return _add(set._inner, bytes32(value));
        }
        /**
         * @dev Removes a value from a set. O(1).
         *
         * Returns true if the value was removed from the set, that is if it was
         * present.
         */
        function remove(UintSet storage set, uint256 value) internal returns (bool) {
            return _remove(set._inner, bytes32(value));
        }
        /**
         * @dev Returns true if the value is in the set. O(1).
         */
        function contains(UintSet storage set, uint256 value) internal view returns (bool) {
            return _contains(set._inner, bytes32(value));
        }
        /**
         * @dev Returns the number of values in the set. O(1).
         */
        function length(UintSet storage set) internal view returns (uint256) {
            return _length(set._inner);
        }
        /**
         * @dev Returns the value stored at position `index` in the set. O(1).
         *
         * Note that there are no guarantees on the ordering of values inside the
         * array, and it may change when more values are added or removed.
         *
         * Requirements:
         *
         * - `index` must be strictly less than {length}.
         */
        function at(UintSet storage set, uint256 index) internal view returns (uint256) {
            return uint256(_at(set._inner, index));
        }
        /**
         * @dev Return the entire set in an array
         *
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
         */
        function values(UintSet storage set) internal view returns (uint256[] memory) {
            bytes32[] memory store = _values(set._inner);
            uint256[] memory result;
            /// @solidity memory-safe-assembly
            assembly {
                result := store
            }
            return result;
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.8.0) (access/AccessControl.sol)
    pragma solidity ^0.8.0;
    import "./IAccessControl.sol";
    import "../utils/Context.sol";
    import "../utils/Strings.sol";
    import "../utils/introspection/ERC165.sol";
    /**
     * @dev Contract module that allows children to implement role-based access
     * control mechanisms. This is a lightweight version that doesn't allow enumerating role
     * members except through off-chain means by accessing the contract event logs. Some
     * applications may benefit from on-chain enumerability, for those cases see
     * {AccessControlEnumerable}.
     *
     * Roles are referred to by their `bytes32` identifier. These should be exposed
     * in the external API and be unique. The best way to achieve this is by
     * using `public constant` hash digests:
     *
     * ```
     * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
     * ```
     *
     * Roles can be used to represent a set of permissions. To restrict access to a
     * function call, use {hasRole}:
     *
     * ```
     * function foo() public {
     *     require(hasRole(MY_ROLE, msg.sender));
     *     ...
     * }
     * ```
     *
     * Roles can be granted and revoked dynamically via the {grantRole} and
     * {revokeRole} functions. Each role has an associated admin role, and only
     * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
     *
     * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
     * that only accounts with this role will be able to grant or revoke other
     * roles. More complex role relationships can be created by using
     * {_setRoleAdmin}.
     *
     * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
     * grant and revoke this role. Extra precautions should be taken to secure
     * accounts that have been granted it.
     */
    abstract contract AccessControl is Context, IAccessControl, ERC165 {
        struct RoleData {
            mapping(address => bool) members;
            bytes32 adminRole;
        }
        mapping(bytes32 => RoleData) private _roles;
        bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
        /**
         * @dev Modifier that checks that an account has a specific role. Reverts
         * with a standardized message including the required role.
         *
         * The format of the revert reason is given by the following regular expression:
         *
         *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
         *
         * _Available since v4.1._
         */
        modifier onlyRole(bytes32 role) {
            _checkRole(role);
            _;
        }
        /**
         * @dev See {IERC165-supportsInterface}.
         */
        function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
            return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
        }
        /**
         * @dev Returns `true` if `account` has been granted `role`.
         */
        function hasRole(bytes32 role, address account) public view virtual override returns (bool) {
            return _roles[role].members[account];
        }
        /**
         * @dev Revert with a standard message if `_msgSender()` is missing `role`.
         * Overriding this function changes the behavior of the {onlyRole} modifier.
         *
         * Format of the revert message is described in {_checkRole}.
         *
         * _Available since v4.6._
         */
        function _checkRole(bytes32 role) internal view virtual {
            _checkRole(role, _msgSender());
        }
        /**
         * @dev Revert with a standard message if `account` is missing `role`.
         *
         * The format of the revert reason is given by the following regular expression:
         *
         *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
         */
        function _checkRole(bytes32 role, address account) internal view virtual {
            if (!hasRole(role, account)) {
                revert(
                    string(
                        abi.encodePacked(
                            "AccessControl: account ",
                            Strings.toHexString(account),
                            " is missing role ",
                            Strings.toHexString(uint256(role), 32)
                        )
                    )
                );
            }
        }
        /**
         * @dev Returns the admin role that controls `role`. See {grantRole} and
         * {revokeRole}.
         *
         * To change a role's admin, use {_setRoleAdmin}.
         */
        function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {
            return _roles[role].adminRole;
        }
        /**
         * @dev Grants `role` to `account`.
         *
         * If `account` had not been already granted `role`, emits a {RoleGranted}
         * event.
         *
         * Requirements:
         *
         * - the caller must have ``role``'s admin role.
         *
         * May emit a {RoleGranted} event.
         */
        function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
            _grantRole(role, account);
        }
        /**
         * @dev Revokes `role` from `account`.
         *
         * If `account` had been granted `role`, emits a {RoleRevoked} event.
         *
         * Requirements:
         *
         * - the caller must have ``role``'s admin role.
         *
         * May emit a {RoleRevoked} event.
         */
        function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
            _revokeRole(role, account);
        }
        /**
         * @dev Revokes `role` from the calling account.
         *
         * Roles are often managed via {grantRole} and {revokeRole}: this function's
         * purpose is to provide a mechanism for accounts to lose their privileges
         * if they are compromised (such as when a trusted device is misplaced).
         *
         * If the calling account had been revoked `role`, emits a {RoleRevoked}
         * event.
         *
         * Requirements:
         *
         * - the caller must be `account`.
         *
         * May emit a {RoleRevoked} event.
         */
        function renounceRole(bytes32 role, address account) public virtual override {
            require(account == _msgSender(), "AccessControl: can only renounce roles for self");
            _revokeRole(role, account);
        }
        /**
         * @dev Grants `role` to `account`.
         *
         * If `account` had not been already granted `role`, emits a {RoleGranted}
         * event. Note that unlike {grantRole}, this function doesn't perform any
         * checks on the calling account.
         *
         * May emit a {RoleGranted} event.
         *
         * [WARNING]
         * ====
         * This function should only be called from the constructor when setting
         * up the initial roles for the system.
         *
         * Using this function in any other way is effectively circumventing the admin
         * system imposed by {AccessControl}.
         * ====
         *
         * NOTE: This function is deprecated in favor of {_grantRole}.
         */
        function _setupRole(bytes32 role, address account) internal virtual {
            _grantRole(role, account);
        }
        /**
         * @dev Sets `adminRole` as ``role``'s admin role.
         *
         * Emits a {RoleAdminChanged} event.
         */
        function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
            bytes32 previousAdminRole = getRoleAdmin(role);
            _roles[role].adminRole = adminRole;
            emit RoleAdminChanged(role, previousAdminRole, adminRole);
        }
        /**
         * @dev Grants `role` to `account`.
         *
         * Internal function without access restriction.
         *
         * May emit a {RoleGranted} event.
         */
        function _grantRole(bytes32 role, address account) internal virtual {
            if (!hasRole(role, account)) {
                _roles[role].members[account] = true;
                emit RoleGranted(role, account, _msgSender());
            }
        }
        /**
         * @dev Revokes `role` from `account`.
         *
         * Internal function without access restriction.
         *
         * May emit a {RoleRevoked} event.
         */
        function _revokeRole(bytes32 role, address account) internal virtual {
            if (hasRole(role, account)) {
                _roles[role].members[account] = false;
                emit RoleRevoked(role, account, _msgSender());
            }
        }
    }
    // SPDX-License-Identifier: MIT-only
    pragma solidity ^0.8.0;
    import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
    /**
     * @title ERC20
     * @notice Gas Efficient ERC20 + EIP-2612 implementation
     * @dev Modified version of Solmate ERC20 (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol),
     * implementing the basic IERC20
     */
    abstract contract ERC20 is IERC20 {
      /*///////////////////////////////////////////////////////////////
                                 METADATA STORAGE
        //////////////////////////////////////////////////////////////*/
      string public name;
      string public symbol;
      uint8 public immutable decimals;
      /*///////////////////////////////////////////////////////////////
                                  ERC20 STORAGE
        //////////////////////////////////////////////////////////////*/
      uint256 public totalSupply;
      mapping(address => uint256) public balanceOf;
      mapping(address => mapping(address => uint256)) public allowance;
      /*///////////////////////////////////////////////////////////////
                                 EIP-2612 STORAGE
        //////////////////////////////////////////////////////////////*/
      bytes32 public constant PERMIT_TYPEHASH =
        keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)');
      uint256 internal immutable INITIAL_CHAIN_ID;
      bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
      mapping(address => uint256) public nonces;
      /*///////////////////////////////////////////////////////////////
                                   CONSTRUCTOR
        //////////////////////////////////////////////////////////////*/
      constructor(string memory _name, string memory _symbol, uint8 _decimals) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;
        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
      }
      /*///////////////////////////////////////////////////////////////
                                  ERC20 LOGIC
        //////////////////////////////////////////////////////////////*/
      function approve(address spender, uint256 amount) public virtual returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
      }
      function transfer(address to, uint256 amount) public virtual returns (bool) {
        balanceOf[msg.sender] -= amount;
        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
          balanceOf[to] += amount;
        }
        emit Transfer(msg.sender, to, amount);
        return true;
      }
      function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
        if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
        balanceOf[from] -= amount;
        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
          balanceOf[to] += amount;
        }
        emit Transfer(from, to, amount);
        return true;
      }
      /*///////////////////////////////////////////////////////////////
                                  EIP-2612 LOGIC
        //////////////////////////////////////////////////////////////*/
      function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
      ) public virtual {
        require(deadline >= block.timestamp, 'PERMIT_DEADLINE_EXPIRED');
        // Unchecked because the only math done is incrementing
        // the owner's nonce which cannot realistically overflow.
        unchecked {
          bytes32 digest = keccak256(
            abi.encodePacked(
              '\\x19\\x01',
              DOMAIN_SEPARATOR(),
              keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
            )
          );
          address recoveredAddress = ecrecover(digest, v, r, s);
          require(recoveredAddress != address(0) && recoveredAddress == owner, 'INVALID_SIGNER');
          allowance[recoveredAddress][spender] = value;
        }
        emit Approval(owner, spender, value);
      }
      function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
      }
      function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
          keccak256(
            abi.encode(
              keccak256(
                'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'
              ),
              keccak256(bytes(name)),
              keccak256('1'),
              block.chainid,
              address(this)
            )
          );
      }
      /*///////////////////////////////////////////////////////////////
                           INTERNAL MINT/BURN LOGIC
        //////////////////////////////////////////////////////////////*/
      function _mint(address to, uint256 amount) internal virtual {
        totalSupply += amount;
        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
          balanceOf[to] += amount;
        }
        emit Transfer(address(0), to, amount);
      }
      function _burn(address from, uint256 amount) internal virtual {
        balanceOf[from] -= amount;
        // Cannot underflow because a user's balance
        // will never be larger than the total supply.
        unchecked {
          totalSupply -= amount;
        }
        emit Transfer(from, address(0), amount);
      }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
    import {IAccessControl} from '@openzeppelin/contracts/access/IAccessControl.sol';
    /**
     * @title IGhoToken
     * @author Aave
     */
    interface IGhoToken is IERC20, IAccessControl {
      struct Facilitator {
        uint128 bucketCapacity;
        uint128 bucketLevel;
        string label;
      }
      /**
       * @dev Emitted when a new facilitator is added
       * @param facilitatorAddress The address of the new facilitator
       * @param label A hashed human readable identifier for the facilitator
       * @param bucketCapacity The initial capacity of the facilitator's bucket
       */
      event FacilitatorAdded(
        address indexed facilitatorAddress,
        bytes32 indexed label,
        uint256 bucketCapacity
      );
      /**
       * @dev Emitted when a facilitator is removed
       * @param facilitatorAddress The address of the removed facilitator
       */
      event FacilitatorRemoved(address indexed facilitatorAddress);
      /**
       * @dev Emitted when the bucket capacity of a facilitator is updated
       * @param facilitatorAddress The address of the facilitator whose bucket capacity is being changed
       * @param oldCapacity The old capacity of the bucket
       * @param newCapacity The new capacity of the bucket
       */
      event FacilitatorBucketCapacityUpdated(
        address indexed facilitatorAddress,
        uint256 oldCapacity,
        uint256 newCapacity
      );
      /**
       * @dev Emitted when the bucket level changed
       * @param facilitatorAddress The address of the facilitator whose bucket level is being changed
       * @param oldLevel The old level of the bucket
       * @param newLevel The new level of the bucket
       */
      event FacilitatorBucketLevelUpdated(
        address indexed facilitatorAddress,
        uint256 oldLevel,
        uint256 newLevel
      );
      /**
       * @notice Returns the identifier of the Facilitator Manager Role
       * @return The bytes32 id hash of the FacilitatorManager role
       */
      function FACILITATOR_MANAGER_ROLE() external pure returns (bytes32);
      /**
       * @notice Returns the identifier of the Bucket Manager Role
       * @return The bytes32 id hash of the BucketManager role
       */
      function BUCKET_MANAGER_ROLE() external pure returns (bytes32);
      /**
       * @notice Mints the requested amount of tokens to the account address.
       * @dev Only facilitators with enough bucket capacity available can mint.
       * @dev The bucket level is increased upon minting.
       * @param account The address receiving the GHO tokens
       * @param amount The amount to mint
       */
      function mint(address account, uint256 amount) external;
      /**
       * @notice Burns the requested amount of tokens from the account address.
       * @dev Only active facilitators (bucket level > 0) can burn.
       * @dev The bucket level is decreased upon burning.
       * @param amount The amount to burn
       */
      function burn(uint256 amount) external;
      /**
       * @notice Add the facilitator passed with the parameters to the facilitators list.
       * @dev Only accounts with `FACILITATOR_MANAGER_ROLE` role can call this function
       * @param facilitatorAddress The address of the facilitator to add
       * @param facilitatorLabel A human readable identifier for the facilitator
       * @param bucketCapacity The upward limit of GHO can be minted by the facilitator
       */
      function addFacilitator(
        address facilitatorAddress,
        string calldata facilitatorLabel,
        uint128 bucketCapacity
      ) external;
      /**
       * @notice Remove the facilitator from the facilitators list.
       * @dev Only accounts with `FACILITATOR_MANAGER_ROLE` role can call this function
       * @param facilitatorAddress The address of the facilitator to remove
       */
      function removeFacilitator(address facilitatorAddress) external;
      /**
       * @notice Set the bucket capacity of the facilitator.
       * @dev Only accounts with `BUCKET_MANAGER_ROLE` role can call this function
       * @param facilitator The address of the facilitator
       * @param newCapacity The new capacity of the bucket
       */
      function setFacilitatorBucketCapacity(address facilitator, uint128 newCapacity) external;
      /**
       * @notice Returns the facilitator data
       * @param facilitator The address of the facilitator
       * @return The facilitator configuration
       */
      function getFacilitator(address facilitator) external view returns (Facilitator memory);
      /**
       * @notice Returns the bucket configuration of the facilitator
       * @param facilitator The address of the facilitator
       * @return The capacity of the facilitator's bucket
       * @return The level of the facilitator's bucket
       */
      function getFacilitatorBucket(address facilitator) external view returns (uint256, uint256);
      /**
       * @notice Returns the list of the addresses of the active facilitator
       * @return The list of the facilitators addresses
       */
      function getFacilitatorsList() external view returns (address[] memory);
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev External interface of AccessControl declared to support ERC165 detection.
     */
    interface IAccessControl {
        /**
         * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
         *
         * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
         * {RoleAdminChanged} not being emitted signaling this.
         *
         * _Available since v3.1._
         */
        event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
        /**
         * @dev Emitted when `account` is granted `role`.
         *
         * `sender` is the account that originated the contract call, an admin role
         * bearer except when using {AccessControl-_setupRole}.
         */
        event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
        /**
         * @dev Emitted when `account` is revoked `role`.
         *
         * `sender` is the account that originated the contract call:
         *   - if using `revokeRole`, it is the admin role bearer
         *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
         */
        event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
        /**
         * @dev Returns `true` if `account` has been granted `role`.
         */
        function hasRole(bytes32 role, address account) external view returns (bool);
        /**
         * @dev Returns the admin role that controls `role`. See {grantRole} and
         * {revokeRole}.
         *
         * To change a role's admin, use {AccessControl-_setRoleAdmin}.
         */
        function getRoleAdmin(bytes32 role) external view returns (bytes32);
        /**
         * @dev Grants `role` to `account`.
         *
         * If `account` had not been already granted `role`, emits a {RoleGranted}
         * event.
         *
         * Requirements:
         *
         * - the caller must have ``role``'s admin role.
         */
        function grantRole(bytes32 role, address account) external;
        /**
         * @dev Revokes `role` from `account`.
         *
         * If `account` had been granted `role`, emits a {RoleRevoked} event.
         *
         * Requirements:
         *
         * - the caller must have ``role``'s admin role.
         */
        function revokeRole(bytes32 role, address account) external;
        /**
         * @dev Revokes `role` from the calling account.
         *
         * Roles are often managed via {grantRole} and {revokeRole}: this function's
         * purpose is to provide a mechanism for accounts to lose their privileges
         * if they are compromised (such as when a trusted device is misplaced).
         *
         * If the calling account had been granted `role`, emits a {RoleRevoked}
         * event.
         *
         * Requirements:
         *
         * - the caller must be `account`.
         */
        function renounceRole(bytes32 role, address account) external;
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev Provides information about the current execution context, including the
     * sender of the transaction and its data. While these are generally available
     * via msg.sender and msg.data, they should not be accessed in such a direct
     * manner, since when dealing with meta-transactions the account sending and
     * paying for execution may not be the actual sender (as far as an application
     * is concerned).
     *
     * This contract is only required for intermediate, library-like contracts.
     */
    abstract contract Context {
        function _msgSender() internal view virtual returns (address) {
            return msg.sender;
        }
        function _msgData() internal view virtual returns (bytes calldata) {
            return msg.data;
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)
    pragma solidity ^0.8.0;
    import "./math/Math.sol";
    /**
     * @dev String operations.
     */
    library Strings {
        bytes16 private constant _SYMBOLS = "0123456789abcdef";
        uint8 private constant _ADDRESS_LENGTH = 20;
        /**
         * @dev Converts a `uint256` to its ASCII `string` decimal representation.
         */
        function toString(uint256 value) internal pure returns (string memory) {
            unchecked {
                uint256 length = Math.log10(value) + 1;
                string memory buffer = new string(length);
                uint256 ptr;
                /// @solidity memory-safe-assembly
                assembly {
                    ptr := add(buffer, add(32, length))
                }
                while (true) {
                    ptr--;
                    /// @solidity memory-safe-assembly
                    assembly {
                        mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                    }
                    value /= 10;
                    if (value == 0) break;
                }
                return buffer;
            }
        }
        /**
         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
         */
        function toHexString(uint256 value) internal pure returns (string memory) {
            unchecked {
                return toHexString(value, Math.log256(value) + 1);
            }
        }
        /**
         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
         */
        function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
            bytes memory buffer = new bytes(2 * length + 2);
            buffer[0] = "0";
            buffer[1] = "x";
            for (uint256 i = 2 * length + 1; i > 1; --i) {
                buffer[i] = _SYMBOLS[value & 0xf];
                value >>= 4;
            }
            require(value == 0, "Strings: hex length insufficient");
            return string(buffer);
        }
        /**
         * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
         */
        function toHexString(address addr) internal pure returns (string memory) {
            return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
    pragma solidity ^0.8.0;
    import "./IERC165.sol";
    /**
     * @dev Implementation of the {IERC165} interface.
     *
     * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
     * for the additional interface id that will be supported. For example:
     *
     * ```solidity
     * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
     *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
     * }
     * ```
     *
     * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
     */
    abstract contract ERC165 is IERC165 {
        /**
         * @dev See {IERC165-supportsInterface}.
         */
        function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
            return interfaceId == type(IERC165).interfaceId;
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP.
     */
    interface IERC20 {
        /**
         * @dev Emitted when `value` tokens are moved from one account (`from`) to
         * another (`to`).
         *
         * Note that `value` may be zero.
         */
        event Transfer(address indexed from, address indexed to, uint256 value);
        /**
         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
         * a call to {approve}. `value` is the new allowance.
         */
        event Approval(address indexed owner, address indexed spender, uint256 value);
        /**
         * @dev Returns the amount of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
        /**
         * @dev Returns the amount of tokens owned by `account`.
         */
        function balanceOf(address account) external view returns (uint256);
        /**
         * @dev Moves `amount` tokens from the caller's account to `to`.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transfer(address to, uint256 amount) external returns (bool);
        /**
         * @dev Returns the remaining number of tokens that `spender` will be
         * allowed to spend on behalf of `owner` through {transferFrom}. This is
         * zero by default.
         *
         * This value changes when {approve} or {transferFrom} are called.
         */
        function allowance(address owner, address spender) external view returns (uint256);
        /**
         * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * IMPORTANT: Beware that changing an allowance with this method brings the risk
         * that someone may use both the old and the new allowance by unfortunate
         * transaction ordering. One possible solution to mitigate this race
         * condition is to first reduce the spender's allowance to 0 and set the
         * desired value afterwards:
         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
         *
         * Emits an {Approval} event.
         */
        function approve(address spender, uint256 amount) external returns (bool);
        /**
         * @dev Moves `amount` tokens from `from` to `to` using the
         * allowance mechanism. `amount` is then deducted from the caller's
         * allowance.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transferFrom(
            address from,
            address to,
            uint256 amount
        ) external returns (bool);
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev Standard math utilities missing in the Solidity language.
     */
    library Math {
        enum Rounding {
            Down, // Toward negative infinity
            Up, // Toward infinity
            Zero // Toward zero
        }
        /**
         * @dev Returns the largest of two numbers.
         */
        function max(uint256 a, uint256 b) internal pure returns (uint256) {
            return a > b ? a : b;
        }
        /**
         * @dev Returns the smallest of two numbers.
         */
        function min(uint256 a, uint256 b) internal pure returns (uint256) {
            return a < b ? a : b;
        }
        /**
         * @dev Returns the average of two numbers. The result is rounded towards
         * zero.
         */
        function average(uint256 a, uint256 b) internal pure returns (uint256) {
            // (a + b) / 2 can overflow.
            return (a & b) + (a ^ b) / 2;
        }
        /**
         * @dev Returns the ceiling of the division of two numbers.
         *
         * This differs from standard division with `/` in that it rounds up instead
         * of rounding down.
         */
        function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
            // (a + b - 1) / b can overflow on addition, so we distribute.
            return a == 0 ? 0 : (a - 1) / b + 1;
        }
        /**
         * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
         * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
         * with further edits by Uniswap Labs also under MIT license.
         */
        function mulDiv(
            uint256 x,
            uint256 y,
            uint256 denominator
        ) internal pure returns (uint256 result) {
            unchecked {
                // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                // variables such that product = prod1 * 2^256 + prod0.
                uint256 prod0; // Least significant 256 bits of the product
                uint256 prod1; // Most significant 256 bits of the product
                assembly {
                    let mm := mulmod(x, y, not(0))
                    prod0 := mul(x, y)
                    prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                }
                // Handle non-overflow cases, 256 by 256 division.
                if (prod1 == 0) {
                    return prod0 / denominator;
                }
                // Make sure the result is less than 2^256. Also prevents denominator == 0.
                require(denominator > prod1);
                ///////////////////////////////////////////////
                // 512 by 256 division.
                ///////////////////////////////////////////////
                // Make division exact by subtracting the remainder from [prod1 prod0].
                uint256 remainder;
                assembly {
                    // Compute remainder using mulmod.
                    remainder := mulmod(x, y, denominator)
                    // Subtract 256 bit number from 512 bit number.
                    prod1 := sub(prod1, gt(remainder, prod0))
                    prod0 := sub(prod0, remainder)
                }
                // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                // See https://cs.stackexchange.com/q/138556/92363.
                // Does not overflow because the denominator cannot be zero at this stage in the function.
                uint256 twos = denominator & (~denominator + 1);
                assembly {
                    // Divide denominator by twos.
                    denominator := div(denominator, twos)
                    // Divide [prod1 prod0] by twos.
                    prod0 := div(prod0, twos)
                    // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                    twos := add(div(sub(0, twos), twos), 1)
                }
                // Shift in bits from prod1 into prod0.
                prod0 |= prod1 * twos;
                // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                // four bits. That is, denominator * inv = 1 mod 2^4.
                uint256 inverse = (3 * denominator) ^ 2;
                // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                // in modular arithmetic, doubling the correct bits in each step.
                inverse *= 2 - denominator * inverse; // inverse mod 2^8
                inverse *= 2 - denominator * inverse; // inverse mod 2^16
                inverse *= 2 - denominator * inverse; // inverse mod 2^32
                inverse *= 2 - denominator * inverse; // inverse mod 2^64
                inverse *= 2 - denominator * inverse; // inverse mod 2^128
                inverse *= 2 - denominator * inverse; // inverse mod 2^256
                // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                // is no longer required.
                result = prod0 * inverse;
                return result;
            }
        }
        /**
         * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
         */
        function mulDiv(
            uint256 x,
            uint256 y,
            uint256 denominator,
            Rounding rounding
        ) internal pure returns (uint256) {
            uint256 result = mulDiv(x, y, denominator);
            if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                result += 1;
            }
            return result;
        }
        /**
         * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
         *
         * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
         */
        function sqrt(uint256 a) internal pure returns (uint256) {
            if (a == 0) {
                return 0;
            }
            // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
            //
            // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
            // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
            //
            // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
            // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
            // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
            //
            // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
            uint256 result = 1 << (log2(a) >> 1);
            // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
            // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
            // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
            // into the expected uint128 result.
            unchecked {
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                return min(result, a / result);
            }
        }
        /**
         * @notice Calculates sqrt(a), following the selected rounding direction.
         */
        function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = sqrt(a);
                return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
            }
        }
        /**
         * @dev Return the log in base 2, rounded down, of a positive value.
         * Returns 0 if given 0.
         */
        function log2(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >> 128 > 0) {
                    value >>= 128;
                    result += 128;
                }
                if (value >> 64 > 0) {
                    value >>= 64;
                    result += 64;
                }
                if (value >> 32 > 0) {
                    value >>= 32;
                    result += 32;
                }
                if (value >> 16 > 0) {
                    value >>= 16;
                    result += 16;
                }
                if (value >> 8 > 0) {
                    value >>= 8;
                    result += 8;
                }
                if (value >> 4 > 0) {
                    value >>= 4;
                    result += 4;
                }
                if (value >> 2 > 0) {
                    value >>= 2;
                    result += 2;
                }
                if (value >> 1 > 0) {
                    result += 1;
                }
            }
            return result;
        }
        /**
         * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log2(value);
                return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
            }
        }
        /**
         * @dev Return the log in base 10, rounded down, of a positive value.
         * Returns 0 if given 0.
         */
        function log10(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >= 10**64) {
                    value /= 10**64;
                    result += 64;
                }
                if (value >= 10**32) {
                    value /= 10**32;
                    result += 32;
                }
                if (value >= 10**16) {
                    value /= 10**16;
                    result += 16;
                }
                if (value >= 10**8) {
                    value /= 10**8;
                    result += 8;
                }
                if (value >= 10**4) {
                    value /= 10**4;
                    result += 4;
                }
                if (value >= 10**2) {
                    value /= 10**2;
                    result += 2;
                }
                if (value >= 10**1) {
                    result += 1;
                }
            }
            return result;
        }
        /**
         * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log10(value);
                return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
            }
        }
        /**
         * @dev Return the log in base 256, rounded down, of a positive value.
         * Returns 0 if given 0.
         *
         * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
         */
        function log256(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >> 128 > 0) {
                    value >>= 128;
                    result += 16;
                }
                if (value >> 64 > 0) {
                    value >>= 64;
                    result += 8;
                }
                if (value >> 32 > 0) {
                    value >>= 32;
                    result += 4;
                }
                if (value >> 16 > 0) {
                    value >>= 16;
                    result += 2;
                }
                if (value >> 8 > 0) {
                    result += 1;
                }
            }
            return result;
        }
        /**
         * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log256(value);
                return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
            }
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev Interface of the ERC165 standard, as defined in the
     * https://eips.ethereum.org/EIPS/eip-165[EIP].
     *
     * Implementers can declare support of contract interfaces, which can then be
     * queried by others ({ERC165Checker}).
     *
     * For an implementation, see {ERC165}.
     */
    interface IERC165 {
        /**
         * @dev Returns true if this contract implements the interface defined by
         * `interfaceId`. See the corresponding
         * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
         * to learn more about how these ids are created.
         *
         * This function call must use less than 30 000 gas.
         */
        function supportsInterface(bytes4 interfaceId) external view returns (bool);
    }
    

    File 4 of 5: WBTC
    pragma solidity 0.4.24;
    
    // File: openzeppelin-solidity/contracts/token/ERC20/ERC20Basic.sol
    
    /**
     * @title ERC20Basic
     * @dev Simpler version of ERC20 interface
     * See https://github.com/ethereum/EIPs/issues/179
     */
    contract ERC20Basic {
      function totalSupply() public view returns (uint256);
      function balanceOf(address _who) public view returns (uint256);
      function transfer(address _to, uint256 _value) public returns (bool);
      event Transfer(address indexed from, address indexed to, uint256 value);
    }
    
    // File: openzeppelin-solidity/contracts/math/SafeMath.sol
    
    /**
     * @title SafeMath
     * @dev Math operations with safety checks that throw on error
     */
    library SafeMath {
    
      /**
      * @dev Multiplies two numbers, throws on overflow.
      */
      function mul(uint256 _a, uint256 _b) internal pure returns (uint256 c) {
        // Gas optimization: this is cheaper than asserting 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
        if (_a == 0) {
          return 0;
        }
    
        c = _a * _b;
        assert(c / _a == _b);
        return c;
      }
    
      /**
      * @dev Integer division of two numbers, truncating the quotient.
      */
      function div(uint256 _a, uint256 _b) internal pure returns (uint256) {
        // assert(_b > 0); // Solidity automatically throws when dividing by 0
        // uint256 c = _a / _b;
        // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold
        return _a / _b;
      }
    
      /**
      * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
      */
      function sub(uint256 _a, uint256 _b) internal pure returns (uint256) {
        assert(_b <= _a);
        return _a - _b;
      }
    
      /**
      * @dev Adds two numbers, throws on overflow.
      */
      function add(uint256 _a, uint256 _b) internal pure returns (uint256 c) {
        c = _a + _b;
        assert(c >= _a);
        return c;
      }
    }
    
    // File: openzeppelin-solidity/contracts/token/ERC20/BasicToken.sol
    
    /**
     * @title Basic token
     * @dev Basic version of StandardToken, with no allowances.
     */
    contract BasicToken is ERC20Basic {
      using SafeMath for uint256;
    
      mapping(address => uint256) internal balances;
    
      uint256 internal totalSupply_;
    
      /**
      * @dev Total number of tokens in existence
      */
      function totalSupply() public view returns (uint256) {
        return totalSupply_;
      }
    
      /**
      * @dev Transfer token for a specified address
      * @param _to The address to transfer to.
      * @param _value The amount to be transferred.
      */
      function transfer(address _to, uint256 _value) public returns (bool) {
        require(_value <= balances[msg.sender]);
        require(_to != address(0));
    
        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        emit Transfer(msg.sender, _to, _value);
        return true;
      }
    
      /**
      * @dev Gets the balance of the specified address.
      * @param _owner The address to query the the balance of.
      * @return An uint256 representing the amount owned by the passed address.
      */
      function balanceOf(address _owner) public view returns (uint256) {
        return balances[_owner];
      }
    
    }
    
    // File: openzeppelin-solidity/contracts/token/ERC20/ERC20.sol
    
    /**
     * @title ERC20 interface
     * @dev see https://github.com/ethereum/EIPs/issues/20
     */
    contract ERC20 is ERC20Basic {
      function allowance(address _owner, address _spender)
        public view returns (uint256);
    
      function transferFrom(address _from, address _to, uint256 _value)
        public returns (bool);
    
      function approve(address _spender, uint256 _value) public returns (bool);
      event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
      );
    }
    
    // File: openzeppelin-solidity/contracts/token/ERC20/StandardToken.sol
    
    /**
     * @title Standard ERC20 token
     *
     * @dev Implementation of the basic standard token.
     * https://github.com/ethereum/EIPs/issues/20
     * Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
     */
    contract StandardToken is ERC20, BasicToken {
    
      mapping (address => mapping (address => uint256)) internal allowed;
    
    
      /**
       * @dev Transfer tokens from one address to another
       * @param _from address The address which you want to send tokens from
       * @param _to address The address which you want to transfer to
       * @param _value uint256 the amount of tokens to be transferred
       */
      function transferFrom(
        address _from,
        address _to,
        uint256 _value
      )
        public
        returns (bool)
      {
        require(_value <= balances[_from]);
        require(_value <= allowed[_from][msg.sender]);
        require(_to != address(0));
    
        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
        emit Transfer(_from, _to, _value);
        return true;
      }
    
      /**
       * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
       * Beware that changing an allowance with this method brings the risk that someone may use both the old
       * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
       * race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
       * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
       * @param _spender The address which will spend the funds.
       * @param _value The amount of tokens to be spent.
       */
      function approve(address _spender, uint256 _value) public returns (bool) {
        allowed[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value);
        return true;
      }
    
      /**
       * @dev Function to check the amount of tokens that an owner allowed to a spender.
       * @param _owner address The address which owns the funds.
       * @param _spender address The address which will spend the funds.
       * @return A uint256 specifying the amount of tokens still available for the spender.
       */
      function allowance(
        address _owner,
        address _spender
       )
        public
        view
        returns (uint256)
      {
        return allowed[_owner][_spender];
      }
    
      /**
       * @dev Increase the amount of tokens that an owner allowed to a spender.
       * approve should be called when allowed[_spender] == 0. To increment
       * allowed value is better to use this function to avoid 2 calls (and wait until
       * the first transaction is mined)
       * From MonolithDAO Token.sol
       * @param _spender The address which will spend the funds.
       * @param _addedValue The amount of tokens to increase the allowance by.
       */
      function increaseApproval(
        address _spender,
        uint256 _addedValue
      )
        public
        returns (bool)
      {
        allowed[msg.sender][_spender] = (
          allowed[msg.sender][_spender].add(_addedValue));
        emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
        return true;
      }
    
      /**
       * @dev Decrease the amount of tokens that an owner allowed to a spender.
       * approve should be called when allowed[_spender] == 0. To decrement
       * allowed value is better to use this function to avoid 2 calls (and wait until
       * the first transaction is mined)
       * From MonolithDAO Token.sol
       * @param _spender The address which will spend the funds.
       * @param _subtractedValue The amount of tokens to decrease the allowance by.
       */
      function decreaseApproval(
        address _spender,
        uint256 _subtractedValue
      )
        public
        returns (bool)
      {
        uint256 oldValue = allowed[msg.sender][_spender];
        if (_subtractedValue >= oldValue) {
          allowed[msg.sender][_spender] = 0;
        } else {
          allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
        }
        emit Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
        return true;
      }
    
    }
    
    // File: openzeppelin-solidity/contracts/token/ERC20/DetailedERC20.sol
    
    /**
     * @title DetailedERC20 token
     * @dev The decimals are only for visualization purposes.
     * All the operations are done using the smallest and indivisible token unit,
     * just as on Ethereum all the operations are done in wei.
     */
    contract DetailedERC20 is ERC20 {
      string public name;
      string public symbol;
      uint8 public decimals;
    
      constructor(string _name, string _symbol, uint8 _decimals) public {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;
      }
    }
    
    // File: openzeppelin-solidity/contracts/ownership/Ownable.sol
    
    /**
     * @title Ownable
     * @dev The Ownable contract has an owner address, and provides basic authorization control
     * functions, this simplifies the implementation of "user permissions".
     */
    contract Ownable {
      address public owner;
    
    
      event OwnershipRenounced(address indexed previousOwner);
      event OwnershipTransferred(
        address indexed previousOwner,
        address indexed newOwner
      );
    
    
      /**
       * @dev The Ownable constructor sets the original `owner` of the contract to the sender
       * account.
       */
      constructor() public {
        owner = msg.sender;
      }
    
      /**
       * @dev Throws if called by any account other than the owner.
       */
      modifier onlyOwner() {
        require(msg.sender == owner);
        _;
      }
    
      /**
       * @dev Allows the current owner to relinquish control of the contract.
       * @notice Renouncing to ownership will leave the contract without an owner.
       * It will not be possible to call the functions with the `onlyOwner`
       * modifier anymore.
       */
      function renounceOwnership() public onlyOwner {
        emit OwnershipRenounced(owner);
        owner = address(0);
      }
    
      /**
       * @dev Allows the current owner to transfer control of the contract to a newOwner.
       * @param _newOwner The address to transfer ownership to.
       */
      function transferOwnership(address _newOwner) public onlyOwner {
        _transferOwnership(_newOwner);
      }
    
      /**
       * @dev Transfers control of the contract to a newOwner.
       * @param _newOwner The address to transfer ownership to.
       */
      function _transferOwnership(address _newOwner) internal {
        require(_newOwner != address(0));
        emit OwnershipTransferred(owner, _newOwner);
        owner = _newOwner;
      }
    }
    
    // File: openzeppelin-solidity/contracts/token/ERC20/MintableToken.sol
    
    /**
     * @title Mintable token
     * @dev Simple ERC20 Token example, with mintable token creation
     * Based on code by TokenMarketNet: https://github.com/TokenMarketNet/ico/blob/master/contracts/MintableToken.sol
     */
    contract MintableToken is StandardToken, Ownable {
      event Mint(address indexed to, uint256 amount);
      event MintFinished();
    
      bool public mintingFinished = false;
    
    
      modifier canMint() {
        require(!mintingFinished);
        _;
      }
    
      modifier hasMintPermission() {
        require(msg.sender == owner);
        _;
      }
    
      /**
       * @dev Function to mint tokens
       * @param _to The address that will receive the minted tokens.
       * @param _amount The amount of tokens to mint.
       * @return A boolean that indicates if the operation was successful.
       */
      function mint(
        address _to,
        uint256 _amount
      )
        public
        hasMintPermission
        canMint
        returns (bool)
      {
        totalSupply_ = totalSupply_.add(_amount);
        balances[_to] = balances[_to].add(_amount);
        emit Mint(_to, _amount);
        emit Transfer(address(0), _to, _amount);
        return true;
      }
    
      /**
       * @dev Function to stop minting new tokens.
       * @return True if the operation was successful.
       */
      function finishMinting() public onlyOwner canMint returns (bool) {
        mintingFinished = true;
        emit MintFinished();
        return true;
      }
    }
    
    // File: openzeppelin-solidity/contracts/token/ERC20/BurnableToken.sol
    
    /**
     * @title Burnable Token
     * @dev Token that can be irreversibly burned (destroyed).
     */
    contract BurnableToken is BasicToken {
    
      event Burn(address indexed burner, uint256 value);
    
      /**
       * @dev Burns a specific amount of tokens.
       * @param _value The amount of token to be burned.
       */
      function burn(uint256 _value) public {
        _burn(msg.sender, _value);
      }
    
      function _burn(address _who, uint256 _value) internal {
        require(_value <= balances[_who]);
        // no need to require value <= totalSupply, since that would imply the
        // sender's balance is greater than the totalSupply, which *should* be an assertion failure
    
        balances[_who] = balances[_who].sub(_value);
        totalSupply_ = totalSupply_.sub(_value);
        emit Burn(_who, _value);
        emit Transfer(_who, address(0), _value);
      }
    }
    
    // File: openzeppelin-solidity/contracts/lifecycle/Pausable.sol
    
    /**
     * @title Pausable
     * @dev Base contract which allows children to implement an emergency stop mechanism.
     */
    contract Pausable is Ownable {
      event Pause();
      event Unpause();
    
      bool public paused = false;
    
    
      /**
       * @dev Modifier to make a function callable only when the contract is not paused.
       */
      modifier whenNotPaused() {
        require(!paused);
        _;
      }
    
      /**
       * @dev Modifier to make a function callable only when the contract is paused.
       */
      modifier whenPaused() {
        require(paused);
        _;
      }
    
      /**
       * @dev called by the owner to pause, triggers stopped state
       */
      function pause() public onlyOwner whenNotPaused {
        paused = true;
        emit Pause();
      }
    
      /**
       * @dev called by the owner to unpause, returns to normal state
       */
      function unpause() public onlyOwner whenPaused {
        paused = false;
        emit Unpause();
      }
    }
    
    // File: openzeppelin-solidity/contracts/token/ERC20/PausableToken.sol
    
    /**
     * @title Pausable token
     * @dev StandardToken modified with pausable transfers.
     **/
    contract PausableToken is StandardToken, Pausable {
    
      function transfer(
        address _to,
        uint256 _value
      )
        public
        whenNotPaused
        returns (bool)
      {
        return super.transfer(_to, _value);
      }
    
      function transferFrom(
        address _from,
        address _to,
        uint256 _value
      )
        public
        whenNotPaused
        returns (bool)
      {
        return super.transferFrom(_from, _to, _value);
      }
    
      function approve(
        address _spender,
        uint256 _value
      )
        public
        whenNotPaused
        returns (bool)
      {
        return super.approve(_spender, _value);
      }
    
      function increaseApproval(
        address _spender,
        uint _addedValue
      )
        public
        whenNotPaused
        returns (bool success)
      {
        return super.increaseApproval(_spender, _addedValue);
      }
    
      function decreaseApproval(
        address _spender,
        uint _subtractedValue
      )
        public
        whenNotPaused
        returns (bool success)
      {
        return super.decreaseApproval(_spender, _subtractedValue);
      }
    }
    
    // File: openzeppelin-solidity/contracts/ownership/Claimable.sol
    
    /**
     * @title Claimable
     * @dev Extension for the Ownable contract, where the ownership needs to be claimed.
     * This allows the new owner to accept the transfer.
     */
    contract Claimable is Ownable {
      address public pendingOwner;
    
      /**
       * @dev Modifier throws if called by any account other than the pendingOwner.
       */
      modifier onlyPendingOwner() {
        require(msg.sender == pendingOwner);
        _;
      }
    
      /**
       * @dev Allows the current owner to set the pendingOwner address.
       * @param newOwner The address to transfer ownership to.
       */
      function transferOwnership(address newOwner) public onlyOwner {
        pendingOwner = newOwner;
      }
    
      /**
       * @dev Allows the pendingOwner address to finalize the transfer.
       */
      function claimOwnership() public onlyPendingOwner {
        emit OwnershipTransferred(owner, pendingOwner);
        owner = pendingOwner;
        pendingOwner = address(0);
      }
    }
    
    // File: openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol
    
    /**
     * @title SafeERC20
     * @dev Wrappers around ERC20 operations that throw on failure.
     * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
     * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
     */
    library SafeERC20 {
      function safeTransfer(
        ERC20Basic _token,
        address _to,
        uint256 _value
      )
        internal
      {
        require(_token.transfer(_to, _value));
      }
    
      function safeTransferFrom(
        ERC20 _token,
        address _from,
        address _to,
        uint256 _value
      )
        internal
      {
        require(_token.transferFrom(_from, _to, _value));
      }
    
      function safeApprove(
        ERC20 _token,
        address _spender,
        uint256 _value
      )
        internal
      {
        require(_token.approve(_spender, _value));
      }
    }
    
    // File: openzeppelin-solidity/contracts/ownership/CanReclaimToken.sol
    
    /**
     * @title Contracts that should be able to recover tokens
     * @author SylTi
     * @dev This allow a contract to recover any ERC20 token received in a contract by transferring the balance to the contract owner.
     * This will prevent any accidental loss of tokens.
     */
    contract CanReclaimToken is Ownable {
      using SafeERC20 for ERC20Basic;
    
      /**
       * @dev Reclaim all ERC20Basic compatible tokens
       * @param _token ERC20Basic The address of the token contract
       */
      function reclaimToken(ERC20Basic _token) external onlyOwner {
        uint256 balance = _token.balanceOf(this);
        _token.safeTransfer(owner, balance);
      }
    
    }
    
    // File: contracts/utils/OwnableContract.sol
    
    // empty block is used as this contract just inherits others.
    contract OwnableContract is CanReclaimToken, Claimable { } /* solhint-disable-line no-empty-blocks */
    
    // File: contracts/token/WBTC.sol
    
    contract WBTC is StandardToken, DetailedERC20("Wrapped BTC", "WBTC", 8),
        MintableToken, BurnableToken, PausableToken, OwnableContract {
    
        function burn(uint value) public onlyOwner {
            super.burn(value);
        }
    
        function finishMinting() public onlyOwner returns (bool) {
            return false;
        }
    
        function renounceOwnership() public onlyOwner {
            revert("renouncing ownership is blocked");
        }
    }

    File 5 of 5: WstETH
    // SPDX-License-Identifier: MIT AND GPL-3.0
    // File: @openzeppelin/contracts/utils/Context.sol
    
    
    pragma solidity >=0.6.0 <0.8.0;
    
    /*
     * @dev Provides information about the current execution context, including the
     * sender of the transaction and its data. While these are generally available
     * via msg.sender and msg.data, they should not be accessed in such a direct
     * manner, since when dealing with GSN meta-transactions the account sending and
     * paying for execution may not be the actual sender (as far as an application
     * is concerned).
     *
     * This contract is only required for intermediate, library-like contracts.
     */
    abstract contract Context {
        function _msgSender() internal view virtual returns (address payable) {
            return msg.sender;
        }
    
        function _msgData() internal view virtual returns (bytes memory) {
            this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
            return msg.data;
        }
    }
    
    // File: @openzeppelin/contracts/token/ERC20/IERC20.sol
    
    
    pragma solidity >=0.6.0 <0.8.0;
    
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP.
     */
    interface IERC20 {
        /**
         * @dev Returns the amount of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
    
        /**
         * @dev Returns the amount of tokens owned by `account`.
         */
        function balanceOf(address account) external view returns (uint256);
    
        /**
         * @dev Moves `amount` tokens from the caller's account to `recipient`.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transfer(address recipient, uint256 amount) external returns (bool);
    
        /**
         * @dev Returns the remaining number of tokens that `spender` will be
         * allowed to spend on behalf of `owner` through {transferFrom}. This is
         * zero by default.
         *
         * This value changes when {approve} or {transferFrom} are called.
         */
        function allowance(address owner, address spender) external view returns (uint256);
    
        /**
         * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * IMPORTANT: Beware that changing an allowance with this method brings the risk
         * that someone may use both the old and the new allowance by unfortunate
         * transaction ordering. One possible solution to mitigate this race
         * condition is to first reduce the spender's allowance to 0 and set the
         * desired value afterwards:
         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
         *
         * Emits an {Approval} event.
         */
        function approve(address spender, uint256 amount) external returns (bool);
    
        /**
         * @dev Moves `amount` tokens from `sender` to `recipient` using the
         * allowance mechanism. `amount` is then deducted from the caller's
         * allowance.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    
        /**
         * @dev Emitted when `value` tokens are moved from one account (`from`) to
         * another (`to`).
         *
         * Note that `value` may be zero.
         */
        event Transfer(address indexed from, address indexed to, uint256 value);
    
        /**
         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
         * a call to {approve}. `value` is the new allowance.
         */
        event Approval(address indexed owner, address indexed spender, uint256 value);
    }
    
    // File: @openzeppelin/contracts/math/SafeMath.sol
    
    
    pragma solidity >=0.6.0 <0.8.0;
    
    /**
     * @dev Wrappers over Solidity's arithmetic operations with added overflow
     * checks.
     *
     * Arithmetic operations in Solidity wrap on overflow. This can easily result
     * in bugs, because programmers usually assume that an overflow raises an
     * error, which is the standard behavior in high level programming languages.
     * `SafeMath` restores this intuition by reverting the transaction when an
     * operation overflows.
     *
     * Using this library instead of the unchecked operations eliminates an entire
     * class of bugs, so it's recommended to use it always.
     */
    library SafeMath {
        /**
         * @dev Returns the addition of two unsigned integers, with an overflow flag.
         *
         * _Available since v3.4._
         */
        function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    
        /**
         * @dev Returns the substraction of two unsigned integers, with an overflow flag.
         *
         * _Available since v3.4._
         */
        function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    
        /**
         * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
         *
         * _Available since v3.4._
         */
        function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    
        /**
         * @dev Returns the division of two unsigned integers, with a division by zero flag.
         *
         * _Available since v3.4._
         */
        function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    
        /**
         * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
         *
         * _Available since v3.4._
         */
        function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    
        /**
         * @dev Returns the addition of two unsigned integers, reverting on
         * overflow.
         *
         * Counterpart to Solidity's `+` operator.
         *
         * Requirements:
         *
         * - Addition cannot overflow.
         */
        function add(uint256 a, uint256 b) internal pure returns (uint256) {
            uint256 c = a + b;
            require(c >= a, "SafeMath: addition overflow");
            return c;
        }
    
        /**
         * @dev Returns the subtraction of two unsigned integers, reverting on
         * overflow (when the result is negative).
         *
         * Counterpart to Solidity's `-` operator.
         *
         * Requirements:
         *
         * - Subtraction cannot overflow.
         */
        function sub(uint256 a, uint256 b) internal pure returns (uint256) {
            require(b <= a, "SafeMath: subtraction overflow");
            return a - b;
        }
    
        /**
         * @dev Returns the multiplication of two unsigned integers, reverting on
         * overflow.
         *
         * Counterpart to Solidity's `*` operator.
         *
         * Requirements:
         *
         * - Multiplication cannot overflow.
         */
        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
            if (a == 0) return 0;
            uint256 c = a * b;
            require(c / a == b, "SafeMath: multiplication overflow");
            return c;
        }
    
        /**
         * @dev Returns the integer division of two unsigned integers, reverting on
         * division by zero. The result is rounded towards zero.
         *
         * Counterpart to Solidity's `/` operator. Note: this function uses a
         * `revert` opcode (which leaves remaining gas untouched) while Solidity
         * uses an invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         *
         * - The divisor cannot be zero.
         */
        function div(uint256 a, uint256 b) internal pure returns (uint256) {
            require(b > 0, "SafeMath: division by zero");
            return a / b;
        }
    
        /**
         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
         * reverting when dividing by zero.
         *
         * Counterpart to Solidity's `%` operator. This function uses a `revert`
         * opcode (which leaves remaining gas untouched) while Solidity uses an
         * invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         *
         * - The divisor cannot be zero.
         */
        function mod(uint256 a, uint256 b) internal pure returns (uint256) {
            require(b > 0, "SafeMath: modulo by zero");
            return a % b;
        }
    
        /**
         * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
         * overflow (when the result is negative).
         *
         * CAUTION: This function is deprecated because it requires allocating memory for the error
         * message unnecessarily. For custom revert reasons use {trySub}.
         *
         * Counterpart to Solidity's `-` operator.
         *
         * Requirements:
         *
         * - Subtraction cannot overflow.
         */
        function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
            require(b <= a, errorMessage);
            return a - b;
        }
    
        /**
         * @dev Returns the integer division of two unsigned integers, reverting with custom message on
         * division by zero. The result is rounded towards zero.
         *
         * CAUTION: This function is deprecated because it requires allocating memory for the error
         * message unnecessarily. For custom revert reasons use {tryDiv}.
         *
         * Counterpart to Solidity's `/` operator. Note: this function uses a
         * `revert` opcode (which leaves remaining gas untouched) while Solidity
         * uses an invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         *
         * - The divisor cannot be zero.
         */
        function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
            require(b > 0, errorMessage);
            return a / b;
        }
    
        /**
         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
         * reverting with custom message when dividing by zero.
         *
         * CAUTION: This function is deprecated because it requires allocating memory for the error
         * message unnecessarily. For custom revert reasons use {tryMod}.
         *
         * Counterpart to Solidity's `%` operator. This function uses a `revert`
         * opcode (which leaves remaining gas untouched) while Solidity uses an
         * invalid opcode to revert (consuming all remaining gas).
         *
         * Requirements:
         *
         * - The divisor cannot be zero.
         */
        function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
            require(b > 0, errorMessage);
            return a % b;
        }
    }
    
    // File: @openzeppelin/contracts/token/ERC20/ERC20.sol
    
    
    pragma solidity >=0.6.0 <0.8.0;
    
    
    
    
    /**
     * @dev Implementation of the {IERC20} interface.
     *
     * This implementation is agnostic to the way tokens are created. This means
     * that a supply mechanism has to be added in a derived contract using {_mint}.
     * For a generic mechanism see {ERC20PresetMinterPauser}.
     *
     * TIP: For a detailed writeup see our guide
     * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
     * to implement supply mechanisms].
     *
     * We have followed general OpenZeppelin guidelines: functions revert instead
     * of returning `false` on failure. This behavior is nonetheless conventional
     * and does not conflict with the expectations of ERC20 applications.
     *
     * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
     * This allows applications to reconstruct the allowance for all accounts just
     * by listening to said events. Other implementations of the EIP may not emit
     * these events, as it isn't required by the specification.
     *
     * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
     * functions have been added to mitigate the well-known issues around setting
     * allowances. See {IERC20-approve}.
     */
    contract ERC20 is Context, IERC20 {
        using SafeMath for uint256;
    
        mapping (address => uint256) private _balances;
    
        mapping (address => mapping (address => uint256)) private _allowances;
    
        uint256 private _totalSupply;
    
        string private _name;
        string private _symbol;
        uint8 private _decimals;
    
        /**
         * @dev Sets the values for {name} and {symbol}, initializes {decimals} with
         * a default value of 18.
         *
         * To select a different value for {decimals}, use {_setupDecimals}.
         *
         * All three of these values are immutable: they can only be set once during
         * construction.
         */
        constructor (string memory name_, string memory symbol_) public {
            _name = name_;
            _symbol = symbol_;
            _decimals = 18;
        }
    
        /**
         * @dev Returns the name of the token.
         */
        function name() public view virtual returns (string memory) {
            return _name;
        }
    
        /**
         * @dev Returns the symbol of the token, usually a shorter version of the
         * name.
         */
        function symbol() public view virtual returns (string memory) {
            return _symbol;
        }
    
        /**
         * @dev Returns the number of decimals used to get its user representation.
         * For example, if `decimals` equals `2`, a balance of `505` tokens should
         * be displayed to a user as `5,05` (`505 / 10 ** 2`).
         *
         * Tokens usually opt for a value of 18, imitating the relationship between
         * Ether and Wei. This is the value {ERC20} uses, unless {_setupDecimals} is
         * called.
         *
         * NOTE: This information is only used for _display_ purposes: it in
         * no way affects any of the arithmetic of the contract, including
         * {IERC20-balanceOf} and {IERC20-transfer}.
         */
        function decimals() public view virtual returns (uint8) {
            return _decimals;
        }
    
        /**
         * @dev See {IERC20-totalSupply}.
         */
        function totalSupply() public view virtual override returns (uint256) {
            return _totalSupply;
        }
    
        /**
         * @dev See {IERC20-balanceOf}.
         */
        function balanceOf(address account) public view virtual override returns (uint256) {
            return _balances[account];
        }
    
        /**
         * @dev See {IERC20-transfer}.
         *
         * Requirements:
         *
         * - `recipient` cannot be the zero address.
         * - the caller must have a balance of at least `amount`.
         */
        function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
            _transfer(_msgSender(), recipient, amount);
            return true;
        }
    
        /**
         * @dev See {IERC20-allowance}.
         */
        function allowance(address owner, address spender) public view virtual override returns (uint256) {
            return _allowances[owner][spender];
        }
    
        /**
         * @dev See {IERC20-approve}.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         */
        function approve(address spender, uint256 amount) public virtual override returns (bool) {
            _approve(_msgSender(), spender, amount);
            return true;
        }
    
        /**
         * @dev See {IERC20-transferFrom}.
         *
         * Emits an {Approval} event indicating the updated allowance. This is not
         * required by the EIP. See the note at the beginning of {ERC20}.
         *
         * Requirements:
         *
         * - `sender` and `recipient` cannot be the zero address.
         * - `sender` must have a balance of at least `amount`.
         * - the caller must have allowance for ``sender``'s tokens of at least
         * `amount`.
         */
        function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
            _transfer(sender, recipient, amount);
            _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
            return true;
        }
    
        /**
         * @dev Atomically increases the allowance granted to `spender` by the caller.
         *
         * This is an alternative to {approve} that can be used as a mitigation for
         * problems described in {IERC20-approve}.
         *
         * Emits an {Approval} event indicating the updated allowance.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         */
        function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
            _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
            return true;
        }
    
        /**
         * @dev Atomically decreases the allowance granted to `spender` by the caller.
         *
         * This is an alternative to {approve} that can be used as a mitigation for
         * problems described in {IERC20-approve}.
         *
         * Emits an {Approval} event indicating the updated allowance.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         * - `spender` must have allowance for the caller of at least
         * `subtractedValue`.
         */
        function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
            _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
            return true;
        }
    
        /**
         * @dev Moves tokens `amount` from `sender` to `recipient`.
         *
         * This is internal function is equivalent to {transfer}, and can be used to
         * e.g. implement automatic token fees, slashing mechanisms, etc.
         *
         * Emits a {Transfer} event.
         *
         * Requirements:
         *
         * - `sender` cannot be the zero address.
         * - `recipient` cannot be the zero address.
         * - `sender` must have a balance of at least `amount`.
         */
        function _transfer(address sender, address recipient, uint256 amount) internal virtual {
            require(sender != address(0), "ERC20: transfer from the zero address");
            require(recipient != address(0), "ERC20: transfer to the zero address");
    
            _beforeTokenTransfer(sender, recipient, amount);
    
            _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
            _balances[recipient] = _balances[recipient].add(amount);
            emit Transfer(sender, recipient, amount);
        }
    
        /** @dev Creates `amount` tokens and assigns them to `account`, increasing
         * the total supply.
         *
         * Emits a {Transfer} event with `from` set to the zero address.
         *
         * Requirements:
         *
         * - `to` cannot be the zero address.
         */
        function _mint(address account, uint256 amount) internal virtual {
            require(account != address(0), "ERC20: mint to the zero address");
    
            _beforeTokenTransfer(address(0), account, amount);
    
            _totalSupply = _totalSupply.add(amount);
            _balances[account] = _balances[account].add(amount);
            emit Transfer(address(0), account, amount);
        }
    
        /**
         * @dev Destroys `amount` tokens from `account`, reducing the
         * total supply.
         *
         * Emits a {Transfer} event with `to` set to the zero address.
         *
         * Requirements:
         *
         * - `account` cannot be the zero address.
         * - `account` must have at least `amount` tokens.
         */
        function _burn(address account, uint256 amount) internal virtual {
            require(account != address(0), "ERC20: burn from the zero address");
    
            _beforeTokenTransfer(account, address(0), amount);
    
            _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
            _totalSupply = _totalSupply.sub(amount);
            emit Transfer(account, address(0), amount);
        }
    
        /**
         * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
         *
         * This internal function is equivalent to `approve`, and can be used to
         * e.g. set automatic allowances for certain subsystems, etc.
         *
         * Emits an {Approval} event.
         *
         * Requirements:
         *
         * - `owner` cannot be the zero address.
         * - `spender` cannot be the zero address.
         */
        function _approve(address owner, address spender, uint256 amount) internal virtual {
            require(owner != address(0), "ERC20: approve from the zero address");
            require(spender != address(0), "ERC20: approve to the zero address");
    
            _allowances[owner][spender] = amount;
            emit Approval(owner, spender, amount);
        }
    
        /**
         * @dev Sets {decimals} to a value other than the default one of 18.
         *
         * WARNING: This function should only be called from the constructor. Most
         * applications that interact with token contracts will not expect
         * {decimals} to ever change, and may work incorrectly if it does.
         */
        function _setupDecimals(uint8 decimals_) internal virtual {
            _decimals = decimals_;
        }
    
        /**
         * @dev Hook that is called before any transfer of tokens. This includes
         * minting and burning.
         *
         * Calling conditions:
         *
         * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
         * will be to transferred to `to`.
         * - when `from` is zero, `amount` tokens will be minted for `to`.
         * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
         * - `from` and `to` are never both zero.
         *
         * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
         */
        function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
    }
    
    // File: @openzeppelin/contracts/drafts/IERC20Permit.sol
    
    
    pragma solidity >=0.6.0 <0.8.0;
    
    /**
     * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
     * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
     *
     * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
     * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
     * need to send a transaction, and thus is not required to hold Ether at all.
     */
    interface IERC20Permit {
        /**
         * @dev Sets `value` as the allowance of `spender` over `owner`'s tokens,
         * given `owner`'s signed approval.
         *
         * IMPORTANT: The same issues {IERC20-approve} has related to transaction
         * ordering also apply here.
         *
         * Emits an {Approval} event.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         * - `deadline` must be a timestamp in the future.
         * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
         * over the EIP712-formatted function arguments.
         * - the signature must use ``owner``'s current nonce (see {nonces}).
         *
         * For more information on the signature format, see the
         * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
         * section].
         */
        function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;
    
        /**
         * @dev Returns the current nonce for `owner`. This value must be
         * included whenever a signature is generated for {permit}.
         *
         * Every successful call to {permit} increases ``owner``'s nonce by one. This
         * prevents a signature from being used multiple times.
         */
        function nonces(address owner) external view returns (uint256);
    
        /**
         * @dev Returns the domain separator used in the encoding of the signature for `permit`, as defined by {EIP712}.
         */
        // solhint-disable-next-line func-name-mixedcase
        function DOMAIN_SEPARATOR() external view returns (bytes32);
    }
    
    // File: @openzeppelin/contracts/cryptography/ECDSA.sol
    
    
    pragma solidity >=0.6.0 <0.8.0;
    
    /**
     * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
     *
     * These functions can be used to verify that a message was signed by the holder
     * of the private keys of a given address.
     */
    library ECDSA {
        /**
         * @dev Returns the address that signed a hashed message (`hash`) with
         * `signature`. This address can then be used for verification purposes.
         *
         * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
         * this function rejects them by requiring the `s` value to be in the lower
         * half order, and the `v` value to be either 27 or 28.
         *
         * IMPORTANT: `hash` _must_ be the result of a hash operation for the
         * verification to be secure: it is possible to craft signatures that
         * recover to arbitrary addresses for non-hashed data. A safe way to ensure
         * this is by receiving a hash of the original message (which may otherwise
         * be too long), and then calling {toEthSignedMessageHash} on it.
         */
        function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
            // Check the signature length
            if (signature.length != 65) {
                revert("ECDSA: invalid signature length");
            }
    
            // Divide the signature in r, s and v variables
            bytes32 r;
            bytes32 s;
            uint8 v;
    
            // ecrecover takes the signature parameters, and the only way to get them
            // currently is to use assembly.
            // solhint-disable-next-line no-inline-assembly
            assembly {
                r := mload(add(signature, 0x20))
                s := mload(add(signature, 0x40))
                v := byte(0, mload(add(signature, 0x60)))
            }
    
            return recover(hash, v, r, s);
        }
    
        /**
         * @dev Overload of {ECDSA-recover-bytes32-bytes-} that receives the `v`,
         * `r` and `s` signature fields separately.
         */
        function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
            // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
            // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
            // the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
            // signatures from current libraries generate a unique signature with an s-value in the lower half order.
            //
            // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
            // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
            // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
            // these malleable signatures as well.
            require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "ECDSA: invalid signature 's' value");
            require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value");
    
            // If the signature is valid (and not malleable), return the signer address
            address signer = ecrecover(hash, v, r, s);
            require(signer != address(0), "ECDSA: invalid signature");
    
            return signer;
        }
    
        /**
         * @dev Returns an Ethereum Signed Message, created from a `hash`. This
         * replicates the behavior of the
         * https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign[`eth_sign`]
         * JSON-RPC method.
         *
         * See {recover}.
         */
        function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
            // 32 is the length in bytes of hash,
            // enforced by the type signature above
            return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
        }
    }
    
    // File: @openzeppelin/contracts/utils/Counters.sol
    
    
    pragma solidity >=0.6.0 <0.8.0;
    
    
    /**
     * @title Counters
     * @author Matt Condon (@shrugs)
     * @dev Provides counters that can only be incremented or decremented by one. This can be used e.g. to track the number
     * of elements in a mapping, issuing ERC721 ids, or counting request ids.
     *
     * Include with `using Counters for Counters.Counter;`
     * Since it is not possible to overflow a 256 bit integer with increments of one, `increment` can skip the {SafeMath}
     * overflow check, thereby saving gas. This does assume however correct usage, in that the underlying `_value` is never
     * directly accessed.
     */
    library Counters {
        using SafeMath for uint256;
    
        struct Counter {
            // This variable should never be directly accessed by users of the library: interactions must be restricted to
            // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
            // this feature: see https://github.com/ethereum/solidity/issues/4637
            uint256 _value; // default: 0
        }
    
        function current(Counter storage counter) internal view returns (uint256) {
            return counter._value;
        }
    
        function increment(Counter storage counter) internal {
            // The {SafeMath} overflow check can be skipped here, see the comment at the top
            counter._value += 1;
        }
    
        function decrement(Counter storage counter) internal {
            counter._value = counter._value.sub(1);
        }
    }
    
    // File: @openzeppelin/contracts/drafts/EIP712.sol
    
    
    pragma solidity >=0.6.0 <0.8.0;
    
    /**
     * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
     *
     * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,
     * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding
     * they need in their contracts using a combination of `abi.encode` and `keccak256`.
     *
     * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
     * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
     * ({_hashTypedDataV4}).
     *
     * The implementation of the domain separator was designed to be as efficient as possible while still properly updating
     * the chain id to protect against replay attacks on an eventual fork of the chain.
     *
     * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
     * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
     *
     * _Available since v3.4._
     */
    abstract contract EIP712 {
        /* solhint-disable var-name-mixedcase */
        // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to
        // invalidate the cached domain separator if the chain id changes.
        bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;
        uint256 private immutable _CACHED_CHAIN_ID;
    
        bytes32 private immutable _HASHED_NAME;
        bytes32 private immutable _HASHED_VERSION;
        bytes32 private immutable _TYPE_HASH;
        /* solhint-enable var-name-mixedcase */
    
        /**
         * @dev Initializes the domain separator and parameter caches.
         *
         * The meaning of `name` and `version` is specified in
         * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
         *
         * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
         * - `version`: the current major version of the signing domain.
         *
         * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
         * contract upgrade].
         */
        constructor(string memory name, string memory version) internal {
            bytes32 hashedName = keccak256(bytes(name));
            bytes32 hashedVersion = keccak256(bytes(version));
            bytes32 typeHash = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
            _HASHED_NAME = hashedName;
            _HASHED_VERSION = hashedVersion;
            _CACHED_CHAIN_ID = _getChainId();
            _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion);
            _TYPE_HASH = typeHash;
        }
    
        /**
         * @dev Returns the domain separator for the current chain.
         */
        function _domainSeparatorV4() internal view virtual returns (bytes32) {
            if (_getChainId() == _CACHED_CHAIN_ID) {
                return _CACHED_DOMAIN_SEPARATOR;
            } else {
                return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);
            }
        }
    
        function _buildDomainSeparator(bytes32 typeHash, bytes32 name, bytes32 version) private view returns (bytes32) {
            return keccak256(
                abi.encode(
                    typeHash,
                    name,
                    version,
                    _getChainId(),
                    address(this)
                )
            );
        }
    
        /**
         * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
         * function returns the hash of the fully encoded EIP712 message for this domain.
         *
         * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
         *
         * ```solidity
         * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
         *     keccak256("Mail(address to,string contents)"),
         *     mailTo,
         *     keccak256(bytes(mailContents))
         * )));
         * address signer = ECDSA.recover(digest, signature);
         * ```
         */
        function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
            return keccak256(abi.encodePacked("\x19\x01", _domainSeparatorV4(), structHash));
        }
    
        function _getChainId() private view returns (uint256 chainId) {
            this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
            // solhint-disable-next-line no-inline-assembly
            assembly {
                chainId := chainid()
            }
        }
    }
    
    // File: @openzeppelin/contracts/drafts/ERC20Permit.sol
    
    
    pragma solidity >=0.6.5 <0.8.0;
    
    
    
    
    
    
    /**
     * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
     * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
     *
     * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
     * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't
     * need to send a transaction, and thus is not required to hold Ether at all.
     *
     * _Available since v3.4._
     */
    abstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 {
        using Counters for Counters.Counter;
    
        mapping (address => Counters.Counter) private _nonces;
    
        // solhint-disable-next-line var-name-mixedcase
        bytes32 private immutable _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
    
        /**
         * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `"1"`.
         *
         * It's a good idea to use the same `name` that is defined as the ERC20 token name.
         */
        constructor(string memory name) internal EIP712(name, "1") {
        }
    
        /**
         * @dev See {IERC20Permit-permit}.
         */
        function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual override {
            // solhint-disable-next-line not-rely-on-time
            require(block.timestamp <= deadline, "ERC20Permit: expired deadline");
    
            bytes32 structHash = keccak256(
                abi.encode(
                    _PERMIT_TYPEHASH,
                    owner,
                    spender,
                    value,
                    _nonces[owner].current(),
                    deadline
                )
            );
    
            bytes32 hash = _hashTypedDataV4(structHash);
    
            address signer = ECDSA.recover(hash, v, r, s);
            require(signer == owner, "ERC20Permit: invalid signature");
    
            _nonces[owner].increment();
            _approve(owner, spender, value);
        }
    
        /**
         * @dev See {IERC20Permit-nonces}.
         */
        function nonces(address owner) public view override returns (uint256) {
            return _nonces[owner].current();
        }
    
        /**
         * @dev See {IERC20Permit-DOMAIN_SEPARATOR}.
         */
        // solhint-disable-next-line func-name-mixedcase
        function DOMAIN_SEPARATOR() external view override returns (bytes32) {
            return _domainSeparatorV4();
        }
    }
    
    // File: contracts/0.6.12/interfaces/IStETH.sol
    
    // SPDX-FileCopyrightText: 2021 Lido <[email protected]>
    
    
    pragma solidity 0.6.12; // latest available for using OZ
    
    
    
    interface IStETH is IERC20 {
        function getPooledEthByShares(uint256 _sharesAmount) external view returns (uint256);
    
        function getSharesByPooledEth(uint256 _pooledEthAmount) external view returns (uint256);
    
        function submit(address _referral) external payable returns (uint256);
    }
    
    // File: contracts/0.6.12/WstETH.sol
    
    // SPDX-FileCopyrightText: 2021 Lido <[email protected]>
    
    
    /* See contracts/COMPILERS.md */
    pragma solidity 0.6.12;
    
    
    
    /**
     * @title StETH token wrapper with static balances.
     * @dev It's an ERC20 token that represents the account's share of the total
     * supply of stETH tokens. WstETH token's balance only changes on transfers,
     * unlike StETH that is also changed when oracles report staking rewards and
     * penalties. It's a "power user" token for DeFi protocols which don't
     * support rebasable tokens.
     *
     * The contract is also a trustless wrapper that accepts stETH tokens and mints
     * wstETH in return. Then the user unwraps, the contract burns user's wstETH
     * and sends user locked stETH in return.
     *
     * The contract provides the staking shortcut: user can send ETH with regular
     * transfer and get wstETH in return. The contract will send ETH to Lido submit
     * method, staking it and wrapping the received stETH.
     *
     */
    contract WstETH is ERC20Permit {
        IStETH public stETH;
    
        /**
         * @param _stETH address of the StETH token to wrap
         */
        constructor(IStETH _stETH)
            public
            ERC20Permit("Wrapped liquid staked Ether 2.0")
            ERC20("Wrapped liquid staked Ether 2.0", "wstETH")
        {
            stETH = _stETH;
        }
    
        /**
         * @notice Exchanges stETH to wstETH
         * @param _stETHAmount amount of stETH to wrap in exchange for wstETH
         * @dev Requirements:
         *  - `_stETHAmount` must be non-zero
         *  - msg.sender must approve at least `_stETHAmount` stETH to this
         *    contract.
         *  - msg.sender must have at least `_stETHAmount` of stETH.
         * User should first approve _stETHAmount to the WstETH contract
         * @return Amount of wstETH user receives after wrap
         */
        function wrap(uint256 _stETHAmount) external returns (uint256) {
            require(_stETHAmount > 0, "wstETH: can't wrap zero stETH");
            uint256 wstETHAmount = stETH.getSharesByPooledEth(_stETHAmount);
            _mint(msg.sender, wstETHAmount);
            stETH.transferFrom(msg.sender, address(this), _stETHAmount);
            return wstETHAmount;
        }
    
        /**
         * @notice Exchanges wstETH to stETH
         * @param _wstETHAmount amount of wstETH to uwrap in exchange for stETH
         * @dev Requirements:
         *  - `_wstETHAmount` must be non-zero
         *  - msg.sender must have at least `_wstETHAmount` wstETH.
         * @return Amount of stETH user receives after unwrap
         */
        function unwrap(uint256 _wstETHAmount) external returns (uint256) {
            require(_wstETHAmount > 0, "wstETH: zero amount unwrap not allowed");
            uint256 stETHAmount = stETH.getPooledEthByShares(_wstETHAmount);
            _burn(msg.sender, _wstETHAmount);
            stETH.transfer(msg.sender, stETHAmount);
            return stETHAmount;
        }
    
        /**
        * @notice Shortcut to stake ETH and auto-wrap returned stETH
        */
        receive() external payable {
            uint256 shares = stETH.submit{value: msg.value}(address(0));
            _mint(msg.sender, shares);
        }
    
        /**
         * @notice Get amount of wstETH for a given amount of stETH
         * @param _stETHAmount amount of stETH
         * @return Amount of wstETH for a given stETH amount
         */
        function getWstETHByStETH(uint256 _stETHAmount) external view returns (uint256) {
            return stETH.getSharesByPooledEth(_stETHAmount);
        }
    
        /**
         * @notice Get amount of stETH for a given amount of wstETH
         * @param _wstETHAmount amount of wstETH
         * @return Amount of stETH for a given wstETH amount
         */
        function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256) {
            return stETH.getPooledEthByShares(_wstETHAmount);
        }
    
        /**
         * @notice Get amount of stETH for a one wstETH
         * @return Amount of stETH for 1 wstETH
         */
        function stEthPerToken() external view returns (uint256) {
            return stETH.getPooledEthByShares(1 ether);
        }
    
        /**
         * @notice Get amount of wstETH for a one stETH
         * @return Amount of wstETH for a 1 stETH
         */
        function tokensPerStEth() external view returns (uint256) {
            return stETH.getSharesByPooledEth(1 ether);
        }
    }