Cryo Explorer Ethereum Mainnet

Address Contract Verified

Address 0x7ae267232fa94AcF622794828b63dc9E6dcd0299
Balance 0.034508 ETH ($71.14)
Nonce 1
Code Size 12811 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

12811 bytes
0x608060405260043610610228575f3560e01c806379ba509711610122578063b0fb162f116100aa578063f2fde38b1161006e578063f2fde38b1461071f578063f3ae24151461073e578063f83d08ba1461076c578063fc2a88c314610780578063fd915ef21461079557610268565b8063b0fb162f14610663578063be6e7a4f14610698578063df15c37e146106b7578063e76d5168146106d9578063f2c399921461070b57610268565b8063a2fb1175116100f1578063a2fb1175146105a9578063a4e2d634146105e8578063a57848b614610611578063a57b8b1414610625578063a86099ba1461064457610268565b806379ba50971461050357806389b66b0a146105175780638da5cb5b146105465780639ed0868d1461057657610268565b806333096e68116101b05780634129b2c9116101745780634129b2c914610455578063497138111461049b5780635bc07e62146104b0578063622b6af4146104cf5780636e67b2a9146104ee57610268565b806333096e68146103ce57806336d152a8146103ed5780633e4840101461040c5780633e66a6471461042b5780633f20b4c91461044057610268565b806312c515d3116101f757806312c515d31461031d57806318e60a32146103325780631fe543e31461036957806321f1d6771461038857806324f746971461039d57610268565b8063049229601461029d57806307b18bde146102c7578063097315b5146102e857806312065fe01461030b57610268565b3661026857604080513381523460208201527ff07fc9e1f2c699914d655cb8501692de5ce105473f7534bd30ed9f10cab4e58991015b60405180910390a1005b604080513381523460208201527ff07fc9e1f2c699914d655cb8501692de5ce105473f7534bd30ed9f10cab4e589910161025e565b3480156102a8575f5ffd5b506102b16107b3565b6040516102be9190612977565b60405180910390f35b3480156102d2575f5ffd5b506102e66102e136600461299d565b61083f565b005b3480156102f3575f5ffd5b506102fd600f5481565b6040519081526020016102be565b348015610316575f5ffd5b50476102fd565b348015610328575f5ffd5b506102fd600a5481565b34801561033d575f5ffd5b5061035161034c3660046129c7565b6109f7565b6040516001600160401b0390911681526020016102be565b348015610374575f5ffd5b506102e6610383366004612a01565b610a6a565b348015610393575f5ffd5b506102fd60095481565b3480156103a8575f5ffd5b50600d546103b99063ffffffff1681565b60405163ffffffff90911681526020016102be565b3480156103d9575f5ffd5b506102e66103e8366004612ad2565b610ad4565b3480156103f8575f5ffd5b506102e6610407366004612b17565b610b43565b348015610417575f5ffd5b506102e6610426366004612b75565b610dc1565b348015610436575f5ffd5b506102fd60105481565b34801561044b575f5ffd5b506102fd60115481565b348015610460575f5ffd5b5061047461046f366004612b17565b610f14565b6040805182516001600160401b0390811682526020938401511692810192909252016102be565b3480156104a6575f5ffd5b506102fd60055481565b3480156104bb575f5ffd5b506102e66104ca366004612bb3565b610fda565b3480156104da575f5ffd5b506102e66104e9366004612b17565b611267565b3480156104f9575f5ffd5b506102fd600b5481565b34801561050e575f5ffd5b506102e66112ae565b348015610522575f5ffd5b5061052b611357565b604080519384526020840192909252908201526060016102be565b348015610551575f5ffd5b505f546001600160a01b03165b6040516001600160a01b0390911681526020016102be565b348015610581575f5ffd5b5061055e7f00000000000000000000000002aae1a04f9828517b3007f83f6181900cad910c81565b3480156105b4575f5ffd5b506105c86105c3366004612b17565b6113cb565b604080516001600160401b039384168152929091166020830152016102be565b3480156105f3575f5ffd5b506004546106019060ff1681565b60405190151581526020016102be565b34801561061c575f5ffd5b506102fd6113fe565b348015610630575f5ffd5b506102e661063f366004612c1d565b6116ac565b34801561064f575f5ffd5b506102e661065e366004612c73565b611788565b34801561066e575f5ffd5b50600d5461068590640100000000900461ffff1681565b60405161ffff90911681526020016102be565b3480156106a3575f5ffd5b506102e66106b2366004612b17565b61186f565b3480156106c2575f5ffd5b506106cb6118d9565b6040516102be929190612d26565b3480156106e4575f5ffd5b507f000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca61055e565b348015610716575f5ffd5b506102e6611a31565b34801561072a575f5ffd5b506102e6610739366004612d53565b611b1e565b348015610749575f5ffd5b50610601610758366004612d53565b60026020525f908152604090205460ff1681565b348015610777575f5ffd5b506102e6611b2f565b34801561078b575f5ffd5b506102fd600e5481565b3480156107a0575f5ffd5b5060045461060190610100900460ff1681565b600380546107c090612d6e565b80601f01602080910402602001604051908101604052809291908181526020018280546107ec90612d6e565b80156108375780601f1061080e57610100808354040283529160200191610837565b820191905f5260205f20905b81548152906001019060200180831161081a57829003601f168201915b505050505081565b610847611c29565b6001600160a01b03821661088c5760405162461bcd60e51b81526020600482015260076024820152667a65726f20746f60c81b60448201526064015b60405180910390fd5b5f81116108cc5760405162461bcd60e51b815260206004820152600e60248201526d616d6f756e74206973207a65726f60901b6044820152606401610883565b47818111806108da57508181145b61091d5760405162461bcd60e51b8152602060048201526014602482015273696e73756666696369656e742062616c616e636560601b6044820152606401610883565b5f836001600160a01b0316836040515f6040518083038185875af1925050503d805f8114610966576040519150601f19603f3d011682016040523d82523d5f602084013e61096b565b606091505b50509050806109ae5760405162461bcd60e51b815260206004820152600f60248201526e1dda5d1a191c985dc819985a5b1959608a1b6044820152606401610883565b604080516001600160a01b0386168152602081018590527fd50b71a2790ecccf5881141fe9ae079e17c66aace5d50ba383d443ecd398ffa591015b60405180910390a150505050565b6001600160401b0381165f90815260086020526040812054808203610a1e57505f92915050565b6007610a2b600183612dba565b81548110610a3b57610a3b612dcd565b905f5260205f2090600491828204019190066008029054906101000a90046001600160401b0316915050919050565b7f00000000000000000000000002aae1a04f9828517b3007f83f6181900cad910c336001600160a01b03821614610ac5576040516345d498b760e11b81523360048201526001600160a01b0382166024820152604401610883565b610acf8383611c7d565b505050565b5f546001600160a01b0316331480610afa5750335f9081526002602052604090205460ff165b610b165760405162461bcd60e51b815260040161088390612de1565b600d805461ffff9092166401000000000265ffffffffffff1990921663ffffffff90931692909217179055565b5f546001600160a01b0316331480610b695750335f9081526002602052604090205460ff165b610b855760405162461bcd60e51b815260040161088390612de1565b60045460ff16610bcb5760405162461bcd60e51b81526020600482015260116024820152701cd9585cdbdb881b9bdd081b1bd8dad959607a1b6044820152606401610883565b600f545f03610c125760405162461bcd60e51b81526020600482015260136024820152721c985b991bdb481cd95959081b9bdd081cd95d606a1b6044820152606401610883565b600554600c5410610c655760405162461bcd60e51b815260206004820152601d60248201527f616c6c2077696e6e65727320616c72656164792066696e616c697a65640000006044820152606401610883565b5f8111610cab5760405162461bcd60e51b815260206004820152601460248201527306d61785069636b73206d757374206265203e20360641b6044820152606401610883565b600c546005545f91610cc7918491610cc291612dba565b611d0a565b6007549091505f610cd9826001612e10565b6001600160401b03811115610cf057610cf06129ed565b604051908082528060200260200182016040528015610d19578160200160208202803683370190505b509050610d2581611d23565b5f610d2f83611d90565b90505f5b84811015610d61575f610d47848487611db6565b90508015610d585750505050505050565b50600101610d33565b50600554600c5403610db9576004805461ff001916610100179055600c546040517f8930cc2aff212dc313d7e7085b6bd11e02c7dee98e13587e317ff140ac96dd6691610db091600390612ea2565b60405180910390a15b505050505b50565b5f546001600160a01b0316331480610de75750335f9081526002602052604090205460ff165b610e035760405162461bcd60e51b815260040161088390612de1565b60045460ff1615610e265760405162461bcd60e51b815260040161088390612eba565b5f5b81811015610edb575f60085f858585818110610e4657610e46612dcd565b9050602002016020810190610e5b91906129c7565b6001600160401b0316815260208101919091526040015f205490508015610ed2575f6007610e8a600184612dba565b81548110610e9a57610e9a612dcd565b905f5260205f2090600491828204019190066008026101000a8154816001600160401b0302191690836001600160401b031602179055505b50600101610e28565b506040518181527f8c5fd9915c371d916ccb8c3b6991f1765de9b59f6d382e034c7e19af928cbf54906020015b60405180910390a15050565b604080518082019091525f80825260208201528115801590610f425750600c54610f3f906001612e10565b82105b610f835760405162461bcd60e51b815260206004820152601260248201527172616e6b206f7574206f6620626f756e647360701b6044820152606401610883565b600c610f90600184612dba565b81548110610fa057610fa0612dcd565b5f918252602091829020604080518082019091529101546001600160401b038082168352600160401b909104169181019190915292915050565b5f546001600160a01b03163314806110005750335f9081526002602052604090205460ff165b61101c5760405162461bcd60e51b815260040161088390612de1565b60045460ff161561103f5760405162461bcd60e51b815260040161088390612eba565b8281146110865760405162461bcd60e51b81526020600482015260156024820152740c2e4e4c2f240d8cadccee8d040dad2e6dac2e8c6d605b1b6044820152606401610883565b5f5b83811015611236575f8585838181106110a3576110a3612dcd565b90506020020160208101906110b891906129c7565b6001600160401b0381165f908152600860205260408120549192508190036111b057600680546001810182555f919091527ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f6004820401805460039092166008026101000a6001600160401b038181021990931692851602919091179055600785858581811061114a5761114a612dcd565b905060200201602081019061115f91906129c7565b81546001810183555f9283526020808420600483040180546001600160401b039485166008600390951685026101000a9081029086021990911617905560065492861684525260409091205561122c565b8484848181106111c2576111c2612dcd565b90506020020160208101906111d791906129c7565b60076111e4600184612dba565b815481106111f4576111f4612dcd565b905f5260205f2090600491828204019190066008026101000a8154816001600160401b0302191690836001600160401b031602179055505b5050600101611088565b506040518381527f15ef75d1021675c2796bc9ad1d872efa8a9dc26491c4d7aaac8ff3d120ee05da906020016109e9565b5f546001600160a01b031633148061128d5750335f9081526002602052604090205460ff165b6112a95760405162461bcd60e51b815260040161088390612de1565b601155565b6001546001600160a01b031633146113015760405162461bcd60e51b815260206004820152601660248201527526bab9ba10313290383937b837b9b2b21037bbb732b960511b6044820152606401610883565b5f8054336001600160a01b0319808316821784556001805490911690556040516001600160a01b0390921692909183917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a350565b6006545f80828180805b838110156113bf575f6007828154811061137d5761137d612dcd565b5f918252602090912060048204015460039091166008026101000a90046001600160401b0316905080156113b657600190930192918201915b50600101611361565b50949590949350915050565b600c81815481106113da575f80fd5b5f918252602090912001546001600160401b038082169250600160401b9091041682565b5f80546001600160a01b03163314806114255750335f9081526002602052604090205460ff165b6114415760405162461bcd60e51b815260040161088390612de1565b60045460ff166114875760405162461bcd60e51b81526020600482015260116024820152701cd9585cdbdb881b9bdd081b1bd8dad959607a1b6044820152606401610883565b600454610100900460ff16156114d35760405162461bcd60e51b8152602060048201526011602482015270185b1c9958591e48199a5b985b1a5e9959607a1b6044820152606401610883565b600f54156115165760405162461bcd60e51b815260206004820152601060248201526f1cd9595908185b1c9958591e481cd95d60821b6044820152606401610883565b600e5415801590611528575060115415155b15611593575f60115460105461153e9190612e10565b90508042118061154d57508042145b6115915760405162461bcd60e51b81526020600482015260156024820152741c195b991a5b99cec81dd85a5d081d1a5b595bdd5d605a1b6044820152606401610883565b505b5f600554116115dc5760405162461bcd60e51b815260206004820152601560248201527477696e6e65727320636f756e74206973207a65726f60581b6044820152606401610883565b5f6115f760405180602001604052806001151581525061203a565b600d549091505f9061161f9063ffffffff811690640100000000900461ffff166001856120ab565b600e8290554260105560405191945091507f918ff637e95f50ea1b787bf5110005d58dfae3d285a530aa9b625e7813f5f7eb90611663908590600190600390612ee1565b60405180910390a17f6d26274d28864a34dc2e77a2751b72d7792490d15be753b53400a7e74756e8998382600360405161169f93929190612f01565b60405180910390a1505090565b6116b4611c29565b5f5b82811015611782578160025f8686858181106116d4576116d4612dcd565b90506020020160208101906116e99190612d53565b6001600160a01b0316815260208101919091526040015f20805460ff19169115159190911790557fff83ce179bad4fbdb0e98074011487cde624295a52d8189d92d5d8b06c914eda84848381811061174357611743612dcd565b90506020020160208101906117589190612d53565b604080516001600160a01b03909216825284151560208301520160405180910390a16001016116b6565b50505050565b5f546001600160a01b03163314806117ae5750335f9081526002602052604090205460ff165b6117ca5760405162461bcd60e51b815260040161088390612de1565b60045460ff1615806117e35750600454610100900460ff165b61182f5760405162461bcd60e51b815260206004820152601d60248201527f70726576696f757320736561736f6e206e6f742066696e616c697a65640000006044820152606401610883565b610acf83838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152508592506121e4915050565b5f546001600160a01b03163314806118955750335f9081526002602052604090205460ff165b6118b15760405162461bcd60e51b815260040161088390612de1565b60045460ff16156118d45760405162461bcd60e51b815260040161088390612eba565b600555565b600c546060908190806001600160401b038111156118f9576118f96129ed565b604051908082528060200260200182016040528015611922578160200160208202803683370190505b509250806001600160401b0381111561193d5761193d6129ed565b604051908082528060200260200182016040528015611966578160200160208202803683370190505b5091505f5b81811015611a2b575f600c828154811061198757611987612dcd565b5f918252602091829020604080518082019091529101546001600160401b03808216808452600160401b909204169282019290925286519092508690849081106119d3576119d3612dcd565b60200260200101906001600160401b031690816001600160401b0316815250508060200151848381518110611a0a57611a0a612dcd565b6001600160401b03909216602092830291909101909101525060010161196b565b50509091565b5f546001600160a01b0316331480611a575750335f9081526002602052604090205460ff165b611a735760405162461bcd60e51b815260040161088390612de1565b60045460ff168015611a8d5750600454610100900460ff16155b611ad95760405162461bcd60e51b815260206004820181905260248201527f6d757374206265206c6f636b656420616e64206e6f742066696e616c697a65646044820152606401610883565b6004805460ff191690556040517f1e100df956c8c0fa7f1fbfccc7997e2a17ab9f80062ac4d9837fc318aa29e18990611b1490600390612f1f565b60405180910390a1565b611b26611c29565b610dbe81612304565b5f546001600160a01b0316331480611b555750335f9081526002602052604090205460ff165b611b715760405162461bcd60e51b815260040161088390612de1565b60045460ff1615611bb55760405162461bcd60e51b815260206004820152600e60248201526d185b1c9958591e481b1bd8dad95960921b6044820152606401610883565b5f5f611bbf6123ac565b600a8290556009819055600b8190556005549193509150821015611be35760058290555b6004805460ff191660011790556005546040517f5cf5237fc5bdc7d9c5b783d4aaf1681853e4a046db42712850457d79dafd9cce91610f08916003918691869190612f31565b5f546001600160a01b03163314611c7b5760405162461bcd60e51b815260206004820152601660248201527527b7363c9031b0b63630b1363290313c9037bbb732b960511b6044820152606401610883565b565b60045460ff168015611c975750600454610100900460ff16155b611c9f575050565b600f5415611cab575050565b805f81518110611cbd57611cbd612dcd565b6020908102919091010151600f8190555f600e8190556010556040517f1261a23477ef791f38c1069ec26ee0c69a67ebde6969d82d1bfd0a394bd8a12b91610f0891859190600390612f01565b5f818310611d185781611d1a565b825b90505b92915050565b6007545f5b81811015610acf575f60078281548110611d4457611d44612dcd565b5f918252602090912060048204015460039091166008026101000a90046001600160401b031690508015611d8757611d8784611d81846001612e10565b8361264b565b50600101611d28565b60015b611d9e826001612e10565b600182901b1015611db15760011b611d93565b919050565b600b545f90808203611e17576004805461ff001916610100179055600c546040517f8930cc2aff212dc313d7e7085b6bd11e02c7dee98e13587e317ff140ac96dd6691611e0591600390612ea2565b60405180910390a16001915050612033565b600f54600c546040515f92611e3492909160039190602001612f5f565b60408051601f19818403018152919052805160209091012090505f611e598383612f9b565b90505f611e72888888611e6d866001612e10565b6126bc565b90505f611e80600183612dba565b90505f60068281548110611e9657611e96612dcd565b905f5260205f2090600491828204019190066008029054906101000a90046001600160401b031690505f60078381548110611ed357611ed3612dcd565b5f91825260208083206004830401546040805180820182526001600160401b03888116825260086003968716026101000a9093048316938101848152600c80546001810182559781905291517fdf6966c971051c3d54ec59162606531493a51404a002842f56009d7e5cf4a8c7909701805491518516600160401b026001600160801b0319909216979094169690961795909517909155925492519093507f1f909cbd03f14ad8c7a18bfe2b3f7f1459a42bf2bbe470166832244602d7a05792611fa292909186918691612fae565b60405180910390a16001600160401b03811615611fdd57611fcd8b85836001600160401b0316612765565b6001600160401b0381168703600b555b5f60078481548110611ff157611ff1612dcd565b905f5260205f2090600491828204019190066008026101000a8154816001600160401b0302191690836001600160401b031602179055505f9750505050505050505b9392505050565b60607f92fd13387c7fe7befbc38d303d6468778fb9731bc4583f17d92989c6fcfdeaaa8260405160240161207391511515815260200190565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b03199093169290921790915292915050565b6040516313c34b7f60e01b815263ffffffff8086166004830152831660248201525f9081906001600160a01b037f00000000000000000000000002aae1a04f9828517b3007f83f6181900cad910c16906313c34b7f90604401602060405180830381865afa15801561211f573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906121439190612fee565b90507f00000000000000000000000002aae1a04f9828517b3007f83f6181900cad910c6001600160a01b0316639cfc058e82888888886040518663ffffffff1660e01b81526004016121989493929190613005565b60206040518083038185885af11580156121b4573d5f5f3e3d5ffd5b50505050506040513d601f19601f820116820180604052508101906121d99190612fee565b915094509492505050565b5f5b6006548110156122435760085f6006838154811061220657612206612dcd565b5f918252602080832060048304015460039092166008026101000a9091046001600160401b031683528201929092526040018120556001016121e6565b5061224f60065f6128d5565b61225a60075f6128d5565b612265600c5f6128f7565b5f6009819055600a819055600b8190556004805461ffff19169055600f819055600e5581511561229557816122be565b61229e436127d6565b6040516020016122ae9190613039565b6040516020818303038152906040525b6003906122cb90826130a6565b5060058190556040517fe63b64ea7bded5e54c330a5bd177fac256472cb3b6ea2d4ee74bc31796ad2b8690610f08906003908490613160565b336001600160a01b0382160361235c5760405162461bcd60e51b815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152606401610883565b600180546001600160a01b0319166001600160a01b038381169182179092555f8054604051929316917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12789190a350565b6006545f81815b838110156125bc575f600682815481106123cf576123cf612dcd565b905f5260205f2090600491828204019190066008029054906101000a90046001600160401b031690505f6007838154811061240c5761240c612dcd565b5f9182526020822060048204015460039091166008026101000a90046001600160401b031691508190036125a0575f612446600188612dba565b90505f6006828154811061245c5761245c612dcd565b905f5260205f2090600491828204019190066008029054906101000a90046001600160401b031690505f6007838154811061249957612499612dcd565b5f91825260208083206004830401546001600160401b03898116855260089283905260408520949094556003909216026101000a90041690508583146125955781600687815481106124ed576124ed612dcd565b905f5260205f2090600491828204019190066008026101000a8154816001600160401b0302191690836001600160401b03160217905550806007878154811061253857612538612dcd565b905f5260205f2090600491828204019190066008026101000a8154816001600160401b0302191690836001600160401b0316021790555085600161257c9190612e10565b6001600160401b0383165f908152600860205260409020555b8298505050506125b5565b806001600160401b0316850194508260010192505b50506123b3565b600654841015611a2b5760068054806125d7576125d7613181565b5f8281526020902060045f199092019182040180546001600160401b03600860038516026101000a02191690559055600780548061261757612617613181565b5f8281526020902060045f199092019182040180546001600160401b03600860038516026101000a021916905590556125bc565b5f6001845161265a9190612dba565b90505f612668826001612e10565b90505b80841015610db9578285858151811061268657612686612dcd565b6020026020010181815161269a9190612e10565b9052506126a984196001612e10565b6126b590851685612e10565b935061266b565b5f80806126ca856001612e10565b90505b851561274f575f6126de8785612e10565b905081811080156127125750848882815181106126fd576126fd612dcd565b6020026020010151846127109190612e10565b105b156127425787818151811061272957612729612dcd565b60200260200101518361273c9190612e10565b92508093505b50600186901c95506126cd565b61275a836001612e10565b979650505050505050565b5f600184516127749190612dba565b90505f612782826001612e10565b90505b80841015610db957828585815181106127a0576127a0612dcd565b602002602001018181516127b49190612dba565b9052506127c384196001612e10565b6127cf90851685612e10565b9350612785565b6060815f036127fc5750506040805180820190915260018152600360fc1b602082015290565b815f5b81156128225761280e81613195565b905061281b600a836131ad565b91506127ff565b5f816001600160401b0381111561283b5761283b6129ed565b6040519080825280601f01601f191660200182016040528015612865576020820181803683370190505b5090505b84156128cd57612878826131c0565b9150612885600a86612f9b565b612890906030612e10565b60f81b8183815181106128a5576128a5612dcd565b60200101906001600160f81b03191690815f1a9053506128c6600a866131ad565b9450612869565b949350505050565b5080545f825560030160049004905f5260205f2090810190610dbe9190612912565b5080545f8255905f5260205f2090810190610dbe919061292a565b5b80821115612926575f8155600101612913565b5090565b5b808211156129265780546001600160801b031916815560010161292b565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f611d1a6020830184612949565b6001600160a01b0381168114610dbe575f5ffd5b5f5f604083850312156129ae575f5ffd5b82356129b981612989565b946020939093013593505050565b5f602082840312156129d7575f5ffd5b81356001600160401b0381168114612033575f5ffd5b634e487b7160e01b5f52604160045260245ffd5b5f5f60408385031215612a12575f5ffd5b8235915060208301356001600160401b03811115612a2e575f5ffd5b8301601f81018513612a3e575f5ffd5b80356001600160401b03811115612a5757612a576129ed565b8060051b604051601f19603f83011681018181106001600160401b0382111715612a8357612a836129ed565b604052918252602081840181019290810188841115612aa0575f5ffd5b6020850194505b83851015612ac357843580825260209586019590935001612aa7565b50809450505050509250929050565b5f5f60408385031215612ae3575f5ffd5b823563ffffffff81168114612af6575f5ffd5b9150602083013561ffff81168114612b0c575f5ffd5b809150509250929050565b5f60208284031215612b27575f5ffd5b5035919050565b5f5f83601f840112612b3e575f5ffd5b5081356001600160401b03811115612b54575f5ffd5b6020830191508360208260051b8501011115612b6e575f5ffd5b9250929050565b5f5f60208385031215612b86575f5ffd5b82356001600160401b03811115612b9b575f5ffd5b612ba785828601612b2e565b90969095509350505050565b5f5f5f5f60408587031215612bc6575f5ffd5b84356001600160401b03811115612bdb575f5ffd5b612be787828801612b2e565b90955093505060208501356001600160401b03811115612c05575f5ffd5b612c1187828801612b2e565b95989497509550505050565b5f5f5f60408486031215612c2f575f5ffd5b83356001600160401b03811115612c44575f5ffd5b612c5086828701612b2e565b90945092505060208401358015158114612c68575f5ffd5b809150509250925092565b5f5f5f60408486031215612c85575f5ffd5b83356001600160401b03811115612c9a575f5ffd5b8401601f81018613612caa575f5ffd5b80356001600160401b03811115612cbf575f5ffd5b866020828401011115612cd0575f5ffd5b6020918201979096509401359392505050565b5f8151808452602084019350602083015f5b82811015612d1c5781516001600160401b0316865260209586019590910190600101612cf5565b5093949350505050565b604081525f612d386040830185612ce3565b8281036020840152612d4a8185612ce3565b95945050505050565b5f60208284031215612d63575f5ffd5b813561203381612989565b600181811c90821680612d8257607f821691505b602082108103612da057634e487b7160e01b5f52602260045260245ffd5b50919050565b634e487b7160e01b5f52601160045260245ffd5b81810381811115611d1d57611d1d612da6565b634e487b7160e01b5f52603260045260245ffd5b60208082526015908201527437b7363c9037bbb732b91037b91036b0b730b3b2b960591b604082015260600190565b80820180821115611d1d57611d1d612da6565b5f8154612e2f81612d6e565b808552600182168015612e495760018114612e6557612e99565b60ff1983166020870152602082151560051b8701019350612e99565b845f5260205f205f5b83811015612e905781546020828a010152600182019150602081019050612e6e565b87016020019450505b50505092915050565b828152604060208201525f6128cd6040830184612e23565b6020808252600d908201526c1cd9585cdbdb881b1bd8dad959609a1b604082015260600190565b8381528215156020820152606060408201525f612d4a6060830184612e23565b838152826020820152606060408201525f612d4a6060830184612e23565b602081525f611d1a6020830184612e23565b608081525f612f436080830187612e23565b6020830195909552506040810192909252606090910152919050565b838152606060208201525f612f776060830185612e23565b9050826040830152949350505050565b634e487b7160e01b5f52601260045260245ffd5b5f82612fa957612fa9612f87565b500690565b8481526001600160401b03841660208201526001600160401b0383166040820152608060608201525f612fe46080830184612e23565b9695505050505050565b5f60208284031215612ffe575f5ffd5b5051919050565b63ffffffff8516815261ffff8416602082015263ffffffff83166040820152608060608201525f612fe46080830184612949565b66029b2b0b9b7b7160cd1b81525f82518060208501600785015e5f920160070191825250919050565b601f821115610acf57805f5260205f20601f840160051c810160208510156130875750805b601f840160051c820191505b81811015610db9575f8155600101613093565b81516001600160401b038111156130bf576130bf6129ed565b6130d3816130cd8454612d6e565b84613062565b6020601f821160018114613105575f83156130ee5750848201515b5f19600385901b1c1916600184901b178455610db9565b5f84815260208120601f198516915b828110156131345787850151825560209485019460019092019101613114565b508482101561315157868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b604081525f6131726040830185612e23565b90508260208301529392505050565b634e487b7160e01b5f52603160045260245ffd5b5f600182016131a6576131a6612da6565b5060010190565b5f826131bb576131bb612f87565b500490565b5f816131ce576131ce612da6565b505f19019056fea264697066735822122062df0d41eb6fe90f09759142c6be8143b9e90b0bc04653b8b0ee38d17ae32a6464736f6c634300081f0033

Verified Source Code Full Match

Compiler: v0.8.31+commit.fd3a2265 EVM: osaka Optimization: Yes (200 runs)
LotteryVrfContract.sol 715 lines
// SPDX-License-Identifier: -- DG --
pragma solidity ^0.8.24;

/**
 * @title WeightedLotteryVRF
 * @notice Weighted lottery with ordered winners and seasonal resets, using Chainlink VRF v2.5 Wrapper (Direct Funding).
 * @author Decentral Games Team
 * @dev
 * - Simple UX: Uses VRFV2PlusWrapperConsumerBase
 * - Lifecycle: upsert/remove -> lock() -> requestDraw() -> fulfillRandomWords() -> finalizeWinners().
 * - Randomness: Direct Funding via wrapper; always pays in native (ETH)
 * - Sampling: Weighted without replacement via Fenwick tree built in memory per finalize call.
 *   Rationale: maintaining a Fenwick tree in storage during upserts/lock significantly increases SSTORE cost
 *   (O(n log n) storage writes across a season) and can trigger out-of-gas with large batches. Rebuilding the
 *   Fenwick in memory at finalization shifts the cost to transient memory (cheap, no long-term rent), keeps the
 *   hot paths (upsert/remove/lock) inexpensive, and enables chunked finalization (maxPicks) by recomputing a
 *   consistent tree each call. Trade-offs: we re-do O(n log n) work per finalize chunk; acceptable because
 *   finalize is called infrequently (once or a few times per season) vs many upserts. This also avoids storage
 *   complexity across season resets and simplifies audits.
 */

import {ConfirmedOwner} from "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol";
import {VRFV2PlusWrapperConsumerBase} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFV2PlusWrapperConsumerBase.sol";
import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";

/// @title WeightedLotteryVRF
/// @notice Seasonal weighted lottery using Chainlink VRF v2.5 Wrapper with native funding.
/// @author Lottery-VRF Team
/// @dev See function-level docs for lifecycle, invariants, and Fenwick rationale.
contract WeightedLotteryVRF is VRFV2PlusWrapperConsumerBase, ConfirmedOwner {
    // --- Access model & state machine ---
    // Access: owner + managers via onlyAdmin for operational actions
    // Lifecycle:
    // 1) Pre-lock: upsert/remove entries; winner count configurable. Mapping indexPlus1 may include IDs with zero counts.
    // 2) lock(): compacts in-place (swap-with-last), updates indexPlus1 on-the-fly, sets snapshot totals.
    // 3) requestDraw(): requests 1 VRF word via wrapper (native-only).
    // 4) fulfillRandomWords(): idempotently stores vrfSeed and clears pending markers.
    // 5) finalizeWinners(): in chunks, builds Fenwick tree in memory, picks weighted winners without replacement until done.
    // After finalize completes: drawn=true. emergencyUnlock() is only allowed while locked and not finalized.

    // Roles
    /// @notice Manager accounts allowed to execute operational calls via onlyAdmin.
    mapping(address => bool) public isManager;
    modifier onlyAdmin() {
        require(
            msg.sender == owner() || isManager[msg.sender],
            "only owner or manager"
        );
        _;
    }

    // Season state
    /// @notice Current season identifier.
    string public seasonId;
    /// @notice True once lock() has been called for the season.
    bool public isLocked;
    /// @notice True when all winners have been finalized for the season.
    bool public drawn;
    /// @notice Target number of winners for the season.
    uint256 public winnersCount;

    // Entries
    uint64[] private idList;
    uint64[] private counts;
    mapping(uint64 => uint256) private indexPlus1; // id => idx+1
    // Invariant (post-lock): indexPlus1 mirrors idList exactly (id => position+1). Pre-lock it may include zero-count IDs.

    // Snapshot
    /// @notice Total weight snapshot at lock() (sum of counts > 0).
    uint256 public totalEntriesAtLock;
    /// @notice Number of unique IDs with non-zero entries at lock().
    uint256 public nonZeroIdsAtLock;
    /// @notice Remaining total weight not yet selected during finalization, in case of batched finalization.
    uint256 public remainingWeight;

    // Winners
    struct Winner {
        uint64 id;
        uint64 entriesAtLock;
    }
    /// @notice Ordered list of winners picked so far.
    Winner[] public winners;

    // VRF via Wrapper
    /// @notice Gas limit provided to the VRF wrapper's callback.
    uint32 public callbackGasLimit = 300000;
    /// @notice Number of block confirmations to wait before VRF fulfills.
    uint16 public requestConfirmations = 3;
    // Payment mode fixed to native; LINK payments are intentionally not supported for simplicity.

    /// @notice Last VRF request id (0 when no request is pending).
    uint256 public lastRequestId;
    /// @notice Random seed set by fulfillRandomWords (0 until set).
    uint256 public vrfSeed;
    /// @notice Timestamp when last VRF request was made (0 when none pending).
    uint256 public requestTimestamp;
    /// @notice Minimum seconds to wait before retrying a pending request (0 disables).
    uint256 public requestTimeout = 1 hours; // admin-configurable safety timeout

    // Events
    /// @notice Emitted when a manager account is added/removed.
    /// @param account The manager address.
    /// @param enabled True if enabled, false if removed.
    event ManagerSet(address account, bool enabled);
    /// @notice Emitted when a new season starts.
    /// @param seasonId Human-readable season id.
    /// @param defaultWinners Initial winners target for the season.
    event SeasonStarted(string seasonId, uint256 defaultWinners);
    /// @notice Emitted after upsertEntries runs.
    /// @param count Number of ids processed.
    event EntriesUpserted(uint256 count);
    /// @notice Emitted after removeEntries runs.
    /// @param count Number of ids processed.
    event EntriesRemoved(uint256 count);
    /// @notice Emitted when the season is locked with snapshot totals.
    /// @param totalIds Number of ids with non-zero entries at lock.
    /// @param totalEntries Sum of all entries at lock.
    /// @param winnersCount Winners target (may be clamped to totalIds).
    event Locked(
        string seasonId,
        uint256 totalIds,
        uint256 totalEntries,
        uint256 winnersCount
    );
    /// @notice Emitted when an emergency unlock occurs before finalization.
    event EmergencyUnlocked(string seasonId);
    /// @notice Emitted when a VRF request is made.
    /// @param requestId The VRF request id.
    /// @param nativePayment Always true; wrapper paid in native token.
    event VRFRequested(uint256 requestId, bool nativePayment, string seasonId);
    /// @notice Emitted with the fee actually charged for a VRF request (in wei of native token).
    /// @param requestId The VRF request id.
    /// @param feeWei The fee paid (wrapper + coordinator + premium) in native wei.
    event VRFFeeCharged(uint256 requestId, uint256 feeWei, string seasonId);
    /// @notice Emitted when the VRF seed is set by the callback.
    /// @param requestId The VRF request id.
    /// @param seed The random seed.
    event VRFSeedSet(uint256 requestId, uint256 seed, string seasonId);
    /// @notice Emitted per winner pick (rank starts at 1).
    /// @param rank The 1-based rank in winners array.
    /// @param id The winning id.
    /// @param entriesAtLock The winner's weight at lock.
    event WinnerPicked(
        uint256 rank,
        uint64 id,
        uint64 entriesAtLock,
        string seasonId
    );
    /// @notice Emitted when all winners are finalized.
    /// @param totalWinners Number of winners finalized.
    event DrawFinalized(uint256 totalWinners, string seasonId);
    // Native funding events
    /// @notice Emitted when native funds are received.
    /// @param from Sender address.
    /// @param amount Amount received.
    event NativeFundsReceived(address from, uint256 amount);
    /// @notice Emitted when native funds are withdrawn by the owner.
    /// @param to Recipient address.
    /// @param amount Amount withdrawn.
    event NativeFundsWithdrawn(address to, uint256 amount);

    /// @notice Initializes the contract with a VRF wrapper and initial season.
    /// @param vrfWrapper Address of the Chainlink VRF v2.5 Wrapper.
    /// @param _initialSeasonId Initial season identifier.
    /// @param _defaultWinners Initial winners target for the first season.
    constructor(
        address vrfWrapper,
        string memory _initialSeasonId,
        uint256 _defaultWinners
    ) VRFV2PlusWrapperConsumerBase(vrfWrapper) ConfirmedOwner(msg.sender) {
        _startSeason(_initialSeasonId, _defaultWinners);
    }

    // Admin
    /// @notice Adds or removes manager accounts.
    /// @param addrs List of addresses to update.
    /// @param enabled True to enable, false to disable.
    function setManagers(
        address[] calldata addrs,
        bool enabled
    ) external onlyOwner {
        for (uint256 i = 0; i < addrs.length; ++i) {
            isManager[addrs[i]] = enabled;
            emit ManagerSet(addrs[i], enabled);
        }
    }

    /// @notice Starts a new season; previous season must be unlocked or finalized.
    /// @param newSeasonId New season identifier.
    /// @param defaultWinners Winners target for the new season.
    function startNewSeason(
        string calldata newSeasonId,
        uint256 defaultWinners
    ) external onlyAdmin {
        require(!isLocked || drawn, "previous season not finalized");
        _startSeason(newSeasonId, defaultWinners);
    }

    /// @notice Internal season initializer; resets state and clears mappings.
    /// @param newSeasonId New season identifier.
    /// @param defaultWinners Winners target for the new season.
    function _startSeason(
        string memory newSeasonId,
        uint256 defaultWinners
    ) internal {
        // Clear-first vs selective clearing rationale:
        // - We clear indexPlus1 for ALL current IDs here, then delete/rebuild arrays and repopulate the mapping.
        // - Selective clearing (only deleting keys for removed IDs) is error-prone while compaction/rebuild happens,
        //   because indices shift and some IDs drop to zero. We'd also have to update moved IDs' indices on-the-fly.
        // - Clear-first is linear and simple: remove all mapping entries, rebuild compact arrays, then repopulate an exact
        //   mapping mirror (id => newIndex+1). This avoids stale pointers and out-of-bounds risks.
        // - Gas trade-off is acceptable since season resets are rare vs many upserts.
        // clear mapping for existing IDs
        for (uint256 i = 0; i < idList.length; ) {
            delete indexPlus1[idList[i]];
            unchecked {
                ++i;
            }
        }
        delete idList;
        delete counts;
        delete winners;
        totalEntriesAtLock = 0;
        nonZeroIdsAtLock = 0;
        remainingWeight = 0;
        isLocked = false;
        drawn = false;
        vrfSeed = 0;
        lastRequestId = 0;
        seasonId = bytes(newSeasonId).length == 0
            ? string(abi.encodePacked("Season ", _toString(block.number)))
            : newSeasonId;
        winnersCount = defaultWinners;
        emit SeasonStarted(seasonId, defaultWinners);
    }

    // Pre-lock mutations
    /// @notice Sets the winners target for the current season (pre-lock only).
    /// @param n New winners target.
    function setWinnersCount(uint256 n) external onlyAdmin {
        require(!isLocked, "season locked");
        winnersCount = n;
    }

    /// @notice Upserts entries for ids (pre-lock only). Replaces existing counts.
    /// @param ids List of ids to upsert.
    /// @param newCounts Corresponding weights for each id.
    function upsertEntries(
        uint64[] calldata ids,
        uint64[] calldata newCounts
    ) external onlyAdmin {
        require(!isLocked, "season locked");
        require(ids.length == newCounts.length, "array length mismatch");
        for (uint256 i = 0; i < ids.length; ) {
            uint64 id = ids[i];
            uint256 idxp1 = indexPlus1[id];
            if (idxp1 == 0) {
                idList.push(id);
                counts.push(newCounts[i]);
                indexPlus1[id] = idList.length; // idx+1
            } else {
                counts[idxp1 - 1] = newCounts[i];
            }
            unchecked {
                ++i;
            }
        }
        emit EntriesUpserted(ids.length);
    }

    /// @notice Sets counts to zero for the given ids (pre-lock only).
    /// @param ids List of ids to remove.
    function removeEntries(uint64[] calldata ids) external onlyAdmin {
        require(!isLocked, "season locked");
        for (uint256 i = 0; i < ids.length; ) {
            uint256 idxp1 = indexPlus1[ids[i]];
            if (idxp1 != 0) counts[idxp1 - 1] = 0;
            unchecked {
                ++i;
            }
        }
        emit EntriesRemoved(ids.length);
    }

    // Lock snapshot
    /// @notice Locks the season, compacts arrays in-place, and snapshots totals.
    function lock() external onlyAdmin {
        require(!isLocked, "already locked");
        (uint256 end, uint256 total) = _compactInPlace();
        nonZeroIdsAtLock = end;
        totalEntriesAtLock = total;
        remainingWeight = total;
        if (winnersCount > end) winnersCount = end;
        isLocked = true;
        emit Locked(seasonId, end, total, winnersCount);
    }

    /// @notice Compacts idList/counts in-place via swap-with-last and returns (newLength, totalWeight).
    /// @dev Updates indexPlus1 for moved IDs and deletes mapping for removed IDs. Physically pops tail.
    /// @return end The new compacted logical length after removals.
    /// @return total The total weight (sum of remaining counts) after compaction.
    function _compactInPlace() internal returns (uint256 end, uint256 total) {
        // Invariants during the loop:
        // - We maintain an active region idList[0..end-1] and counts[0..end-1]. Elements in [end..len-1] are garbage.
        // - Mapping correctness: For any position j in [0, end), indexPlus1[idList[j]] == j+1. For removed IDs, the
        //   mapping entry is deleted. We only ever set mapping for IDs we place inside [0, end).
        // - Progress variables: 0 <= i <= end <= len. We increment i only when we keep an element. When we remove at i,
        //   we swap in the last element (end-1) and decrement end, then re-process the same i without incrementing.
        // - Sum correctness: 'total' accumulates the sum of counts for all kept (non-zero) elements encountered so far.
        //   Removed elements contribute 0 to 'total'.
        uint256 len = idList.length;
        end = len; // new logical length after compaction
        total = 0;
        uint256 i = 0;
        while (i < end) {
            uint64 cid = idList[i];
            uint64 c = counts[i];
            if (c == 0) {
                // Remove this ID by swapping in the last element (if different)
                uint256 lastIndex = end - 1;
                uint64 lastId = idList[lastIndex];
                uint64 lastCount = counts[lastIndex];
                // Delete mapping for removed id
                delete indexPlus1[cid];
                if (i != lastIndex) {
                    idList[i] = lastId;
                    counts[i] = lastCount;
                    // Update mapping for the moved id
                    indexPlus1[lastId] = i + 1;
                }
                // Shrink logical end; do not increment i to process the swapped-in element
                end = lastIndex;
            } else {
                // Keep; accumulate total and advance
                unchecked {
                    total += c;
                }
                unchecked {
                    ++i;
                }
            }
        }
        // Physically shrink arrays to 'end'
        while (idList.length > end) {
            idList.pop();
            counts.pop();
        }
        // Post-conditions: arrays compact; mapping mirrors idList; 'total' equals sum of remaining counts.
        return (end, total);
    }

    /// @notice Unlocks a locked season before finalization (emergency only).
    function emergencyUnlock() external onlyAdmin {
        require(isLocked && !drawn, "must be locked and not finalized");
        isLocked = false;
        emit EmergencyUnlocked(seasonId);
    }

    // VRF Wrapper config
    /// @notice Sets VRF wrapper config parameters.
    /// @param _callbackGasLimit Gas limit for the wrapper callback.
    /// @param _confirmations Number of block confirmations to wait.
    function setWrapperConfig(
        uint32 _callbackGasLimit,
        uint16 _confirmations
    ) external onlyAdmin {
        callbackGasLimit = _callbackGasLimit;
        requestConfirmations = _confirmations;
    }

    /// @notice Sets the timeout window to allow retrying a stuck VRF request.
    /// @param seconds_ Seconds to wait before allowing a retry (0 disables).
    function setRequestTimeout(uint256 seconds_) external onlyAdmin {
        // 0 disables timeout protection (not recommended)
        requestTimeout = seconds_;
    }

    // --- Native funding (for wrapper payments) ---
    // The VRF v2.5 Wrapper will pull native funds from this contract when using requestRandomnessPayInNative.
    // Fund this contract by sending native tokens (ETH) directly to it. We expose receive/fallback and
    // a simple withdraw for the owner.
    /// @notice Accept native token funding for VRF payments.
    receive() external payable {
        emit NativeFundsReceived(msg.sender, msg.value);
    }
    /// @notice Fallback to accept native token funding.
    fallback() external payable {
        emit NativeFundsReceived(msg.sender, msg.value);
    }

    /// @notice Withdraws native funds to an address (owner-only).
    /// @param to Recipient address.
    /// @param amount Amount to withdraw.
    function withdrawNative(
        address payable to,
        uint256 amount
    ) external onlyOwner {
        require(to != address(0), "zero to");
        require(amount > 0, "amount is zero");
        // Strict-inequality friendly form: allow equality via logical OR
        uint256 bal = address(this).balance;
        require(bal > amount || bal == amount, "insufficient balance");
        (bool ok, ) = to.call{value: amount}("");
        require(ok, "withdraw failed");
        emit NativeFundsWithdrawn(to, amount);
    }

    // Request randomness via wrapper
    /// @notice Requests VRF randomness (native payment pulled from this contract's balance; non-payable like Chainlink sample).
    /// @return requestId The VRF request identifier.
    function requestDraw() external onlyAdmin returns (uint256 requestId) {
        require(isLocked, "season not locked");
        require(!drawn, "already finalized");
        require(vrfSeed == 0, "seed already set");
        if (lastRequestId != 0 && requestTimeout != 0) {
            uint256 readyAt = requestTimestamp + requestTimeout;
            // Strict-inequality friendly form: allow equality via logical OR
            require(
                block.timestamp > readyAt || block.timestamp == readyAt,
                "pending; wait timeout"
            );
        }
        require(winnersCount > 0, "winners count is zero");
        // We only need a single 256-bit word; we stretch it deterministically
        // for multiple picks during finalization. Requesting more words costs
        // more and increases callback gas without improving security here.
        bytes memory extraArgs = VRFV2PlusClient._argsToBytes(
            VRFV2PlusClient.ExtraArgsV1({nativePayment: true})
        );
        uint256 feeWei;
        (requestId, feeWei) = requestRandomnessPayInNative(
            callbackGasLimit,
            requestConfirmations,
            1,
            extraArgs
        );
        lastRequestId = requestId;
        requestTimestamp = block.timestamp;
        emit VRFRequested(requestId, true, seasonId);
        emit VRFFeeCharged(requestId, feeWei, seasonId);
        return requestId;
    }

    // VRF callback
    /// @notice VRF callback hook sets the seed once; late/duplicate calls are ignored.
    /// @param requestId The VRF request id.
    /// @param randomWords Array with a single random word.
    function fulfillRandomWords(
        uint256 requestId,
        uint256[] memory randomWords
    ) internal override {
        /// @dev Idempotent callback: ignores late/duplicate callbacks instead of reverting.
        if (!(isLocked && !drawn)) return;
        if (vrfSeed != 0) return;
        vrfSeed = randomWords[0];
        // Clear pending markers to reflect success
        lastRequestId = 0;
        requestTimestamp = 0;
        emit VRFSeedSet(requestId, vrfSeed, seasonId);
    }

    // Finalization
    /// @notice Picks up to maxPicks winners (weighted without replacement) until winnersCount is reached.
    /// @param maxPicks Maximum winners to finalize in this transaction. To be used to batch the operation in case revealing all winners in a single call passes the gas limits.
    function finalizeWinners(uint256 maxPicks) external onlyAdmin {
        // Trade-off rationale:
        // - Pros: avoids O(n log n) SSTORE during upserts/lock; reduces gas on hot paths; no storage state to keep in sync
        //   across season resets; enables chunked finalize (we recompute a clean tree each call for maxPicks).
        // - Cons: recomputation costs O(n log n) memory ops per call; acceptable since finalize is rare compared to upserts.
        // - Security: we zero out picked counts and use remainingWeight so recomputation remains consistent across chunks.
        require(isLocked, "season not locked");
        require(vrfSeed != 0, "random seed not set");
        require(winners.length < winnersCount, "all winners already finalized");
        require(maxPicks > 0, "maxPicks must be > 0");
        uint256 picks = _min(maxPicks, winnersCount - winners.length);
        uint256 n = counts.length;
        uint256[] memory fenwickMem = new uint256[](n + 1);
        _buildFenwickFromCounts(fenwickMem);
        uint256 bit = _largestPowerOfTwoLE(n);
        for (uint256 i = 0; i < picks; ) {
            bool done = _pickOneWinner(fenwickMem, bit, n);
            if (done) return;
            unchecked {
                ++i;
            }
        }
        if (winners.length == winnersCount) {
            drawn = true;
            emit DrawFinalized(winners.length, seasonId);
        }
    }

    /// @notice Builds Fenwick tree in memory from current counts[].
    /// @param fenwickMem The Fenwick array (length n+1) to populate from counts[].
    function _buildFenwickFromCounts(
        uint256[] memory fenwickMem
    ) internal view {
        uint256 n = counts.length;
        for (uint256 i = 0; i < n; ) {
            uint256 c = counts[i];
            if (c > 0) _fenwickAddMem(fenwickMem, i + 1, c);
            unchecked {
                ++i;
            }
        }
    }

    /// @notice Returns the largest power of two <= n using strict inequality form.
    /// @param n The number of elements in the Fenwick tree (counts length).
    /// @return bit The largest power-of-two step not exceeding n.
    function _largestPowerOfTwoLE(
        uint256 n
    ) internal pure returns (uint256 bit) {
        bit = 1;
        while ((bit << 1) < (n + 1)) {
            bit <<= 1;
        }
    }

    /// @notice Picks one winner, mutating fenwickMem, counts, remainingWeight; finalizes if none left.
    /// @param fenwickMem The Fenwick array over current (non-zero) counts.
    /// @param bit The largest power-of-two step size for Fenwick search.
    /// @param n The number of entries (counts length).
    /// @return done True if drawing completed in this call (remainingWeight == 0), and DrawFinalized emitted.
    function _pickOneWinner(
        uint256[] memory fenwickMem,
        uint256 bit,
        uint256 n
    ) internal returns (bool done) {
        uint256 rw = remainingWeight;
        if (rw == 0) {
            drawn = true;
            emit DrawFinalized(winners.length, seasonId);
            return true;
        }
        uint256 rnd = uint256(
            keccak256(abi.encode(vrfSeed, seasonId, winners.length))
        );
        uint256 ticket = rnd % rw;
        uint256 idx = _fenwickFindMem(fenwickMem, bit, n, ticket + 1);
        uint256 arrIdx = idx - 1;
        uint64 idVal = idList[arrIdx];
        uint64 cnt = counts[arrIdx];
        winners.push(Winner({id: idVal, entriesAtLock: cnt}));
        emit WinnerPicked(winners.length, idVal, cnt, seasonId);
        if (cnt > 0) {
            _fenwickSubMem(fenwickMem, idx, cnt);
            unchecked {
                remainingWeight = rw - cnt;
            }
        }
        counts[arrIdx] = 0;
        return false;
    }

    // Views
    /// @notice Returns the current entries count for an id.
    /// @param id The id to query.
    /// @return entries The current count.
    function entryOf(uint64 id) external view returns (uint64 entries) {
        uint256 p = indexPlus1[id];
        if (p == 0) return 0;
        return counts[p - 1];
    }
    /// @notice Returns the winner at given 1-based rank.
    /// @param rank1 1-based rank in winners array.
    /// @return w Winner struct at that rank.
    function getWinner(uint256 rank1) external view returns (Winner memory w) {
        // Use strict inequality form
        require(rank1 != 0 && rank1 < winners.length + 1, "rank out of bounds");
        return winners[rank1 - 1];
    }
    /// @notice Returns parallel arrays of winner ids and their entries at lock.
    /// @return ids Winner ids in order.
    /// @return entriesAtLock Entries-at-lock for each winner.
    function getWinners()
        external
        view
        returns (uint64[] memory ids, uint64[] memory entriesAtLock)
    {
        uint256 n = winners.length;
        ids = new uint64[](n);
        entriesAtLock = new uint64[](n);
        for (uint256 i = 0; i < n; ) {
            Winner memory w = winners[i];
            ids[i] = w.id;
            entriesAtLock[i] = w.entriesAtLock;
            unchecked {
                ++i;
            }
        }
    }

    /// @notice Computes live totals over entries regardless of lock state.
    /// @dev O(n) over the current tracked ids. Safe for off-chain calls; avoid heavy on-chain use.
    /// @return idsTracked Number of ids currently tracked (pre-lock may include zero-count ids).
    /// @return nonZeroIds Number of ids with count > 0 at the moment of the call.
    /// @return totalWeight Sum of all counts > 0 at the moment of the call.
    function currentTotals()
        external
        view
        returns (uint256 idsTracked, uint256 nonZeroIds, uint256 totalWeight)
    {
        uint256 len = idList.length;
        idsTracked = len;
        uint256 nz = 0;
        uint256 sum = 0;
        for (uint256 i = 0; i < len; ) {
            uint256 c = counts[i];
            if (c > 0) {
                unchecked {
                    ++nz;
                    sum += c;
                }
            }
            unchecked {
                ++i;
            }
        }
        return (idsTracked, nz, sum);
    }

    // Fenwick helpers (memory)
    // Note: memory-only Fenwick helpers; we deliberately avoid storage writes for gas and simplicity reasons.
    // Fenwick basics:
    // - f[] encodes partial sums so that adding at index i updates f[i], f[i + LSB(i)], f[i + 2*LSB(i)], ...
    // - LSB(i) (least significant set bit) is computed as i & (~i + 1) (two's complement trick).
    // - Prefix sums and find-by-prefix operations run in O(log n).
    /// @notice Fenwick tree point add (memory-only, 1-based index).
    /// @param f Fenwick array (length n+1).
    /// @param i Index (1-based).
    /// @param delta Amount to add.
    function _fenwickAddMem(
        uint256[] memory f,
        uint256 i,
        uint256 delta
    ) internal pure {
        uint256 n = f.length - 1;
        uint256 limit = n + 1;
        while (i < limit) {
            f[i] += delta;
            i += i & (~i + 1); // advance by LSB(i)
        }
    }
    /// @notice Fenwick tree point subtract (memory-only, 1-based index).
    /// @param f Fenwick array (length n+1).
    /// @param i Index (1-based).
    /// @param amount Amount to subtract.
    function _fenwickSubMem(
        uint256[] memory f,
        uint256 i,
        uint256 amount
    ) internal pure {
        uint256 n = f.length - 1;
        uint256 limit = n + 1;
        while (i < limit) {
            f[i] -= amount;
            i += i & (~i + 1); // advance by LSB(i)
        }
    }
    /// @notice Fenwick find-by-prefix-sum (memory-only).
    /// @param f Fenwick array (length n+1).
    /// @param bit Largest power-of-two step <= n.
    /// @param n Number of elements (not including f[0]).
    /// @param target The 1-based rank to find.
    /// @return idx 1-based index of the found element.
    function _fenwickFindMem(
        uint256[] memory f,
        uint256 bit,
        uint256 n,
        uint256 target
    ) internal pure returns (uint256 idx) {
        idx = 0;
        uint256 sum = 0;
        uint256 limit = n + 1;
        for (; bit > 0; bit >>= 1) {
            uint256 next = idx + bit;
            if (next < limit && sum + f[next] < target) {
                sum += f[next];
                idx = next;
            }
        }
        return idx + 1;
    }

    // Utils
    /// @notice Returns the smaller of two numbers.
    /// @param a First number.
    /// @param b Second number.
    /// @return The minimum of a and b.
    function _min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }
    /// @notice Converts uint256 to decimal string without leading zeros.
    /// @param value The value to convert.
    /// @return str The decimal string.
    function _toString(
        uint256 value
    ) internal pure returns (string memory str) {
        if (value == 0) return "0";
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            ++digits;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            --digits;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }
}
VRFV2PlusClient.sol 27 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

// End consumer library.
library VRFV2PlusClient {
  // extraArgs will evolve to support new features
  bytes4 public constant EXTRA_ARGS_V1_TAG = bytes4(keccak256("VRF ExtraArgsV1"));

  struct ExtraArgsV1 {
    bool nativePayment;
  }

  struct RandomWordsRequest {
    bytes32 keyHash;
    uint256 subId;
    uint16 requestConfirmations;
    uint32 callbackGasLimit;
    uint32 numWords;
    bytes extraArgs;
  }

  function _argsToBytes(
    ExtraArgsV1 memory extraArgs
  ) internal pure returns (bytes memory bts) {
    return abi.encodeWithSelector(EXTRA_ARGS_V1_TAG, extraArgs);
  }
}
VRFV2PlusWrapperConsumerBase.sol 118 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {LinkTokenInterface} from "../../shared/interfaces/LinkTokenInterface.sol";
import {IVRFV2PlusWrapper} from "./interfaces/IVRFV2PlusWrapper.sol";

/**
 *
 * @notice Interface for contracts using VRF randomness through the VRF V2 wrapper
 * ********************************************************************************
 * @dev PURPOSE
 *
 * @dev Create VRF V2+ requests without the need for subscription management. Rather than creating
 * @dev and funding a VRF V2+ subscription, a user can use this wrapper to create one off requests,
 * @dev paying up front rather than at fulfillment.
 *
 * @dev Since the price is determined using the gas price of the request transaction rather than
 * @dev the fulfillment transaction, the wrapper charges an additional premium on callback gas
 * @dev usage, in addition to some extra overhead costs associated with the VRFV2Wrapper contract.
 * *****************************************************************************
 * @dev USAGE
 *
 * @dev Calling contracts must inherit from VRFV2PlusWrapperConsumerBase. The consumer must be funded
 * @dev with enough LINK or ether to make the request, otherwise requests will revert. To request randomness,
 * @dev call the 'requestRandomWords' function with the desired VRF parameters. This function handles
 * @dev paying for the request based on the current pricing.
 *
 * @dev Consumers must implement the fullfillRandomWords function, which will be called during
 * @dev fulfillment with the randomness result.
 */
abstract contract VRFV2PlusWrapperConsumerBase {
  error OnlyVRFWrapperCanFulfill(address have, address want);

  LinkTokenInterface internal immutable i_linkToken;
  IVRFV2PlusWrapper public immutable i_vrfV2PlusWrapper;

  /**
   * @param _vrfV2PlusWrapper is the address of the VRFV2Wrapper contract
   */
  constructor(
    address _vrfV2PlusWrapper
  ) {
    IVRFV2PlusWrapper vrfV2PlusWrapper = IVRFV2PlusWrapper(_vrfV2PlusWrapper);

    i_linkToken = LinkTokenInterface(vrfV2PlusWrapper.link());
    i_vrfV2PlusWrapper = vrfV2PlusWrapper;
  }

  /**
   * @dev Requests randomness from the VRF V2+ wrapper.
   *
   * @param _callbackGasLimit is the gas limit that should be used when calling the consumer's
   *        fulfillRandomWords function.
   * @param _requestConfirmations is the number of confirmations to wait before fulfilling the
   *        request. A higher number of confirmations increases security by reducing the likelihood
   *        that a chain re-org changes a published randomness outcome.
   * @param _numWords is the number of random words to request.
   *
   * @return requestId is the VRF V2+ request ID of the newly created randomness request.
   */
  // solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
  function requestRandomness(
    uint32 _callbackGasLimit,
    uint16 _requestConfirmations,
    uint32 _numWords,
    bytes memory extraArgs
  ) internal returns (uint256 requestId, uint256 reqPrice) {
    reqPrice = i_vrfV2PlusWrapper.calculateRequestPrice(_callbackGasLimit, _numWords);
    i_linkToken.transferAndCall(
      address(i_vrfV2PlusWrapper), reqPrice, abi.encode(_callbackGasLimit, _requestConfirmations, _numWords, extraArgs)
    );
    return (i_vrfV2PlusWrapper.lastRequestId(), reqPrice);
  }

  // solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
  function requestRandomnessPayInNative(
    uint32 _callbackGasLimit,
    uint16 _requestConfirmations,
    uint32 _numWords,
    bytes memory extraArgs
  ) internal returns (uint256 requestId, uint256 requestPrice) {
    requestPrice = i_vrfV2PlusWrapper.calculateRequestPriceNative(_callbackGasLimit, _numWords);
    return (
      i_vrfV2PlusWrapper.requestRandomWordsInNative{value: requestPrice}(
        _callbackGasLimit, _requestConfirmations, _numWords, extraArgs
      ),
      requestPrice
    );
  }

  /**
   * @notice fulfillRandomWords handles the VRF V2 wrapper response. The consuming contract must
   * @notice implement it.
   *
   * @param _requestId is the VRF V2 request ID.
   * @param _randomWords is the randomness result.
   */
  // solhint-disable-next-line chainlink-solidity/prefix-internal-functions-with-underscore
  function fulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) internal virtual;

  function rawFulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) external {
    address vrfWrapperAddr = address(i_vrfV2PlusWrapper);
    if (msg.sender != vrfWrapperAddr) {
      revert OnlyVRFWrapperCanFulfill(msg.sender, vrfWrapperAddr);
    }
    fulfillRandomWords(_requestId, _randomWords);
  }

  /// @notice getBalance returns the native balance of the consumer contract
  function getBalance() public view returns (uint256) {
    return address(this).balance;
  }

  /// @notice getLinkToken returns the link token contract
  function getLinkToken() public view returns (LinkTokenInterface) {
    return i_linkToken;
  }
}
ConfirmedOwner.sol 12 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

/// @title The ConfirmedOwner contract
/// @notice A contract with helpers for basic contract ownership.
contract ConfirmedOwner is ConfirmedOwnerWithProposal {
  constructor(
    address newOwner
  ) ConfirmedOwnerWithProposal(newOwner, address(0)) {}
}
IVRFV2PlusWrapper.sol 83 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IVRFV2PlusWrapper {
  /**
   * @return the request ID of the most recent VRF V2 request made by this wrapper. This should only
   * be relied option within the same transaction that the request was made.
   */
  function lastRequestId() external view returns (uint256);

  /**
   * @notice Calculates the price of a VRF request with the given callbackGasLimit at the current
   * @notice block.
   *
   * @dev This function relies on the transaction gas price which is not automatically set during
   * @dev simulation. To estimate the price at a specific gas price, use the estimatePrice function.
   *
   * @param _callbackGasLimit is the gas limit used to estimate the price.
   * @param _numWords is the number of words to request.
   */
  function calculateRequestPrice(uint32 _callbackGasLimit, uint32 _numWords) external view returns (uint256);

  /**
   * @notice Calculates the price of a VRF request in native with the given callbackGasLimit at the current
   * @notice block.
   *
   * @dev This function relies on the transaction gas price which is not automatically set during
   * @dev simulation. To estimate the price at a specific gas price, use the estimatePrice function.
   *
   * @param _callbackGasLimit is the gas limit used to estimate the price.
   * @param _numWords is the number of words to request.
   */
  function calculateRequestPriceNative(uint32 _callbackGasLimit, uint32 _numWords) external view returns (uint256);

  /**
   * @notice Estimates the price of a VRF request with a specific gas limit and gas price.
   *
   * @dev This is a convenience function that can be called in simulation to better understand
   * @dev pricing.
   *
   * @param _callbackGasLimit is the gas limit used to estimate the price.
   * @param _numWords is the number of words to request.
   * @param _requestGasPriceWei is the gas price in wei used for the estimation.
   */
  function estimateRequestPrice(
    uint32 _callbackGasLimit,
    uint32 _numWords,
    uint256 _requestGasPriceWei
  ) external view returns (uint256);

  /**
   * @notice Estimates the price of a VRF request in native with a specific gas limit and gas price.
   *
   * @dev This is a convenience function that can be called in simulation to better understand
   * @dev pricing.
   *
   * @param _callbackGasLimit is the gas limit used to estimate the price.
   * @param _numWords is the number of words to request.
   * @param _requestGasPriceWei is the gas price in wei used for the estimation.
   */
  function estimateRequestPriceNative(
    uint32 _callbackGasLimit,
    uint32 _numWords,
    uint256 _requestGasPriceWei
  ) external view returns (uint256);

  /**
   * @notice Requests randomness from the VRF V2 wrapper, paying in native token.
   *
   * @param _callbackGasLimit is the gas limit for the request.
   * @param _requestConfirmations number of request confirmations to wait before serving a request.
   * @param _numWords is the number of words to request.
   */
  function requestRandomWordsInNative(
    uint32 _callbackGasLimit,
    uint16 _requestConfirmations,
    uint32 _numWords,
    bytes calldata extraArgs
  ) external payable returns (uint256 requestId);

  function link() external view returns (address);
  function linkNativeFeed() external view returns (address);
}
LinkTokenInterface.sol 31 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// solhint-disable-next-line interface-starts-with-i
interface LinkTokenInterface {
  function allowance(address owner, address spender) external view returns (uint256 remaining);

  function approve(address spender, uint256 value) external returns (bool success);

  function balanceOf(
    address owner
  ) external view returns (uint256 balance);

  function decimals() external view returns (uint8 decimalPlaces);

  function decreaseApproval(address spender, uint256 addedValue) external returns (bool success);

  function increaseApproval(address spender, uint256 subtractedValue) external;

  function name() external view returns (string memory tokenName);

  function symbol() external view returns (string memory tokenSymbol);

  function totalSupply() external view returns (uint256 totalTokensIssued);

  function transfer(address to, uint256 value) external returns (bool success);

  function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool success);

  function transferFrom(address from, address to, uint256 value) external returns (bool success);
}
ConfirmedOwnerWithProposal.sol 72 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IOwnable} from "../interfaces/IOwnable.sol";

/// @title The ConfirmedOwner contract
/// @notice A contract with helpers for basic contract ownership.
contract ConfirmedOwnerWithProposal is IOwnable {
  address private s_owner;
  address private s_pendingOwner;

  event OwnershipTransferRequested(address indexed from, address indexed to);
  event OwnershipTransferred(address indexed from, address indexed to);

  constructor(address newOwner, address pendingOwner) {
    // solhint-disable-next-line gas-custom-errors
    require(newOwner != address(0), "Cannot set owner to zero");

    s_owner = newOwner;
    if (pendingOwner != address(0)) {
      _transferOwnership(pendingOwner);
    }
  }

  /// @notice Allows an owner to begin transferring ownership to a new address.
  function transferOwnership(
    address to
  ) public override onlyOwner {
    _transferOwnership(to);
  }

  /// @notice Allows an ownership transfer to be completed by the recipient.
  function acceptOwnership() external override {
    // solhint-disable-next-line gas-custom-errors
    require(msg.sender == s_pendingOwner, "Must be proposed owner");

    address oldOwner = s_owner;
    s_owner = msg.sender;
    s_pendingOwner = address(0);

    emit OwnershipTransferred(oldOwner, msg.sender);
  }

  /// @notice Get the current owner
  function owner() public view override returns (address) {
    return s_owner;
  }

  /// @notice validate, transfer ownership, and emit relevant events
  function _transferOwnership(
    address to
  ) private {
    // solhint-disable-next-line gas-custom-errors
    require(to != msg.sender, "Cannot transfer to self");

    s_pendingOwner = to;

    emit OwnershipTransferRequested(s_owner, to);
  }

  /// @notice validate access
  function _validateOwnership() internal view {
    // solhint-disable-next-line gas-custom-errors
    require(msg.sender == s_owner, "Only callable by owner");
  }

  /// @notice Reverts if called by anyone other than the contract owner.
  modifier onlyOwner() {
    _validateOwnership();
    _;
  }
}
IOwnable.sol 12 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IOwnable {
  function owner() external returns (address);

  function transferOwnership(
    address recipient
  ) external;

  function acceptOwnership() external;
}

Read Contract

callbackGasLimit 0x24f74697 → uint32
currentTotals 0x89b66b0a → uint256, uint256, uint256
drawn 0xfd915ef2 → bool
entryOf 0x18e60a32 → uint64
getBalance 0x12065fe0 → uint256
getLinkToken 0xe76d5168 → address
getWinner 0x4129b2c9 → tuple
getWinners 0xdf15c37e → uint64[], uint64[]
i_vrfV2PlusWrapper 0x9ed0868d → address
isLocked 0xa4e2d634 → bool
isManager 0xf3ae2415 → bool
lastRequestId 0xfc2a88c3 → uint256
nonZeroIdsAtLock 0x12c515d3 → uint256
owner 0x8da5cb5b → address
remainingWeight 0x6e67b2a9 → uint256
requestConfirmations 0xb0fb162f → uint16
requestTimeout 0x3f20b4c9 → uint256
requestTimestamp 0x3e66a647 → uint256
seasonId 0x04922960 → string
totalEntriesAtLock 0x21f1d677 → uint256
vrfSeed 0x097315b5 → uint256
winners 0xa2fb1175 → uint64, uint64
winnersCount 0x49713811 → uint256

Write Contract 15 functions

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

acceptOwnership 0x79ba5097
No parameters
emergencyUnlock 0xf2c39992
No parameters
finalizeWinners 0x36d152a8
uint256 maxPicks
lock 0xf83d08ba
No parameters
rawFulfillRandomWords 0x1fe543e3
uint256 _requestId
uint256[] _randomWords
removeEntries 0x3e484010
uint64[] ids
requestDraw 0xa57848b6
No parameters
returns: uint256
setManagers 0xa57b8b14
address[] addrs
bool enabled
setRequestTimeout 0x622b6af4
uint256 seconds_
setWinnersCount 0xbe6e7a4f
uint256 n
setWrapperConfig 0x33096e68
uint32 _callbackGasLimit
uint16 _confirmations
startNewSeason 0xa86099ba
string newSeasonId
uint256 defaultWinners
transferOwnership 0xf2fde38b
address to
upsertEntries 0x5bc07e62
uint64[] ids
uint64[] newCounts
withdrawNative 0x07b18bde
address to
uint256 amount

Recent Transactions

No transactions found for this address