Cryo Explorer Ethereum Mainnet

Address Contract Verified

Address 0xc229C139e6F048cdE6E473F163004e50756eB214
Balance 0 ETH
Nonce 1
Code Size 9905 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

9905 bytes
0x608060405234801561001057600080fd5b50600436106101fb5760003560e01c80638da5cb5b1161011a578063b4f2bb6d116100ad578063e1b202ee1161007c578063e1b202ee14610568578063e30c39781461057b578063e5711e8b1461058c578063f2fde38b1461059f578063f93f3b63146105b2576101fb565b8063b4f2bb6d14610444578063b5217bb414610459578063b5d5b5fa146104ce578063dac6275214610519576101fb565b8063a8d49e64116100e9578063a8d49e64146103df578063aac8f967146103f2578063b3a2199d1461041e578063b4638dee14610431576101fb565b80638da5cb5b146103855780638f661c7f146103965780639c13cd4d146103a9578063a80dcfee146103bc576101fb565b80634adbe55111610192578063715018a611610161578063715018a61461034f57806372f702f31461035757806379ba50971461036a578063839006f214610372576101fb565b80634adbe551146102f65780635a8fc5441461031657806369883b4e14610329578063704802751461033c576101fb565b806318c3c124116101ce57806318c3c124146102865780632b7832b31461029957806331623f2c146102b0578063429b62e5146102c3576101fb565b806309f2c0191461021f5780630f29cd581461024b578063168c367d146102605780631785f53c14610273575b33604051633892853160e11b815260040161021691906121b8565b60405180910390fd5b61023261022d3660046121cc565b6105d2565b6040516102429493929190612221565b60405180910390f35b61025e61025936600461227f565b610686565b005b61025e61026e366004612342565b610996565b61025e6102813660046123fa565b610ab6565b61025e61029436600461241e565b610b68565b6102a260045481565b604051908152602001610242565b61025e6102be366004612342565b610cc4565b6102e66102d13660046123fa565b60076020526000908152604090205460ff1681565b6040519015158152602001610242565b600554610309906001600160a01b031681565b60405161024291906121b8565b61025e610324366004612453565b610dd6565b6102a26103373660046121cc565b610fdd565b61025e61034a3660046123fa565b610ffe565b61025e61111d565b600354610309906001600160a01b031681565b61025e611170565b61025e6103803660046123fa565b6111ab565b6001546001600160a01b0316610309565b61025e6103a43660046121cc565b6112ca565b61025e6103b736600461247f565b6113d9565b6102e66103ca3660046123fa565b60086020526000908152604090205460ff1681565b61025e6103ed3660046123fa565b611597565b6102e66104003660046123fa565b6001600160a01b031660009081526008602052604090205460ff1690565b61025e61042c366004612453565b611643565b61025e61043f36600461241e565b6117b5565b61044c611a57565b60405161024291906124c0565b61049d6104673660046121cc565b60096020526000908152604090208054600182015460028301546004909301549192909160ff8083169261010090048116911685565b60408051958652602086019490945291151592840192909252901515606083015260ff16608082015260a001610242565b6104e16104dc3660046124d3565b611aaf565b6040805195865260208601949094526001600160801b039092169284019290925260608301919091521515608082015260a001610242565b61052c6105273660046124ff565b611b09565b604080519586526001600160801b03909416602086015261ffff9092169284019290925260ff918216606084015216608082015260a001610242565b61025e6105763660046124ff565b611ba8565b6002546001600160a01b0316610309565b61025e61059a36600461247f565b611cd6565b61025e6105ad3660046123fa565b611e66565b6105c56105c03660046123fa565b611ed7565b6040516102429190612521565b600081815260096020526040812060028101548291829160609190610100900460ff16610612576040516302721e1f60e61b815260040160405180910390fd5b8054600182015460028301546005840180546040805160208084028201810190925282815260ff9094169391839183018282801561066f57602002820191906000526020600020905b81548152602001906001019080831161065b575b505050505090509450945094509450509193509193565b3360009081526007602052604090205460ff161580156106b157506001546001600160a01b03163314155b156106d157336040516334abcb3560e11b815260040161021691906121b8565b60008781526009602052604090206002810154610100900460ff16610709576040516302721e1f60e61b815260040160405180910390fd5b600281015460ff1661072e576040516367ca989360e01b815260040160405180910390fd5b8560000361074f5760405163686ee6fd60e11b815260040160405180910390fd5b60008781526003820160205260409020600101541561078157604051631e2c5f2160e01b815260040160405180910390fd5b8054600182015461079291906125b5565b8611156107b257604051634c9038bd60e01b815260040160405180910390fd5b60018101546107c187426125c8565b11156107e0576040516376b6b46d60e11b815260040160405180910390fd5b6040518060c00160405280888152602001878152602001866001600160801b031681526020018561ffff1681526020018460ff1681526020018360ff16815250816003016000898152602001908152602001600020600082015181600001556020820151816001015560408201518160020160006101000a8154816001600160801b0302191690836001600160801b0316021790555060608201518160020160106101000a81548161ffff021916908361ffff16021790555060808201518160020160126101000a81548160ff021916908360ff16021790555060a08201518160020160136101000a81548160ff021916908360ff160217905550905050806005018790806001815401808255809150506001900390600052602060002001600090919091909150558060040160009054906101000a900460ff1660ff168160050180549050111561094757600581015460048201805460ff191660ff9092169190911790555b604080518781526001600160801b038716602082015288918a917f35b63c68833bcda78042a3ed3988eddeeb194ce5c06033acf78aba4abf242435910160405180910390a35050505050505050565b3360009081526007602052604090205460ff161580156109c157506001546001600160a01b03163314155b80156109d857506005546001600160a01b03163314155b156109f857336040516334abcb3560e11b815260040161021691906121b8565b60005b63ffffffff8116841115610a765760016008600087878563ffffffff16818110610a2757610a276125db565b9050602002016020810190610a3c91906123fa565b6001600160a01b031681526020810191909152604001600020805460ff191691151591909117905580610a6e816125f1565b9150506109fb565b507f23cb5cca8a35963a25ebb2c4eac3c7c15cbe85da4d760bd499b63b5aef4f882b8282604051610aa8929190612616565b60405180910390a150505050565b610abe611f86565b6001600160a01b03811660009081526007602052604090205460ff16610af95780604051630ed580c760e41b815260040161021691906121b8565b6001600160a01b0381166000908152600760205260408120805460ff191690556004805460019290610b2c9084906125b5565b90915550506040516001600160a01b038216907fa3b62bc36326052d97ea62d63c3d60308ed4c3ea8ac079dd8499f1e9c4f80c0f90600090a250565b3360009081526007602052604090205460ff16158015610b9357506001546001600160a01b03163314155b15610bb357336040516334abcb3560e11b815260040161021691906121b8565b60008381526009602052604090206002810154610100900460ff16610beb576040516302721e1f60e61b815260040160405180910390fd5b600281015460ff16610c10576040516367ca989360e01b815260040160405180910390fd5b610c1a8184611fb5565b506000838152600382016020526040902060028101546001600160801b03808516911603610c6657604051633728b83d60e01b81526001600160801b0384166004820152602401610216565b6002810180546001600160801b0319166001600160801b038516908117909155604051908152849086907f612ba4ef12b2b79991215d177141f394f4fcfc6750f259397144dae2bbc83d889060200160405180910390a35050505050565b3360009081526007602052604090205460ff16158015610cef57506001546001600160a01b03163314155b8015610d0657506005546001600160a01b03163314155b15610d2657336040516334abcb3560e11b815260040161021691906121b8565b60005b63ffffffff8116841115610da45760006008600087878563ffffffff16818110610d5557610d556125db565b9050602002016020810190610d6a91906123fa565b6001600160a01b031681526020810191909152604001600020805460ff191691151591909117905580610d9c816125f1565b915050610d29565b507f6476b062b144687c53f4c1237758938ce24fbd5f0bc2bf1246da9953262c3a618282604051610aa8929190612616565b610dde61201d565b336000908152600a60209081526040808320868452600990925282206002810154919291610100900460ff16610e27576040516302721e1f60e61b815260040160405180910390fd5b600281015460ff16610e4c576040516367ca989360e01b815260040160405180910390fd5b610e568186611fb5565b5060005b8354811015610fac576000848281548110610e7757610e776125db565b90600052602060002090600502019050878160000154148015610e9d5750868160010154145b8015610eac5750858160030154145b8015610ebd5750600481015460ff16155b15610fa3576000878152600380850160205260409091206001015490820154610ee691906125c8565b42108015610ef75750826001015442105b15610f155760405163e733132560e01b815260040160405180910390fd5b60048101805460ff191660011790556002810154600354610f4c916001600160a01b039091169033906001600160801b0316612047565b6002810154604080516001600160801b03909216825242602083015288918a9133917f20ca19c7c6129b642ec3848d7e90fcfb517179d94feaf74a19a56202d201d58a910160405180910390a46001935050610fac565b50600101610e5a565b5081610fcb576040516326c1ea3760e11b815260040160405180910390fd5b505050610fd86001600055565b505050565b60068181548110610fed57600080fd5b600091825260209091200154905081565b611006611f86565b6001600160a01b038116158061102957506001546001600160a01b038281169116145b156110495780604051634726455360e11b815260040161021691906121b8565b60026004541061106c5760405163d04f1bb960e01b815260040160405180910390fd5b6001600160a01b03811660009081526007602052604090205460ff16156110a857806040516344097eab60e01b815260040161021691906121b8565b6001600160a01b0381166000908152600760205260408120805460ff1916600190811790915560048054919290916110e19084906125c8565b90915550506040516001600160a01b038216907f44d6d25963f097ad14f29f06854a01f575648a1ef82f30e562ccd3889717e33990600090a250565b60405162461bcd60e51b815260206004820152602260248201527f4f776e6572736869702072656e6f756e63656d656e742069732064697361626c604482015261195960f21b6064820152608401610216565b60025433906001600160a01b0316811461119f578060405163118cdaa760e01b815260040161021691906121b8565b6111a8816120a6565b50565b6111b361201d565b3360009081526007602052604090205460ff161580156111de57506001546001600160a01b03163314155b156111fe57336040516334abcb3560e11b815260040161021691906121b8565b6001600160a01b0381166112275780604051634726455360e11b815260040161021691906121b8565b47600081900361123757506112c0565b6000826001600160a01b03168260405160006040518083038185875af1925050503d8060008114611284576040519150601f19603f3d011682016040523d82523d6000602084013e611289565b606091505b50509050806112bd5760405163163528f160e11b81526001600160a01b038416600482015260248101839052604401610216565b50505b6111a86001600055565b3360009081526007602052604090205460ff161580156112f557506001546001600160a01b03163314155b1561131557336040516334abcb3560e11b815260040161021691906121b8565b60008181526009602052604090206002810154610100900460ff1661134d576040516302721e1f60e61b815260040160405180910390fd5b600281015460ff16611372576040516367ca989360e01b815260040160405180910390fd5b8054421061139357604051633567509560e21b815260040160405180910390fd5b60028101805460ff191690556040516000815282907f70584590905e6addd91f9f2e0b4b304922e6138df0bea959547fe979a2655c519060200160405180910390a25050565b6113e161201d565b3360009081526007602052604090205460ff1615801561140c57506001546001600160a01b03163314155b1561142c57336040516334abcb3560e11b815260040161021691906121b8565b6001600160a01b0383166114555782604051634726455360e11b815260040161021691906121b8565b6001600160a01b03821661147e5781604051634726455360e11b815260040161021691906121b8565b6040516331a9108f60e11b815260048101829052829030906001600160a01b03831690636352211e90602401602060405180830381865afa1580156114c7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114eb9190612645565b6001600160a01b03161461152457604051630878c0f760e21b81526001600160a01b038416600482015260248101839052604401610216565b604051632142170760e11b81523060048201526001600160a01b038581166024830152604482018490528216906342842e0e90606401600060405180830381600087803b15801561157457600080fd5b505af1158015611588573d6000803e3d6000fd5b5050505050610fd86001600055565b61159f611f86565b6001600160a01b0381166115c85780604051634726455360e11b815260040161021691906121b8565b6005546001600160a01b03908116908216036115f95780604051636e41cf5b60e01b815260040161021691906121b8565b600580546001600160a01b0319166001600160a01b0383169081179091556040517f61725271db3871459e92d02112e0678bf8f5b83a1ef17c5084933d16fc772e3090600090a250565b3360009081526007602052604090205460ff1615801561166e57506001546001600160a01b03163314155b1561168e57336040516334abcb3560e11b815260040161021691906121b8565b81158061169b5750428211155b156116bc576040516313ec045b60e21b815260048101839052602401610216565b8181116116df576040516313ec045b60e21b815260048101829052602401610216565b600083815260096020526040902060020154610100900460ff161561171757604051630188c99160e11b815260040160405180910390fd5b6000838152600960209081526040808320858155600180820186905560028201805461ffff19166101011790556006805491820181559094527ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f909301869055805185815291820184905285917f704e80777b19ae920d50255d1ebd98770035d35b444f893abba5b885b232f547910160405180910390a250505050565b3360009081526008602052604090205460ff166117e75733604051631c60f2e560e21b815260040161021691906121b8565b6117ef61201d565b806001600160801b031660000361182457604051633728b83d60e01b81526001600160801b0382166004820152602401610216565b60008381526009602052604090206002810154610100900460ff1661185c576040516302721e1f60e61b815260040160405180910390fd5b600281015460ff16611881576040516367ca989360e01b815260040160405180910390fd5b80544210156118a35760405163a0899fdd60e01b815260040160405180910390fd5b428160010154116118c7576040516310bbe4a760e21b815260040160405180910390fd5b6118d18184611fb5565b506000838152600382016020526040902060028101546001600160801b03908116908416101561191457604051632fcd1a0f60e01b815260040160405180910390fd5b816001015481600101544261192991906125c8565b1115611948576040516376b6b46d60e11b815260040160405180910390fd5b336000818152600a60209081526040808320815160a0810183528a81528084018a81526001600160801b038a8116948301858152426060850190815260808501898152865460018082018955978b529890992094516005909802909401968755915193860193909355516002850180546001600160801b03191691909316179091555160038084019190915592516004909201805460ff1916921515929092179091559054611a04926001600160a01b039091169130906120bf565b604080516001600160801b03851681524260208201528591879133917f21ebfff898d23a2bd3410e967c33b57666543a425320588a2c362da1687a7904910160405180910390a45050610fd86001600055565b60606006805480602002602001604051908101604052809291908181526020018280548015611aa557602002820191906000526020600020905b815481526020019060010190808311611a91575b5050505050905090565b600a6020528160005260406000208181548110611acb57600080fd5b6000918252602090912060059091020180546001820154600283015460038401546004909401549295509093506001600160801b0316919060ff1685565b600082815260096020526040812060028101548291829182918291610100900460ff16611b49576040516302721e1f60e61b815260040160405180910390fd5b611b538188611fb5565b50600096875260030160205250506040909320600181015460029091015490956001600160801b038216955061ffff600160801b830416945060ff600160901b830481169450600160981b9092049091169150565b3360009081526007602052604090205460ff16158015611bd357506001546001600160a01b03163314155b15611bf357336040516334abcb3560e11b815260040161021691906121b8565b60008281526009602052604090206002810154610100900460ff16611c2b576040516302721e1f60e61b815260040160405180910390fd5b600281015460ff16611c50576040516367ca989360e01b815260040160405180910390fd5b42816001015411611c74576040516310bbe4a760e21b815260040160405180910390fd5b80548211611c95576040516338af65f760e01b815260040160405180910390fd5b6001810182905560405182815283907f93a9cc9ab27a298cf5e8c5c8677676c67746e38da2f4aadf64da88b67541855e9060200160405180910390a2505050565b611cde61201d565b3360009081526007602052604090205460ff16158015611d0957506001546001600160a01b03163314155b15611d2957336040516334abcb3560e11b815260040161021691906121b8565b6001600160a01b038316611d525782604051634726455360e11b815260040161021691906121b8565b6001600160a01b038216611d7b5781604051634726455360e11b815260040161021691906121b8565b6040516370a0823160e01b815283906000906001600160a01b038316906370a0823190611dac9030906004016121b8565b602060405180830381865afa158015611dc9573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ded9190612662565b905082811015611e1a5760405163b7ddd88b60e01b81526004810182905260248101849052604401610216565b821580611e25575080155b15611e4657604051633728b83d60e01b815260048101849052602401610216565b611e5a6001600160a01b0383168585612047565b5050610fd86001600055565b611e6e611f86565b600280546001600160a01b0383166001600160a01b03199091168117909155611e9f6001546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b6001600160a01b0381166000908152600a60209081526040808320805482518185028101850190935280835260609492939192909184015b82821015611f7b5760008481526020908190206040805160a08101825260058602909201805483526001808201548486015260028201546001600160801b031692840192909252600381015460608401526004015460ff16151560808301529083529092019101611f0f565b505050509050919050565b6001546001600160a01b03163314611fb3573360405163118cdaa760e01b815260040161021691906121b8565b565b6000805b6005840154811015611ffd5782846005018281548110611fdb57611fdb6125db565b906000526020600020015403611ff5576001915050612017565b600101611fb9565b5060405163a291b30360e01b815260040160405180910390fd5b92915050565b60026000540361204057604051633ee5aeb560e01b815260040160405180910390fd5b6002600055565b6040516001600160a01b03838116602483015260448201839052610fd891859182169063a9059cbb906064015b604051602081830303815290604052915060e01b6020820180516001600160e01b0383818316178352505050506120fe565b600280546001600160a01b03191690556111a881612166565b6040516001600160a01b0384811660248301528381166044830152606482018390526120f89186918216906323b872dd90608401612074565b50505050565b600080602060008451602086016000885af180612121576040513d6000823e3d81fd5b50506000513d91508115612139578060011415612146565b6001600160a01b0384163b155b156120f85783604051635274afe760e01b815260040161021691906121b8565b600180546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6001600160a01b0391909116815260200190565b6000602082840312156121de57600080fd5b5035919050565b600081518084526020840193506020830160005b828110156122175781518652602095860195909101906001016121f9565b5093949350505050565b848152836020820152821515604082015260806060820152600061224860808301846121e5565b9695505050505050565b80356001600160801b038116811461226957600080fd5b919050565b803560ff8116811461226957600080fd5b600080600080600080600060e0888a03121561229a57600080fd5b8735965060208801359550604088013594506122b860608901612252565b9350608088013561ffff811681146122cf57600080fd5b92506122dd60a0890161226e565b91506122eb60c0890161226e565b905092959891949750929550565b60008083601f84011261230b57600080fd5b50813567ffffffffffffffff81111561232357600080fd5b60208301915083602082850101111561233b57600080fd5b9250929050565b6000806000806040858703121561235857600080fd5b843567ffffffffffffffff81111561236f57600080fd5b8501601f8101871361238057600080fd5b803567ffffffffffffffff81111561239757600080fd5b8760208260051b84010111156123ac57600080fd5b60209182019550935085013567ffffffffffffffff8111156123cd57600080fd5b6123d9878288016122f9565b95989497509550505050565b6001600160a01b03811681146111a857600080fd5b60006020828403121561240c57600080fd5b8135612417816123e5565b9392505050565b60008060006060848603121561243357600080fd5b833592506020840135915061244a60408501612252565b90509250925092565b60008060006060848603121561246857600080fd5b505081359360208301359350604090920135919050565b60008060006060848603121561249457600080fd5b833561249f816123e5565b925060208401356124af816123e5565b929592945050506040919091013590565b60208152600061241760208301846121e5565b600080604083850312156124e657600080fd5b82356124f1816123e5565b946020939093013593505050565b6000806040838503121561251257600080fd5b50508035926020909101359150565b602080825282518282018190526000918401906040840190835b8181101561259457835180518452602081015160208501526001600160801b036040820151166040850152606081015160608501526080810151151560808501525060a08301925060208401935060018101905061253b565b509095945050505050565b634e487b7160e01b600052601160045260246000fd5b818103818111156120175761201761259f565b808201808211156120175761201761259f565b634e487b7160e01b600052603260045260246000fd5b600063ffffffff821663ffffffff810361260d5761260d61259f565b60010192915050565b60208152816020820152818360408301376000818301604090810191909152601f909201601f19160101919050565b60006020828403121561265757600080fd5b8151612417816123e5565b60006020828403121561267457600080fd5b505191905056fea2646970667358221220dbbc48add9fadfaf4acc9651e319b751b521019695083c3156b907f506e1f49e64736f6c634300081c0033

Verified Source Code Full Match

Compiler: v0.8.28+commit.7893614a EVM: paris Optimization: Yes (150 runs)
Ownable.sol 100 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../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.
 *
 * The initial owner is set to the address provided by the deployer. 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;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

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

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @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 {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @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 {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _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);
    }
}
Ownable2Step.sol 67 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.20;

import {Ownable} from "./Ownable.sol";

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * This extension of the {Ownable} contract includes a two-step mechanism to transfer
 * ownership, where the new owner must call {acceptOwnership} in order to replace the
 * old one. This can help prevent common mistakes, such as transfers of ownership to
 * incorrect accounts, or to contracts that are unable to interact with the
 * permission system.
 *
 * The initial owner is specified at deployment time in the constructor for `Ownable`. This
 * can later be changed with {transferOwnership} and {acceptOwnership}.
 *
 * This module is used through inheritance. It will make available all functions
 * from parent (Ownable).
 */
abstract contract Ownable2Step is Ownable {
    address private _pendingOwner;

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

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

    /**
     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current owner.
     *
     * Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.
     */
    function transferOwnership(address newOwner) public virtual override onlyOwner {
        _pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner(), newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual override {
        delete _pendingOwner;
        super._transferOwnership(newOwner);
    }

    /**
     * @dev The new owner accepts the ownership transfer.
     */
    function acceptOwnership() public virtual {
        address sender = _msgSender();
        if (pendingOwner() != sender) {
            revert OwnableUnauthorizedAccount(sender);
        }
        _transferOwnership(sender);
    }
}
IERC1363.sol 86 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (interfaces/IERC1363.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC165} from "./IERC165.sol";

/**
 * @title IERC1363
 * @dev Interface of the ERC-1363 standard as defined in the https://eips.ethereum.org/EIPS/eip-1363[ERC-1363].
 *
 * Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract
 * after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction.
 */
interface IERC1363 is IERC20, IERC165 {
    /*
     * Note: the ERC-165 identifier for this interface is 0xb0202a11.
     * 0xb0202a11 ===
     *   bytes4(keccak256('transferAndCall(address,uint256)')) ^
     *   bytes4(keccak256('transferAndCall(address,uint256,bytes)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256)')) ^
     *   bytes4(keccak256('transferFromAndCall(address,address,uint256,bytes)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256)')) ^
     *   bytes4(keccak256('approveAndCall(address,uint256,bytes)'))
     */

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism
     * and then calls {IERC1363Receiver-onTransferReceived} on `to`.
     * @param from The address which you want to send tokens from.
     * @param to The address which you want to transfer to.
     * @param value The amount of tokens to be transferred.
     * @param data Additional data with no specified format, sent in call to `to`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function transferFromAndCall(address from, address to, uint256 value, bytes calldata data) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value) external returns (bool);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens and then calls {IERC1363Spender-onApprovalReceived} on `spender`.
     * @param spender The address which will spend the funds.
     * @param value The amount of tokens to be spent.
     * @param data Additional data with no specified format, sent in call to `spender`.
     * @return A boolean value indicating whether the operation succeeded unless throwing.
     */
    function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
}
IERC165.sol 6 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../utils/introspection/IERC165.sol";
IERC20.sol 6 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../token/ERC20/IERC20.sol";
IERC20.sol 79 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-20 standard as defined in the ERC.
 */
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);
}
SafeERC20.sol 199 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";
import {Address} from "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 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 {
    /**
     * @dev An operation with an ERC-20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @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.encodeCall(token.transfer, (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.encodeCall(token.transferFrom, (from, to, 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.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @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.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

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

    /**
     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target
     * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @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 {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @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 silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}
IERC721.sol 135 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC-721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC-721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
     *   {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the address zero.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}
Address.sol 150 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Address.sol)

pragma solidity ^0.8.20;

import {Errors} from "./Errors.sol";

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @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.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert Errors.InsufficientBalance(address(this).balance, amount);
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert Errors.FailedCall();
        }
    }

    /**
     * @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 or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {Errors.FailedCall} error.
     *
     * 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.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @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`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert Errors.InsufficientBalance(address(this).balance, value);
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

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

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

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {Errors.FailedCall}) in case
     * of an unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {Errors.FailedCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {Errors.FailedCall}.
     */
    function _revert(bytes memory returndata) 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
            assembly ("memory-safe") {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert Errors.FailedCall();
        }
    }
}
Context.sol 28 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @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;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}
Errors.sol 34 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Errors.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of common custom errors used in multiple contracts
 *
 * IMPORTANT: Backwards compatibility is not guaranteed in future versions of the library.
 * It is recommended to avoid relying on the error API for critical functionality.
 *
 * _Available since v5.1._
 */
library Errors {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error InsufficientBalance(uint256 balance, uint256 needed);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedCall();

    /**
     * @dev The deployment failed.
     */
    error FailedDeployment();

    /**
     * @dev A necessary precompile is missing.
     */
    error MissingPrecompile(address);
}
IERC165.sol 25 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
ReentrancyGuard.sol 87 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

/**
 * @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 EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * 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;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    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
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // 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;
    }
}
CrediHardStake.sol 991 lines
/**
 * @title CrediHardStaking
 * @dev A contract for staking ERC20 tokens into multiple pools with defined staking periods.
 * Supports admin and whitelist management, as well as tracking of staking details.
 * Inherits security features from SafeERC20, ReentrancyGuard, and Ownable2Step.
 */
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";

contract CrediHardStaking is ReentrancyGuard, Ownable2Step {
    using SafeERC20 for IERC20;

    // -------------------------------------------- Variables {{{ --------------------------------------------
    /**
     * @dev ERC20 token used for staking.
     */
    IERC20 public stakingToken;

    /**
     * @notice Tracks the current number of admin accounts.
     * @dev This counter is updated when admins are added or removed. It is limited by `MAX_ADMINS`.
     */
    uint256 public adminCount;

    /**
     * @notice The maximum number of admins allowed for the contract.
     * @dev This value is set as a constant to 2 and ensures the number of admins does not exceed this limit.
     */
    uint256 private constant MAX_ADMINS = 2;

    /**
     * @dev Whitelist admin address.
     */
    address public whitelistAdmin;

    /**
     * @dev Array to track all created pool IDs.
     */
    bytes32[] public poolIds;

    struct StakingPeriod {
        bytes32 periodId;
        uint256 duration; // in seconds
        uint128 minAmount; // minimum amount to stake
        uint16 yield;
        uint8 yieldDecimals; // Number of decimals (e.g., 1 for 10.5, 2 for 10.55)
        uint8 yieldPaymentFreq;
    }

    struct Pool {
        uint256 startTime;
        uint256 endTime;
        bool isValid; // Pool status
        bool exists; // Explicit flag to track if the pool exists
        mapping(bytes32 periodId => StakingPeriod stakePeriod) stakingPeriods; // periodId => StakingPeriod
        uint8 totalPeriods;
        bytes32[] periodIds; // Stores periodIds for quick lookup
    }

    struct StakeInfo {
        bytes32 poolId;
        bytes32 periodId;
        uint128 amount;
        uint256 stakeTime;
        bool withdrawn;
    }

    /**
     * @dev Mapping of admin addresses.
     */
    mapping(address adminAddr => bool flag) public admins;

    /**
     * @dev Mapping of whitelisted wallets.
     */
    mapping(address walletAddr => bool flag) public whitelistedWallets;

    /**
     * @dev Mapping of pool IDs to their respective Pool structures.
     */
    mapping(bytes32 poolId => Pool poolData) public pools;

    /**
     * @dev Mapping of users to their respective staking records.
     */
    mapping(address userAddr => StakeInfo[] stakeRecords) public userStakes;
    // -------------------------------------------- Variables }}} --------------------------------------------

    // -------------------------------------------- Events {{{ --------------------------------------------
    /**
     * @dev Emitted when an admin is added.
     */
    event AdminAdded(address indexed admin);

    /**
     * @dev Emitted when an admin is removed.
     */
    event AdminRemoved(address indexed admin);

    /**
     * @dev Emitted when the whitelist admin is set.
     */
    event WhitelistAdminSet(address indexed whitelistAdmin);

    /**
     * @dev Emitted when multiple wallets are whitelisted in a batch.
     */
    event WalletsWhitelisted(string batchId);

    /**
     * @dev Emitted when multiple wallets are removed from the whitelist in a batch.
     */
    event WalletsRemovedFromWhitelist(string batchId);

    /**
     * @dev Emitted when a new pool is created.
     */
    event PoolCreated(bytes32 indexed poolId, uint256 startTime, uint256 endTime);

    /**
     * @dev Emitted when a pool's status is updated.
     */
    event PoolStatusUpdated(bytes32 indexed poolId, bool isValid);

    /**
     * @dev Emitted when a pool's end time is updated.
     */
    event PoolEndTimeUpdated(bytes32 indexed poolId, uint256 newEndTime);

    /**
     * @dev Emitted when a staking period is set within a pool.
     */
    event StakingPeriodSet(bytes32 indexed poolId, bytes32 indexed periodId, uint256 duration, uint128 minAmount);

    /**
     * @dev Emitted when the minimum staking amount for a period is updated.
     */
    event MinAmountUpdated(bytes32 indexed poolId, bytes32 indexed periodId, uint128 newMinAmount);

    /**
     * @dev Emitted when a user stakes tokens.
     */
    event Staked(address indexed user, bytes32 indexed poolId, bytes32 indexed periodId, uint128 amount, uint256 stakeTime);

    /**
     * @dev Emitted when a user unstakes tokens.
     */
    event Unstaked(address indexed user, bytes32 indexed poolId, bytes32 indexed periodId, uint128 amount, uint256 unstakeTime);
    // -------------------------------------------- Events }}} --------------------------------------------

    // -------------------------------------------- Custom Errors {{{ --------------------------------------------
    /**
     * @notice Thrown when the provided address is invalid (e.g., zero address).
     * @param providedAddress The invalid address provided.
     */
    error InvalidAddress(address providedAddress);
    /**
     * @notice Thrown when the contract does not have sufficient balance.
     * @param to The recipient address where the Ether was intended to be sent.
     * @param amount The amount of Ether that failed to transfer.
     */
    error EtherTransferFailed(address to, uint256 amount);
    /**
     * @notice Thrown when the contract does not have sufficient balance.
     * @param balance The current balance of the contract.
     * @param required The required balance for the operation.
     */
    error InsufficientContractBalance(uint256 balance, uint256 required);
    /**
     * @notice Thrown when the provided amount is invalid (e.g., zero).
     * @param amount The invalid amount provided.
     */
    error InvalidAmount(uint256 amount);
    /**
     * @notice Thrown when the contract does not own the specified NFT.
     * @param nft The address of the NFT contract.
     * @param id The ID of the NFT that the contract is expected to own.
     */
    error NotNFTOwner(address nft, uint256 id);
    /**
     * @notice Thrown when an invalid or unauthorized call is made to the contract.
     * @dev This error is used to signal that the caller attempted an operation 
     *      or interaction that is not allowed or supported by the contract.
     * @param sender The address of the caller who made the invalid call.
     */
    error InvalidCall(address sender);
    /**
     * @notice Thrown when the caller is neither an admin nor the owner.
     * @param caller The address of the caller.
     */
    error CallerNotWhitelistAdminOrAdminOrOwner(address caller);
    /**
     * @notice Thrown when the address is already an admin.
     * @param admin The address that is already an admin.
     */
    error AlreadyAnAdmin(address admin);
    /**
     * @notice Thrown when the maximum number of admins has been reached.
     */
    error AdminLimitReached();
    /**
     * @notice Thrown when trying to remove a non-existent admin.
     * @param admin The address that is not an admin.
     */
    error AdminDoesNotExist(address admin);
    /**
     * @dev Custom error for when a wallet is not whitelisted.
     * This occurs when a user attempts to perform an action that requires whitelisting.
     * @param caller The address of the user attempting the action.
     */
    error WalletNotWhitelisted(address caller);
    /**
     * @dev Custom error for when the provided time is invalid.
     * This occurs when the start or end time is not valid.
     * @param time The time which is failed the validation.
     */
    error InvalidTime(uint256 time);
    /**
     * @dev Custom error for when a pool with the given ID already exists.
     * Ensures pool uniqueness and prevents duplicate entries.
     */
    error PoolAlreadyExists();
    /**
     * @dev Custom error for when a pool does not exist.
     */
    error PoolDoesNotExist();
    /**
     * @dev Custom error for when a pool is already invalid.
     */
    error PoolAlreadyInvalid();
    /**
     * @dev Custom error for when a pool cannot be deactivated after its start time.
     */
    error CannotDeactivateAfterStartTime();
    /**
     * @dev Custom error for when a pool has already ended and its end time cannot be updated.
     */
    error PoolAlreadyEnded();
    /**
     * @dev Custom error for when a new end time is invalid because it is before the pool's start time.
     */
    error InvalidEndTime();
    /**
     * @dev Custom error for when a staking duration is invalid (duration is zero).
     */
    error InvalidStakingDuration();
    /**
     * @dev Custom error for when staking has not started yet.
     */
    error StakingNotStarted();
    /**
     * @dev Custom error for when the staking amount is below the minimum requirement.
     */
    error AmountBelowMinimum();
    /**
     * @dev Custom error for when the token transfer fails.
     */
    error TokenTransferFailed();
    /**
     * @dev Custom error for when the staking period or pool has not yet ended.
     */
    error StakingPeriodNotEnded();
    /**
     * @dev Custom error for when no matching active stake is found.
     */
    error NoActiveStakeFound();
    /**
     * @dev Custom error for when the same whitelistAdmin is added.
     * @param whitelistAdmin The address that is being set as whitelistAdmin.
     */
    error AlreadyWhitelistAdmin(address whitelistAdmin);
    /**
     * @dev Custom error for when the staking exceed the pool endtime.
     */
    error StakingPeriodExceedsPoolEnd();
    /**
     * @dev Custom error for when the staking period Id duplication.
     */
    error StakingPeriodAlreadyExists();
    /**
     * @dev Custom error for when the staking duration exceeds Pool duration.
     */
    error StakingDurationExceedsPoolDuration();
    /**
     * @dev Custom error for when the staking period Id not available in the Pool.
     */
    error PeriodDoesNotExist();
    // -------------------------------------------- Custom Errors }}} --------------------------------------------

    // -------------------------------------------- MODIFIERS {{{ --------------------------------------------
    /**
     * @dev Modifier to restrict access to only admins or the contract owner.
     */
    modifier onlyAdmin() {
        if (!admins[msg.sender] && msg.sender != owner()) {
            revert CallerNotWhitelistAdminOrAdminOrOwner(msg.sender);
        }
        _;
    }

    /**
     * @dev Modifier to restrict access to the whitelist admin, contract owner, or an admin.
     */
    modifier onlyWhitelistAdmin() {
        if (!admins[msg.sender] && msg.sender != owner() && msg.sender != whitelistAdmin) {
            revert CallerNotWhitelistAdminOrAdminOrOwner(msg.sender);
        }
        _;
    }

    /**
     * @dev Modifier to restrict actions to only whitelisted wallets.
     */
    modifier onlyWhitelisted() {
        if (!whitelistedWallets[msg.sender]) {
            revert WalletNotWhitelisted(msg.sender);
        }
        _;
    }
    // -------------------------------------------- MODIFIERS }}} --------------------------------------------

    /**
     * @dev Constructor that initializes the staking token and assigns the contract deployer as the owner.
     * @param _stakingToken Address of the ERC20 token that will be used for staking.
     */
    constructor(address _stakingToken) Ownable(_msgSender()) {
        if (_stakingToken == address(0)) {
            revert InvalidAddress(_stakingToken);
        }
        stakingToken = IERC20(_stakingToken);
    }

    /**
     * @notice Prevents the renouncement of contract ownership.
     * @dev Overrides the default `renounceOwnership` function to always revert. 
     *      This ensures that the contract ownership cannot be renounced.
     * @custom:throws Always reverts with the message "Ownership renouncement is disabled".
     */
    function renounceOwnership() public pure override {
        revert("Ownership renouncement is disabled");
    }

    // -------------------------------------------- OWNER Functions {{{ --------------------------------------------
    /**
     * @notice Adds a new admin to the contract.
     * @dev Only the contract owner can add a new admin. The number of admins is limited by `MAX_ADMINS`.
     * @param _admin The address to be added as an admin.
     * @custom:requirements
     * - `_admin` must not be the zero address.
     * - The total number of admins must not exceed `MAX_ADMINS`.
     * - The address must not already be an admin.
     * @custom:throws InvalidAddress Thrown if the `_admin` address is the zero address.
     * @custom:throws AdminLimitReached Thrown if the number of admins has reached the `MAX_ADMINS` limit.
     * @custom:throws AlreadyAnAdmin Thrown if the `_admin` address is already an admin.
     * @custom:emits Emits `AdminAdded` with the address of the new admin.
     */
    function addAdmin(address _admin) external onlyOwner {
        if (_admin == address(0) || _admin == owner()) {
            revert InvalidAddress(_admin);
        }
        if (adminCount >= MAX_ADMINS) {
            revert AdminLimitReached();
        }
        if (admins[_admin]) {
            revert AlreadyAnAdmin(_admin);
        }

        admins[_admin] = true;
        adminCount += 1;
        emit AdminAdded(_admin);
    }

    /**
     * @notice Removes an existing admin from the contract.
     * @dev Only the contract owner can remove an admin. The `adminCount` is decremented upon successful removal.
     * @param _admin The address of the admin to be removed.
     * @custom:requirements
     * - The `_admin` address must already be an admin.
     * @custom:throws AdminDoesNotExist Thrown if the `admin` address is not an admin.
     * @custom:emits Emits `AdminRemoved` with the address of the removed admin.
     */
    function removeAdmin(address _admin) external onlyOwner {
        if (!admins[_admin]) {
            revert AdminDoesNotExist(_admin);
        }

        admins[_admin] = false;
        adminCount -= 1;
        emit AdminRemoved(_admin);
    }

    /**
     * @dev Sets the whitelist admin for the contract.
     * Can only be called by the owner.
     * Ensures that the provided address is not a zero address.
     * @param _admin Address of the new whitelist admin.
     */
    function setWhitelistAdmin(address _admin) external onlyOwner {
        if (_admin == address(0)) {
            revert InvalidAddress(_admin);
        }
        if (_admin == whitelistAdmin) {
            revert AlreadyWhitelistAdmin(_admin);
        }

        whitelistAdmin = _admin;
        emit WhitelistAdminSet(_admin);
    }
    // -------------------------------------------- OWNER Functions }}} --------------------------------------------

    // -------------------------------------------- WhiteListAdmin Functions }}} --------------------------------------------
    /**
     * @dev Whitelists multiple wallets in a batch.
     * Emits a single event with a unique batch identifier to track the operation.
     * Can only be called by the whitelist admin.
     * @param _wallets Array of wallet addresses to be whitelisted.
     * @param batchId Unique identifier for this batch operation.
     */
    function whitelistWallets(address[] calldata _wallets, string calldata batchId) external onlyWhitelistAdmin {
        for (uint32 i = 0; i < _wallets.length; i++) {
            whitelistedWallets[_wallets[i]] = true;
        }
        emit WalletsWhitelisted(batchId); // Single event with batch identifier
    }

    /**
     * @dev Removes multiple wallets from the whitelist in a batch.
     * Emits a single event with a unique batch identifier to track the operation.
     * Can only be called by the whitelist admin.
     * @param _wallets Array of wallet addresses to be removed from the whitelist.
     * @param batchId Unique identifier for this batch operation.
     */
    // Remove multiple wallets from whitelist with a batch ID
    function removeWalletsFromWhitelist(address[] calldata _wallets, string calldata batchId) external onlyWhitelistAdmin {
        for (uint32 i = 0; i < _wallets.length; i++) {
            whitelistedWallets[_wallets[i]] = false;
        }
        emit WalletsRemovedFromWhitelist(batchId); // Single event with batch identifier
    }
    // -------------------------------------------- WhiteListAdmin Functions }}} --------------------------------------------

    // -------------------------------------------- Admin Functions {{{ --------------------------------------------
    // -------------------------------------------- Pool Management {{{ --------------------------------------------
    /**
     * @dev Creates a new staking pool with a specified start and end time.
     * Can only be called by an admin.
     * Ensures the pool ID is unique before creation.
     * Newly created pools start in an valid state.
     * @param poolId Unique identifier for the pool.
     * @param _startTime The timestamp when the pool starts accepting stakes.
     * @param _endTime The timestamp when the pool stops accepting stakes.
     */
    function createPool(bytes32 poolId, uint256 _startTime, uint256 _endTime) external onlyAdmin {
        if (_startTime == 0 || _startTime <= block.timestamp) {
            revert InvalidTime(_startTime);
        }
        if (_endTime <= _startTime) {
            revert InvalidTime(_endTime);
        }
        if (pools[poolId].exists) {
            revert PoolAlreadyExists();
        }

        Pool storage pool = pools[poolId];
        pool.startTime = _startTime;
        pool.endTime = _endTime;
        pool.isValid = true; // Pool starts as valid
        pool.exists = true; // Explicitly mark the pool as created

        poolIds.push(poolId); // Store the pool ID for tracking

        emit PoolCreated(poolId, _startTime, _endTime);
    }

    /**
     * @dev Sets a pool to invalid status.
     * Can only be called by an admin.
     * Ensures the pool exists and is currently valid.
     * The pool can only be deactivated before the start time.
     * @param _poolId Unique identifier of the pool to be deactivated.
     */
    function setPoolInvalid(bytes32 _poolId) external onlyAdmin {
        Pool storage pool = pools[_poolId]; // Use bytes32 as the key

        if (!pool.exists) {
            revert PoolDoesNotExist();
        }
        if (!pool.isValid) {
            revert PoolAlreadyInvalid();
        }
        if (block.timestamp >= pool.startTime) {
            revert CannotDeactivateAfterStartTime();
        }

        pool.isValid = false;
        emit PoolStatusUpdated(_poolId, false); // Updated event parameter type
    }

    /**
     * @dev Updates the end time of an existing pool.
     * Can only be called by an admin.
     * Ensures the pool exists and is still valid.
     * The new end time must be after the pool's start time.
     * The pool must not have already ended.
     * @param _poolId Unique identifier of the pool.
     * @param _newEndTime The new end time for the pool.
     */
    function updatePoolEndTime(bytes32 _poolId, uint256 _newEndTime) external onlyAdmin {
        Pool storage pool = pools[_poolId];
        if (!pool.exists) {
            revert PoolDoesNotExist();
        }
        if (!pool.isValid) {
            revert PoolAlreadyInvalid();
        }

        if (pool.endTime <= block.timestamp) {
            revert PoolAlreadyEnded();
        }
        if (_newEndTime <= pool.startTime) {
            revert InvalidEndTime();
        }

        pool.endTime = _newEndTime;
        emit PoolEndTimeUpdated(_poolId, _newEndTime);
    }
    // -------------------------------------------- Pool Management }}} --------------------------------------------

    // -------------------------------------------- Staking Period Management {{{ --------------------------------------------
    /**
     * @dev Sets a staking period within a specified pool.
     * Can only be called by an admin.
     * Ensures the pool exists and is valid before adding the staking period.
     * Stores the staking period with a unique period ID.
     * Updates the total number of staking periods in the pool.
     * @param _poolId Unique identifier of the pool.
     * @param _periodId Unique identifier of the staking period.
     * @param _duration Duration of the staking period in seconds.
     * @param _minAmount Minimum amount required to stake in this period.
     * @param _yield Yield percentage for the staking period.
     * @param _yieldDecimals Number of decimals in the yield.
     * @param _yieldPaymentFreq Frequency of yield payments for the staking period.
     */
    function setStakingPeriod(
        bytes32 _poolId,
        bytes32 _periodId,
        uint256 _duration,
        uint128 _minAmount,
        uint16 _yield,
        uint8 _yieldDecimals,
        uint8 _yieldPaymentFreq
    ) external onlyAdmin {
        Pool storage pool = pools[_poolId];
        if (!pool.exists) {
            revert PoolDoesNotExist();
        }
        if (!pool.isValid) {
            revert PoolAlreadyInvalid();
        }
        if (_duration == 0) {
            revert InvalidStakingDuration();
        }
        if (pool.stakingPeriods[_periodId].duration > 0) { // If duration is set, period exists
            revert StakingPeriodAlreadyExists();
        }
        // Ensure the staking period duration does not exceed the total pool duration
        if (_duration > pool.endTime - pool.startTime) {
            revert StakingDurationExceedsPoolDuration();
        }
        // Ensure that the staking period can still be valid considering the current time
        if (block.timestamp + _duration > pool.endTime) {
            revert StakingPeriodExceedsPoolEnd();
        }

        pool.stakingPeriods[_periodId] = StakingPeriod(_periodId, _duration, _minAmount, _yield, _yieldDecimals, _yieldPaymentFreq);
        pool.periodIds.push(_periodId); // Store period ID for quick lookup

        if (pool.periodIds.length > pool.totalPeriods) {
            pool.totalPeriods = uint8(pool.periodIds.length);
        }

        emit StakingPeriodSet(_poolId, _periodId, _duration, _minAmount);
    }

    /**
     * @dev Updates the minimum staking amount for a specific staking period within a pool.
     * Can only be called by an admin.
     * Ensures the pool exists and is valid before updating the staking period.
     * Ensures the staking period is valid before modifying the minimum amount.
     * @param _poolId Unique identifier of the pool.
     * @param _periodId Unique identifier of the staking period.
     * @param _newMinAmount The new minimum staking amount for the specified period.
     */
    function updateMinAmount(bytes32 _poolId, bytes32 _periodId, uint128 _newMinAmount) external onlyAdmin {
        Pool storage pool = pools[_poolId];
        if (!pool.exists) {
            revert PoolDoesNotExist();
        }
        if (!pool.isValid) {
            revert PoolAlreadyInvalid();
        }

        // check if periodId exists
        _validatePeriodExists(pool, _periodId);

        // Retrieve the staking period
        StakingPeriod storage period = pool.stakingPeriods[_periodId];

        // Check if the new minAmount is the same as the current one
        if (period.minAmount == _newMinAmount) {
            revert InvalidAmount(_newMinAmount);
        }

        // Now it's safe to update minAmount
        period.minAmount = _newMinAmount;

        emit MinAmountUpdated(_poolId, _periodId, _newMinAmount);
    }
    // -------------------------------------------- Staking Period Management }}} --------------------------------------------
    // -------------------------------------------- Admin Functions }}} --------------------------------------------

    // -------------------------------------------- Staking {{{ --------------------------------------------
    /**
     * @dev Allows a whitelisted user to stake a specified amount into a staking pool and period.
     * Ensures the pool exists and is valid before allowing staking.
     * Ensures the staking period is valid and the amount meets the minimum staking requirement.
     * Transfers the staked tokens from the user to the contract.
     * Emits a `Staked` event upon successful staking.
     * @param _poolId Unique identifier of the pool where tokens are staked.
     * @param _periodId Unique identifier of the staking period within the pool.
     * @param _amount Amount of tokens to be staked.
     */
    function stake(bytes32 _poolId, bytes32 _periodId, uint128 _amount) external onlyWhitelisted nonReentrant {
        if (_amount == 0) {
            revert InvalidAmount(_amount);
        }

        Pool storage pool = pools[_poolId];
        if (!pool.exists) {
            revert PoolDoesNotExist();
        }
        if (!pool.isValid) {
            revert PoolAlreadyInvalid();
        }

        if (block.timestamp < pool.startTime) {
            revert StakingNotStarted();
        }

        if (pool.endTime <= block.timestamp) {
            revert PoolAlreadyEnded();
        }

        // check if periodId exists
        _validatePeriodExists(pool, _periodId);

        StakingPeriod storage period = pool.stakingPeriods[_periodId];

        if (_amount < period.minAmount) {
            revert AmountBelowMinimum();
        }

        if (block.timestamp + period.duration > pool.endTime) {
            revert StakingPeriodExceedsPoolEnd();
        }

        userStakes[msg.sender].push(StakeInfo({
            poolId: _poolId,
            periodId: _periodId,
            amount: _amount,
            stakeTime: block.timestamp,
            withdrawn: false
        }));

        // Transfer tokens to contract
        stakingToken.safeTransferFrom(msg.sender, address(this), _amount);

        emit Staked(msg.sender, _poolId, _periodId, _amount, block.timestamp);
    }

    /**
     * @dev Allows a user to unstake tokens from a staking pool and period.
     * Ensures the pool exists and is valid before allowing unstaking.
     * Ensures the user has an active stake in the specified pool and period.
     * Ensures the staking period or pool has ended before unstaking is permitted.
     * Allows partial unstaking while keeping track of the remaining staked amount.
     * Transfers the unstaked tokens back to the user.
     * Emits an `Unstaked` event upon successful unstaking.
     * @param _poolId Unique identifier of the pool from which tokens are unstaked.
     * @param _periodId Unique identifier of the staking period from which tokens are unstaked.
     * @param _stakeTime Time at which the tokens are staked.
     */
    function unstake(bytes32 _poolId, bytes32 _periodId, uint256 _stakeTime) external nonReentrant {
        StakeInfo[] storage stakes = userStakes[msg.sender]; // Fetch user's stakes
        bool found = false;

        // Validate Pool Exists & Is Valid
        Pool storage pool = pools[_poolId];
        if (!pool.exists) {
            revert PoolDoesNotExist();
        }
        if (!pool.isValid) {
            revert PoolAlreadyInvalid();
        }

        // Validate Period Exists
        _validatePeriodExists(pool, _periodId);

        for (uint256 i = 0; i < stakes.length; i++) {
            StakeInfo storage stakeInfo = stakes[i];

            if (
                stakeInfo.poolId == _poolId &&
                stakeInfo.periodId == _periodId &&
                stakeInfo.stakeTime == _stakeTime &&
                !stakeInfo.withdrawn
            ) {
                // Ensure staking period or pool has ended
                if (block.timestamp < stakeInfo.stakeTime + pool.stakingPeriods[_periodId].duration &&
                    block.timestamp < pool.endTime) {
                    revert StakingPeriodNotEnded();
                }

                // Mark as withdrawn
                stakeInfo.withdrawn = true;

                // Transfer full staked amount back to user
                stakingToken.safeTransfer(msg.sender, stakeInfo.amount);

                emit Unstaked(msg.sender, _poolId, _periodId, stakeInfo.amount, block.timestamp);

                found = true;
                break; // Stop once the specific stake is found and processed
            }
        }

        if (!found) {
            revert NoActiveStakeFound();
        }
    }

    // -------------------------------------------- Staking }}} --------------------------------------------

    // -------------------------------------------- GET {{{ --------------------------------------------
    /**
     * @dev Retrieves all staking details of a specified user.
     * Returns an array of `StakeInfo` structs containing staking records.
     * @param user Address of the user whose staking details are being retrieved.
     * @return An array of `StakeInfo` structures containing the user's staking records.
     */
    function getUserStakingDetails(address user) external view returns (StakeInfo[] memory) {
        return userStakes[user];
    }

    /**
     * @dev Checks if a given wallet address is whitelisted.
     * @param _wallet Address of the wallet to check.
     * @return Boolean value indicating whether the wallet is whitelisted.
     */
    function isWalletWhitelisted(address _wallet) external view returns (bool) {
        return whitelistedWallets[_wallet];
    }

    /**
     * @dev Retrieves all created pool IDs.
     * @return An array of bytes32 values representing the pool IDs.
     */
    function getPoolIds() external view returns (bytes32[] memory) {
        return poolIds; // Directly return the stored pool IDs
    }

    /**
     * @notice Retrieves high-level information about a pool, including period IDs.
     * @dev Ensures the pool exists before returning its data.
     * @param poolId The unique identifier of the pool.
     * @return startTime The start time of the pool.
     * @return endTime The end time of the pool.
     * @return isValid The validity status of the pool.
     * @return periodIds The array of staking period IDs associated with the pool.
     * @dev Use `getStakingPeriod` to retrieve detailed staking period info.
     */
    function getPoolInfo(bytes32 poolId) external view returns (
            uint256 startTime,
            uint256 endTime,
            bool isValid,
            bytes32[] memory periodIds
        )
    {
        Pool storage pool = pools[poolId];

        if (!pool.exists) {
            revert PoolDoesNotExist();
        }

        return (pool.startTime, pool.endTime, pool.isValid, pool.periodIds);
    }

    /**
     * @notice Retrieves the staking period details for a given pool and period ID.
     * @dev Ensures the pool exists and that the specified period ID is part of the pool's `periodIds[]` list
     *      before accessing the stakingPeriods mapping. This prevents returning uninitialized data.
     * @param poolId The unique identifier of the pool.
     * @param periodId The unique identifier of the staking period.
     * @return duration The staking duration in seconds.
     * @return minAmount The minimum amount required to stake.
     * @return yield The yield percentage, stored as an integer with `yieldDecimals` precision.
     * @return yieldDecimals The number of decimal places in the yield value.
     * @return yieldPaymentFreq The frequency of yield payments.
     * @dev Reverts if the pool does not exist or if the period ID is not found in `periodIds[]`.
     */
    function getStakingPeriodInfo(bytes32 poolId, bytes32 periodId) 
        external 
        view 
        returns (
            uint256 duration, 
            uint128 minAmount, 
            uint16 yield, 
            uint8 yieldDecimals, 
            uint8 yieldPaymentFreq
        ) 
    {
        Pool storage pool = pools[poolId];

        if (!pool.exists) {
            revert PoolDoesNotExist();
        }

        // check if periodId exists
        _validatePeriodExists(pool, periodId);

        StakingPeriod storage period = pool.stakingPeriods[periodId];

        return (
            period.duration,
            period.minAmount,
            period.yield,
            period.yieldDecimals,
            period.yieldPaymentFreq
        );
    }

    /**
     * @dev Checks if a periodId exists in the given pool.
     * @param pool The pool struct to check within.
     * @param periodId The staking period ID to look for.
     * @return bool Returns `true` if the periodId exists, otherwise reverts.
     */
    function _validatePeriodExists(Pool storage pool, bytes32 periodId) internal view returns (bool) {
        for (uint256 i = 0; i < pool.periodIds.length; i++) {
            if (pool.periodIds[i] == periodId) {
                return true;
            }
        }
        revert PeriodDoesNotExist();
    }
    // -------------------------------------------- GET }}} --------------------------------------------

    // -------------------------------------------- RESCUE {{{ --------------------------------------------
    /**
     * @notice Withdraws all Ether from the contract and transfers it to a specified address.
     * @dev Allows an admin to rescue any Ether accidentally sent to the contract or reclaim fees.
     * @param to The recipient address to which the Ether will be sent.
     * 
     * @custom:requirements
     * - The caller must have the `onlyAdmin` role.
     * - The `to` address must not be the zero address.
     * - The contract must have a non-zero Ether balance.
     * - The Ether transfer must succeed.
     * 
     * @custom:throws InvalidAddress Thrown if the `to` address is the zero address.
     * @custom:throws EtherTransferFailed Thrown if the Ether transfer fails.
     * 
     * @custom:details
     * - The function transfers the entire Ether balance of the contract to the recipient address.
     * - Uses a low-level call with `call` to handle Ether transfer securely and check success.
     * 
     * @custom:emits None.
     */
    function rescue(address to) public nonReentrant onlyAdmin {
        if (to == address(0)) {
            revert InvalidAddress(to);
        }

        uint256 amount = address(this).balance;
        if (amount == 0) return; // No Ether to transfer

        (bool success, ) = to.call{value: amount}("");
        if (!success) {
            revert EtherTransferFailed(to, amount);
        }
    }

    /**
     * @notice Withdraws all ERC20 tokens of a specified type from the contract and transfers them to a recipient.
     * @dev Allows an admin to rescue ERC20 tokens accidentally sent to the contract.
     * @param token The address of the ERC20 token to be rescued.
     * @param to The recipient address to which the ERC20 tokens will be sent.
     * 
     * @custom:requirements
     * - The caller must have the `onlyAdmin` role.
     * - The `token` address must not be the zero address.
     * - The `to` address must not be the zero address.
     * - The contract must hold a non-zero balance of the specified ERC20 token.
     * 
     * @custom:throws InvalidAddress Thrown if the `token` or `to` address is the zero address.
     * 
     * @custom:emits None.
     */
    function rescueToken(address token, address to, uint256 amount) public nonReentrant onlyAdmin {
        if (token == address(0)) {
            revert InvalidAddress(token);
        }

        if (to == address(0)) {
            revert InvalidAddress(to);
        }

        IERC20 erc20Token = IERC20(token);
        uint256 contractBalance = erc20Token.balanceOf(address(this));

        if (contractBalance < amount) {
            revert InsufficientContractBalance(contractBalance, amount);
        }

        if (amount == 0 || contractBalance == 0) {
            revert InvalidAmount(amount);
        }

        erc20Token.safeTransfer(to, amount);
    }

    /**
     * @notice Withdraws an ERC721 NFT from the contract and transfers it to a specified address.
     * @dev Allows the admin to rescue an NFT that was accidentally sent to the contract.
     *      The function checks ownership of the NFT and uses a safe transfer mechanism.
     * @param receiver The address to which the NFT will be transferred.
     * @param nft The address of the ERC721 contract.
     * @param id The ID of the NFT to be rescued.
     * 
     * @custom:requirements
     * - The caller must have the `onlyAdmin` role.
     * - `receiver` must not be the zero address.
     * - `nft` must not be the zero address.
     * - The contract must own the NFT with the specified `id`.
     *
     * @custom:throws InvalidAddress Thrown if the `receiver` or `nft` address is the zero address.
     * @custom:throws NotNFTOwner Thrown if the contract does not own the NFT with the specified `id`.
     * 
     * @custom:emits None.
     */
    function rescueNFT(address receiver, address nft, uint256 id) public nonReentrant onlyAdmin {
        if (receiver == address(0)) {
            revert InvalidAddress(receiver);
        }
        if (nft == address(0)) {
            revert InvalidAddress(nft);
        }

        IERC721 nftContract = IERC721(nft);

        // Verify ownership of the NFT
        if (nftContract.ownerOf(id) != address(this)) {
            revert NotNFTOwner(nft, id);
        }

        // Safe transfer of the NFT
        nftContract.safeTransferFrom(address(this), receiver, id);
    }

    /**
     * @notice Reverts any unrecognized calls to the contract.
     * @dev This function is triggered when an unrecognized function selector is called on the contract.
     *      It also prevents Ether transfers since the function is not payable.
     *
     * @custom:requirements
     * - This function is triggered for calls to non-existent functions or invalid transactions.
     * - Ether cannot be sent to the contract as this function is not payable.
     *
     * @custom:throws InvalidCall Thrown when an unrecognized call is made to the contract.
     */
    fallback() external {
        revert InvalidCall(msg.sender);
    }
    // -------------------------------------------- RESCUE }}} --------------------------------------------
}

Read Contract

adminCount 0x2b7832b3 → uint256
admins 0x429b62e5 → bool
getPoolIds 0xb4f2bb6d → bytes32[]
getPoolInfo 0x09f2c019 → uint256, uint256, bool, bytes32[]
getStakingPeriodInfo 0xdac62752 → uint256, uint128, uint16, uint8, uint8
getUserStakingDetails 0xf93f3b63 → tuple[]
isWalletWhitelisted 0xaac8f967 → bool
owner 0x8da5cb5b → address
pendingOwner 0xe30c3978 → address
poolIds 0x69883b4e → bytes32
pools 0xb5217bb4 → uint256, uint256, bool, bool, uint8
renounceOwnership 0x715018a6
stakingToken 0x72f702f3 → address
userStakes 0xb5d5b5fa → bytes32, bytes32, uint128, uint256, bool
whitelistAdmin 0x4adbe551 → address
whitelistedWallets 0xa80dcfee → bool

Write Contract 17 functions

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

acceptOwnership 0x79ba5097
No parameters
addAdmin 0x70480275
address _admin
createPool 0xb3a2199d
bytes32 poolId
uint256 _startTime
uint256 _endTime
removeAdmin 0x1785f53c
address _admin
removeWalletsFromWhitelist 0x31623f2c
address[] _wallets
string batchId
rescue 0x839006f2
address to
rescueNFT 0x9c13cd4d
address receiver
address nft
uint256 id
rescueToken 0xe5711e8b
address token
address to
uint256 amount
setPoolInvalid 0x8f661c7f
bytes32 _poolId
setStakingPeriod 0x0f29cd58
bytes32 _poolId
bytes32 _periodId
uint256 _duration
uint128 _minAmount
uint16 _yield
uint8 _yieldDecimals
uint8 _yieldPaymentFreq
setWhitelistAdmin 0xa8d49e64
address _admin
stake 0xb4638dee
bytes32 _poolId
bytes32 _periodId
uint128 _amount
transferOwnership 0xf2fde38b
address newOwner
unstake 0x5a8fc544
bytes32 _poolId
bytes32 _periodId
uint256 _stakeTime
updateMinAmount 0x18c3c124
bytes32 _poolId
bytes32 _periodId
uint128 _newMinAmount
updatePoolEndTime 0xe1b202ee
bytes32 _poolId
uint256 _newEndTime
whitelistWallets 0x168c367d
address[] _wallets
string batchId

Recent Transactions

No transactions found for this address