Address Contract Verified
Address
0xaCFbb0Ce140298e2aA994d52E9c373FC9C3a8e42
Balance
0.119225 ETH
Nonce
1
Code Size
19132 bytes
Creator
0xdC5D8a3c...3825 at tx 0xa24fb699...57d230
Indexed Transactions
0
Contract Bytecode
19132 bytes
0x60806040526004361061026b5760003560e01c806371ce3185116101445780638aee8127116100b6578063e4d2620e1161007a578063e4d2620e146109d2578063e7617bdd14610a0f578063e82b4feb14610a3a578063f2fde38b14610a51578063f7c618c114610a7a578063fc77203c14610aa557610343565b80638aee8127146109115780638da5cb5b1461093a578063b054190814610965578063d46e42ee1461097c578063e30c3978146109a757610343565b80637a87589d116101085780637a87589d146107ff5780637aba20bf146108285780637aca82e6146108535780637dfcdd29146108905780637e2ab50f146108bb57806380e8555a146108e657610343565b806371ce31851461071857806372f702f3146107555780637407c6951461078057806379237db5146107bd57806379ba5097146107e857610343565b80633e58a27a116101dd5780635298d1e3116101a15780635298d1e31461061a578063567e98f9146106455780635bc789d914610670578063631ab8b21461069b5780636497a8a0146106d8578063715018a61461070157610343565b80633e58a27a1461054757806341ce8d211461055e5780634712b0691461059b5780634cdc58e1146105c6578063517c840c1461060357610343565b80631fe414ea1161022f5780631fe414ea1461042557806327596fce146104625780632a480d59146104795780632e17de78146104a4578063308e401e146104cd57806331fd52221461050a57610343565b806310087fb1146103485780631162a72714610371578063156f685a1461039c5780631959a002146103c75780631967e9451461040e57610343565b36610343576000349050600081036102b8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102af9061377f565b60405180910390fd5b80600960008282546102ca91906137d8565b9250508190555060008190506000600854111561032657806008541061030c5780600860008282546102fc919061380c565b9250508190555060009050610325565b6008548161031a919061380c565b905060006008819055505b5b60008103610335575050610341565b61033e81610ad0565b50505b005b600080fd5b34801561035457600080fd5b5061036f600480360381019061036a91906138aa565b610f12565b005b34801561037d57600080fd5b50610386611237565b60405161039391906138f9565b60405180910390f35b3480156103a857600080fd5b506103b161123e565b6040516103be91906138f9565b60405180910390f35b3480156103d357600080fd5b506103ee60048036038101906103e99190613972565b611244565b6040516104059b9a99989796959493929190613a16565b60405180910390f35b34801561041a57600080fd5b506104236112ab565b005b34801561043157600080fd5b5061044c60048036038101906104479190613ae6565b61138d565b60405161045991906138f9565b60405180910390f35b34801561046e57600080fd5b506104776113a5565b005b34801561048557600080fd5b5061048e61173e565b60405161049b91906138f9565b60405180910390f35b3480156104b057600080fd5b506104cb60048036038101906104c69190613b13565b611743565b005b3480156104d957600080fd5b506104f460048036038101906104ef9190613972565b611a28565b60405161050191906138f9565b60405180910390f35b34801561051657600080fd5b50610531600480360381019061052c9190613972565b611b54565b60405161053e91906138f9565b60405180910390f35b34801561055357600080fd5b5061055c611ba0565b005b34801561056a57600080fd5b5061058560048036038101906105809190613ae6565b611dfe565b60405161059291906138f9565b60405180910390f35b3480156105a757600080fd5b506105b0611e74565b6040516105bd91906138f9565b60405180910390f35b3480156105d257600080fd5b506105ed60048036038101906105e89190613972565b611e7a565b6040516105fa91906138f9565b60405180910390f35b34801561060f57600080fd5b50610618611ec6565b005b34801561062657600080fd5b5061062f612178565b60405161063c91906138f9565b60405180910390f35b34801561065157600080fd5b5061065a61217e565b60405161066791906138f9565b60405180910390f35b34801561067c57600080fd5b50610685612184565b6040516106929190613b4f565b60405180910390f35b3480156106a757600080fd5b506106c260048036038101906106bd9190613972565b6121aa565b6040516106cf91906138f9565b60405180910390f35b3480156106e457600080fd5b506106ff60048036038101906106fa9190613972565b6122fa565b005b34801561070d57600080fd5b50610716612431565b005b34801561072457600080fd5b5061073f600480360381019061073a9190613ae6565b612474565b60405161074c91906138f9565b60405180910390f35b34801561076157600080fd5b5061076a61248c565b6040516107779190613bc9565b60405180910390f35b34801561078c57600080fd5b506107a760048036038101906107a29190613972565b6124b0565b6040516107b491906138f9565b60405180910390f35b3480156107c957600080fd5b506107d2612529565b6040516107df91906138f9565b60405180910390f35b3480156107f457600080fd5b506107fd61252f565b005b34801561080b57600080fd5b5061082660048036038101906108219190613be4565b6125be565b005b34801561083457600080fd5b5061083d6128a5565b60405161084a91906138f9565b60405180910390f35b34801561085f57600080fd5b5061087a60048036038101906108759190613ae6565b6128ab565b60405161088791906138f9565b60405180910390f35b34801561089c57600080fd5b506108a5612921565b6040516108b291906138f9565b60405180910390f35b3480156108c757600080fd5b506108d0612927565b6040516108dd91906138f9565b60405180910390f35b3480156108f257600080fd5b506108fb61292d565b60405161090891906138f9565b60405180910390f35b34801561091d57600080fd5b5061093860048036038101906109339190613972565b612934565b005b34801561094657600080fd5b5061094f612a6b565b60405161095c9190613b4f565b60405180910390f35b34801561097157600080fd5b5061097a612a94565b005b34801561098857600080fd5b50610991612c21565b60405161099e91906138f9565b60405180910390f35b3480156109b357600080fd5b506109bc612c27565b6040516109c99190613b4f565b60405180910390f35b3480156109de57600080fd5b506109f960048036038101906109f49190613972565b612c51565b604051610a069190613c11565b60405180910390f35b348015610a1b57600080fd5b50610a24612caa565b604051610a3191906138f9565b60405180910390f35b348015610a4657600080fd5b50610a4f612cb1565b005b348015610a5d57600080fd5b50610a786004803603810190610a739190613972565b612e15565b005b348015610a8657600080fd5b50610a8f612ec2565b604051610a9c9190613bc9565b60405180910390f35b348015610ab157600080fd5b50610aba612ee8565b604051610ac791906138f9565b60405180910390f35b6000810315610f0f57600060065403610b01578060076000828254610af591906137d8565b92505081905550610f0f565b60006127106101f483610b149190613c2c565b610b1e9190613c9d565b905060006127106105dc84610b339190613c2c565b610b3d9190613c9d565b90506000612710611f4085610b529190613c2c565b610b5c9190613c9d565b90506000806005600060016003811115610b7957610b7861399f565b5b6003811115610b8b57610b8a61399f565b5b815260200190815260200160002054118015610ba75750600084115b15610c66576005600060016003811115610bc457610bc361399f565b5b6003811115610bd657610bd561399f565b5b81526020019081526020016000205469d3c21bcecceda100000085610bfb9190613c2c565b610c059190613c9d565b6004600060016003811115610c1d57610c1c61399f565b5b6003811115610c2f57610c2e61399f565b5b81526020019081526020016000206000828254610c4c91906137d8565b925050819055508381610c5f91906137d8565b9050610c8a565b6000841115610c89578360076000828254610c8191906137d8565b925050819055505b5b60006005600060026003811115610ca457610ca361399f565b5b6003811115610cb657610cb561399f565b5b815260200190815260200160002054118015610cd25750600083115b15610d91576005600060026003811115610cef57610cee61399f565b5b6003811115610d0157610d0061399f565b5b81526020019081526020016000205469d3c21bcecceda100000084610d269190613c2c565b610d309190613c9d565b6004600060026003811115610d4857610d4761399f565b5b6003811115610d5a57610d5961399f565b5b81526020019081526020016000206000828254610d7791906137d8565b925050819055508281610d8a91906137d8565b9050610db5565b6000831115610db4578260076000828254610dac91906137d8565b925050819055505b5b600060056000600380811115610dce57610dcd61399f565b5b6003811115610de057610ddf61399f565b5b815260200190815260200160002054118015610dfc5750600082115b15610eb95760056000600380811115610e1857610e1761399f565b5b6003811115610e2a57610e2961399f565b5b81526020019081526020016000205469d3c21bcecceda100000083610e4f9190613c2c565b610e599190613c9d565b60046000600380811115610e7057610e6f61399f565b5b6003811115610e8257610e8161399f565b5b81526020019081526020016000206000828254610e9f91906137d8565b925050819055508181610eb291906137d8565b9050610edd565b6000821115610edc578160076000828254610ed491906137d8565b925050819055505b5b80851115610f0a578085610ef1919061380c565b60076000828254610f0291906137d8565b925050819055505b505050505b50565b610f1a612eee565b6000600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020905060008303610fa0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f9790613d1a565b60405180910390fd5b6000610fab83612f32565b9050610fb633612fb0565b60008260000154111561104d57610fcc3361312e565b8160000154600560008460090160009054906101000a900460ff166003811115610ff957610ff861399f565b5b600381111561100b5761100a61399f565b5b81526020019081526020016000206000828254611028919061380c565b92505081905550816000015460066000828254611045919061380c565b925050819055505b8382600001600082825461106191906137d8565b92505081905550808260090160006101000a81548160ff0219169083600381111561108f5761108e61399f565b5b021790555061109d81613229565b426110a891906137d8565b82600a01819055508160000154600560008360038111156110cc576110cb61399f565b5b60038111156110de576110dd61399f565b5b815260200190815260200160002060008282546110fb91906137d8565b9250508190555081600001546006600082825461111891906137d8565b9250508190555069d3c21bcecceda1000000600460008360038111156111415761114061399f565b5b60038111156111535761115261399f565b5b81526020019081526020016000205483600001546111719190613c2c565b61117b9190613c9d565b82600101819055504282600601819055506111d93330867f000000000000000000000000c5190e7fec4d97a3a3b1ab42dfedac608e2d079373ffffffffffffffffffffffffffffffffffffffff1661330f909392919063ffffffff16565b3373ffffffffffffffffffffffffffffffffffffffff167ff4d012fd2f7cf012e2924304024d2fcba8b56030caafdfafe555950932ccf92f8583604051611221929190613d3a565b60405180910390a25050611233613391565b5050565b625c490081565b60085481565b60036020528060005260406000206000915090508060000154908060010154908060020154908060030154908060040154908060050154908060060154908060070154908060080154908060090160009054906101000a900460ff169080600a015490508b565b6112b361339b565b60006007549050600081036112fd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016112f490613daf565b60405180910390fd5b600060065403611342576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161133990613e41565b60405180910390fd5b600060078190555061135381610ad0565b7f01230b2ddf765d486521aae8ae8d00d1c1bc43144d8519bbde078a14ae1c3d928160405161138291906138f9565b60405180910390a150565b60046020528060005260406000206000915090505481565b6113ad612eee565b6000600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020905060008160000154148015611408575060008160020154145b15611448576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161143f90613ef9565b60405180910390fd5b6000600381111561145c5761145b61399f565b5b8160090160009054906101000a900460ff1660038111156114805761147f61399f565b5b148015611491575060008160000154115b156114d1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114c890613f8b565b60405180910390fd5b6114da3361312e565b60008160020154905060008103611526576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161151d90613ff7565b60405180910390fd5b6000826000015411156115a95769d3c21bcecceda1000000600460008460090160009054906101000a900460ff1660038111156115665761156561399f565b5b60038111156115785761157761399f565b5b81526020019081526020016000205483600001546115969190613c2c565b6115a09190613c9d565b82600101819055505b6000814710156115b957476115bb565b815b905060008103611600576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016115f790614063565b60405180910390fd5b60003373ffffffffffffffffffffffffffffffffffffffff1682604051611626906140b4565b60006040518083038185875af1925050503d8060008114611663576040519150601f19603f3d011682016040523d82523d6000602084013e611668565b606091505b50509050806116ac576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016116a390614115565b60405180910390fd5b818460020160008282546116c0919061380c565b92505081905550818460030160008282546116db91906137d8565b925050819055503373ffffffffffffffffffffffffffffffffffffffff167f47cee97cb7acd717b3c0aa1435d004cd5b3c8c57d70dbceb4e4458bbd60e39d48360405161172891906138f9565b60405180910390a25050505061173c613391565b565b60c881565b61174b612eee565b6000600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000209050600082036117d1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016117c890614181565b60405180910390fd5b8181600001541015611818576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161180f906141ed565b60405180910390fd5b6000600381111561182c5761182b61399f565b5b8160090160009054906101000a900460ff1660038111156118505761184f61399f565b5b03611890576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161188790614259565b60405180910390fd5b61189933612fb0565b6118a23361312e565b818160000160008282546118b6919061380c565b92505081905550428160080181905550818160070160008282546118da91906137d8565b9250508190555081600560008360090160009054906101000a900460ff16600381111561190a5761190961399f565b5b600381111561191c5761191b61399f565b5b81526020019081526020016000206000828254611939919061380c565b925050819055508160066000828254611952919061380c565b9250508190555069d3c21bcecceda1000000600460008360090160009054906101000a900460ff16600381111561198c5761198b61399f565b5b600381111561199e5761199d61399f565b5b81526020019081526020016000205482600001546119bc9190613c2c565b6119c69190613c9d565b81600101819055503373ffffffffffffffffffffffffffffffffffffffff167f85082129d87b2fe11527cb1b3b7a520aeb5aa6913f88a3d8757fe40d1db02fdd83604051611a1491906138f9565b60405180910390a250611a25613391565b50565b600080600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002090506000808260000154118015611ab7575060006003811115611a9057611a8f61399f565b5b8260090160009054906101000a900460ff166003811115611ab457611ab361399f565b5b14155b15611b3b57816001015469d3c21bcecceda1000000600460008560090160009054906101000a900460ff166003811115611af457611af361399f565b5b6003811115611b0657611b0561399f565b5b8152602001908152602001600020548460000154611b249190613c2c565b611b2e9190613c9d565b611b38919061380c565b90505b808260020154611b4b91906137d8565b92505050919050565b6000600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600701549050919050565b611ba8612eee565b6000600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020905060008160070154905060008103611c37576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c2e906142eb565b60405180910390fd5b611c4033612fb0565b611c493361312e565b6000826007018190555080826000016000828254611c6791906137d8565b92505081905550611c888260090160009054906101000a900460ff16613229565b42611c9391906137d8565b82600a018190555080600560008460090160009054906101000a900460ff166003811115611cc457611cc361399f565b5b6003811115611cd657611cd561399f565b5b81526020019081526020016000206000828254611cf391906137d8565b925050819055508060066000828254611d0c91906137d8565b9250508190555069d3c21bcecceda1000000600460008460090160009054906101000a900460ff166003811115611d4657611d4561399f565b5b6003811115611d5857611d5761399f565b5b8152602001908152602001600020548360000154611d769190613c2c565b611d809190613c9d565b82600101819055504282600601819055503373ffffffffffffffffffffffffffffffffffffffff167f4945b153f5e5a5f10f7e019e95fa345e57da1be60c489efe6f8dbf882c13ba4e828460090160009054906101000a900460ff16604051611dea929190613d3a565b60405180910390a25050611dfc613391565b565b6000806003811115611e1357611e1261399f565b5b826003811115611e2657611e2561399f565b5b03611e345760009050611e6f565b60046000836003811115611e4b57611e4a61399f565b5b6003811115611e5d57611e5c61399f565b5b81526020019081526020016000205490505b919050565b61271081565b6000600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600a01549050919050565b611ece612eee565b600073ffffffffffffffffffffffffffffffffffffffff16600a60009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603611f5f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611f5690614357565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600b60009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603611ff0576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611fe7906143c3565b60405180910390fd5b611ff933612fb0565b6000600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020905060008160040154905060008103612088576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161207f9061442f565b60405180910390fd5b60008260040181905550808260050160008282546120a691906137d8565b9250508190555061211e600b60009054906101000a900473ffffffffffffffffffffffffffffffffffffffff163383600a60009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1661330f909392919063ffffffff16565b3373ffffffffffffffffffffffffffffffffffffffff167f153ae99140bfd8bd5ee9c22d86ba154db46dc361e08543023fb52ade1faf0c7a8260405161216491906138f9565b60405180910390a25050612176613391565b565b611f4081565b60065481565b600b60009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600080600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002090506000816000015414806122365750600060038111156122105761220f61399f565b5b8160090160009054906101000a900460ff1660038111156122345761223361399f565b5b145b156122485780600401549150506122f5565b60008160060154148061225f575042816006015410155b156122715780600401549150506122f5565b6000816006015442612283919061380c565b905060006122a18360090160009054906101000a900460ff16613422565b905060006301e133806127106122b79190613c2c565b838386600001546122c89190613c2c565b6122d29190613c2c565b6122dc9190613c9d565b90508084600401546122ee91906137d8565b9450505050505b919050565b61230261339b565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603612371576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612368906144c1565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff16600b60009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f321d81113982fee0c5293dfd1a282ded452ca06c71662019faf356e7a614958760405160405180910390a380600b60006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b61243961339b565b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161246b90614553565b60405180910390fd5b60056020528060005260406000206000915090505481565b7f000000000000000000000000c5190e7fec4d97a3a3b1ab42dfedac608e2d079381565b600080600360008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002090506000816007015411801561250c575080600a01544210155b1561251e578060070154915050612524565b60009150505b919050565b6101f481565b6000612539613504565b90508073ffffffffffffffffffffffffffffffffffffffff1661255a612c27565b73ffffffffffffffffffffffffffffffffffffffff16146125b257806040517f118cdaa70000000000000000000000000000000000000000000000000000000081526004016125a99190613b4f565b60405180910390fd5b6125bb8161350c565b50565b6000600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020905080600a0154421015612648576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161263f906145e5565b60405180910390fd5b600081600001540361268f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161268690614677565b60405180910390fd5b600061269a83612f32565b90506126a533612fb0565b6126ae3361312e565b8160000154600560008460090160009054906101000a900460ff1660038111156126db576126da61399f565b5b60038111156126ed576126ec61399f565b5b8152602001908152602001600020600082825461270a919061380c565b92505081905550816000015460066000828254612727919061380c565b92505081905550600082600001549050818360090160006101000a81548160ff0219169083600381111561275e5761275d61399f565b5b021790555061276c82613229565b4261277791906137d8565b83600a018190555080600560008460038111156127975761279661399f565b5b60038111156127a9576127a861399f565b5b815260200190815260200160002060008282546127c691906137d8565b9250508190555080600660008282546127df91906137d8565b9250508190555069d3c21bcecceda1000000600460008460038111156128085761280761399f565b5b600381111561281a5761281961399f565b5b815260200190815260200160002054826128349190613c2c565b61283e9190613c9d565b83600101819055504283600601819055503373ffffffffffffffffffffffffffffffffffffffff167f4945b153f5e5a5f10f7e019e95fa345e57da1be60c489efe6f8dbf882c13ba4e8284604051612897929190613d3a565b60405180910390a250505050565b60095481565b60008060038111156128c0576128bf61399f565b5b8260038111156128d3576128d261399f565b5b036128e1576000905061291c565b600560008360038111156128f8576128f761399f565b5b600381111561290a5761290961399f565b5b81526020019081526020016000205490505b919050565b60075481565b6103e881565b6212750081565b61293c61339b565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036129ab576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016129a290614709565b60405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff16600a60009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167fab27a2419bd7a3bc605bff66b38aacb84061d9e20edab7f7680ce52e6fcd925660405160405180910390a380600a60006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b612a9c612eee565b6000600360003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020905060008160070154905060008103612b2b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612b229061479b565b60405180910390fd5b81600a0154421015612b72576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612b69906145e5565b60405180910390fd5b60008260070181905550612bc733827f000000000000000000000000c5190e7fec4d97a3a3b1ab42dfedac608e2d079373ffffffffffffffffffffffffffffffffffffffff1661353d9092919063ffffffff16565b3373ffffffffffffffffffffffffffffffffffffffff167f884edad9ce6fa2440d8a54cc123490eb96d2768479d49ff9c7366125a942436482604051612c0d91906138f9565b60405180910390a25050612c1f613391565b565b6105dc81565b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060090160009054906101000a900460ff169050919050565b622e248081565b612cb961339b565b612cc1612eee565b600047905060008103612d09576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612d0090614807565b60405180910390fd5b60003373ffffffffffffffffffffffffffffffffffffffff1682604051612d2f906140b4565b60006040518083038185875af1925050503d8060008114612d6c576040519150601f19603f3d011682016040523d82523d6000602084013e612d71565b606091505b5050905080612db5576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612dac90614899565b60405180910390fd5b8160086000828254612dc791906137d8565b925050819055507f99010623e00d801536d5e2da7de373bda9592873b6d101d65738aa2582e6452282600854604051612e019291906148b9565b60405180910390a15050612e13613391565b565b612e1d61339b565b80600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff16612e7d612a6b565b73ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b600a60009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6105dc81565b6002805403612f29576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60028081905550565b6000600e8260ff1603612f485760019050612fab565b60238260ff1603612f5c5760029050612fab565b60468260ff1603612f705760039050612fab565b6040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612fa29061492e565b60405180910390fd5b919050565b6000600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020905060008160000154148061303b5750600060038111156130155761301461399f565b5b8160090160009054906101000a900460ff1660038111156130395761303861399f565b5b145b8061304a575060008160060154145b1561307c5760008160000154118015613067575060008160060154145b15613076574281600601819055505b5061312b565b600081600601544261308e919061380c565b90506000810361309f57505061312b565b60006130bb8360090160009054906101000a900460ff16613422565b905060006301e133806127106130d19190613c2c565b838386600001546130e29190613c2c565b6130ec9190613c2c565b6130f69190613c9d565b9050600081111561311d578084600401600082825461311591906137d8565b925050819055505b428460060181905550505050505b50565b6000600360008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000209050600081600001541115613225576000816001015469d3c21bcecceda1000000600460008560090160009054906101000a900460ff1660038111156131b8576131b761399f565b5b60038111156131ca576131c961399f565b5b81526020019081526020016000205484600001546131e89190613c2c565b6131f29190613c9d565b6131fc919061380c565b90506000811115613223578082600201600082825461321b91906137d8565b925050819055505b505b5050565b60006001600381111561323f5761323e61399f565b5b8260038111156132525761325161399f565b5b036132625762127500905061330a565b600260038111156132765761327561399f565b5b8260038111156132895761328861399f565b5b0361329957622e2480905061330a565b6003808111156132ac576132ab61399f565b5b8260038111156132bf576132be61399f565b5b036132cf57625c4900905061330a565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016133019061499a565b60405180910390fd5b919050565b61338b848573ffffffffffffffffffffffffffffffffffffffff166323b872dd868686604051602401613344939291906149ba565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506135bc565b50505050565b6001600281905550565b6133a3613504565b73ffffffffffffffffffffffffffffffffffffffff166133c1612a6b565b73ffffffffffffffffffffffffffffffffffffffff1614613420576133e4613504565b6040517f118cdaa70000000000000000000000000000000000000000000000000000000081526004016134179190613b4f565b60405180910390fd5b565b6000600160038111156134385761343761399f565b5b82600381111561344b5761344a61399f565b5b036134595760c890506134ff565b6002600381111561346d5761346c61399f565b5b8260038111156134805761347f61399f565b5b0361348f576103e890506134ff565b6003808111156134a2576134a161399f565b5b8260038111156134b5576134b461399f565b5b036134c4576105dc90506134ff565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016134f690614a3d565b60405180910390fd5b919050565b600033905090565b600160006101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905561353a8161365e565b50565b6135b7838473ffffffffffffffffffffffffffffffffffffffff1663a9059cbb8585604051602401613570929190614a5d565b604051602081830303815290604052915060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506135bc565b505050565b600080602060008451602086016000885af1806135df576040513d6000823e3d81fd5b3d9250600051915050600082146135fa576001811415613616565b60008473ffffffffffffffffffffffffffffffffffffffff163b145b1561365857836040517f5274afe700000000000000000000000000000000000000000000000000000000815260040161364f9190613b4f565b60405180910390fd5b50505050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050816000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b600082825260208201905092915050565b7f43616e6e6f74206465706f736974203020455448000000000000000000000000600082015250565b6000613769601483613722565b915061377482613733565b602082019050919050565b600060208201905081810360008301526137988161375c565b9050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006137e38261379f565b91506137ee8361379f565b9250828201905080821115613806576138056137a9565b5b92915050565b60006138178261379f565b91506138228361379f565b925082820390508181111561383a576138396137a9565b5b92915050565b600080fd5b61384e8161379f565b811461385957600080fd5b50565b60008135905061386b81613845565b92915050565b600060ff82169050919050565b61388781613871565b811461389257600080fd5b50565b6000813590506138a48161387e565b92915050565b600080604083850312156138c1576138c0613840565b5b60006138cf8582860161385c565b92505060206138e085828601613895565b9150509250929050565b6138f38161379f565b82525050565b600060208201905061390e60008301846138ea565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061393f82613914565b9050919050565b61394f81613934565b811461395a57600080fd5b50565b60008135905061396c81613946565b92915050565b60006020828403121561398857613987613840565b5b60006139968482850161395d565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b600481106139df576139de61399f565b5b50565b60008190506139f0826139ce565b919050565b6000613a00826139e2565b9050919050565b613a10816139f5565b82525050565b600061016082019050613a2c600083018e6138ea565b613a39602083018d6138ea565b613a46604083018c6138ea565b613a53606083018b6138ea565b613a60608083018a6138ea565b613a6d60a08301896138ea565b613a7a60c08301886138ea565b613a8760e08301876138ea565b613a956101008301866138ea565b613aa3610120830185613a07565b613ab16101408301846138ea565b9c9b505050505050505050505050565b60048110613ace57600080fd5b50565b600081359050613ae081613ac1565b92915050565b600060208284031215613afc57613afb613840565b5b6000613b0a84828501613ad1565b91505092915050565b600060208284031215613b2957613b28613840565b5b6000613b378482850161385c565b91505092915050565b613b4981613934565b82525050565b6000602082019050613b646000830184613b40565b92915050565b6000819050919050565b6000613b8f613b8a613b8584613914565b613b6a565b613914565b9050919050565b6000613ba182613b74565b9050919050565b6000613bb382613b96565b9050919050565b613bc381613ba8565b82525050565b6000602082019050613bde6000830184613bba565b92915050565b600060208284031215613bfa57613bf9613840565b5b6000613c0884828501613895565b91505092915050565b6000602082019050613c266000830184613a07565b92915050565b6000613c378261379f565b9150613c428361379f565b9250828202613c508161379f565b91508282048414831517613c6757613c666137a9565b5b5092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b6000613ca88261379f565b9150613cb38361379f565b925082613cc357613cc2613c6e565b5b828204905092915050565b7f43616e6e6f74207374616b65203020746f6b656e730000000000000000000000600082015250565b6000613d04601583613722565b9150613d0f82613cce565b602082019050919050565b60006020820190508181036000830152613d3381613cf7565b9050919050565b6000604082019050613d4f60008301856138ea565b613d5c6020830184613a07565b9392505050565b7f4e6f20756e616c6c6f63617465642045544820746f2064697374726962757465600082015250565b6000613d99602083613722565b9150613da482613d63565b602082019050919050565b60006020820190508181036000830152613dc881613d8c565b9050919050565b7f4e6f207374616b65727320696e20616e79207469657220746f2064697374726960008201527f62757465207265776172647320746f0000000000000000000000000000000000602082015250565b6000613e2b602f83613722565b9150613e3682613dcf565b604082019050919050565b60006020820190508181036000830152613e5a81613e1e565b9050919050565b7f4e6f20455448207265776172647320746f20636c61696d206f72206e6f20616360008201527f74697665207374616b6520666f722070656e64696e672063616c63756c61746960208201527f6f6e000000000000000000000000000000000000000000000000000000000000604082015250565b6000613ee3604283613722565b9150613eee82613e61565b606082019050919050565b60006020820190508181036000830152613f1281613ed6565b9050919050565b7f5573657220696e20696e76616c6964207469657220776974682061637469766560008201527f207374616b650000000000000000000000000000000000000000000000000000602082015250565b6000613f75602683613722565b9150613f8082613f19565b604082019050919050565b60006020820190508181036000830152613fa481613f68565b9050919050565b7f4e6f20455448207265776172647320746f20636c61696d000000000000000000600082015250565b6000613fe1601783613722565b9150613fec82613fab565b602082019050919050565b6000602082019050818103600083015261401081613fd4565b9050919050565b7f4e6f2045544820617661696c61626c6520746f20636c61696d00000000000000600082015250565b600061404d601983613722565b915061405882614017565b602082019050919050565b6000602082019050818103600083015261407c81614040565b9050919050565b600081905092915050565b50565b600061409e600083614083565b91506140a98261408e565b600082019050919050565b60006140bf82614091565b9150819050919050565b7f455448207472616e73666572206661696c656400000000000000000000000000600082015250565b60006140ff601383613722565b915061410a826140c9565b602082019050919050565b6000602082019050818103600083015261412e816140f2565b9050919050565b7f43616e6e6f7420756e7374616b65203020746f6b656e73000000000000000000600082015250565b600061416b601783613722565b915061417682614135565b602082019050919050565b6000602082019050818103600083015261419a8161415e565b9050919050565b7f496e73756666696369656e74207374616b656420616d6f756e74000000000000600082015250565b60006141d7601a83613722565b91506141e2826141a1565b602082019050919050565b60006020820190508181036000830152614206816141ca565b9050919050565b7f55736572206e6f7420696e20612076616c6964207374616b696e672074696572600082015250565b6000614243602083613722565b915061424e8261420d565b602082019050919050565b6000602082019050818103600083015261427281614236565b9050919050565b7f4e6f2070656e64696e6720756e7374616b6520616d6f756e7420746f2072657360008201527f74616b6500000000000000000000000000000000000000000000000000000000602082015250565b60006142d5602483613722565b91506142e082614279565b604082019050919050565b60006020820190508181036000830152614304816142c8565b9050919050565b7f52657761726420746f6b656e206e6f7420736574000000000000000000000000600082015250565b6000614341601483613722565b915061434c8261430b565b602082019050919050565b6000602082019050818103600083015261437081614334565b9050919050565b7f546f6b656e207661756c74206e6f742073657400000000000000000000000000600082015250565b60006143ad601383613722565b91506143b882614377565b602082019050919050565b600060208201905081810360008301526143dc816143a0565b9050919050565b7f4e6f20465849207265776172647320746f20636c61696d000000000000000000600082015250565b6000614419601783613722565b9150614424826143e3565b602082019050919050565b600060208201905081810360008301526144488161440c565b9050919050565b7f43616e6e6f742073657420746f6b656e207661756c7420746f207a65726f206160008201527f6464726573730000000000000000000000000000000000000000000000000000602082015250565b60006144ab602683613722565b91506144b68261444f565b604082019050919050565b600060208201905081810360008301526144da8161449e565b9050919050565b7f52656e6f756e63696e67206f776e6572736869702069732064697361626c656460008201527f2e00000000000000000000000000000000000000000000000000000000000000602082015250565b600061453d602183613722565b9150614548826144e1565b604082019050919050565b6000602082019050818103600083015261456c81614530565b9050919050565b7f5374616b65206c6f636b2d696e20706572696f64206e6f74207965742070617360008201527f7365640000000000000000000000000000000000000000000000000000000000602082015250565b60006145cf602383613722565b91506145da82614573565b604082019050919050565b600060208201905081810360008301526145fe816145c2565b9050919050565b7f4e6f20616d6f756e742063757272656e746c79207374616b656420746f20726560008201527f7374616b65000000000000000000000000000000000000000000000000000000602082015250565b6000614661602583613722565b915061466c82614605565b604082019050919050565b6000602082019050818103600083015261469081614654565b9050919050565b7f43616e6e6f74207365742072657761726420746f6b656e20746f207a65726f2060008201527f6164647265737300000000000000000000000000000000000000000000000000602082015250565b60006146f3602783613722565b91506146fe82614697565b604082019050919050565b60006020820190508181036000830152614722816146e6565b9050919050565b7f4e6f2070656e64696e6720756e7374616b6520616d6f756e7420746f20636c6160008201527f696d000000000000000000000000000000000000000000000000000000000000602082015250565b6000614785602283613722565b915061479082614729565b604082019050919050565b600060208201905081810360008301526147b481614778565b9050919050565b7f4e6f2045544820696e20636f6e747261637420746f2077697468647261770000600082015250565b60006147f1601e83613722565b91506147fc826147bb565b602082019050919050565b60006020820190508181036000830152614820816147e4565b9050919050565b7f455448207472616e73666572206661696c656420647572696e6720656d65726760008201527f656e6379207769746864726177616c0000000000000000000000000000000000602082015250565b6000614883602f83613722565b915061488e82614827565b604082019050919050565b600060208201905081810360008301526148b281614876565b9050919050565b60006040820190506148ce60008301856138ea565b6148db60208301846138ea565b9392505050565b7f496e76616c6964207374616b696e67206475726174696f6e0000000000000000600082015250565b6000614918601883613722565b9150614923826148e2565b602082019050919050565b600060208201905081810360008301526149478161490b565b9050919050565b7f496e76616c6964207469657220666f72206475726174696f6e00000000000000600082015250565b6000614984601983613722565b915061498f8261494e565b602082019050919050565b600060208201905081810360008301526149b381614977565b9050919050565b60006060820190506149cf6000830186613b40565b6149dc6020830185613b40565b6149e960408301846138ea565b949350505050565b7f496e76616c6964207469657220666f7220415059000000000000000000000000600082015250565b6000614a27601483613722565b9150614a32826149f1565b602082019050919050565b60006020820190508181036000830152614a5681614a1a565b9050919050565b6000604082019050614a726000830185613b40565b614a7f60208301846138ea565b939250505056fea2646970667358221220fa60be03d13e3d74c4e788d672593793041a4f79a8e12d148d130e1e5d35a4ac64736f6c634300081e0033
Verified Source Code Full Match
Compiler: v0.8.30+commit.73712a01
EVM: paris
Optimization: No
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 212 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.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 Variant of {safeTransfer} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransfer(IERC20 token, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Variant of {safeTransferFrom} that returns a bool instead of reverting if the operation is not successful.
*/
function trySafeTransferFrom(IERC20 token, address from, address to, uint256 value) internal returns (bool) {
return _callOptionalReturnBool(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);
}
}
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;
}
}
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;
}
}
FXIStaking.sol 810 lines
// SPDX-License-Identifier: None
pragma solidity 0.8.30;
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
/**
* @title FXIStaking
* @notice A staking contract that allows users to stake FXI tokens and earn ETH rewards based on tiered staking durations.
* @dev Implements a reward distribution mechanism using accumulated rewards per share for different time tiers.
* Key mechanisms:
* - Users stake FXI tokens to earn ETH rewards.
* - Rewards are distributed proportionally based on staked amounts and time tiers (14, 35 or 70 days).
* - Users also earn FXI rewards based on APY for their tier and stake duration.
* - Rewards are calculated using an accumulated rewards per share mechanism specific to each tier for ETH.
* - FXI rewards are calculated based on time-staked and an APY per tier.
* - ETH rewards can be deposited by anyone and are distributed to stakers according to their tier's allocation.
* - FXI rewards are transferred from a designated tokenVault.
*/
contract FXIStaking is Ownable2Step, ReentrancyGuard {
using SafeERC20 for IERC20;
// Enum to define staking tiers
enum StakingTier {
NONE,
TIER_14_DAYS,
TIER_35_DAYS,
TIER_70_DAYS
}
struct UserInfo {
uint256 stakedAmount;
uint256 rewardDebt; // Reward debt specific to the user's current tier (for ETH rewards)
uint256 claimableRewards; // Accumulated ETH rewards ready to be claimed
uint256 claimedAmount; // Total ETH rewards claimed by the user (for analytics)
uint256 claimableFxiRewards; // Accumulated FXI rewards ready to be claimed
uint256 claimedFxiAmount; // Total FXI rewards claimed by the user
uint256 lastFxiRewardUpdateTime; // Timestamp of the last FXI reward calculation update
uint256 pendingUnstakeAmount; // Amount of tokens pending withdrawal
uint256 lastUnstakeTime; // Timestamp of the last unstake action
StakingTier currentTier; // Current staking tier of the user
uint256 stakeUnlockTime; // Timestamp when the user's current stake can be withdrawn
}
mapping(address => UserInfo) public userInfo;
// Per-tier state variables for ETH rewards
mapping(StakingTier => uint256) public accRewardsPerShareTier;
mapping(StakingTier => uint256) public totalStakedAmountTier;
uint256 public totalStakedAmount; // Total FXI staked across all tiers
uint256 public unallocatedETH; // ETH received when no stakers or specific tier has no stakers
uint256 public ethDebt; // ETH owed back to the contract after emergency withdrawal
uint256 public globalETHCollected; // Total ETH ever deposited for rewards
IERC20 public immutable stakingToken;
IERC20 public rewardToken; // The ERC20 token used for APY-based rewards (e.g., FXI or FXONE)
address public tokenVault; // Address holding the rewardTokens, from which this contract can transferFrom
// Tier configuration constants
uint256 public constant TIER_14_DURATION_SECONDS = 14 days;
uint256 public constant TIER_35_DURATION_SECONDS = 35 days;
uint256 public constant TIER_70_DURATION_SECONDS = 70 days;
// ETH Reward Tier Allocations
uint256 public constant TIER_14_ALLOCATION_BPS = 500; // 5%
uint256 public constant TIER_35_ALLOCATION_BPS = 1500; // 15%
uint256 public constant TIER_70_ALLOCATION_BPS = 8000; // 80%
uint256 public constant TOTAL_ALLOCATION_BPS = 10000; // 100%
// FXI (or rewardToken) APY per Tier (Basis Points)
uint256 public constant TIER_14_APY_BPS = 200; // 2.00%
uint256 public constant TIER_35_APY_BPS = 1000; // 10.00%
uint256 public constant TIER_70_APY_BPS = 1500; // 15.00%
uint256 private constant SECONDS_IN_YEAR = 365 days;
uint256 private constant REWARD_PRECISION = 1e24;
event Stake(address indexed user, uint256 amount, StakingTier tier);
event Unstake(address indexed user, uint256 amount);
event Withdraw(address indexed user, uint256 amount);
event Claim(address indexed user, uint256 amount); // For ETH rewards
event ClaimFxiRewards(address indexed user, uint256 amount);
event RewardTokenChanged(
address indexed oldToken,
address indexed newToken
);
event TokenVaultChanged(address indexed oldVault, address indexed newVault);
event RestakeAtPeriodEnd(
address indexed user,
uint256 principalAmount,
StakingTier newTier
);
event EmergencyWithdrawal(uint256 amount, uint256 ethDebt);
event PoolRefreshed(uint256 amountDistributed);
constructor(
address _stakingToken,
address _rewardToken,
address _tokenVaultAddr
) Ownable(msg.sender) {
if (_stakingToken == address(0))
revert("Staking token cannot be zero address");
if (_rewardToken == address(0))
revert("Reward token cannot be zero address");
if (_tokenVaultAddr == address(0))
revert("Token vault cannot be zero address");
stakingToken = IERC20(_stakingToken);
rewardToken = IERC20(_rewardToken);
tokenVault = _tokenVaultAddr;
}
/**
* @notice Converts a day count (14, 35, 70) to its corresponding StakingTier enum.
* @dev Reverts if the number of days is invalid.
* @param _days The number of days for the staking tier.
* @return The StakingTier enum value.
*/
function _getStakingTierFromDays(
uint8 _days
) internal pure returns (StakingTier) {
if (_days == 14) {
return StakingTier.TIER_14_DAYS;
} else if (_days == 35) {
return StakingTier.TIER_35_DAYS;
} else if (_days == 70) {
return StakingTier.TIER_70_DAYS;
} else {
revert("Invalid staking duration");
}
}
/**
* @notice Gets the duration in seconds for a given staking tier.
* @param _tier The StakingTier enum value.
* @return The duration in seconds.
*/
function _getTierDurationInSeconds(
StakingTier _tier
) internal pure returns (uint256) {
if (_tier == StakingTier.TIER_14_DAYS) {
return TIER_14_DURATION_SECONDS;
} else if (_tier == StakingTier.TIER_35_DAYS) {
return TIER_35_DURATION_SECONDS;
} else if (_tier == StakingTier.TIER_70_DAYS) {
return TIER_70_DURATION_SECONDS;
} else {
revert("Invalid tier for duration");
}
}
/**
* @notice Gets the APY in Basis Points (BPS) for a given staking tier for FXI rewards.
* @param _tier The StakingTier enum value.
* @return The APY in BPS.
*/
function _getTierApyBps(StakingTier _tier) internal pure returns (uint256) {
if (_tier == StakingTier.TIER_14_DAYS) {
return TIER_14_APY_BPS;
} else if (_tier == StakingTier.TIER_35_DAYS) {
return TIER_35_APY_BPS;
} else if (_tier == StakingTier.TIER_70_DAYS) {
return TIER_70_APY_BPS;
} else {
revert("Invalid tier for APY");
}
}
/**
* @notice Internal function to calculate and update pending FXI rewards for a user.
* @dev This should be called before any action that might change stake amount or tier, or before claiming.
* It updates user.claimableFxiRewards and user.lastFxiRewardUpdateTime.
* @param _userAddr The address of the user.
*/
function _updateFxiRewards(address _userAddr) internal {
UserInfo storage user = userInfo[_userAddr];
if (
user.stakedAmount == 0 ||
user.currentTier == StakingTier.NONE ||
user.lastFxiRewardUpdateTime == 0
) {
// No stake, no valid tier, or it's the very first moment of staking (timer not started)
// If lastFxiRewardUpdateTime is already block.timestamp due to a previous call in the same tx, timeElapsed would be 0.
if (user.stakedAmount > 0 && user.lastFxiRewardUpdateTime == 0) {
// This handles the case where a user stakes for the first time.
// The timer effectively starts "now" for future calculations.
user.lastFxiRewardUpdateTime = block.timestamp;
}
return;
}
uint256 timeElapsed = block.timestamp - user.lastFxiRewardUpdateTime;
if (timeElapsed == 0) {
return; // No time passed, no new rewards
}
uint256 tierApyBps = _getTierApyBps(user.currentTier);
// FXI Reward = (Staked Amount * APY_BPS * Time Elapsed) / (BPS_DENOMINATOR * SECONDS_IN_YEAR)
uint256 fxiReward = (user.stakedAmount * tierApyBps * timeElapsed) /
(10000 * SECONDS_IN_YEAR);
if (fxiReward > 0) {
user.claimableFxiRewards += fxiReward;
}
user.lastFxiRewardUpdateTime = block.timestamp; // Always update to current time after calculation
}
/**
* @notice Internal function to calculate and update pending ETH rewards for a user.
* @dev This should be called before any action that might change stake amount or tier.
* It updates user.claimableRewards.
* @param _userAddr The address of the user.
*/
function _crystallizeEthRewards(address _userAddr) internal {
UserInfo storage user = userInfo[_userAddr];
if (user.stakedAmount > 0) {
uint256 pending = ((user.stakedAmount *
accRewardsPerShareTier[user.currentTier]) / REWARD_PRECISION) -
user.rewardDebt;
if (pending > 0) {
user.claimableRewards += pending;
}
// Reward debt is updated after this call in the calling function context
// as it depends on whether the stake amount/tier changes.
}
}
/**
* @notice Allows users to stake FXI tokens into a specified tier.
* @dev Handles new stakes, adding to existing stakes, and tier changes.
* If changing tiers or adding to a stake, pending ETH rewards from the old tier are crystallized.
* Pending FXI rewards up to this point are also calculated and banked.
* The entire stake (existing + new) is then moved to the new tier, and the lock-in period is reset.
* @param _amount The amount of FXI tokens to add to the stake.
* @param _days The staking duration tier chosen by the user (14, 35, or 70 days).
*/
function stake(uint256 _amount, uint8 _days) external nonReentrant {
UserInfo storage user = userInfo[msg.sender];
if (_amount == 0) {
revert("Cannot stake 0 tokens");
}
StakingTier newTier = _getStakingTierFromDays(_days);
_updateFxiRewards(msg.sender); // Calculate and bank pending FXI rewards before state changes
// 1. Crystallize ETH rewards and update totals if user is already staked
if (user.stakedAmount > 0) {
_crystallizeEthRewards(msg.sender);
totalStakedAmountTier[user.currentTier] -= user.stakedAmount;
totalStakedAmount -= user.stakedAmount;
}
// 3. Update user's total staked amount (state change before external call)
user.stakedAmount += _amount;
// 4. Update user's tier and lock time for the *entire* stake
user.currentTier = newTier;
user.stakeUnlockTime =
block.timestamp +
_getTierDurationInSeconds(newTier);
// 5. Add the user's *total current* staked amount to the new tier's accounting
totalStakedAmountTier[newTier] += user.stakedAmount;
totalStakedAmount += user.stakedAmount;
// 6. Update ETH reward debt for the new tier and total new stake
user.rewardDebt =
(user.stakedAmount * accRewardsPerShareTier[newTier]) /
REWARD_PRECISION;
// 7. Reset FXI reward timer for the new/updated stake
user.lastFxiRewardUpdateTime = block.timestamp;
// Perform the token transfer (interaction) last
stakingToken.safeTransferFrom(msg.sender, address(this), _amount);
emit Stake(msg.sender, _amount, newTier);
}
/**
* @notice Initiates the unstaking process for FXI tokens.
* @dev Moves a specified amount of tokens from active stake to pending unstake.
* Pending ETH rewards for the user's current tier are calculated and added to their claimable balance.
* Pending FXI rewards are also calculated and banked.
* @param _amount The amount of FXI tokens to unstake.
*/
function unstake(uint256 _amount) external nonReentrant {
UserInfo storage user = userInfo[msg.sender];
if (_amount == 0) {
revert("Cannot unstake 0 tokens");
}
if (user.stakedAmount < _amount) {
revert("Insufficient staked amount");
}
if (user.currentTier == StakingTier.NONE) {
revert("User not in a valid staking tier");
}
_updateFxiRewards(msg.sender); // Calculate and bank pending FXI rewards
_crystallizeEthRewards(msg.sender);
user.stakedAmount -= _amount;
user.lastUnstakeTime = block.timestamp;
user.pendingUnstakeAmount += _amount;
totalStakedAmountTier[user.currentTier] -= _amount;
totalStakedAmount -= _amount;
user.rewardDebt =
(user.stakedAmount * accRewardsPerShareTier[user.currentTier]) /
REWARD_PRECISION;
// If user.stakedAmount becomes 0, future calls to _updateFxiRewards will correctly do nothing
// until/unless they stake again. lastFxiRewardUpdateTime remains, but timeElapsed calculations
// will be based on 0 stakedAmount if called directly, or _updateFxiRewards will return early.
// Explicitly setting user.lastFxiRewardUpdateTime = 0 if stakedAmount is 0 might be an option,
// but current _updateFxiRewards logic handles it.
emit Unstake(msg.sender, _amount);
}
/**
* @notice Allows users to cancel their unstake and restake their pending tokens.
* @dev Moves tokens from pending unstake back to the user's current active staking tier.
* The lock-in period for the restaked amount is reset according to the tier's duration.
* Pending FXI rewards are calculated and banked before restaking.
*/
function restakeTokens() external nonReentrant {
UserInfo storage user = userInfo[msg.sender];
uint256 amountToRestake = user.pendingUnstakeAmount;
if (amountToRestake == 0) {
revert("No pending unstake amount to restake");
}
_updateFxiRewards(msg.sender); // Calculate and bank pending FXI rewards
_crystallizeEthRewards(msg.sender);
user.pendingUnstakeAmount = 0;
user.stakedAmount += amountToRestake;
// By restaking, user recommits, so stakeUnlockTime is updated for the entire stake.
user.stakeUnlockTime =
block.timestamp +
_getTierDurationInSeconds(user.currentTier);
totalStakedAmountTier[user.currentTier] += amountToRestake;
totalStakedAmount += amountToRestake;
user.rewardDebt =
(user.stakedAmount * accRewardsPerShareTier[user.currentTier]) /
REWARD_PRECISION;
user.lastFxiRewardUpdateTime = block.timestamp; // Reset FXI reward timer
emit RestakeAtPeriodEnd(msg.sender, amountToRestake, user.currentTier);
}
/**
* @notice Withdraws unstaked tokens after the stake unlock period.
* @dev Transfers tokens from pendingUnstakeAmount to the user if stakeUnlockTime has passed.
*/
function claimUnstakedToken() external nonReentrant {
UserInfo storage user = userInfo[msg.sender];
uint256 toWithdraw = user.pendingUnstakeAmount;
if (toWithdraw == 0) {
revert("No pending unstake amount to claim");
}
// Check against stakeUnlockTime, which was set when the stake (that these tokens originated from) was made/updated.
if (block.timestamp < user.stakeUnlockTime) {
revert("Stake lock-in period not yet passed");
}
user.pendingUnstakeAmount = 0;
stakingToken.safeTransfer(msg.sender, toWithdraw);
emit Withdraw(msg.sender, toWithdraw);
}
/**
* @notice Claims accumulated ETH rewards.
* @dev Calculates and transfers available ETH rewards to the user based on their current tier and stake.
* Also ensures FXI rewards are up-to-date before ETH claim.
*/
function claimEthRewards() external nonReentrant {
UserInfo storage user = userInfo[msg.sender];
if (user.stakedAmount == 0 && user.claimableRewards == 0) {
revert(
"No ETH rewards to claim or no active stake for pending calculation"
);
}
if (user.currentTier == StakingTier.NONE && user.stakedAmount > 0) {
revert("User in invalid tier with active stake");
}
_crystallizeEthRewards(msg.sender); // This will add pending from active stake to user.claimableRewards
uint256 totalClaimable = user.claimableRewards;
if (totalClaimable == 0) {
revert("No ETH rewards to claim");
}
if (user.stakedAmount > 0) {
user.rewardDebt =
(user.stakedAmount * accRewardsPerShareTier[user.currentTier]) /
REWARD_PRECISION;
}
uint256 amountToDistribute = address(this).balance >= totalClaimable
? totalClaimable
: address(this).balance;
if (amountToDistribute == 0) {
revert("No ETH available to claim");
}
(bool sent, ) = msg.sender.call{value: amountToDistribute}("");
if (!sent) {
revert("ETH transfer failed");
}
user.claimableRewards -= amountToDistribute;
user.claimedAmount += amountToDistribute;
emit Claim(msg.sender, amountToDistribute);
}
/**
* @notice Claims accumulated FXI (or current rewardToken) rewards.
* @dev Transfers available rewardToken rewards from the tokenVault to the user.
*/
function claimFxiRewards() external nonReentrant {
if (address(rewardToken) == address(0)) {
revert("Reward token not set");
}
if (tokenVault == address(0)) {
revert("Token vault not set");
}
_updateFxiRewards(msg.sender); // Calculate and bank all pending FXI rewards
UserInfo storage user = userInfo[msg.sender];
uint256 amountToClaim = user.claimableFxiRewards;
if (amountToClaim == 0) {
revert("No FXI rewards to claim");
}
user.claimableFxiRewards = 0;
user.claimedFxiAmount += amountToClaim;
// This contract needs approval from tokenVault to transfer 'amountToClaim' of rewardToken
rewardToken.safeTransferFrom(tokenVault, msg.sender, amountToClaim);
emit ClaimFxiRewards(msg.sender, amountToClaim);
}
/**
* @notice Internal function to distribute ETH to tiers.
* @dev Updates accRewardsPerShareTier for each tier with active stakers.
* Any amount that cannot be distributed to a tier (e.g., tier has no stakers)
* is added back to unallocatedETH.
* @param _amountToDistribute The total ETH amount to be distributed among tiers.
*/
function _distributeEth(uint256 _amountToDistribute) internal {
if (_amountToDistribute == 0) {
return;
}
if (totalStakedAmount == 0) {
unallocatedETH += _amountToDistribute;
return;
}
uint256 ethForTier14 = (_amountToDistribute * TIER_14_ALLOCATION_BPS) /
TOTAL_ALLOCATION_BPS;
uint256 ethForTier35 = (_amountToDistribute * TIER_35_ALLOCATION_BPS) /
TOTAL_ALLOCATION_BPS;
uint256 ethForTier70 = (_amountToDistribute * TIER_70_ALLOCATION_BPS) /
TOTAL_ALLOCATION_BPS;
uint256 distributedSum = 0;
if (
totalStakedAmountTier[StakingTier.TIER_14_DAYS] > 0 &&
ethForTier14 > 0
) {
accRewardsPerShareTier[StakingTier.TIER_14_DAYS] +=
(ethForTier14 * REWARD_PRECISION) /
totalStakedAmountTier[StakingTier.TIER_14_DAYS];
distributedSum += ethForTier14;
} else if (ethForTier14 > 0) {
unallocatedETH += ethForTier14;
}
if (
totalStakedAmountTier[StakingTier.TIER_35_DAYS] > 0 &&
ethForTier35 > 0
) {
accRewardsPerShareTier[StakingTier.TIER_35_DAYS] +=
(ethForTier35 * REWARD_PRECISION) /
totalStakedAmountTier[StakingTier.TIER_35_DAYS];
distributedSum += ethForTier35;
} else if (ethForTier35 > 0) {
unallocatedETH += ethForTier35;
}
if (
totalStakedAmountTier[StakingTier.TIER_70_DAYS] > 0 &&
ethForTier70 > 0
) {
accRewardsPerShareTier[StakingTier.TIER_70_DAYS] +=
(ethForTier70 * REWARD_PRECISION) /
totalStakedAmountTier[StakingTier.TIER_70_DAYS];
distributedSum += ethForTier70;
} else if (ethForTier70 > 0) {
unallocatedETH += ethForTier70;
}
// Handle any dust from BPS calculations or if _amountToDistribute was not fully covered by tier allocations
if (_amountToDistribute > distributedSum) {
unallocatedETH += (_amountToDistribute - distributedSum);
}
}
/**
* @notice Distributes unallocated ETH rewards to stakers based on tier allocations.
* @dev Only callable by contract owner.
* Updates accRewardsPerShareTier for each tier with active stakers.
*/
function refreshPool() external onlyOwner {
uint256 amountToDistribute = unallocatedETH;
if (amountToDistribute == 0) {
revert("No unallocated ETH to distribute");
}
if (totalStakedAmount == 0) {
revert("No stakers in any tier to distribute rewards to");
}
unallocatedETH = 0; // Clear before distribution attempts
_distributeEth(amountToDistribute);
emit PoolRefreshed(amountToDistribute);
}
/**
* @notice Receives ETH deposits for rewards and distributes them according to tier allocations.
* @dev Handles ETH deposits, tracks depositors, and updates accRewardsPerShareTier.
* If a tier has no stakers, its allocated portion becomes unallocatedETH.
* Manages ethDebt for emergency withdrawals.
*/
receive() external payable {
uint256 amount = msg.value;
if (amount == 0) {
revert("Cannot deposit 0 ETH");
}
globalETHCollected += amount;
uint256 effectiveAmount = amount;
if (ethDebt > 0) {
if (ethDebt >= effectiveAmount) {
ethDebt -= effectiveAmount;
effectiveAmount = 0;
} else {
effectiveAmount -= ethDebt;
ethDebt = 0;
}
}
if (effectiveAmount == 0) {
// All deposited ETH went to repay ethDebt
return;
}
_distributeEth(effectiveAmount);
}
/**
* @notice Emergency function to withdraw all ETH from the contract balance.
* @dev Only callable by contract owner. Tracks withdrawn amount as ethDebt.
*/
function emergencyWithdrawUnclaimedRewards()
external
onlyOwner
nonReentrant
{
uint256 amount = address(this).balance;
if (amount == 0) {
revert("No ETH in contract to withdraw");
}
(bool sent, ) = msg.sender.call{value: amount}("");
if (!sent) {
revert("ETH transfer failed during emergency withdrawal");
}
ethDebt += amount;
emit EmergencyWithdrawal(amount, ethDebt);
}
/**
* @notice Allows the owner to change the ERC20 token used for APY-based rewards.
* @param _newRewardToken The address of the new reward token contract.
*/
function setRewardToken(address _newRewardToken) external onlyOwner {
if (_newRewardToken == address(0)) {
revert("Cannot set reward token to zero address");
}
emit RewardTokenChanged(address(rewardToken), _newRewardToken);
rewardToken = IERC20(_newRewardToken);
}
/**
* @notice Allows the owner to change the address of the token vault.
* @param _newTokenVault The address of the new token vault.
*/
function setTokenVault(address _newTokenVault) external onlyOwner {
if (_newTokenVault == address(0)) {
revert("Cannot set token vault to zero address");
}
emit TokenVaultChanged(tokenVault, _newTokenVault);
tokenVault = _newTokenVault;
}
/**
* @notice Allows a user to restake their entire principal into a new (or same) tier after their current lock-in period has ended.
* @dev This function will crystallize any pending ETH and FXI rewards before restaking the principal.
* The user's claimable ETH and FXI balances will be updated with these crystallized rewards.
* The principal amount is then moved to the specified new tier, and the lock-in period is reset.
* @param _days The staking duration tier chosen by the user for the new staking period (14, 35, or 70 days).
*/
function restakeAtPeriodEnd(uint8 _days) external {
UserInfo storage user = userInfo[msg.sender];
if (block.timestamp < user.stakeUnlockTime) {
revert("Stake lock-in period not yet passed");
}
if (user.stakedAmount == 0) {
revert("No amount currently staked to restake");
}
StakingTier newTier = _getStakingTierFromDays(_days);
// 1. Crystallize FXI rewards
_updateFxiRewards(msg.sender);
_crystallizeEthRewards(msg.sender);
// Temporarily remove from old tier's accounting. Will be added back to global and new tier.
totalStakedAmountTier[user.currentTier] -= user.stakedAmount;
totalStakedAmount -= user.stakedAmount;
uint256 principalToRestake = user.stakedAmount; // This is the full staked principal
// 3. Update user's tier and lock time for the *entire* principal
user.currentTier = newTier;
user.stakeUnlockTime =
block.timestamp +
_getTierDurationInSeconds(newTier);
// 4. Add the user's principal to the new tier's accounting and global total
totalStakedAmountTier[newTier] += principalToRestake;
totalStakedAmount += principalToRestake; // Add back to global total
// 5. Update ETH reward debt for the new tier and principal
user.rewardDebt =
(principalToRestake * accRewardsPerShareTier[newTier]) /
REWARD_PRECISION;
// 6. Reset FXI reward timer for the new staking period
user.lastFxiRewardUpdateTime = block.timestamp;
emit RestakeAtPeriodEnd(msg.sender, principalToRestake, newTier);
}
/**
* @notice Calculates total claimable ETH rewards for a staker.
* @param _staker Address of the staker.
* @return Total claimable ETH rewards (already banked + pending from active stake).
*/
function getClaimableRewards(
address _staker
) external view returns (uint256) {
UserInfo storage user = userInfo[_staker];
uint256 pendingFromActiveStake = 0;
if (user.stakedAmount > 0 && user.currentTier != StakingTier.NONE) {
pendingFromActiveStake =
((user.stakedAmount *
accRewardsPerShareTier[user.currentTier]) /
REWARD_PRECISION) -
user.rewardDebt;
}
return user.claimableRewards + pendingFromActiveStake;
}
/**
* @notice Calculates total claimable FXI (or current rewardToken) rewards for a staker.
* @dev This includes already banked rewards plus pending rewards from the current active stake.
* @param _staker Address of the staker.
* @return Total claimable FXI rewards.
*/
function getClaimableFxiRewards(
address _staker
) external view returns (uint256) {
// Note: For view functions, we can't modify state (like user.lastFxiRewardUpdateTime).
// So, we calculate pending without altering the stored lastFxiRewardUpdateTime.
UserInfo storage user = userInfo[_staker]; // Use storage for direct access
if (user.stakedAmount == 0 || user.currentTier == StakingTier.NONE) {
// If no stake or no tier, only return already banked (crystallized) FXI rewards.
// lastFxiRewardUpdateTime might be 0 if never staked or if fully unstaked and rewards claimed.
return user.claimableFxiRewards;
}
// If lastFxiRewardUpdateTime is 0 but they have a stake (e.g., mid-transaction before it's set, or an edge case),
// we should not calculate pending rewards based on a zero timestamp as that would be incorrect.
// The _updateFxiRewards handles setting it correctly on first stake.
// Here, if it's 0, it means no rewards have started accruing for the current period yet.
if (
user.lastFxiRewardUpdateTime == 0 ||
user.lastFxiRewardUpdateTime >= block.timestamp
) {
return user.claimableFxiRewards; // No time elapsed or timer not started for current period
}
uint256 timeElapsed = block.timestamp - user.lastFxiRewardUpdateTime;
uint256 tierApyBps = _getTierApyBps(user.currentTier);
uint256 pendingFxiReward = (user.stakedAmount *
tierApyBps *
timeElapsed) / (10000 * SECONDS_IN_YEAR);
return user.claimableFxiRewards + pendingFxiReward;
}
/**
* @notice Checks if unstaked tokens are available for withdrawal based on stakeUnlockTime.
* @param _staker Address of the staker.
* @return Amount of tokens in pendingUnstakeAmount if stakeUnlockTime has passed, otherwise 0.
*/
function getClaimableUnstakedToken(
address _staker
) external view returns (uint256) {
UserInfo storage user = userInfo[_staker];
if (
user.pendingUnstakeAmount > 0 &&
block.timestamp >= user.stakeUnlockTime
) {
return user.pendingUnstakeAmount;
} else {
return 0;
}
}
/**
* @notice Returns amount of tokens currently in the pending unstake state for a staker.
* @param _staker Address of the staker.
* @return Amount of tokens pending unstake.
*/
function getPendingUnstakeAmount(
address _staker
) external view returns (uint256) {
return userInfo[_staker].pendingUnstakeAmount;
}
/**
* @notice Returns the total amount staked in a specific tier.
* @param _tier The staking tier.
* @return Total amount staked in that tier.
*/
function getTotalStakedInTier(
StakingTier _tier
) external view returns (uint256) {
// Ensure _tier is a valid, active tier before accessing mapping
if (_tier == StakingTier.NONE) return 0; // Or revert, depending on desired behavior
return totalStakedAmountTier[_tier];
}
/**
* @notice Returns the accumulated ETH rewards per share for a specific tier.
* @param _tier The staking tier.
* @return Accumulated ETH rewards per share for that tier (scaled by 1e24).
*/
function getAccRewardsPerShareForTier(
StakingTier _tier
) external view returns (uint256) {
if (_tier == StakingTier.NONE) return 0;
return accRewardsPerShareTier[_tier];
}
/**
* @notice Returns the current staking tier for a user.
* @param _staker Address of the staker.
* @return The StakingTier enum value.
*/
function getUserTier(address _staker) external view returns (StakingTier) {
return userInfo[_staker].currentTier;
}
/**
* @notice Returns the stake unlock time for a user.
* @param _staker Address of the staker.
* @return Timestamp when the user's stake can be withdrawn.
*/
function getUserStakeUnlockTime(
address _staker
) external view returns (uint256) {
return userInfo[_staker].stakeUnlockTime;
}
/**
* @notice Disables the ability to renounce ownership of the contract.
* @dev Overrides OpenZeppelin's Ownable2Step renounceOwnership function to prevent accidental loss of contract control.
* This ensures the contract always has an owner who can perform administrative functions.
*/
function renounceOwnership() public override onlyOwner {
revert("Renouncing ownership is disabled.");
}
}
Read Contract
TIER_14_ALLOCATION_BPS 0x79237db5 → uint256
TIER_14_APY_BPS 0x2a480d59 → uint256
TIER_14_DURATION_SECONDS 0x80e8555a → uint256
TIER_35_ALLOCATION_BPS 0xfc77203c → uint256
TIER_35_APY_BPS 0x7e2ab50f → uint256
TIER_35_DURATION_SECONDS 0xe7617bdd → uint256
TIER_70_ALLOCATION_BPS 0x5298d1e3 → uint256
TIER_70_APY_BPS 0xd46e42ee → uint256
TIER_70_DURATION_SECONDS 0x1162a727 → uint256
TOTAL_ALLOCATION_BPS 0x4712b069 → uint256
accRewardsPerShareTier 0x1fe414ea → uint256
ethDebt 0x156f685a → uint256
getAccRewardsPerShareForTier 0x41ce8d21 → uint256
getClaimableFxiRewards 0x631ab8b2 → uint256
getClaimableRewards 0x308e401e → uint256
getClaimableUnstakedToken 0x7407c695 → uint256
getPendingUnstakeAmount 0x31fd5222 → uint256
getTotalStakedInTier 0x7aca82e6 → uint256
getUserStakeUnlockTime 0x4cdc58e1 → uint256
getUserTier 0xe4d2620e → uint8
globalETHCollected 0x7aba20bf → uint256
owner 0x8da5cb5b → address
pendingOwner 0xe30c3978 → address
rewardToken 0xf7c618c1 → address
stakingToken 0x72f702f3 → address
tokenVault 0x5bc789d9 → address
totalStakedAmount 0x567e98f9 → uint256
totalStakedAmountTier 0x71ce3185 → uint256
unallocatedETH 0x7dfcdd29 → uint256
userInfo 0x1959a002 → uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint256, uint8, uint256
Write Contract 14 functions
These functions modify contract state and require a wallet transaction to execute.
acceptOwnership 0x79ba5097
No parameters
claimEthRewards 0x27596fce
No parameters
claimFxiRewards 0x517c840c
No parameters
claimUnstakedToken 0xb0541908
No parameters
emergencyWithdrawUnclaimedRewards 0xe82b4feb
No parameters
refreshPool 0x1967e945
No parameters
renounceOwnership 0x715018a6
No parameters
restakeAtPeriodEnd 0x7a87589d
uint8 _days
restakeTokens 0x3e58a27a
No parameters
setRewardToken 0x8aee8127
address _newRewardToken
setTokenVault 0x6497a8a0
address _newTokenVault
stake 0x10087fb1
uint256 _amount
uint8 _days
transferOwnership 0xf2fde38b
address newOwner
unstake 0x2e17de78
uint256 _amount
Recent Transactions
No transactions found for this address