Cryo Explorer Ethereum Mainnet

Address Contract Verified

Address 0x70BAa068bDA5f0f2dB9f447F307D9167f734FB89
Balance 0 ETH
Nonce 1
Code Size 16370 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

16370 bytes
0x6080604081815260048036101561002c575b505050361561002457610022613bad565b005b610022613bad565b60009260e0843560e01c918263068cc51414612d145750816307fbcb8a14612bfd57816308a1943e14612a775781630962ef79146129445781630e15561a14612924578163111e3b5b1461276b57816313007d551461274157816315945006146124c1578163187cefa81461227a57816319129e5a1461223557816325693406146122155781632e17de7814611e0d5781632fcb305814611df057816331d7a26214611dc757816332c641b514611da157816341ee13be14611d175781634c8f2a7814611ccd5781634f7ff50314611caf57816356d7356814611c855781635af01b9114611c655781635b7f415c14611c485781635c975abb14611c235781636e4f88c814611b8f5781636e76fc8f14611b655781636f49712b14611b26578163715018a614611ade5781637349209914611a0357816378d60a5b146119ca5781637cabd293146117ad578163817b1cd21461178d5781638adaac5a146114fa5781638da5cb5b146114d1578163917bb9981461123457816398bceccc146111a0578163a694fc3a14610ddf578163aa09d5b714610c6a578163b1e95e9414610c3e578163beb8314c14610c0d578163c513e19e14610be3578163c82c6ed614610a45578163c93c8f3414610a06578163de0deede146109e1578163e63ab1e9146109b7578163e95abd521461086d578163ec1371f21461084d578163f043a5871461074157508063f0b33435146103d0578063f2fde38b14610320578063f3466dfa146102eb578063f6ed2017146102af5763fd5e6dd10361001157346102ab5760203660031901126102ab573591600d548310156102a85750610292602092613164565b905491519160018060a01b039160031b1c168152f35b80fd5b8280fd5b5050346102e75760203660031901126102e75760209181906001600160a01b036102d7612ec8565b1681526010845220549051908152f35b5080fd5b5050346102e757816003193601126102e75761031c90610309613082565b9051918291602083526020830190613124565b0390f35b50346102ab5760203660031901126102ab5761033a612ec8565b90610343613c45565b6001600160a01b0391821692831561037e57505082546001600160a01b031981168317845516600080516020613f1d8339815191528380a380f35b906020608492519162461bcd60e51b8352820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152fd5b508290346102e757602092836003193601126102ab5781356001600160401b0380821161073d573660238301121561073d578184013561040f8161302d565b9261041c8551948561300a565b81845236602483830101116107395781879260248a930183870137840101526003546001600160a01b0390811680151590819087908a90836106b8575b84156106aa575b8415610621575b50505050610475915061328b565b8151156105e257610484613082565b9382519182116105cf575061049a600654613048565b601f811161058b575b5085601f821160011461051557918186979261050494600080516020613e9d833981519152989161050a575b508160011b916000199060031b1c1916176006555b6104f78451958587968752860190613124565b9184830390850152613124565b0390a180f35b9050820151896104cf565b6006865286862090601f198316875b818110610574575092610504949260019282600080516020613e9d8339815191529a9b961061055b575b5050811b016006556104e4565b84015160001960f88460031b161c19169055898061054e565b919289600181928689015181550194019201610524565b60068652868620601f830160051c8101918884106105c5575b601f0160051c01905b8181106105ba57506104a3565b8681556001016105ad565b90915081906105a4565b634e487b7160e01b865260419052602485fd5b825162461bcd60e51b81528085018790526019602482015278506f6f6c206e616d652063616e6e6f7420626520656d70747960381b6044820152606490fd5b92935090918361063d575b505050610475915088868982610467565b8751638da5cb5b60e01b815293509091839182905afa9081156106a0579061047592918891610673575b5016331485888a61062c565b6106939150893d8b11610699575b61068b818361300a565b810190613197565b89610667565b503d610681565b85513d89823e3d90fd5b9350848a5416331493610460565b935050508551632474521560e21b8152600080516020613f5d833981519152888201523360248201528981604481865afa90811561072f5788918b918b91610702575b5093610459565b6107229150823d8411610728575b61071a818361300a565b81019061317f565b8c6106fb565b503d610710565b87513d8b823e3d90fd5b8680fd5b8480fd5b915050346102ab576020806003193601126108495790916001600160a01b03610768612ec8565b168452600b82528084209182549161077f83613233565b9261078c8251948561300a565b8084528284018095885283882088915b83831061081c575050505080519482860193838752518094528186019496915b8483106107c95786860387f35b87518051875280850151878601528082015187830152606080820151908801526080808201519088015260a0808201519088015260c09081015115159087015296830196948101946001909201916107bc565b600786600192610833859d999a9b9d9c989c6135ac565b815201920192019190989695949897939761079c565b8380fd5b505050346102e757816003193601126102e7576020906011549051908152f35b505091346102a857602092836003193601126102e757803590600d5482101561097d575061089a90613164565b60018060a01b0391549060031b1c16808252600b84528282209182546108bf81613233565b936108cc8651958661300a565b81855282528582208290878087015b84841061095e57505050505080948180965b85518810156109335761090c90610904898861324a565b515190613406565b9660c0610919828861324a565b51015161092a575b600101966108ed565b60019350610921565b86908160a0975193878152601086522054938251968752860152840152606083015215156080820152f35b60019160079161096d856135ac565b81520192019201919088906108db565b835162461bcd60e51b81529081018590526014602482015273092dcecc2d8d2c840e6e8c2d6cae440d2dcc8caf60631b6044820152606490fd5b505050346102e757816003193601126102e75760209051600080516020613efd8339815191528152f35b84346102a85760203660031901126102a857610a036109fe612ec8565b613a8a565b80f35b505050346102e75760203660031901126102e75760209160ff9082906001600160a01b03610a32612ec8565b1681526009855220541690519015158152f35b5050346102ab57602080600319360112610849576003548235926001600160a01b03918216801515928392909183610b77575b8415610b69575b8415610ad0575b8887600080516020613f7d833981519152888b610aa28a61328b565b610aaf600a5485106132e9565b6002610aba85612f54565b5001600160ff198254161790555160018152a280f35b929350909183610af9575b50610aa29150600080516020613f7d83398151915290503880610a86565b8651638da5cb5b60e01b81529293508491839182905afa908115610b5f5791610aa291600080516020613f7d8339815191529596938891610b42575b5016331491949338610adb565b610b599150853d87116106995761068b818361300a565b38610b35565b85513d88823e3d90fd5b935081885416331493610a7f565b8751632474521560e21b8152600080516020613f5d833981519152838201523360248201529094508581604481885afa908115610bd9578991610bbc575b5093610a78565b610bd39150863d88116107285761071a818361300a565b38610bb5565b88513d8b823e3d90fd5b505050346102e757816003193601126102e75760025490516001600160a01b039091168152602090f35b505050346102e757806003193601126102e757602090610c37610c2e612ec8565b60243590613952565b9051908152f35b8483346102e75760203660031901126102e757610a0390610c6460ff6012541615613331565b356137dd565b5050346102ab57826003193601126102ab576003546001600160a01b039081168015159283929183610d70575b8415610d62575b8415610ce3575b86610caf866131b6565b600160ff1960125416176012557f51bd770c2b635efdffcc64ac0fd7ab9e459e21f6823fb6140afea01a883454478180a180f35b929350909183610cff575b505050610caf915038808080610ca5565b8451638da5cb5b60e01b8152929350602091839182905afa908115610d5557610caf93508491610d36575b50163314388080610cee565b610d4f915060203d6020116106995761068b818361300a565b38610d2a565b50505051903d90823e3d90fd5b935081865416331493610c9e565b8551632474521560e21b8152600080516020613efd83398151915283820152336024820152909450602081604481885afa908115610dd5578791610db6575b5093610c97565b610dcf915060203d6020116107285761071a818361300a565b38610daf565b86513d89823e3d90fd5b5050346102ab57602092836003193601126102a857813591610dff613bef565b60ff91610e1160ff6012541615613331565b610e1e600a5485106132e9565b60ff6002610e2b86612f54565b500154161561116457600754958254871015611133573382526009815260ff86832054166110e5578194825b338452600b83528784208054821015610e9c576006610e77838993612fa2565b50015416610e88575b600101610e57565b95610e9460019161320e565b969050610e80565b5050908591878960018095101561109457610eb69061320e565b600755338552600c8352808520805460ff19908116861790915560098452818620805490911685179055600d54600160401b81101561104257610f018186610f089301600d55613164565b33916137a0565b600d54600019929083810190811161105557338752600e855282872055610f2e81612f54565b505461106857610f85610f43603c5b42613406565b338852600b865283882060055491855192610f5d84612fef565b83528488840152428684015260608301524260808301528860a08301528760c0830152613714565b60055492610f9584600854613406565b600855338752600b8552828720549081019081116110555782519184835285830152828201527fb4caaf29adda3eefee3ad552a8e85058589bf834c7466cae4ee58787f70589ed60603392a260025481516323b872dd60e01b948101949094523360248501523060448501526064808501939093529183526001600160a01b039091169060a08301908382106001600160401b038311176110425761103d9495965052613ce8565b805580f35b634e487b7160e01b865260418752602486fd5b634e487b7160e01b875260118852602487fd5b61107181612f54565b5054620151809081810291818304149015171561105557610f43610f8591610f3d565b815162461bcd60e51b8152808801859052602560248201527f4d61782061637469766520706f736974696f6e732070657220757365722072656044820152641858da195960da1b6064820152608490fd5b8260849187519162461bcd60e51b83528201526024808201527f557365722068617320616c7265616479207374616b656420696e2074686973206044820152631c1bdbdb60e21b6064820152fd5b8260649187519162461bcd60e51b8352820152600c60248201526b141bdbdb081a5cc8199d5b1b60a21b6044820152fd5b845162461bcd60e51b815280830187905260166024820152754c6f636b20706572696f64206e6f742061637469766560501b6044820152606490fd5b5050346102ab57816003193601126102ab576111ba612ec8565b6001600160a01b03168352600b602052818320805460243594908510156102a8575060e0936111e891612fa2565b5080549260018201549260028301546003840154918401549260ff600660058701549601541695815197885260208801528601526060850152608084015260a0830152151560c0820152f35b838584346102e757826003193601126102e75761124f612ec8565b60035460243592916001600160a01b03918291908216801515908190869082611458575b821561144a575b82156113ce575b505061128d9150613413565b611295613bef565b16918215611393576112a884151561352e565b85516370a0823160e01b81523082820152602091908281602481885afa908115611389579086918891611358575b501061131a57507f2f8340289d8ecee041d6e69b9cc66ffd45085d2f8a3346525306ae17c735d4f18391611313969751868152a284541690613c9d565b6001805580f35b60649187519162461bcd60e51b8352820152601a602482015279496e73756666696369656e7420746f6b656e2062616c616e636560301b6044820152fd5b809250848092503d8311611382575b611371818361300a565b8101031261073957859051896112d6565b503d611367565b88513d89823e3d90fd5b606490602087519162461bcd60e51b83528201526015602482015274496e76616c696420746f6b656e206164647265737360581b6044820152fd5b909150826113e4575b505061128d90858a611281565b8951638da5cb5b60e01b81529394506020925083919082905afa908115611440578380939261128d928991611421575b50163314905084896113d7565b61143a915060203d6020116106995761068b818361300a565b8a611414565b87513d88823e3d90fd5b91508489541633149161127a565b9293945050508751632474521560e21b8152600080516020613f9d83398151915286820152336024820152602081604481855afa9081156114c757859493929187918a916114a8575b5091611273565b6114c1915060203d6020116107285761071a818361300a565b8b6114a1565b89513d8a823e3d90fd5b505050346102e757816003193601126102e757905490516001600160a01b039091168152602090f35b838584346102e75760603660031901126102e75780356024938435926044359586151590818803610739576003546001600160a01b0390811680151590819086908261171c575b831561170e575b8315611696575b50505061155c915061328b565b610e428511611660576103e8861161162d57835161157981612fbe565b8581526020810191878352858201938452600a54600160401b81101561161b578060016115a99201600a55612f54565b95909561160b5750509260026105049593611601937fb2a0d0bd5b71b97f620cc66cd8cc09589756f020f4eede7ff0613d87df7b90b09a9b9651845551600184015551151591019060ff801983541691151516179055565b5193849384612f89565b634e487b7160e01b8a5289905288fd5b50634e487b7160e01b89526041855288fd5b600e91506020606494519362461bcd60e51b85528401528201526d084dedceae640e8dede40d0d2ced60931b6044820152fd5b601191506020606494519362461bcd60e51b8552840152820152704475726174696f6e20746f6f206c6f6e6760781b6044820152fd5b91925090826116af575b505061155c91508a858161154f565b8751638da5cb5b60e01b81529250602091839182905afa908115611704579061155c92918a916116e5575b50163314848b6116a0565b6116fe915060203d6020116106995761068b818361300a565b8b6116da565b86513d8b823e3d90fd5b9250838b5416331492611548565b9250508651632474521560e21b8152600080516020613f5d833981519152878201523385820152602081604481865afa908115611783579087918c91611764575b5092611541565b61177d915060203d6020116107285761071a818361300a565b8d61175d565b88513d8d823e3d90fd5b505050346102e757816003193601126102e7576020906008549051908152f35b828585346102e757602090816003193601126102ab5783359060018060a01b038381600354168781151592839283611959575b841561194b575b84156118d2575b505050506117fc915061328b565b811561189957620f4240821161185f57633b9aca008083029283040361184c577fe9f7bd8152fa5518ff66612ae9fc1d1cbd692666f7c0a0d49d02f261d03c8fb19394508160055551908152a180f35b634e487b7160e01b845260118552602484fd5b5162461bcd60e51b815280850183905260156024820152740a6e8c2d6ca40c2dadeeadce840e8dede40d0d2ced605b1b6044820152606490fd5b5162461bcd60e51b81528085018390526014602482015273125b9d985b1a59081cdd185ad948185b5bdd5b9d60621b6044820152606490fd5b9293509091836118ee575b5050506117fc9150878786826117ee565b8551638da5cb5b60e01b815293509091839182905afa90811561194157906117fc92918791611924575b501633148685896118dd565b61193b9150863d88116106995761068b818361300a565b88611918565b83513d88823e3d90fd5b9350848954163314936117e7565b935050508351632474521560e21b8152600080516020613f5d833981519152898201523360248201528681604481865afa9081156119c057899188918a916119a3575b50936117e0565b6119ba9150823d84116107285761071a818361300a565b8b61199c565b85513d8a823e3d90fd5b505050346102e75760203660031901126102e75760209181906001600160a01b036119f3612ec8565b168152600e845220549051908152f35b505090346102ab57826003193601126102ab57611a1e613bef565b611a2d60ff6012541615613331565b338352601060205280832054918215611aa7575033835260106020528281812055611a5a826011546133f9565b601155611a768380808086335af1611a706134bc565b506134ec565b519081527f8a43c4352486ec339f487f64af78ca5cbf06cd47833f073d3baf3a193e50316160203392a26001805580f35b6020606492519162461bcd60e51b835282015260126024820152714e6f2070656e64696e67207265776172647360701b6044820152fd5b84346102a857806003193601126102a857611af7613c45565b80546001600160a01b03198116825581906001600160a01b0316600080516020613f1d8339815191528280a380f35b505050346102e75760203660031901126102e75760209160ff9082906001600160a01b03611b52612ec8565b168152600c855220541690519015158152f35b505050346102e757816003193601126102e75760209051600080516020613f9d8339815191528152f35b8385346102a857806003193601126102a8578151600d805480835290835260208083019492937fd7b6990105719101dabeb77144f2a3385c8033acd3af97e9423a695e81ad1eb592915b828210611c035761031c8686611bf1828b038361300a565b51918291602083526020830190612ee3565b83546001600160a01b031687529586019560019384019390910190611bd9565b505050346102e757816003193601126102e75760209060ff6012541690519015158152f35b505050346102e757816003193601126102e7576020905160098152f35b505050346102e757816003193601126102e7576020906005549051908152f35b505050346102e757816003193601126102e75760209051600080516020613f5d8339815191528152f35b5050346102ab57826003193601126102ab5760209250549051908152f35b838584346102e75760203660031901126102e7573590600a548210156102a85750611cf790612f54565b509061031c82549160ff6002600186015495015416905193849384612f89565b8385346102a857806003193601126102a857600a54611d3581613233565b91611d428451938461300a565b818352600a815260207fc65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a88185015b848410611d845786518061031c8882612e72565b600383600192611d938561325e565b815201920193019290611d70565b505050346102e757816003193601126102e757602090633b9aca00600554049051908152f35b505050346102e75760203660031901126102e75760209181906001600160a01b036102d7612ec8565b505050346102e757816003193601126102e7576020905160018152f35b828585346102e757602090816003193601126102ab578335611e2d613bef565b60ff9260ff6012541615611e4081613331565b338652600b808352611e56858820548510613372565b338752600b8352611e6984868920612fa2565b50916006966006840191611e8060ff8454166133b7565b600385015442106121da57611e9490613331565b611e9d866137dd565b835497896005860181815497555560ff1992838154169055611ec1896008546133f9565b60085584151593846121c6575b338b52600b8752888b208054908c948d5b83811061219557508286106120b5575b505050505015611f9c575b5085969750611f23600080516020613f3d833981519152963360018060a01b0360025416613c9d565b611f3b575b5082519485528401523392a26001805580f35b33875260108252838720611f50828254613406565b9055611f5e81601154613406565b6011558351818152600080516020613ebd833981519152833392a283519081528282820152600080516020613e7d833981519152843392a286611f28565b338852600c84528588208181541690556009845285882090815416905560075480156120a257600019908101600755338852600e845285882054600d5480830190811161208f57808203612050575b5050600d54801561203d57600080516020613f3d833981519152979899500161201381613164565b81549060018060a01b039060031b1b19169055600d55338852600e83528785812055879695611efa565b634e487b7160e01b895260318a52602489fd5b61205990613164565b905460039190911b1c6001600160a01b031661207e8161207884613164565b906137a0565b8952600e8552868920558980611feb565b634e487b7160e01b8a5260118b5260248afd5b634e487b7160e01b885260118952602488fd5b908d9e98959d916120cd8f9e99969e9c98959c613601565b9d838f5b8682106121385750505050505050338c52600b87526120f1898d20613697565b8b5b8a51811015612124578061211e8c8f6001948e612117928f8f33835252209261324a565b5190613714565b016120f3565b509296509297509298978a80808080611eef565b83836121448488612fa2565b50015416612157575b506001018f6120d1565b85612186839761218c936121766121706001978b612fa2565b506135ac565b612180838361324a565b5261324a565b5061320e565b9490508f61214d565b81866121a18386612fa2565b500154166121b2575b600101611edf565b956121be60019161320e565b9690506121aa565b6121d286600f546133f9565b600f55611ece565b875162461bcd60e51b8152808c018790526015602482015274131bd8dac81c195c9a5bd9081b9bdd08195b991959605a1b6044820152606490fd5b505050346102e757816003193601126102e7576020906007549051908152f35b84346102a85760203660031901126102a85761224f612ec8565b612257613c45565b600380546001600160a01b0319166001600160a01b039290921691909117905580f35b8385346102a857806003193601126102a85790600d5461229981613233565b926122a68351948561300a565b8184526122b282613233565b916020601f1980940136828801376122c98261357a565b946122d38361357a565b6122dc8461357a565b966122e685613233565b946122f38451968761300a565b8086526122ff81613233565b8686019801368937865b8181106123975750509661234f8361235d9361234260a09b6123358998519e8f9e8f8181520190612ee3565b8d8103898f015290612f20565b918b8303908c0152612f20565b9088820360608a0152612f20565b92868403608088015251928381520193925b82811061237e57505050500390f35b835115158552869550938101939281019260010161236f565b96926123a9889b9a969392999b613164565b60018060a01b0391549060031b1c1691828552600b8452858520998a546123cf81613233565b9b6123dc89519d8e61300a565b818d52875285872087878e015b83821061249e5750505050859c869d87905b8d5182101561243d579060c06124228f949361241c8491610904838961324a565b9561324a565b510151612434575b60010190916123fb565b60019f5061242a565b849f9396929e979c94600196999c9e92508561245e9185612180838d61324a565b525161246a848961324a565b52895260108a528689205461247f838a61324a565b5261248a828c61324a565b901515905201999199989795969498612309565b9e9f9e6007896001926124b0866135ac565b8152019301910190919f9e9f6123e9565b505090346102ab57806003193601126102ab576001600160a01b036024358181169380359392909185900361273d57818160035416801515918291826126d5575b83156126c7575b831561264f575b50505061251d9150613413565b612525613bef565b612530841515613474565b61253b83151561352e565b82471061260d57600f54906011548083116000146126065761255d90836133f9565b84116125b75750600080516020613edd83398151915292602092909182116125ae5761258b82600f546133f9565b600f555b6125a286808080868a5af1611a706134bc565b51908152a26001805580f35b85600f5561258f565b608490602084519162461bcd60e51b83528201526024808201527f43616e6e6f742077697468647261772070656e64696e672075736572207265776044820152636172647360e01b6064820152fd5b508561255d565b6020606492519162461bcd60e51b8352820152601d60248201527f496e73756666696369656e7420636f6e74726163742062616c616e63650000006044820152fd5b9192509082612668575b505061251d9150388381612510565b8551638da5cb5b60e01b81529250602091839182905afa9081156126bd579061251d9291889161269e575b501633148238612659565b6126b7915060203d6020116106995761068b818361300a565b38612693565b84513d89823e3d90fd5b925083895416331492612509565b9250508451632474521560e21b8152600080516020613f9d83398151915285820152336024820152602081604481865afa908115611704579085918a9161271e575b5092612502565b612737915060203d6020116107285761071a818361300a565b38612717565b8580fd5b505050346102e757816003193601126102e75760035490516001600160a01b039091168152602090f35b5050346102ab576020908160031936011261084957612788612ec8565b6003546001600160a01b039190839083168015159081908790826128bb575b83156128ad575b831561283f575b5050506127c29150613413565b6127ca613bef565b16926127d7841515613474565b47918215612809575090600080516020613edd833981519152929185600f556125a286808080868a5af1611a706134bc565b83606492519162461bcd60e51b835282015260126024820152714e6f2045544820746f20776974686472617760701b6044820152fd5b9192509082612859575b50506127c29150849186386127b5565b909150875192838092638da5cb5b60e01b82525afa908115610dd5576127c29184918991612890575b501633148491508538612849565b6128a79150873d89116106995761068b818361300a565b38612882565b9250858a54163314926127ae565b8951632474521560e21b8152600080516020613f9d833981519152898201523360248201529450925087905083604481855afa928315610bd957869388918b91612907575b50926127a7565b61291e9150823d84116107285761071a818361300a565b38612900565b505050346102e757816003193601126102e757602090600f549051908152f35b838584346102e757602092836003193601126102ab578135612964613bef565b600560ff601254161561297681613331565b338652600b875261298b848720548410613372565b338652600b87526129b86129a184868920612fa2565b50916129b360ff6006850154166133b7565b613331565b6129c1836137dd565b01948554938415612a40575084600080516020613e7d833981519152949596556129ed85600f546133f9565b600f5533865260108152828620612a05868254613406565b9055612a1385601154613406565b6011558251858152600080516020613ebd833981519152823392a282519485528401523392a26001805580f35b60649184519162461bcd60e51b835282015260136024820152724e6f207265776172647320746f20636c61696d60681b6044820152fd5b5050346102ab57602080600319360112610849576003548235926001600160a01b03918216801515928392909183612b9b575b8415612b8d575b8415612afe575b8887600080516020613f7d833981519152888b612ad48a61328b565b612ae1600a5485106132e9565b6002612aec85612f54565b5001805460ff1916905551848152a280f35b929350909183612b27575b50612ad49150600080516020613f7d83398151915290503880612ab8565b8651638da5cb5b60e01b81529293508491839182905afa908115610b5f5791612ad491600080516020613f7d8339815191529596938891612b70575b5016331491949338612b09565b612b879150853d87116106995761068b818361300a565b38612b63565b935081885416331493612ab1565b8751632474521560e21b8152600080516020613f5d833981519152838201523360248201529094508581604481885afa908115610bd9578991612be0575b5093612aaa565b612bf79150863d88116107285761071a818361300a565b38612bd9565b505050346102e757816003193601126102e757819082600a54935b848110612ce25750612c2983613233565b92612c368351948561300a565b808452612c45601f1991613233565b01815b818110612cbb575050805b848110612c675782518061031c8682612e72565b60ff6002612c7483612f54565b50015416612c85575b600101612c53565b90612cb3600191612c9e612c9885612f54565b5061325e565b612ca8828861324a565b52612186818761324a565b919050612c7d565b6020908451612cc981612fbe565b8481528285818301528587830152828801015201612c48565b60ff6002612cef83612f54565b50015416612d00575b600101612c18565b92612d0c60019161320e565b939050612cf8565b915050346108495783600319360112610849576003546001600160a01b0390811680151593849390919084612e0e575b508415612e00575b8415612d8e575b86612d5d866131b6565b60ff19601254166012557f6e5f3ea47490e2bf72ff8cfc4afda3a330f81a79e55d1ad11f4650934c5540848180a180f35b929350909183612daa575b505050612d5d915038808080612d53565b8451638da5cb5b60e01b8152929350602091839182905afa908115610d5557612d5d93508491612de1575b50163314388080612d99565b612dfa915060203d6020116106995761068b818361300a565b38612dd5565b935081865416331493612d4c565b632474521560e21b8152600080516020613efd83398151915283820152336024820152909450602081604481885afa908115610dd5578791612e53575b509338612d44565b612e6c915060203d6020116107285761071a818361300a565b38612e4b565b60208082019080835283518092528060408094019401926000905b838210612e9c57505050505090565b845180518752808401518785015281015115158682015260609095019493820193600190910190612e8d565b600435906001600160a01b0382168203612ede57565b600080fd5b90815180825260208080930193019160005b828110612f03575050505090565b83516001600160a01b031685529381019392810192600101612ef5565b90815180825260208080930193019160005b828110612f40575050505090565b835185529381019392810192600101612f32565b600a54811015612f7357600a6000526003602060002091020190600090565b634e487b7160e01b600052603260045260246000fd5b9081526020810191909152901515604082015260600190565b8054821015612f73576000526007602060002091020190600090565b606081019081106001600160401b03821117612fd957604052565b634e487b7160e01b600052604160045260246000fd5b60e081019081106001600160401b03821117612fd957604052565b601f909101601f19168101906001600160401b03821190821017612fd957604052565b6001600160401b038111612fd957601f01601f191660200190565b90600182811c92168015613078575b602083101461306257565b634e487b7160e01b600052602260045260246000fd5b91607f1691613057565b604051906000826006549161309683613048565b80835260209360019081811690811561310457506001146130c2575b50506130c09250038361300a565b565b90939150600660005281600020936000915b8183106130ec5750506130c0935082010138806130b2565b855488840185015294850194879450918301916130d4565b9150506130c094925060ff191682840152151560051b82010138806130b2565b919082519283825260005b848110613150575050826000602080949584010152601f8019910116010190565b60208183018101518483018201520161312f565b600d54811015612f7357600d60005260206000200190600090565b90816020910312612ede57518015158103612ede5790565b90816020910312612ede57516001600160a01b0381168103612ede5790565b156131bd57565b60405162461bcd60e51b815260206004820152602360248201527f4163636573732064656e6965643a2050617573657220726f6c652072657175696044820152621c995960ea1b6064820152608490fd5b600019811461321d5760010190565b634e487b7160e01b600052601160045260246000fd5b6001600160401b038111612fd95760051b60200190565b8051821015612f735760209160051b010190565b9060405161326b81612fbe565b604060ff6002839580548552600181015460208601520154161515910152565b1561329257565b60405162461bcd60e51b815260206004820152602960248201527f4163636573732064656e6965643a20506f6f6c204d616e6167657220726f6c65604482015268081c995c5d5a5c995960ba1b6064820152608490fd5b156132f057565b60405162461bcd60e51b8152602060048201526019602482015278092dcecc2d8d2c840d8dec6d640e0cae4d2dec840d2dcc8caf603b1b6044820152606490fd5b1561333857565b60405162461bcd60e51b815260206004820152601260248201527110dbdb9d1c9858dd081a5cc81c185d5cd95960721b6044820152606490fd5b1561337957565b60405162461bcd60e51b8152602060048201526016602482015275092dcecc2d8d2c840e0dee6d2e8d2dedc40d2dcc8caf60531b6044820152606490fd5b156133be57565b60405162461bcd60e51b8152602060048201526013602482015272506f736974696f6e206e6f742061637469766560681b6044820152606490fd5b9190820391821161321d57565b9190820180921161321d57565b1561341a57565b60405162461bcd60e51b815260206004820152602c60248201527f4163636573732064656e6965643a20456d657267656e63792041646d696e207260448201526b1bdb19481c995c5d5a5c995960a21b6064820152608490fd5b1561347b57565b60405162461bcd60e51b8152602060048201526019602482015278496e76616c696420726563697069656e74206164647265737360381b6044820152606490fd5b3d156134e7573d906134cd8261302d565b916134db604051938461300a565b82523d6000602084013e565b606090565b156134f357565b60405162461bcd60e51b8152602060048201526013602482015272115512081d1c985b9cd9995c8819985a5b1959606a1b6044820152606490fd5b1561353557565b60405162461bcd60e51b815260206004820152601d60248201527f416d6f756e74206d7573742062652067726561746572207468616e20300000006044820152606490fd5b9061358482613233565b613591604051918261300a565b82815280926135a2601f1991613233565b0190602036910137565b906040516135b981612fef565b60c060ff600683958054855260018101546020860152600281015460408601526003810154606086015260048101546080860152600581015460a08601520154161515910152565b9061360b82613233565b60409061361b604051918261300a565b838152809361362c601f1991613233565b019160005b83811061363e5750505050565b602090825161364c81612fef565b60008152826000818301526000858301526000606083015260006080830152600060a0830152600060c0830152828601015201613631565b8181029291811591840414171561321d57565b80546000808355816136a857505050565b6007928260070292600784040361370057815260208120918201915b8281106136d15750505050565b8082859255826001820155826002820155826003820155826004820155826005820155826006820155016136c4565b634e487b7160e01b82526011600452602482fd5b8054600160401b811015612fd95761373191600182018155612fa2565b61378a57600660c0836130c0945184556020810151600185015560408101516002850155606081015160038501556080810151600485015560a081015160058501550151151591019060ff801983541691151516179055565b634e487b7160e01b600052600060045260246000fd5b919060018060a01b038084549260031b9316831b921b1916179055565b81156137c7570490565b634e487b7160e01b600052601260045260246000fd5b61380b600091338352600b6020526137fa60408420548210613372565b338352600b60205260408320612fa2565b5060ff6006820154161561390b5760048101916138298354426133f9565b90811561390557600f541580156138fb575b6138f357613868600161385081860154612f54565b500154606461386186549283613684565b0490613406565b91600f5492670de0b6b3a76400008085029085820414851517156138df576138b96138d095936138b4600596946138ae6a1a1601fc4ea7109e00000095600854906137bd565b90613684565b613684565b049050818111156138d75750925b01918254613406565b9055429055565b9050926138c7565b634e487b7160e01b84526011600452602484fd5b505050429055565b506008541561383b565b50505050565b5050565b1561391657565b60405162461bcd60e51b8152602060048201526014602482015273496e76616c69642075736572206164647265737360601b6044820152606490fd5b6001600160a01b0316919061399a906121709061397085151561390f565b600094808652600b60205261398a60408720548310613372565b8552600b60205260408520612fa2565b60c081015115613a87576139b26080820151426133f9565b80158015613a7d575b8015613a73575b613a6a576139ef60646139e760016139dd6020870151612f54565b5001548551613684565b048351613406565b90600f5491670de0b6b3a7640000808402908482041484151715613a5657613a3660a094936138b4613a4b9899946138ae6a1a1601fc4ea7109e00000095600854906137bd565b04905081811115613a4e5750915b0151613406565b90565b905091613a44565b634e487b7160e01b87526011600452602487fd5b5060a001519150565b50600854156139c2565b50600f54156139bb565b50565b6001600160a01b031690613a9f82151561390f565b6000828152600b90602091600b6020526040926040832095865484855b828110613b7a5750818114613b6f57613ad490613601565b978590865b838110613b2c5750505050808452600b602052613af860408520613697565b835b8751811015613b2257600190828652848452613b1c878720612117838c61324a565b01613afa565b5050505050509050565b8a60ff6006613b3b8486612fa2565b50015416613b4d575b50600101613ad9565b836121868395613b669361217661217060019789612fa2565b9290508a613b44565b505050505050509050565b60ff6006613b88838d612fa2565b50015416613b99575b600101613abc565b90613ba560019161320e565b919050613b91565b34613bb457565b613bc034600f54613406565b600f557ff8fad42e780bfa5459be3fe691e8ba1aec70342250112139c5771c3fd155f3126020604051348152a1565b600260015414613c00576002600155565b60405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606490fd5b6000546001600160a01b03163303613c5957565b606460405162461bcd60e51b815260206004820152602060248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152fd5b60405163a9059cbb60e01b60208201526001600160a01b03909216602483015260448083019390935291815260808101916001600160401b03831182841017612fd9576130c0926040525b60408051908101916001600160a01b03166001600160401b03831182841017612fd957613d57926040526000806020958685527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c656487860152868151910182855af1613d516134bc565b91613ddf565b805190828215928315613dc7575b50505015613d705750565b6084906040519062461bcd60e51b82526004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152fd5b613dd7935082018101910161317f565b388281613d65565b91929015613e415750815115613df3575090565b3b15613dfc5790565b60405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606490fd5b825190915015613e545750805190602001fd5b60405162461bcd60e51b815260206004820152908190613e78906024830190613124565b0390fdfedacbdde355ba930696a362ea6738feb9f8bd52dfb3d81947558fd3217e23e325677be211a4b773a206d325890d5503c1975d50c59eedeab80cf87e73564eaae2d15aaf8bd108cbef0ed609f6e2d426c0ec55c04a2827337733cdef957ba25f25950c94b02ba024a521c6b640039b594ccc5b07e716fa5a927a7714ff8cac8d6b65d7a28e3265b37a6474929f336521b332c1681b933f6cb9f3376673440d862a8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e07fc4727e062e336010f2c282598ef5f14facb3de68cf8195c2f23e1454b2b74e6077685936c8169d09204a1d97db12e41713588c38e1d29a61867d3dcee98affc3043dcee3d974176e2e3d35b1379aa6fa2219384e8d60e91b9a2d68da954acd5358bcfd81d1ef3da152b1755e1c3c6739686fa7e83dbcad0071568cc4b73a63a264697066735822122090c6b13fa834ea54589a2cf5bfc8a8d7cacea8c83162ace66b963aa9e24d265a64736f6c63430008180033

Verified Source Code Full Match

Compiler: v0.8.24+commit.e11b9ed9 EVM: paris Optimization: Yes (1 runs)
Ownable.sol 83 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
Pausable.sol 105 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract Pausable is Context {
    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    bool private _paused;

    /**
     * @dev Initializes the contract in unpaused state.
     */
    constructor() {
        _paused = false;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        require(!paused(), "Pausable: paused");
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        require(paused(), "Pausable: not paused");
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }
}
ReentrancyGuard.sol 77 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == _ENTERED;
    }
}
IERC20Permit.sol 60 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^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);
}
IERC20.sol 78 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.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);
}
SafeERC20.sol 143 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.3) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return
            success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
    }
}
Address.sol 244 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}
Context.sol 24 lines
// 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;
    }
}
AccessControl.sol 353 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/Pausable.sol";

/**
 * ██████╗  ██████╗  █████╗ ██╗    ███████╗ ██████╗ █████╗ ██╗     ██████╗ ███████╗██████╗
 * ██╔══██╗██╔═══██╗██╔══██╗██║    ██╔════╝██╔════╝██╔══██╗██║     ██╔══██╗██╔════╝██╔══██╗
 * ██████╔╝██║   ██║███████║██║    ███████╗██║     ███████║██║     ██████╔╝█████╗  ██████╔╝
 * ██╔══██╗██║   ██║██╔══██║██║    ╚════██║██║     ██╔══██║██║     ██╔═══╝ ██╔══╝  ██╔══██╗
 * ██║  ██║╚██████╔╝██║  ██║██║    ███████║╚██████╗██║  ██║███████╗██║     ███████╗██║  ██║
 * ╚═╝  ╚═╝ ╚═════╝ ╚═╝  ╚═╝╚═╝    ╚══════╝ ╚═════╝╚═╝  ╚═╝╚══════╝╚═╝     ╚══════╝╚═╝  ╚═╝
 *
 * ROAI Scalper - Advanced AI Trading & Staking Platform
 *
 * Website: https://roaiscalper.com
 * Twitter: https://x.com/roaiscalper
 * Telegram: https://t.me/roaiscalper_official
 *
 * Professional-grade staking infrastructure with multi-signature security,
 * role-based access control, and automated reward distribution.
 */

/**
 * @title AccessControl
 * @author ROAI Scalper Team
 * @notice Advanced role-based access control system for ROAI staking platform
 * @dev Features:
 *      - Granular role-based permissions
 *      - Multi-signature integration support
 *      - Emergency pause/unpause capabilities
 *      - Batch role management
 *      - Comprehensive access tracking
 *
 * Architecture:
 * - Centralized permission management for all platform contracts
 * - Role-based access with metadata and member tracking
 * - Emergency functions with appropriate role restrictions
 * - Pausable system for security incidents
 *
 * Security Features:
 * - Owner-only role management
 * - ReentrancyGuard protection
 * - Pausable emergency controls
 * - Comprehensive input validation
 * - Event logging for all role changes
 *
 * Roles:
 * - POOL_MANAGER_ROLE: Can create and manage staking pools
 * - REWARD_DISTRIBUTOR_ROLE: Can distribute rewards to pools
 * - EMERGENCY_ADMIN_ROLE: Can execute emergency functions
 * - PAUSER_ROLE: Can pause/unpause system functions
 *
 * @custom:security-contact [email protected]
 */
contract AccessControl is Ownable, ReentrancyGuard, Pausable {
    
    // Role definitions
    bytes32 public constant POOL_MANAGER_ROLE = keccak256("POOL_MANAGER_ROLE");
    bytes32 public constant REWARD_DISTRIBUTOR_ROLE = keccak256("REWARD_DISTRIBUTOR_ROLE");
    bytes32 public constant EMERGENCY_ADMIN_ROLE = keccak256("EMERGENCY_ADMIN_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    
    // Role storage
    mapping(bytes32 => mapping(address => bool)) private _roles;
    mapping(bytes32 => address[]) private _roleMembers;
    mapping(address => bytes32[]) private _userRoles;
    
    // Role metadata
    struct RoleInfo {
        string name;
        string description;
        bool active;
        uint256 memberCount;
    }
    
    mapping(bytes32 => RoleInfo) public roleInfo;
    bytes32[] public allRoles;
    
    // Events
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
    event RoleCreated(bytes32 indexed role, string name, string description);
    event RoleDeactivated(bytes32 indexed role);
    event RoleActivated(bytes32 indexed role);
    
    /**
     * @dev Constructor sets up default roles
     */
    constructor() {
        _setupDefaultRoles();
        
        // Grant all roles to deployer initially
        _grantRole(POOL_MANAGER_ROLE, msg.sender);
        _grantRole(REWARD_DISTRIBUTOR_ROLE, msg.sender);
        _grantRole(EMERGENCY_ADMIN_ROLE, msg.sender);
        _grantRole(PAUSER_ROLE, msg.sender);
    }
    
    /**
     * @dev Setup default roles with metadata
     */
    function _setupDefaultRoles() internal {
        _createRole(POOL_MANAGER_ROLE, "Pool Manager", "Can create and manage staking pools");
        _createRole(REWARD_DISTRIBUTOR_ROLE, "Reward Distributor", "Can distribute rewards to pools");
        _createRole(EMERGENCY_ADMIN_ROLE, "Emergency Admin", "Can execute emergency functions");
        _createRole(PAUSER_ROLE, "Pauser", "Can pause/unpause system functions");
    }
    
    /**
     * @dev Create a new role
     */
    function _createRole(bytes32 role, string memory name, string memory description) internal {
        require(bytes(roleInfo[role].name).length == 0, "Role already exists");
        
        roleInfo[role] = RoleInfo({
            name: name,
            description: description,
            active: true,
            memberCount: 0
        });
        
        allRoles.push(role);
        emit RoleCreated(role, name, description);
    }
    
    /**
     * @dev Grant role to account
     */
    function grantRole(bytes32 role, address account) external onlyOwner {
        require(account != address(0), "Invalid account address");
        require(roleInfo[role].active, "Role is not active");
        
        _grantRole(role, account);
    }
    
    /**
     * @dev Internal function to grant role
     */
    function _grantRole(bytes32 role, address account) internal {
        if (!_roles[role][account]) {
            _roles[role][account] = true;
            _roleMembers[role].push(account);
            _userRoles[account].push(role);
            roleInfo[role].memberCount++;
            
            emit RoleGranted(role, account, msg.sender);
        }
    }
    
    /**
     * @dev Revoke role from account
     */
    function revokeRole(bytes32 role, address account) external onlyOwner {
        require(account != address(0), "Invalid account address");
        
        _revokeRole(role, account);
    }
    
    /**
     * @dev Internal function to revoke role
     */
    function _revokeRole(bytes32 role, address account) internal {
        if (_roles[role][account]) {
            _roles[role][account] = false;
            
            // Remove from role members array
            address[] storage members = _roleMembers[role];
            for (uint256 i = 0; i < members.length; i++) {
                if (members[i] == account) {
                    members[i] = members[members.length - 1];
                    members.pop();
                    break;
                }
            }
            
            // Remove from user roles array
            bytes32[] storage userRoles = _userRoles[account];
            for (uint256 i = 0; i < userRoles.length; i++) {
                if (userRoles[i] == role) {
                    userRoles[i] = userRoles[userRoles.length - 1];
                    userRoles.pop();
                    break;
                }
            }
            
            roleInfo[role].memberCount--;
            emit RoleRevoked(role, account, msg.sender);
        }
    }
    
    /**
     * @dev Check if account has role
     */
    function hasRole(bytes32 role, address account) public view returns (bool) {
        return _roles[role][account] && roleInfo[role].active;
    }
    
    /**
     * @dev Get all members of a role
     */
    function getRoleMembers(bytes32 role) external view returns (address[] memory) {
        return _roleMembers[role];
    }
    
    /**
     * @dev Get all roles for a user
     */
    function getUserRoles(address account) external view returns (bytes32[] memory) {
        return _userRoles[account];
    }
    
    /**
     * @dev Get all available roles
     */
    function getAllRoles() external view returns (bytes32[] memory) {
        return allRoles;
    }
    
    /**
     * @dev Get role information
     */
    function getRoleInfo(bytes32 role) external view returns (RoleInfo memory) {
        return roleInfo[role];
    }
    
    /**
     * @dev Deactivate a role (prevents new grants, existing members keep access)
     */
    function deactivateRole(bytes32 role) external onlyOwner {
        require(roleInfo[role].active, "Role already inactive");
        roleInfo[role].active = false;
        emit RoleDeactivated(role);
    }
    
    /**
     * @dev Activate a role
     */
    function activateRole(bytes32 role) external onlyOwner {
        require(!roleInfo[role].active, "Role already active");
        roleInfo[role].active = true;
        emit RoleActivated(role);
    }
    
    /**
     * @dev Modifier to check if caller has specific role
     */
    modifier onlyRole(bytes32 role) {
        require(hasRole(role, msg.sender) || msg.sender == owner(), "Access denied: insufficient role");
        _;
    }
    
    /**
     * @dev Modifier to check if caller has any of the specified roles
     */
    modifier onlyRoles(bytes32[] memory roles) {
        bool hasAnyRole = false;
        for (uint256 i = 0; i < roles.length; i++) {
            if (hasRole(roles[i], msg.sender)) {
                hasAnyRole = true;
                break;
            }
        }
        require(hasAnyRole || msg.sender == owner(), "Access denied: insufficient role");
        _;
    }
    
    /**
     * @dev Emergency pause function
     */
    function emergencyPause() external onlyRole(EMERGENCY_ADMIN_ROLE) {
        _pause();
    }
    
    /**
     * @dev Emergency unpause function
     */
    function emergencyUnpause() external onlyRole(EMERGENCY_ADMIN_ROLE) {
        _unpause();
    }
    
    /**
     * @dev Pause function for pausers
     */
    function pauseSystem() external onlyRole(PAUSER_ROLE) {
        _pause();
    }
    
    /**
     * @dev Unpause function for pausers
     */
    function unpauseSystem() external onlyRole(PAUSER_ROLE) {
        _unpause();
    }
    
    /**
     * @dev Check if system is paused
     */
    function isPaused() external view returns (bool) {
        return paused();
    }
    
    /**
     * @dev Batch grant roles to multiple accounts
     */
    function batchGrantRoles(bytes32[] calldata roles, address[] calldata accounts) external onlyOwner {
        require(roles.length == accounts.length, "Arrays length mismatch");
        
        for (uint256 i = 0; i < roles.length; i++) {
            require(accounts[i] != address(0), "Invalid account address");
            require(roleInfo[roles[i]].active, "Role is not active");
            _grantRole(roles[i], accounts[i]);
        }
    }
    
    /**
     * @dev Batch revoke roles from multiple accounts
     */
    function batchRevokeRoles(bytes32[] calldata roles, address[] calldata accounts) external onlyOwner {
        require(roles.length == accounts.length, "Arrays length mismatch");
        
        for (uint256 i = 0; i < roles.length; i++) {
            require(accounts[i] != address(0), "Invalid account address");
            _revokeRole(roles[i], accounts[i]);
        }
    }
    
    /**
     * @dev Get comprehensive access summary for an account
     */
    function getAccessSummary(address account) external view returns (
        bytes32[] memory roles,
        string[] memory roleNames,
        bool isOwner,
        bool systemPaused
    ) {
        bytes32[] memory userRoles = _userRoles[account];
        string[] memory names = new string[](userRoles.length);
        
        for (uint256 i = 0; i < userRoles.length; i++) {
            names[i] = roleInfo[userRoles[i]].name;
        }
        
        return (
            userRoles,
            names,
            account == owner(),
            paused()
        );
    }
}
IERC20.sol 79 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @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 value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` 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 value) external returns (bool);
} 
IERC20Metadata.sol 26 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol"; // Adjusted import path

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
} 
ROAIStakingFactory.sol 330 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "./ROAIStakingPool.sol"; // Import the actual ROAIStakingPool contract

/**
 * ██████╗  ██████╗  █████╗ ██╗    ███████╗ ██████╗ █████╗ ██╗     ██████╗ ███████╗██████╗
 * ██╔══██╗██╔═══██╗██╔══██╗██║    ██╔════╝██╔════╝██╔══██╗██║     ██╔══██╗██╔════╝██╔══██╗
 * ██████╔╝██║   ██║███████║██║    ███████╗██║     ███████║██║     ██████╔╝█████╗  ██████╔╝
 * ██╔══██╗██║   ██║██╔══██║██║    ╚════██║██║     ██╔══██║██║     ██╔═══╝ ██╔══╝  ██╔══██╗
 * ██║  ██║╚██████╔╝██║  ██║██║    ███████║╚██████╗██║  ██║███████╗██║     ███████╗██║  ██║
 * ╚═╝  ╚═╝ ╚═════╝ ╚═╝  ╚═╝╚═╝    ╚══════╝ ╚═════╝╚═╝  ╚═╝╚══════╝╚═╝     ╚══════╝╚═╝  ╚═╝
 *
 * ROAI Scalper - Advanced AI Trading & Staking Platform
 *
 * Website: https://roaiscalper.com
 * Twitter: https://x.com/roaiscalper
 * Telegram: https://t.me/roaiscalper_official
 *
 * Professional-grade staking infrastructure with multi-signature security,
 * role-based access control, and automated reward distribution.
 */

// Forward declaration of ROAIStakingPool interface to avoid import conflicts
interface IROAIStakingPool { // This interface is still useful for the call
    function transferOwnership(address newOwner) external;
}

// The local stub 'contract ROAIStakingPool' has been removed.
// 'type(ROAIStakingPool).creationCode' will now refer to the imported contract's bytecode.

/**
 * @title ROAIStakingFactory
 * @author ROAI Scalper Team
 * @notice Factory contract for creating and managing multiple ROAI staking pools
 * @dev Features:
 *      - Deterministic pool creation with CREATE2
 *      - Multi-pool reward distribution
 *      - AccessControl integration for new pools
 *      - Centralized pool management
 *      - Secure ownership transfer capabilities
 *
 * Architecture:
 * - Creates staking pools with standardized parameters
 * - Integrates with AccessControl for role-based permissions
 * - Manages reward distribution across multiple pools
 * - Provides pool discovery and validation
 *
 * Security Features:
 * - Secure salt generation for CREATE2
 * - Input validation for all parameters
 * - Access control integration
 * - Emergency functions support
 *
 * @custom:security-contact [email protected]
 */
contract ROAIStakingFactory is Ownable, ReentrancyGuard {
    // Token contract
    IERC20 public roaiToken;

    // Array of all staking pools
    address[] public stakingPools;

    // Mapping to check if an address is a staking pool
    mapping(address => bool) public isStakingPool;

    // Reward pool address
    address public rewardPool;

    // AccessControl contract address
    address public accessControl;

    // FIXED: Add nonce for secure salt generation
    uint256 private _poolCreationNonce;

    // DoS protection storage
    mapping(address => uint256) public failedDistributions;

    // Events
    event StakingPoolCreated(address poolAddress, uint256 maxStakers, uint256 fixedStakeAmount, string name);
    event RewardsDistributed(address[] pools, uint256[] percentages, uint256 totalAmount);
    event RewardPoolUpdated(address newRewardPool);
    event AccessControlUpdated(address newAccessControl);
    event DistributionFailed(address indexed pool, uint256 amount, string reason);
    event BatchDistributionCompleted(uint256 totalDistributed, uint256 totalFailed);
    event FailedDistributionRetried(address indexed pool, uint256 amount);

    /**
     * @dev Constructor
     * @param _roaiToken Address of the ROAI token contract
     */
    constructor(address _roaiToken) {
        require(_roaiToken != address(0), "Invalid token address");
        require(_roaiToken.code.length > 0, "Token must be a contract");
        _transferOwnership(msg.sender);
        roaiToken = IERC20(_roaiToken);
    }

    /**
     * @dev Set reward pool address
     * @param _rewardPool Address of the reward pool contract
     */
    function setRewardPool(address _rewardPool) external onlyOwner {
        require(_rewardPool != address(0), "Invalid reward pool address");
        require(_rewardPool.code.length > 0, "Reward pool must be a contract");
        rewardPool = _rewardPool;
        emit RewardPoolUpdated(_rewardPool);
    }

    /**
     * @dev Set AccessControl contract address
     * @param _accessControl Address of the AccessControl contract
     */
    function setAccessControl(address _accessControl) external onlyOwner {
        require(_accessControl != address(0), "Invalid AccessControl address");
        accessControl = _accessControl;
        emit AccessControlUpdated(_accessControl);
    }

    /**
     * @dev Create a new staking pool
     * @param _maxStakers Maximum number of stakers allowed in the pool
     * @param _fixedStakeAmount Fixed amount of tokens required to stake (in whole tokens)
     * @param _name Name of the staking pool
     * @return Address of the newly created staking pool
     */
    function createStakingPool(
        uint256 _maxStakers,
        uint256 _fixedStakeAmount,
        string memory _name
    ) external onlyOwner returns (address) {
        // FIXED: Add comprehensive input validation
        require(_maxStakers > 0 && _maxStakers <= 10000, "Invalid max stakers");
        require(_fixedStakeAmount > 0, "Invalid stake amount");
        require(bytes(_name).length > 0 && bytes(_name).length <= 50, "Invalid pool name");

        // FIXED: Validate AccessControl address is set before pool creation
        require(accessControl != address(0), "AccessControl not set");

        // Deploy a new ROAIStakingPool contract using the create2 opcode
        // This avoids directly importing the ROAIStakingPool contract

        // Get the bytecode of ROAIStakingPool from the contract
        bytes memory bytecode = type(ROAIStakingPool).creationCode;

        // Encode constructor parameters (including AccessControl address)
        bytes memory constructorArgs = abi.encode(
            address(roaiToken),
            _maxStakers,
            _fixedStakeAmount,
            _name,
            msg.sender,
            accessControl  // Pass AccessControl address to new pools
        );

        // Combine bytecode and constructor arguments
        bytes memory combinedBytecode = abi.encodePacked(bytecode, constructorArgs);

        // FIXED: Create secure salt with enhanced entropy to prevent predictability attacks
        // Include multiple sources of entropy for maximum security
        bytes32 salt = keccak256(abi.encodePacked(
            msg.sender,
            _poolCreationNonce,
            _name,
            _maxStakers,
            _fixedStakeAmount,
            block.timestamp,
            block.prevrandao, // Updated from block.difficulty for post-merge compatibility
            block.gaslimit,
            blockhash(block.number - 1),
            blockhash(block.number - 2),
            tx.gasprice,
            tx.origin
        ));

        // Increment nonce for next deployment
        _poolCreationNonce++;

        // Deploy the contract using create2
        address poolAddress;
        assembly {
            poolAddress := create2(0, add(combinedBytecode, 32), mload(combinedBytecode), salt)
            if iszero(extcodesize(poolAddress)) {
                revert(0, 0)
            }
        }

        // Register the new pool
        stakingPools.push(poolAddress);
        isStakingPool[poolAddress] = true;

        // The ROAIStakingPool's ownership is set to msg.sender (the factory's owner)
        // in its constructor. This explicit transfer is not needed and causes an error
        // because the factory contract itself is not the owner of the new pool.
        // IROAIStakingPool(poolAddress).transferOwnership(msg.sender);

        emit StakingPoolCreated(poolAddress, _maxStakers, _fixedStakeAmount, _name);

        return poolAddress;
    }

    /**
     * @dev Get the number of staking pools
     * @return Number of staking pools
     */
    function getStakingPoolCount() external view returns (uint256) {
        return stakingPools.length;
    }

    /**
     * @dev Distribute rewards to multiple staking pools
     * @param _poolAddresses Array of pool addresses to distribute rewards to
     * @param _percentages Array of percentages for each pool (must sum to 100)
     * @param _totalAmount Total amount of ETH to distribute
     * This can only be called by the reward pool
     */
    function distributeRewardsToMultiplePools(
        address[] calldata _poolAddresses,
        uint256[] calldata _percentages,
        uint256 _totalAmount
    ) external nonReentrant {
        require(msg.sender == rewardPool, "Only reward pool can distribute");
        require(_poolAddresses.length == _percentages.length, "Arrays must be same length");

        uint256 totalPercentage = 0;
        for (uint256 i = 0; i < _percentages.length; i++) {
            totalPercentage += _percentages[i];
        }
        require(totalPercentage == 100, "Percentages must sum to 100");

        // FIXED: Perform additional validations BEFORE external calls (CEI pattern)
        for (uint256 i = 0; i < _poolAddresses.length; i++) {
            require(_poolAddresses[i] != address(0), "Invalid pool address");
            require(isStakingPool[_poolAddresses[i]], "Not a valid staking pool");
            // FIXED: Validate pool exists and has code
            require(_poolAddresses[i].code.length > 0, "Pool no longer alive");
        }

        // Track successful and failed distributions
        uint256 totalDistributed = 0;
        uint256 totalFailed = 0;

        for (uint256 i = 0; i < _poolAddresses.length; i++) {
            address poolAddress = _poolAddresses[i];
            uint256 amount = (_totalAmount * _percentages[i]) / 100;

            // Skip zero amounts to avoid unnecessary gas costs
            if (amount > 0) {
                // FIXED: Use try/catch pattern to prevent single pool failure from reverting entire batch
                try this.safeTransferETH(poolAddress, amount) {
                    totalDistributed += amount;
                } catch Error(string memory reason) {
                    // Store failed distribution for retry and continue with others
                    failedDistributions[poolAddress] += amount;
                    totalFailed += amount;
                    emit DistributionFailed(poolAddress, amount, reason);
                } catch {
                    // Store failed distribution for retry and continue with others
                    failedDistributions[poolAddress] += amount;
                    totalFailed += amount;
                    emit DistributionFailed(poolAddress, amount, "Unknown error");
                }
            }
        }

        emit RewardsDistributed(_poolAddresses, _percentages, _totalAmount);
        emit BatchDistributionCompleted(totalDistributed, totalFailed);
    }

    /**
     * @dev Safe ETH transfer function that can be called externally for try/catch pattern
     * @param _to Address to send ETH to
     * @param _amount Amount of ETH to send
     */
    function safeTransferETH(address _to, uint256 _amount) external {
        require(msg.sender == address(this), "Only self can call");

        // Reserve 1/64th of remaining gas for this call (EIP-150)
        uint256 gasToUse = gasleft() - (gasleft() / 64);

        (bool success, bytes memory returnData) = _to.call{value: _amount, gas: gasToUse}("");
        if (!success) {
            // Provide detailed error information
            if (returnData.length > 0) {
                assembly {
                    let returnDataSize := mload(returnData)
                    revert(add(32, returnData), returnDataSize)
                }
            } else {
                revert("ETH transfer failed");
            }
        }
    }

    /**
     * @dev Retry failed distribution to a specific pool
     * @param _pool Address of the pool to retry distribution to
     */
    function retryFailedDistribution(address _pool) external onlyOwner nonReentrant {
        require(_pool != address(0), "Invalid pool address");
        require(isStakingPool[_pool], "Not a valid staking pool");

        uint256 amount = failedDistributions[_pool];
        require(amount > 0, "No failed distribution for this pool");
        require(address(this).balance >= amount, "Insufficient contract balance");

        // Clear the failed distribution before retry
        failedDistributions[_pool] = 0;

        // Attempt to send ETH to the pool
        (bool success, ) = _pool.call{value: amount}("");
        if (!success) {
            // If it fails again, restore the failed amount
            failedDistributions[_pool] = amount;
            revert("Retry distribution failed");
        }

        emit FailedDistributionRetried(_pool, amount);
    }

    /**
     * @dev Receive ETH (for direct distributions)
     */
    receive() external payable {
        // This allows the contract to receive ETH if needed
    }
}
ROAIStakingPool.sol 883 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

/**
 * ██████╗  ██████╗  █████╗ ██╗    ███████╗ ██████╗ █████╗ ██╗     ██████╗ ███████╗██████╗
 * ██╔══██╗██╔═══██╗██╔══██╗██║    ██╔════╝██╔════╝██╔══██╗██║     ██╔══██╗██╔════╝██╔══██╗
 * ██████╔╝██║   ██║███████║██║    ███████╗██║     ███████║██║     ██████╔╝█████╗  ██████╔╝
 * ██╔══██╗██║   ██║██╔══██║██║    ╚════██║██║     ██╔══██║██║     ██╔═══╝ ██╔══╝  ██╔══██╗
 * ██║  ██║╚██████╔╝██║  ██║██║    ███████║╚██████╗██║  ██║███████╗██║     ███████╗██║  ██║
 * ╚═╝  ╚═╝ ╚═════╝ ╚═╝  ╚═╝╚═╝    ╚══════╝ ╚═════╝╚═╝  ╚═╝╚══════╝╚═╝     ╚══════╝╚═╝  ╚═╝
 *
 * ROAI Scalper - Advanced AI Trading & Staking Platform
 *
 * Website: https://roaiscalper.com
 * Twitter: https://x.com/roaiscalper
 * Telegram: https://t.me/roaiscalper_official
 *
 * Professional-grade staking infrastructure with multi-signature security,
 * role-based access control, and automated reward distribution.
 */

// Interface for AccessControl contract
interface IAccessControl {
    function hasRole(bytes32 role, address account) external view returns (bool);
    function owner() external view returns (address);
}

/**
 * @title ROAIStakingPool
 * @author ROAI Scalper Team
 * @notice Advanced staking pool contract for ROAI tokens with ETH rewards
 * @dev Features:
 *      - Fixed-amount staking with multiple lock periods
 *      - Tiered bonus system (30, 90, 180 days)
 *      - Role-based access control integration
 *      - Emergency functions with multi-signature security
 *      - Gas-efficient reward claiming
 *      - Pausable operations for security
 *
 * Security Features:
 * - AccessControl integration for decentralized governance
 * - ReentrancyGuard protection
 * - Emergency withdrawal capabilities
 * - Multi-signature transaction support
 * - Comprehensive input validation
 *
 * @custom:security-contact [email protected]
 */
contract ROAIStakingPool is Ownable, ReentrancyGuard {
    // FIXED: Use SafeERC20 for secure token transfers
    using SafeERC20 for IERC20;

    // Token contract
    IERC20 public roaiToken;
    uint256 public constant TOKEN_DECIMALS = 9; // Define token decimals

    // AccessControl integration
    IAccessControl public accessControl;

    // Role definitions (must match AccessControl contract)
    bytes32 public constant POOL_MANAGER_ROLE = keccak256("POOL_MANAGER_ROLE");
    bytes32 public constant EMERGENCY_ADMIN_ROLE = keccak256("EMERGENCY_ADMIN_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

    // Pool configuration
    uint256 public maxStakers;
    uint256 public fixedStakeAmount; // Stores fixed stake amount in ATOMIC UNITS (e.g., 25000 * 10**9)
    string public poolName;
    uint256 public currentStakerCount;
    uint256 public totalStaked; // Stores total staked in ATOMIC UNITS

    // FIXED: Add position limit per user to prevent unbounded growth
    uint256 public constant MAX_POSITIONS_PER_USER = 1;

    // Mapping to track if user has already staked (for single stake per user enforcement)
    mapping(address => bool) public hasStaked;

    // Lock period configuration
    struct LockPeriod {
        uint256 durationDays;
        uint256 bonusPercentage;
        bool active; // Added as per improvement plan
    }

    LockPeriod[] public lockPeriods;

    // Staking position
    struct StakingPosition {
        uint256 amount; // Stores amount in ATOMIC UNITS
        uint256 lockPeriodIndex;
        uint256 startTime;
        uint256 endTime;
        uint256 lastRewardCalculation;
        uint256 accumulatedRewards; // Stores ETH rewards in wei
        bool active;
    }

    // Mapping from user address to their staking positions
    mapping(address => StakingPosition[]) public stakingPositions;

    // Mapping to track if an address is currently staking
    mapping(address => bool) public isStaking;

    // Array to track all stakers for enumeration
    address[] public stakers;
    mapping(address => uint256) public stakerIndex; // Maps staker address to index in stakers array

    // Total rewards in the pool (ETH in wei)
    uint256 public totalRewards;

    // FIXED: Pull-based reward pattern to prevent DoS/theft vectors
    mapping(address => uint256) public pendingRewards;
    uint256 public totalPendingRewards;

    // Pool status
    bool public paused; // Added as per improvement plan

    // Access control modifiers
    modifier onlyPoolManager() {
        require(
            (address(accessControl) != address(0) && accessControl.hasRole(POOL_MANAGER_ROLE, msg.sender)) ||
            msg.sender == owner() ||
            (address(accessControl) != address(0) && msg.sender == accessControl.owner()),
            "Access denied: Pool Manager role required"
        );
        _;
    }

    modifier onlyEmergencyAdmin() {
        require(
            (address(accessControl) != address(0) && accessControl.hasRole(EMERGENCY_ADMIN_ROLE, msg.sender)) ||
            msg.sender == owner() ||
            (address(accessControl) != address(0) && msg.sender == accessControl.owner()),
            "Access denied: Emergency Admin role required"
        );
        _;
    }

    modifier onlyPauser() {
        require(
            (address(accessControl) != address(0) && accessControl.hasRole(PAUSER_ROLE, msg.sender)) ||
            msg.sender == owner() ||
            (address(accessControl) != address(0) && msg.sender == accessControl.owner()),
            "Access denied: Pauser role required"
        );
        _;
    }

    // Events
    event Staked(address indexed user, uint256 amount, uint256 lockPeriodIndex, uint256 positionIndex);
    event Unstaked(address indexed user, uint256 amount, uint256 positionIndex);
    event RewardsClaimed(address indexed user, uint256 amount, uint256 positionIndex);
    event RewardsAdded(uint256 amount);
    event LockPeriodAdded(uint256 durationDays, uint256 bonusPercentage, bool active);
    event LockPeriodStatusUpdated(uint256 indexed index, bool active);
    event FixedStakeAmountUpdated(uint256 newFixedStakeAmount);
    event PoolNameUpdated(string oldName, string newName);
    event PoolPaused();
    event PoolUnpaused();
    event EmergencyTokensWithdrawn(address indexed token, uint256 amount);
    event EmergencyETHWithdrawn(address indexed to, uint256 amount);
    event RewardsPending(address indexed user, uint256 amount);
    event RewardsWithdrawn(address indexed user, uint256 amount);

    /**
     * @dev Constructor
     * @param _roaiToken Address of the ROAI token contract
     * @param _maxStakers Maximum number of stakers allowed in the pool
     * @param _fixedStakeAmountInWholeTokens Fixed amount of WHOLE tokens required to stake (e.g., 25000)
     * @param _poolName Name of the staking pool
     * @param _owner Address of the contract owner
     * @param _accessControl Address of the AccessControl contract (can be zero for backward compatibility)
     */
    constructor(
        address _roaiToken,
        uint256 _maxStakers,
        uint256 _fixedStakeAmountInWholeTokens,
        string memory _poolName,
        address _owner,
        address _accessControl
    ) {
        // FIXED: Add comprehensive input validation
        require(_roaiToken != address(0), "Invalid token address");
        require(_owner != address(0), "Invalid owner address");
        require(_maxStakers > 0 && _maxStakers <= 10000, "Invalid max stakers");
        require(_fixedStakeAmountInWholeTokens > 0, "Invalid stake amount");
        require(bytes(_poolName).length > 0 && bytes(_poolName).length <= 50, "Invalid pool name");
        // Note: _accessControl can be zero for backward compatibility

        _transferOwnership(_owner);
        roaiToken = IERC20(_roaiToken);
        maxStakers = _maxStakers;
        fixedStakeAmount = _fixedStakeAmountInWholeTokens * (10**TOKEN_DECIMALS); // Convert to atomic units
        poolName = _poolName;

        // Set AccessControl contract (can be zero for backward compatibility)
        if (_accessControl != address(0)) {
            accessControl = IAccessControl(_accessControl);
        }

        // Initialize with default lock periods (marked as active)
        // 1 minute for testing, then 30, 60, 90, 180 days
        _addLockPeriod(0, 0, true);   // 1 minute for testing (0 days + 1 minute in seconds)
        _addLockPeriod(30, 5, true);  // 30 days, 5% bonus
        _addLockPeriod(60, 15, true); // 60 days, 15% bonus
        _addLockPeriod(90, 25, true); // 90 days, 25% bonus
        _addLockPeriod(180, 35, true); // 180 days, 35% bonus
    }

    /**
     * @dev Set AccessControl contract address (callable by owner)
     * @param _accessControl Address of the AccessControl contract
     */
    function setAccessControl(address _accessControl) external onlyOwner {
        accessControl = IAccessControl(_accessControl);
    }

    // Internal function to add lock periods, used by constructor and admin function
    function _addLockPeriod(uint256 _durationDays, uint256 _bonusPercentage, bool _active) private {
        lockPeriods.push(LockPeriod({
            durationDays: _durationDays,
            bonusPercentage: _bonusPercentage,
            active: _active
        }));
        emit LockPeriodAdded(_durationDays, _bonusPercentage, _active);
    }

    /**
     * @dev Add a new lock period (callable by pool manager)
     * @param _durationDays Duration in days
     * @param _bonusPercentage Bonus percentage for this lock period
     * @param _active Whether the lock period is initially active
     */
    function addLockPeriod(uint256 _durationDays, uint256 _bonusPercentage, bool _active) public onlyPoolManager {
        // FIXED: Add input validation
        require(_durationDays <= 3650, "Duration too long"); // Max 10 years
        require(_bonusPercentage <= 1000, "Bonus too high"); // Max 1000% bonus

        _addLockPeriod(_durationDays, _bonusPercentage, _active);
    }

    /**
     * @dev Disable a lock period (callable by pool manager)
     * @param _index Index of the lock period to disable
     */
    function disableLockPeriod(uint256 _index) public onlyPoolManager {
        require(_index < lockPeriods.length, "Invalid lock period index");
        lockPeriods[_index].active = false;
        emit LockPeriodStatusUpdated(_index, false);
    }

    /**
     * @dev Enable a lock period (callable by pool manager)
     * @param _index Index of the lock period to enable
     */
    function enableLockPeriod(uint256 _index) public onlyPoolManager {
        require(_index < lockPeriods.length, "Invalid lock period index");
        lockPeriods[_index].active = true;
        emit LockPeriodStatusUpdated(_index, true);
    }
    
    /**
     * @dev Update the fixed stake amount (callable by pool manager)
     * @param _newFixedStakeAmountInWholeTokens New fixed stake amount in WHOLE tokens (e.g., 20000)
     */
    function updateFixedStakeAmount(uint256 _newFixedStakeAmountInWholeTokens) public onlyPoolManager {
        // FIXED: Add input validation
        require(_newFixedStakeAmountInWholeTokens > 0, "Invalid stake amount");
        require(_newFixedStakeAmountInWholeTokens <= 1000000, "Stake amount too high"); // Max 1M tokens

        fixedStakeAmount = _newFixedStakeAmountInWholeTokens * (10**TOKEN_DECIMALS); // Convert to atomic units
        emit FixedStakeAmountUpdated(fixedStakeAmount);
    }

    /**
     * @dev Update the pool name (callable by pool manager)
     * @param _newPoolName New name for the pool
     */
    function updatePoolName(string memory _newPoolName) public onlyPoolManager {
        require(bytes(_newPoolName).length > 0, "Pool name cannot be empty");
        string memory oldName = poolName;
        poolName = _newPoolName;
        emit PoolNameUpdated(oldName, _newPoolName);
    }

    /**
     * @dev Pause staking and unstaking operations (callable by pauser)
     */
    function pausePool() public onlyPauser {
        paused = true;
        emit PoolPaused();
    }

    /**
     * @dev Unpause staking and unstaking operations (callable by pauser)
     */
    function unpausePool() public onlyPauser {
        paused = false;
        emit PoolUnpaused();
    }

    /**
     * @dev Emergency withdraw of tokens from the contract (callable by emergency admin)
     * @param _tokenAddress The address of the ERC20 token to withdraw
     * @param _amount The amount of tokens to withdraw (in atomic units)
     */
    function emergencyWithdrawTokens(address _tokenAddress, uint256 _amount) public onlyEmergencyAdmin nonReentrant {
        require(_tokenAddress != address(0), "Invalid token address");
        require(_amount > 0, "Amount must be greater than 0");

        IERC20 token = IERC20(_tokenAddress);
        require(token.balanceOf(address(this)) >= _amount, "Insufficient token balance");

        // FIXED: Follow CEI pattern - emit event before external call for consistency
        emit EmergencyTokensWithdrawn(_tokenAddress, _amount);

        // FIXED: Use SafeERC20 for secure token transfer
        token.safeTransfer(owner(), _amount);
    }

    /**
     * @dev Emergency withdraw of ETH from the contract (callable by emergency admin)
     * @param _amount The amount of ETH to withdraw (in wei)
     * @param _to Address to send ETH to
     * FIXED: Add restrictions to prevent abuse
     */
    function emergencyWithdrawETH(uint256 _amount, address payable _to) external onlyEmergencyAdmin nonReentrant {
        require(_to != address(0), "Invalid recipient address");
        require(_amount > 0, "Amount must be greater than 0");
        require(address(this).balance >= _amount, "Insufficient contract balance");

        // FIXED: Restrict emergency withdrawals to not drain all user rewards
        uint256 maxWithdrawable = totalRewards > totalPendingRewards ? totalRewards - totalPendingRewards : 0;
        require(_amount <= maxWithdrawable, "Cannot withdraw pending user rewards");

        // FIXED: Follow CEI pattern - update state BEFORE external call
        uint256 amountToWithdraw = _amount;

        // FIXED: Account for pending rewards in emergency withdrawal
        uint256 availableForWithdrawal = totalRewards;
        if (amountToWithdraw <= availableForWithdrawal) {
            totalRewards -= amountToWithdraw;
        } else {
            totalRewards = 0;
        }

        // External interaction last
        (bool success, ) = _to.call{value: amountToWithdraw}("");
        require(success, "ETH transfer failed");

        emit EmergencyETHWithdrawn(_to, amountToWithdraw);
    }

    /**
     * @dev Emergency withdraw all ETH from the contract (callable by emergency admin)
     * @param _to Address to send all ETH to
     * FIXED: Add warning and restrictions for complete drainage
     */
    function emergencyWithdrawAllETH(address payable _to) external onlyEmergencyAdmin nonReentrant {
        require(_to != address(0), "Invalid recipient address");
        uint256 balance = address(this).balance;
        require(balance > 0, "No ETH to withdraw");

        // FIXED: This function should only be used in extreme emergencies
        // as it will prevent users from withdrawing their pending rewards

        // FIXED: Follow CEI pattern - update state BEFORE external call
        uint256 amountToWithdraw = balance;

        // Reset totalRewards since we're withdrawing everything
        totalRewards = 0;

        // External interaction last
        (bool success, ) = _to.call{value: amountToWithdraw}("");
        require(success, "ETH transfer failed");

        emit EmergencyETHWithdrawn(_to, amountToWithdraw);
    }
    
    /**
     * @dev Stake tokens. Uses the fixed stake amount defined for this pool.
     * @param _lockPeriodIndex Index of the lock period to use
     */
    function stake(uint256 _lockPeriodIndex) external nonReentrant whenNotPaused {
        require(_lockPeriodIndex < lockPeriods.length, "Invalid lock period index");
        require(lockPeriods[_lockPeriodIndex].active, "Lock period not active");
        require(currentStakerCount < maxStakers, "Pool is full");
        require(!hasStaked[msg.sender], "User has already staked in this pool");

        // FIXED: Check ACTIVE position limit per user to prevent unbounded growth
        uint256 activePositions = 0;
        for (uint256 i = 0; i < stakingPositions[msg.sender].length; i++) {
            if (stakingPositions[msg.sender][i].active) {
                activePositions++;
            }
        }
        require(activePositions < MAX_POSITIONS_PER_USER, "Max active positions per user reached");

        // Perform state updates BEFORE interaction
        currentStakerCount++;
        isStaking[msg.sender] = true;
        hasStaked[msg.sender] = true;

        // Add user to stakers array for enumeration
        stakers.push(msg.sender);
        stakerIndex[msg.sender] = stakers.length - 1;

        uint256 lockDurationSeconds;
        if (lockPeriods[_lockPeriodIndex].durationDays == 0) {
            // Special case for testing: 1 minute lock period
            lockDurationSeconds = 1 minutes;
        } else {
            lockDurationSeconds = lockPeriods[_lockPeriodIndex].durationDays * 1 days;
        }
        uint256 endTime = block.timestamp + lockDurationSeconds;

        stakingPositions[msg.sender].push(StakingPosition({
            amount: fixedStakeAmount,
            lockPeriodIndex: _lockPeriodIndex,
            startTime: block.timestamp,
            endTime: endTime,
            lastRewardCalculation: block.timestamp,
            accumulatedRewards: 0,
            active: true
        }));

        totalStaked += fixedStakeAmount;

        emit Staked(msg.sender, fixedStakeAmount, _lockPeriodIndex, stakingPositions[msg.sender].length - 1);

        // FIXED: Use SafeERC20 for secure token transfer
        roaiToken.safeTransferFrom(msg.sender, address(this), fixedStakeAmount);
    }

    /**
     * @dev Unstake tokens. Position amount is in ATOMIC UNITS.
     * @param _positionIndex Index of the staking position to unstake
     */
    function unstake(uint256 _positionIndex) external nonReentrant whenNotPaused { // Added whenNotPaused
        require(_positionIndex < stakingPositions[msg.sender].length, "Invalid position index");

        StakingPosition storage position = stakingPositions[msg.sender][_positionIndex];
        require(position.active, "Position not active");
        require(block.timestamp >= position.endTime, "Lock period not ended");

        // FIXED: Calculate any remaining rewards BEFORE marking position inactive
        updateRewards(_positionIndex);

        // FIXED: Store values before state changes (CEI pattern)
        uint256 stakedAmount = position.amount;
        uint256 accumulatedRewards = position.accumulatedRewards;

        // FIXED: Update state variables BEFORE external calls
        position.amount = 0;
        position.accumulatedRewards = 0;
        position.active = false;
        totalStaked -= stakedAmount;

        // FIXED: Only reduce totalRewards if there are rewards to claim
        if (accumulatedRewards > 0) {
            totalRewards -= accumulatedRewards;
        }

        // FIXED: Automatically clean up inactive positions and check if user has any active positions left
        StakingPosition[] storage positions = stakingPositions[msg.sender];
        uint256 originalLength = positions.length;

        // Count active positions and create new array with only active positions
        uint256 activeCount = 0;
        for (uint256 i = 0; i < positions.length; i++) {
            if (positions[i].active) {
                activeCount++;
            }
        }

        // If there are inactive positions, clean them up
        if (activeCount < originalLength) {
            // Create new array with only active positions
            StakingPosition[] memory activePositions = new StakingPosition[](activeCount);
            uint256 activeIndex = 0;

            for (uint256 i = 0; i < positions.length; i++) {
                if (positions[i].active) {
                    activePositions[activeIndex] = positions[i];
                    activeIndex++;
                }
            }

            // Replace the storage array
            delete stakingPositions[msg.sender];
            for (uint256 i = 0; i < activePositions.length; i++) {
                stakingPositions[msg.sender].push(activePositions[i]);
            }
        }

        // If no active positions, user is no longer staking
        if (activeCount == 0) {
            isStaking[msg.sender] = false;
            hasStaked[msg.sender] = false; // Allow user to stake again
            currentStakerCount--;

            // Remove user from stakers array
            uint256 userIndex = stakerIndex[msg.sender];
            uint256 lastIndex = stakers.length - 1;

            if (userIndex != lastIndex) {
                // Move the last staker to the position of the removed staker
                address lastStaker = stakers[lastIndex];
                stakers[userIndex] = lastStaker;
                stakerIndex[lastStaker] = userIndex;
            }

            stakers.pop();
            delete stakerIndex[msg.sender];
        }

        // FIXED: Use SafeERC20 for secure token transfer back to user
        roaiToken.safeTransfer(msg.sender, stakedAmount);

        // FIXED: Use pull-based pattern for rewards to prevent DoS/theft vectors
        if (accumulatedRewards > 0) {
            pendingRewards[msg.sender] += accumulatedRewards;
            totalPendingRewards += accumulatedRewards;
            emit RewardsPending(msg.sender, accumulatedRewards);
            emit RewardsClaimed(msg.sender, accumulatedRewards, _positionIndex);
        }

        emit Unstaked(msg.sender, stakedAmount, _positionIndex);
    }

    /**
     * @dev Claim rewards. Rewards are in ETH (wei).
     * @param _positionIndex Index of the staking position to claim rewards for
     * FIXED: Use pull-based pattern to prevent DoS/theft vectors
     */
    function claimRewards(uint256 _positionIndex) external nonReentrant whenNotPaused { // Added whenNotPaused
        require(_positionIndex < stakingPositions[msg.sender].length, "Invalid position index");

        StakingPosition storage position = stakingPositions[msg.sender][_positionIndex];
        require(position.active, "Position not active");

        // Update rewards
        updateRewards(_positionIndex);

        // Add rewards to pending instead of direct transfer
        uint256 rewards = position.accumulatedRewards;
        require(rewards > 0, "No rewards to claim");

        position.accumulatedRewards = 0;
        totalRewards -= rewards;

        // FIXED: Add to pending rewards instead of direct transfer
        pendingRewards[msg.sender] += rewards;
        totalPendingRewards += rewards;

        emit RewardsPending(msg.sender, rewards);
        emit RewardsClaimed(msg.sender, rewards, _positionIndex);
    }

    /**
     * @dev Withdraw pending rewards (pull-based pattern)
     * FIXED: Separate function for withdrawing rewards to prevent DoS/theft vectors
     */
    function withdrawPendingRewards() external nonReentrant whenNotPaused {
        uint256 pending = pendingRewards[msg.sender];
        require(pending > 0, "No pending rewards");

        // Update state before external call (CEI pattern)
        pendingRewards[msg.sender] = 0;
        totalPendingRewards -= pending;

        // Transfer ETH to user
        (bool success, ) = payable(msg.sender).call{value: pending}("");
        require(success, "ETH transfer failed");

        emit RewardsWithdrawn(msg.sender, pending);
    }

    /**
     * @dev Get pending rewards for a user
     * @param _user Address of the user
     * @return Amount of pending rewards in wei
     */
    function getPendingRewards(address _user) external view returns (uint256) {
        return pendingRewards[_user];
    }

    /**
     * @dev Get all stakers in the pool
     * @return Array of staker addresses
     */
    function getAllStakers() external view returns (address[] memory) {
        return stakers;
    }

    /**
     * @dev Get staker information by index
     * @param _index Index of the staker in the stakers array
     * @return staker Address of the staker
     * @return userTotalStaked Total amount staked by the user
     * @return positionCount Number of positions the user has
     * @return pendingRewardsAmount Pending rewards for the user
     * @return isActive Whether the user has active positions
     */
    function getStakerInfo(uint256 _index) external view returns (
        address staker,
        uint256 userTotalStaked,
        uint256 positionCount,
        uint256 pendingRewardsAmount,
        bool isActive
    ) {
        require(_index < stakers.length, "Invalid staker index");

        address stakerAddress = stakers[_index];
        StakingPosition[] memory positions = stakingPositions[stakerAddress];

        uint256 stakerTotalStaked = 0;
        bool hasActivePosition = false;

        for (uint256 i = 0; i < positions.length; i++) {
            stakerTotalStaked += positions[i].amount;
            if (positions[i].active) {
                hasActivePosition = true;
            }
        }

        return (
            stakerAddress,
            stakerTotalStaked,
            positions.length,
            pendingRewards[stakerAddress],
            hasActivePosition
        );
    }

    /**
     * @dev Get detailed information for all stakers
     * @return stakerAddresses Array of staker addresses
     * @return userTotalStakedAmounts Array of total staked amounts for each staker
     * @return positionCounts Array of position counts for each staker
     * @return pendingRewardsAmounts Array of pending rewards for each staker
     * @return activeStatuses Array of active status for each staker
     */
    function getAllStakersInfo() external view returns (
        address[] memory stakerAddresses,
        uint256[] memory userTotalStakedAmounts,
        uint256[] memory positionCounts,
        uint256[] memory pendingRewardsAmounts,
        bool[] memory activeStatuses
    ) {
        uint256 stakerCount = stakers.length;

        stakerAddresses = new address[](stakerCount);
        userTotalStakedAmounts = new uint256[](stakerCount);
        positionCounts = new uint256[](stakerCount);
        pendingRewardsAmounts = new uint256[](stakerCount);
        activeStatuses = new bool[](stakerCount);

        for (uint256 i = 0; i < stakerCount; i++) {
            address stakerAddress = stakers[i];
            StakingPosition[] memory positions = stakingPositions[stakerAddress];

            uint256 stakerTotalStaked = 0;
            bool hasActivePosition = false;

            for (uint256 j = 0; j < positions.length; j++) {
                stakerTotalStaked += positions[j].amount;
                if (positions[j].active) {
                    hasActivePosition = true;
                }
            }

            stakerAddresses[i] = stakerAddress;
            userTotalStakedAmounts[i] = stakerTotalStaked;
            positionCounts[i] = positions.length;
            pendingRewardsAmounts[i] = pendingRewards[stakerAddress];
            activeStatuses[i] = hasActivePosition;
        }

        return (stakerAddresses, userTotalStakedAmounts, positionCounts, pendingRewardsAmounts, activeStatuses);
    }

    /**
     * @dev Update rewards for a staking position
     * @param _positionIndex Index of the staking position to update
     */
    function updateRewards(uint256 _positionIndex) public whenNotPaused {
        require(_positionIndex < stakingPositions[msg.sender].length, "Invalid position index");

        StakingPosition storage position = stakingPositions[msg.sender][_positionIndex];
        if (!position.active) return;

        // Calculate time since last reward calculation
        uint256 timeElapsed = block.timestamp - position.lastRewardCalculation;
        if (timeElapsed == 0) return;

        // If there are no rewards in the pool, just update the last calculation time
        if (totalRewards == 0 || totalStaked == 0) {
            position.lastRewardCalculation = block.timestamp;
            return;
        }

        // Calculate effective staking amount with bonus
        uint256 bonusPercentage = lockPeriods[position.lockPeriodIndex].bonusPercentage;
        // Fixed precision issue: multiply before divide to avoid precision loss
        uint256 bonusAmount = (position.amount * bonusPercentage) / 100;
        uint256 effectiveAmount = position.amount + bonusAmount;

        // Fixed precision issue: use scaled arithmetic to prevent precision loss
        // Scale by 1e18 for precision, then divide at the end
        uint256 scaledRewardRate = (totalRewards * 1e18) / totalStaked;
        uint256 scaledNewRewards = (effectiveAmount * scaledRewardRate * timeElapsed) / (365 days * 1e18);

        // Cap rewards to available total
        uint256 newRewards = scaledNewRewards > totalRewards ? totalRewards : scaledNewRewards;

        position.accumulatedRewards += newRewards;
        position.lastRewardCalculation = block.timestamp;
    }

    /**
     * @dev Get all staking positions for a user
     * @param _user Address of the user
     * @return Array of staking positions
     */
    function getStakingPositions(address _user) external view returns (StakingPosition[] memory) {
        return stakingPositions[_user];
    }

    /**
     * @dev Get all lock periods
     * @return Array of lock periods
     */
    function getLockPeriods() external view returns (LockPeriod[] memory) {
        return lockPeriods;
    }

    /**
     * @dev Get the fixed stake amount in whole tokens
     * @return Fixed stake amount in whole tokens
     */
    function getFixedStakeAmountInWholeTokens() external view returns (uint256) {
        return fixedStakeAmount / (10**TOKEN_DECIMALS);
    }

    /**
     * @dev Calculate rewards for a staking position
     * @param _user Address of the user
     * @param _positionIndex Index of the staking position
     * @return Calculated rewards
     */
    function calculateRewards(address _user, uint256 _positionIndex) external view returns (uint256) {
        require(_user != address(0), "Invalid user address");
        require(_positionIndex < stakingPositions[_user].length, "Invalid position index");

        StakingPosition memory position = stakingPositions[_user][_positionIndex];
        if (!position.active) return 0;

        // Calculate time since last reward calculation
        uint256 timeElapsed = block.timestamp - position.lastRewardCalculation;
        if (timeElapsed == 0 || totalRewards == 0 || totalStaked == 0) {
            return position.accumulatedRewards;
        }

        // Calculate effective staking amount with bonus
        uint256 bonusPercentage = lockPeriods[position.lockPeriodIndex].bonusPercentage;
        // Fixed precision issue: multiply before divide to avoid precision loss
        uint256 bonusAmount = (position.amount * bonusPercentage) / 100;
        uint256 effectiveAmount = position.amount + bonusAmount;

        // Fixed precision issue: use scaled arithmetic to prevent precision loss
        // Scale by 1e18 for precision, then divide at the end
        uint256 scaledRewardRate = (totalRewards * 1e18) / totalStaked;
        uint256 scaledNewRewards = (effectiveAmount * scaledRewardRate * timeElapsed) / (365 days * 1e18);

        // Cap rewards to available total
        uint256 newRewards = scaledNewRewards > totalRewards ? totalRewards : scaledNewRewards;

        return position.accumulatedRewards + newRewards;
    }

    /**
     * @dev Receive ETH rewards
     */
    receive() external payable {
        // Only add to totalRewards if value > 0
        if (msg.value > 0) {
            totalRewards += msg.value;
            emit RewardsAdded(msg.value);
        }
    }

    /**
     * @dev Fallback function to handle ETH sent via selfdestruct
     * @notice This prevents accounting mismatches when ETH is sent via selfdestruct
     */
    fallback() external payable {
        // Credit any ETH sent via selfdestruct to totalRewards
        if (msg.value > 0) {
            totalRewards += msg.value;
            emit RewardsAdded(msg.value);
        }
    }

    /**
     * @dev Get all active lock periods
     * @return Array of active lock periods
     */
    function getActiveLockPeriods() external view returns (LockPeriod[] memory) {
        uint256 activeCount = 0;
        for (uint256 i = 0; i < lockPeriods.length; i++) {
            if (lockPeriods[i].active) {
                activeCount++;
            }
        }

        LockPeriod[] memory activePeriods = new LockPeriod[](activeCount);
        uint256 currentIndex = 0;
        for (uint256 i = 0; i < lockPeriods.length; i++) {
            if (lockPeriods[i].active) {
                activePeriods[currentIndex] = lockPeriods[i];
                currentIndex++;
            }
        }
        return activePeriods;
    }

    /**
     * @dev Clean up inactive positions for a user to free up space
     * @param _user Address of the user to clean up positions for
     * NOTE: This function is now mostly redundant since unstaking automatically cleans up,
     * but kept for backward compatibility and manual cleanup if needed
     */
    function cleanupInactivePositions(address _user) external {
        require(_user != address(0), "Invalid user address");

        StakingPosition[] storage positions = stakingPositions[_user];
        uint256 originalLength = positions.length;

        // Count active positions
        uint256 activeCount = 0;
        for (uint256 i = 0; i < positions.length; i++) {
            if (positions[i].active) {
                activeCount++;
            }
        }

        // If all positions are active, nothing to clean
        if (activeCount == originalLength) {
            return;
        }

        // Create new array with only active positions
        StakingPosition[] memory activePositions = new StakingPosition[](activeCount);
        uint256 activeIndex = 0;

        for (uint256 i = 0; i < positions.length; i++) {
            if (positions[i].active) {
                activePositions[activeIndex] = positions[i];
                activeIndex++;
            }
        }

        // Replace the storage array
        delete stakingPositions[_user];
        for (uint256 i = 0; i < activePositions.length; i++) {
            stakingPositions[_user].push(activePositions[i]);
        }
    }

    /**
     * @dev Modifier to allow actions only when the contract is not paused.
     */
    modifier whenNotPaused() {
        require(!paused, "Contract is paused");
        _;
    }
}
SimpleRewardDistributor.sol 304 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

/**
 * ██████╗  ██████╗  █████╗ ██╗    ███████╗ ██████╗ █████╗ ██╗     ██████╗ ███████╗██████╗
 * ██╔══██╗██╔═══██╗██╔══██╗██║    ██╔════╝██╔════╝██╔══██╗██║     ██╔══██╗██╔════╝██╔══██╗
 * ██████╔╝██║   ██║███████║██║    ███████╗██║     ███████║██║     ██████╔╝█████╗  ██████╔╝
 * ██╔══██╗██║   ██║██╔══██║██║    ╚════██║██║     ██╔══██║██║     ██╔═══╝ ██╔══╝  ██╔══██╗
 * ██║  ██║╚██████╔╝██║  ██║██║    ███████║╚██████╗██║  ██║███████╗██║     ███████╗██║  ██║
 * ╚═╝  ╚═╝ ╚═════╝ ╚═╝  ╚═╝╚═╝    ╚══════╝ ╚═════╝╚═╝  ╚═╝╚══════╝╚═╝     ╚══════╝╚═╝  ╚═╝
 *
 * ROAI Scalper - Advanced AI Trading & Staking Platform
 *
 * Website: https://roaiscalper.com
 * Twitter: https://x.com/roaiscalper
 * Telegram: https://t.me/roaiscalper_official
 *
 * Professional-grade staking infrastructure with multi-signature security,
 * role-based access control, and automated reward distribution.
 */

/**
 * @title SimpleRewardDistributor
 * @author ROAI Scalper Team
 * @notice Simplified reward distribution contract for efficient ETH distribution to staking pools
 * @dev Features:
 *      - Direct ETH transfers to multiple pools
 *      - Configurable reserve percentage for treasury
 *      - Emergency withdrawal capabilities
 *      - Multi-signature security integration
 *      - Gas-optimized distribution logic
 *
 * Architecture:
 * - Receives ETH deposits from various sources
 * - Maintains configurable reserve percentage
 * - Distributes remaining ETH to pools based on percentages
 * - Provides emergency functions for treasury management
 *
 * Security Features:
 * - ReentrancyGuard protection
 * - Owner-only administrative functions
 * - Treasury wallet integration
 * - Comprehensive input validation
 * - Emergency withdrawal capabilities
 *
 * @custom:security-contact [email protected]
 */
contract SimpleRewardDistributor is Ownable, ReentrancyGuard {

    // Reserve percentage (e.g., 20 = 20%)
    uint256 public reservePercentage = 20;

    // Maximum reserve percentage allowed
    uint256 public constant MAX_RESERVE_PERCENTAGE = 50;

    // Treasury wallet address for emergency withdrawals
    address public treasuryWallet;

    // Events
    event RewardsDistributed(address[] pools, uint256[] amounts, uint256 totalDistributed);
    event ReservePercentageUpdated(uint256 oldPercentage, uint256 newPercentage);
    event ETHDeposited(address indexed from, uint256 amount);
    event EmergencyWithdrawal(address indexed to, uint256 amount);
    event TreasuryWalletUpdated(address indexed oldTreasury, address indexed newTreasury);
    
    /**
     * @dev Constructor
     * @param _owner Address of the contract owner
     * @param _treasuryWallet Address of the treasury wallet (optional, can be set later)
     */
    constructor(address _owner, address _treasuryWallet) {
        _transferOwnership(_owner);
        if (_treasuryWallet != address(0)) {
            treasuryWallet = _treasuryWallet;
            emit TreasuryWalletUpdated(address(0), _treasuryWallet);
        }
    }
    
    /**
     * @dev Get the total balance of this contract
     * @return Total ETH balance in wei
     */
    function getTotalBalance() public view returns (uint256) {
        return address(this).balance;
    }
    
    /**
     * @dev Get the reserve amount (not distributable)
     * @return Reserve amount in wei
     */
    function getReserveAmount() public view returns (uint256) {
        return (address(this).balance * reservePercentage) / 100;
    }
    
    /**
     * @dev Get distributable amount (total balance minus reserve)
     * @return Amount of ETH that can be distributed in wei
     */
    function getDistributableAmount() public view returns (uint256) {
        uint256 totalBalance = address(this).balance;
        uint256 reserveAmount = getReserveAmount();
        return totalBalance > reserveAmount ? totalBalance - reserveAmount : 0;
    }
    
    /**
     * @dev Set treasury wallet address
     * @param _treasuryWallet New treasury wallet address
     */
    function setTreasuryWallet(address _treasuryWallet) external onlyOwner {
        require(_treasuryWallet != address(0), "Invalid treasury address");

        address oldTreasury = treasuryWallet;
        treasuryWallet = _treasuryWallet;

        emit TreasuryWalletUpdated(oldTreasury, _treasuryWallet);
    }

    /**
     * @dev Get treasury wallet address
     * @return Address of the treasury wallet
     */
    function getTreasuryWallet() external view returns (address) {
        return treasuryWallet;
    }

    /**
     * @dev Set reserve percentage
     * @param _reservePercentage New reserve percentage (0-50)
     */
    function setReservePercentage(uint256 _reservePercentage) external onlyOwner {
        require(_reservePercentage <= MAX_RESERVE_PERCENTAGE, "Reserve cannot exceed 50%");

        uint256 oldPercentage = reservePercentage;
        reservePercentage = _reservePercentage;

        emit ReservePercentageUpdated(oldPercentage, _reservePercentage);
    }
    
    /**
     * @dev Distribute rewards to multiple pools
     * @param _poolAddresses Array of pool addresses to distribute rewards to
     * @param _percentages Array of percentages for each pool (must sum to 100)
     */
    function distributeRewards(
        address[] calldata _poolAddresses,
        uint256[] calldata _percentages
    ) external onlyOwner nonReentrant {
        require(_poolAddresses.length > 0, "No pools specified");
        require(_poolAddresses.length == _percentages.length, "Arrays length mismatch");
        
        // Validate percentages sum to 100
        uint256 totalPercentage = 0;
        for (uint256 i = 0; i < _percentages.length; i++) {
            totalPercentage += _percentages[i];
        }
        require(totalPercentage == 100, "Percentages must sum to 100");
        
        // Get distributable amount
        uint256 distributableAmount = getDistributableAmount();
        require(distributableAmount > 0, "No funds available for distribution");
        
        // Calculate and distribute amounts
        uint256[] memory amounts = new uint256[](_poolAddresses.length);
        uint256 totalDistributed = 0;
        
        for (uint256 i = 0; i < _poolAddresses.length; i++) {
            require(_poolAddresses[i] != address(0), "Invalid pool address");
            
            // Calculate amount for this pool
            uint256 amount = (distributableAmount * _percentages[i]) / 100;
            amounts[i] = amount;
            
            // Only transfer if amount > 0
            if (amount > 0) {
                // Direct ETH transfer to pool
                (bool success, ) = payable(_poolAddresses[i]).call{value: amount}("");
                require(success, string(abi.encodePacked("Transfer failed to pool: ", _addressToString(_poolAddresses[i]))));
                
                totalDistributed += amount;
            }
        }
        
        emit RewardsDistributed(_poolAddresses, amounts, totalDistributed);
    }
    
    /**
     * @dev Emergency withdrawal of ETH to treasury wallet
     * @param _amount Amount of ETH to withdraw (in wei)
     */
    function emergencyWithdrawToTreasury(uint256 _amount) external onlyOwner {
        require(treasuryWallet != address(0), "Treasury wallet not set");
        require(_amount > 0, "Amount must be greater than 0");
        require(address(this).balance >= _amount, "Insufficient contract balance");

        (bool success, ) = payable(treasuryWallet).call{value: _amount}("");
        require(success, "ETH transfer failed");

        emit EmergencyWithdrawal(treasuryWallet, _amount);
    }

    /**
     * @dev Emergency withdrawal of all ETH to treasury wallet
     */
    function emergencyWithdrawAllToTreasury() external onlyOwner {
        require(treasuryWallet != address(0), "Treasury wallet not set");
        uint256 balance = address(this).balance;
        require(balance > 0, "No ETH to withdraw");

        (bool success, ) = payable(treasuryWallet).call{value: balance}("");
        require(success, "ETH transfer failed");

        emit EmergencyWithdrawal(treasuryWallet, balance);
    }

    /**
     * @dev Emergency withdrawal of ETH (owner only) - Legacy function for custom address
     * @param _amount Amount of ETH to withdraw (in wei)
     * @param _to Address to send ETH to
     */
    function emergencyWithdrawETH(uint256 _amount, address payable _to) external onlyOwner {
        require(_to != address(0), "Invalid recipient address");
        require(_amount > 0, "Amount must be greater than 0");
        require(address(this).balance >= _amount, "Insufficient contract balance");

        (bool success, ) = _to.call{value: _amount}("");
        require(success, "ETH transfer failed");

        emit EmergencyWithdrawal(_to, _amount);
    }

    /**
     * @dev Emergency withdrawal of all ETH (owner only) - Legacy function for custom address
     * @param _to Address to send all ETH to
     */
    function emergencyWithdrawAllETH(address payable _to) external onlyOwner {
        require(_to != address(0), "Invalid recipient address");
        uint256 balance = address(this).balance;
        require(balance > 0, "No ETH to withdraw");

        (bool success, ) = _to.call{value: balance}("");
        require(success, "ETH transfer failed");

        emit EmergencyWithdrawal(_to, balance);
    }
    
    /**
     * @dev Receive ETH deposits
     */
    receive() external payable {
        require(msg.value > 0, "Cannot deposit zero ETH");
        emit ETHDeposited(msg.sender, msg.value);
    }
    
    /**
     * @dev Fallback function to receive ETH
     */
    fallback() external payable {
        require(msg.value > 0, "Cannot deposit zero ETH");
        emit ETHDeposited(msg.sender, msg.value);
    }
    
    /**
     * @dev Helper function to convert address to string
     * @param _addr Address to convert
     * @return String representation of the address
     */
    function _addressToString(address _addr) internal pure returns (string memory) {
        bytes32 value = bytes32(uint256(uint160(_addr)));
        bytes memory alphabet = "0123456789abcdef";
        bytes memory str = new bytes(42);
        str[0] = '0';
        str[1] = 'x';
        for (uint256 i = 0; i < 20; i++) {
            str[2+i*2] = alphabet[uint8(value[i + 12] >> 4)];
            str[3+i*2] = alphabet[uint8(value[i + 12] & 0x0f)];
        }
        return string(str);
    }
    
    /**
     * @dev Get contract info for debugging
     * @return totalBalance Current total balance
     * @return reserveAmount Current reserve amount
     * @return distributableAmount Current distributable amount
     * @return reservePerc Current reserve percentage
     * @return treasury Current treasury wallet address
     */
    function getContractInfo() external view returns (
        uint256 totalBalance,
        uint256 reserveAmount,
        uint256 distributableAmount,
        uint256 reservePerc,
        address treasury
    ) {
        totalBalance = getTotalBalance();
        reserveAmount = getReserveAmount();
        distributableAmount = getDistributableAmount();
        reservePerc = reservePercentage;
        treasury = treasuryWallet;
    }
}

Read Contract

EMERGENCY_ADMIN_ROLE 0x6e76fc8f → bytes32
MAX_POSITIONS_PER_USER 0x2fcb3058 → uint256
PAUSER_ROLE 0xe63ab1e9 → bytes32
POOL_MANAGER_ROLE 0x56d73568 → bytes32
TOKEN_DECIMALS 0x5b7f415c → uint256
accessControl 0x13007d55 → address
calculateRewards 0xbeb8314c → uint256
currentStakerCount 0x25693406 → uint256
fixedStakeAmount 0x5af01b91 → uint256
getActiveLockPeriods 0x07fbcb8a → tuple[]
getAllStakers 0x6e4f88c8 → address[]
getAllStakersInfo 0x187cefa8 → address[], uint256[], uint256[], uint256[], bool[]
getFixedStakeAmountInWholeTokens 0x32c641b5 → uint256
getLockPeriods 0x41ee13be → tuple[]
getPendingRewards 0xf6ed2017 → uint256
getStakerInfo 0xe95abd52 → address, uint256, uint256, uint256, bool
getStakingPositions 0xf043a587 → tuple[]
hasStaked 0xc93c8f34 → bool
isStaking 0x6f49712b → bool
lockPeriods 0x4c8f2a78 → uint256, uint256, bool
maxStakers 0x4f7ff503 → uint256
owner 0x8da5cb5b → address
paused 0x5c975abb → bool
pendingRewards 0x31d7a262 → uint256
poolName 0xf3466dfa → string
roaiToken 0xc513e19e → address
stakerIndex 0x78d60a5b → uint256
stakers 0xfd5e6dd1 → address
stakingPositions 0x98bceccc → uint256, uint256, uint256, uint256, uint256, uint256, bool
totalPendingRewards 0xec1371f2 → uint256
totalRewards 0x0e15561a → uint256
totalStaked 0x817b1cd2 → uint256

Write Contract 19 functions

These functions modify contract state and require a wallet transaction to execute.

addLockPeriod 0x8adaac5a
uint256 _durationDays
uint256 _bonusPercentage
bool _active
claimRewards 0x0962ef79
uint256 _positionIndex
cleanupInactivePositions 0xde0deede
address _user
disableLockPeriod 0x08a1943e
uint256 _index
emergencyWithdrawAllETH 0x111e3b5b
address _to
emergencyWithdrawETH 0x15945006
uint256 _amount
address _to
emergencyWithdrawTokens 0x917bb998
address _tokenAddress
uint256 _amount
enableLockPeriod 0xc82c6ed6
uint256 _index
pausePool 0xaa09d5b7
No parameters
renounceOwnership 0x715018a6
No parameters
setAccessControl 0x19129e5a
address _accessControl
stake 0xa694fc3a
uint256 _lockPeriodIndex
transferOwnership 0xf2fde38b
address newOwner
unpausePool 0x068cc514
No parameters
unstake 0x2e17de78
uint256 _positionIndex
updateFixedStakeAmount 0x7cabd293
uint256 _newFixedStakeAmountInWholeTokens
updatePoolName 0xf0b33435
string _newPoolName
updateRewards 0xb1e95e94
uint256 _positionIndex
withdrawPendingRewards 0x73492099
No parameters

Recent Transactions

No transactions found for this address