Address Contract Verified
Address
0xc229C139e6F048cdE6E473F163004e50756eB214
Balance
0 ETH
Nonce
1
Code Size
9905 bytes
Creator
0x9e4D6297...034e at tx 0x43cdb813...abbb83
Indexed Transactions
0
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