Address Contract Verified
Address
0x70BAa068bDA5f0f2dB9f447F307D9167f734FB89
Balance
0 ETH
Nonce
1
Code Size
16370 bytes
Creator
0xC67b306b...213D at tx 0x7deed45f...f7a4cb
Indexed Transactions
0
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