Cryo Explorer Ethereum Mainnet

Address Contract Verified

Address 0x636dEb767Cd7D0f15ca4aB8eA9a9b26E98B426AC
Balance 0 ETH
Nonce 413
Code Size 19297 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

19297 bytes
0x608060405234801561000f575f80fd5b50600436106102ff575f3560e01c80638a00b67a11610195578063adfcdb9d116100e4578063cf84a88c1161009e578063e772b76511610079578063e772b76514610923578063ecf22e321461094a578063ee1c71721461095d578063f556a79c14610970575f80fd5b8063cf84a88c14610876578063e4a61d95146108ee578063e76c01e414610915575f80fd5b8063adfcdb9d146107f6578063b1d8f27414610809578063b285a8701461082a578063bfd79cae1461083d578063c20fb59e14610850578063cc9baa1214610863575f80fd5b8063900cf0cf1161014f578063a5e8455d1161012a578063a5e8455d1461079b578063a70b9f0c146107a4578063a9952a0c146107cb578063ac85912e146107ee575f80fd5b8063900cf0cf1461073957806391050f4014610741578063972e6d6614610788575f80fd5b80638a00b67a146106785780638b7d38a11461069f5780638d4f0b6c146106a75780638da5cb5b146106ce5780638eb9399e146106df5780638f32d59b1461071b575f80fd5b8063524fcc8011610251578063727d0f351161020b5780638113630d116101e65780638113630d1461062c57806382a15be11461063f5780638777e0951461065257806388edf9be14610665575f80fd5b8063727d0f35146105cb57806378a1bdd4146105f25780637f6ec45514610605575f80fd5b8063524fcc80146104b257806352d420b91461051657806353905402146105295780635c79696c1461055d57806364b4f7511461057d5780636bca7c55146105a4575f80fd5b80632591003a116102bc578063415c2d9611610297578063415c2d961461044157806346d62a631461046157806350283275146104885780635245b1ea14610490575f80fd5b80632591003a146103e857806338f3e2851461040f578063394ff4061461042e575f80fd5b8063070f418a146103035780630df9049b146103185780630ecc535f146103525780630f2f6aa5146103a657806315e5a1e5146103b95780631cd2a05d146103c1575b5f80fd5b61031661031136600461400f565b610997565b005b61033f7f000000000000000000000000000000000000000000000000000000000007e90081565b6040519081526020015b60405180910390f35b61038661036036600461403c565b60086020525f908152604090208054600182015460028301546003909301549192909184565b604080519485526020850193909352918301526060820152608001610349565b6103166103b436600461403c565b6109a6565b61033f610b9f565b61033f7f000000000000000000000000000000000000000000000000000000000000000081565b61033f7f00000000000000000000000000000000000000000000000000005af3107a400081565b61033f61041d36600461403c565b600b6020525f908152604090205481565b61031661043c366004614078565b610c0e565b61045461044f36600461410f565b610d19565b604051610349919061416f565b61033f7f0000000000000000000000000000000000000000000000000000000067884c0081565b61033f610e26565b6104a361049e3660046142e2565b610e7c565b60405161034993929190614390565b6104ee6104c036600461403c565b60096020525f9081526040902080546001820154600283015460038401546004909401549293919290919085565b604080519586526020860194909452928401919091526060830152608082015260a001610349565b61031661052436600461400f565b611037565b61038661053736600461403c565b60056020525f908152604090208054600182015460028301546003909301549192909184565b610565611042565b6040516001600160a01b039091168152602001610349565b61033f7f00000000000000000000000000000000000000000000003635c9adc5dea0000081565b61033f7f0000000000000000000000000000000000000000000000000de0b6b3a764000181565b6105657f000000000000000000000000b01dd87b29d187f3e3a4bf6cdaebfb97f3d9ab9881565b61031661060036600461403c565b6110ab565b6105657f0000000000000000000000006dea81c8171d0ba574754ef6f8b412f2ed88c54d81565b61031661063a3660046143b8565b6114d1565b61031661064d36600461445f565b611627565b61033f61066036600461400f565b611c57565b610316610673366004614519565b611d1f565b61033f7f00000000000000000000000000000000000000000000000000470de4df82000081565b61033f611daf565b6105657f0000000000000000000000005e17a49ec5efea6e54dab5468eb649b1d3bb572881565b6001546001600160a01b0316610565565b6106e7611dce565b604080518451815260209485015185820152835191810191909152929091015160608301521515608082015260a001610349565b6001546001600160a01b031633146040519015158152602001610349565b61033f611e7d565b6104ee61074f36600461456b565b600a60209081525f9283526040808420909152908252902080546001820154600283015460038401546004909401549293919290919085565b6104a361079636600461403c565b611ede565b61033f60025481565b61033f7f0000000000000000000000000000000000000000000000000000000000093a8081565b6003546004546107d9919082565b60408051928352602083019190915201610349565b61033f611f18565b61033f61080436600461459c565b611f26565b61081c61081736600461403c565b611f3c565b6040516103499291906145c5565b610316610838366004614605565b611fa3565b6107d961084b36600461403c565b6121a5565b61033f61085e36600461403c565b6122bc565b61056561087136600461403c565b612504565b61088961088436600461403c565b61253e565b60408051845181526020808601518183015285830151828401526060958601518683015284516080808401919091529085015160a08301529184015160c08201529383015160e085015290910151610100830152151561012082015261014001610349565b61033f7f000000000000000000000000000000000000000000000000000000000000000481565b6006546007546107d9919082565b61033f7f000000000000000000000000000000000000000000000000000000000000000081565b610316610958366004614605565b6126a9565b61031661096b366004614640565b6127be565b6105657f0000000000000000000000004f9fbb3f1e99b56e0fe2892e623ed36a76fc605d81565b6109a381600133611fa3565b50565b6109ae6127ce565b5f806109b86127f6565b915091505f806109c78561294e565b915091505f6109d886868585610e7c565b50909150600590508160068111156109f2576109f261437c565b14610a555760405162461bcd60e51b815260206004820152602860248201527f476f7665726e616e63653a2063616e6e6f742d756e72656769737465722d696e604482015267697469617469766560c01b60648201526084015b60405180910390fd5b5f610a5e611e7d565b9050610a6b600182614677565b836080015110610a7d57610a7d61468a565b825185511015610a8f57610a8f61468a565b826020015185602001511015610aa757610aa761468a565b825185518690610ab8908390614677565b9052506020808401519086018051610ad1908390614677565b90525084516006556020808601516007556001600160a01b0388165f908152600b90915260408082205f1990555160248101839052610b459089906205573090849060440160408051601f198184030181529190526020810180516001600160e01b031663955161cd60e01b179052612a18565b90507f4b2a06fb31ab5670eed8302066f58582b640ec5a17d06ddd383d30e3e11f1b64888383610b75575f610b78565b60015b604051610b87939291906146b2565b60405180910390a1505050505050506109a360015f55565b5f7f0000000000000000000000000000000000000000000000000000000000093a806001610bcb611e7d565b610bd59190614677565b610bdf91906146d6565b610c09907f0000000000000000000000000000000000000000000000000000000067884c006146ed565b905090565b610c166127ce565b5f610c2085612a79565b90505f805f80846001600160a01b031663eb876bf78a338b8b8b6040518663ffffffff1660e01b8152600401610c5a959493929190614700565b6080604051808303815f875af1158015610c76573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610c9a91906147a4565b604080516001600160a01b038c168152602081018f9052908101859052606081018490526080810183905260a081018290529397509195509350915033907f5cd37895bb9287e542a426d6abbc93d7daca0979bd43264d320d4fd224a5fc5e9060c00160405180910390a25050505050610d1360015f55565b50505050565b6060816001600160401b03811115610d3357610d336141e7565b604051908082528060200260200182016040528015610d6657816020015b6060815260200190600190039081610d515790505b5090505f5b82811015610e1f575f8030868685818110610d8857610d886147d7565b9050602002810190610d9a91906147eb565b604051610da892919061482d565b5f60405180830381855af49150503d805f8114610de0576040519150601f19603f3d011682016040523d82523d5f602084013e610de5565b606091505b509150915081610df757805181602001fd5b80848481518110610e0a57610e0a6147d7565b60209081029190910101525050600101610d6b565b5092915050565b5f7f0000000000000000000000000000000000000000000000000000000000093a80610e727f0000000000000000000000000000000000000000000000000000000067884c0042614677565b610c099190614850565b6001600160a01b0384165f908152600b602052604081205481908190808203610eae575f805f9350935093505061102d565b5f610eb7611e7d565b9050808203610ed15760015f80945094509450505061102d565b6001600160a01b0389165f90815260096020526040902060040154935060018201610f055750600693505f915061102d9050565b610f10600182614677565b8410610f215760049450505061102d565b5f610f2e895f0151611c57565b905080885f0151118015610f46575060608801518851115b15610f7857885160025489515f9291610f5e916146d6565b610f689190614863565b60039750945061102d9350505050565b610f83600183614677565b7f00000000000000000000000000000000000000000000000000000000000000048860800151610fb391906146ed565b108061100d57508751606089015111801561100d5750670de0b6b3a7640000610ffc7f0000000000000000000000000000000000000000000000000de0b6b3a7640001836146d6565b6110069190614863565b8860600151115b156110225750600594505f925061102d915050565b50600294505f925050505b9450945094915050565b6109a3815f336126a9565b5f8061106e7f0000000000000000000000005e17a49ec5efea6e54dab5468eb649b1d3bb572833612b4e565b6040519091506001600160a01b0382169033907fda66ba232f4fb8c122b7026f55eeff1d0b9cf2560b7873b2bba6eaab4c3d5989905f90a3919050565b6110b36127ce565b5f6110bc611e7d565b90506002811161111f5760405162461bcd60e51b815260206004820152602860248201527f476f7665726e616e63653a20726567697374726174696f6e2d6e6f742d7965746044820152670b595b98589b195960c21b6064820152608401610a4c565b6001600160a01b0382166111755760405162461bcd60e51b815260206004820152601860248201527f476f7665726e616e63653a207a65726f2d6164647265737300000000000000006044820152606401610a4c565b5f61117f83611ede565b509091505f90508160068111156111985761119861437c565b146111f75760405162461bcd60e51b815260206004820152602960248201527f476f7665726e616e63653a20696e69746961746976652d616c72656164792d726044820152681959da5cdd195c995960ba1b6064820152608401610a4c565b5f61120133612504565b90505f61120c6127f6565b50335f8181526008602090815260409182902082516080810184528154815260018201549281019290925260028101549282019290925260039091015460608201529192506112a7907f000000000000000000000000b01dd87b29d187f3e3a4bf6cdaebfb97f3d9ab986001600160a01b031690307f00000000000000000000000000000000000000000000003635c9adc5dea00000612bb9565b8151602082015160608301515f916112be916146ed565b9050670de0b6b3a76400006112f37f00000000000000000000000000000000000000000000000000005af3107a4000846146d6565b6112fd9190614863565b6040516305a4d3f160e21b81526001600160a01b038781166004830152611399917f0000000000000000000000004f9fbb3f1e99b56e0fe2892e623ed36a76fc605d909116906316934fc490602401602060405180830381865afa158015611367573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061138b9190614876565b611393610b9f565b84611f26565b10156113e75760405162461bcd60e51b815260206004820152601d60248201527f476f7665726e616e63653a20696e73756666696369656e742d6c7174790000006044820152606401610a4c565b6001600160a01b0388165f908152600b6020526040902087905561140c600188614677565b6001600160a01b0389165f9081526009602052604080822060040192909255905160248101899052611474908a90620557309084906044015b60408051601f198184030181529190526020810180516001600160e01b0316632695d74d60e01b179052612a18565b90507f40318a3f9214cb8917bf252f11a84a919bf9acee5e9b12049e1c99cb52b3b71c89338a846114a5575f6114a8565b60015b6040516114b8949392919061488d565b60405180910390a150505050505050506109a360015f55565b6001546001600160a01b0316331461152b5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610a4c565b5f5b815181101561161e576001600b5f84848151811061154d5761154d6147d7565b60200260200101516001600160a01b03166001600160a01b031681526020019081526020015f20819055505f6115af83838151811061158e5761158e6147d7565b6020026020010151620557305f600160405160240161144591815260200190565b90507f40318a3f9214cb8917bf252f11a84a919bf9acee5e9b12049e1c99cb52b3b71c8383815181106115e4576115e46147d7565b6020026020010151336001846115fa575f6115fd565b60015b60405161160d949392919061488d565b60405180910390a15060010161152d565b506109a3612c20565b61162f6127ce565b848314801561163d57508281145b6116935760405162461bcd60e51b815260206004820152602160248201527f476f7665726e616e63653a2061727261792d6c656e6774682d6d69736d6174636044820152600d60fb1b6064820152608401610a4c565b61169d8888612c69565b6116a78686612c69565b6116e28484808060200260200160405190810160405280939291908181526020018383602002808284375f92019190915250612d4692505050565b61171d8282808060200260200160405190810160405280939291908181526020018383602002808284375f92019190915250612d4692505050565b6117888484808060200260200160405190810160405280939291908181526020018383602002808284375f92019190915250506040805160208088028281018201909352878252909350879250869182918501908490808284375f92019190915250612db892505050565b6117f38484808060200260200160405190810160405280939291908181526020018383602002808284375f92019190915250506040805160208088028281018201909352878252909350879250869182918501908490808284375f92019190915250612e5692505050565b5f6117fe8989612ef4565b335f9081526008602090815260409182902082516080810184528154815260018201549281019290925260028101549282018390526003015460608201529192501561187e5760405162461bcd60e51b815260206004820152600f60248201526e1b5d5cdd0818994818481c995cd95d608a1b6044820152606401610a4c565b80515f0361189e5760405162461bcd60e51b8152600401610a4c906148b9565b7f000000000000000000000000000000000000000000000000000000000007e9006118c7610e26565b1115611a3e575f5b87811015611a3c575f805b84518110156119c6578a8a848181106118f5576118f56147d7565b905060200201602081019061190a919061403c565b6001600160a01b0316858281518110611925576119256147d7565b60200260200101515f01516001600160a01b0316036119be5760019150848181518110611954576119546147d7565b602002602001015160200151898985818110611972576119726147d7565b9050602002013513156119b95760405162461bcd60e51b815260206004820152600f60248201526e43616e6e6f7420696e63726561736560881b6044820152606401610a4c565b6119c6565b6001016118da565b5080611a33578787838181106119de576119de6147d7565b905060200201355f14611a335760405162461bcd60e51b815260206004820181905260248201527f4d757374206265207a65726f20666f72206e657720696e6974696174697665736044820152606401610a4c565b506001016118cf565b505b5f876001600160401b03811115611a5757611a576141e7565b604051908082528060200260200182016040528015611a80578160200160208202803683370190505b5090505f886001600160401b03811115611a9c57611a9c6141e7565b604051908082528060200260200182016040528015611ac5578160200160208202803683370190505b5090505f5b89811015611ba057365f805f8c8c86818110611ae857611ae86147d7565b9050602002013513611afc57898986611b00565b8b8b875b9250925092505f838386818110611b1957611b196147d7565b9050602002013590505f885f0151828a60200151611b3791906146d6565b611b419190614863565b905081895f01818151611b549190614677565b905250602089018051829190611b6b908390614677565b90525082518190849088908110611b8457611b846147d7565b6020908102919091010152505060019093019250611aca915050565b50611c408a8a808060200260200160405190810160405280939291908181526020018383602002808284375f9201919091525050604080516020808e0282810182019093528d82529093508d92508c9182918501908490808284375f9201919091525050604080516020808d0282810182019093528c82529093508c92508b9182918501908490808284375f9201919091525088925087915061336a9050565b50505050611c4d60015f55565b5050505050505050565b5f815f03611c6657505f919050565b5f8083670de0b6b3a7640000600254611c7f91906146d6565b611c899190614863565b90508015611cd15780611cc4670de0b6b3a76400007f00000000000000000000000000000000000000000000000000000000000000006146d6565b611cce9190614863565b91505b611d17670de0b6b3a7640000611d077f00000000000000000000000000000000000000000000000000470de4df820000876146d6565b611d119190614863565b83613bf7565b949350505050565b611d276127ce565b611d318383612c69565b611d3b8383612ef4565b508015611da157335f9081526008602052604090206002015415611da15760405162461bcd60e51b815260206004820152601b60248201527f476f7665726e616e63653a206d757374206265206120726573657400000000006044820152606401610a4c565b611daa60015f55565b505050565b5f80611db96127f6565b509050611dc8815f0151611c57565b91505090565b604080518082019091525f8082526020820152604080518082019091525f80825260208201525f80611dfe611e7d565b60408051808201825260035481526004546020808301919091528251808401909352600654835260075490830152955093509050611e3d600182614677565b84602001511015611e7757825160019250611e6490611e5a610b9f565b8560200151611f26565b8452611e71600182614677565b60208501525b50909192565b5f7f0000000000000000000000000000000000000000000000000000000000093a80611ec97f0000000000000000000000000000000000000000000000000000000067884c0042614677565b611ed39190614863565b610c099060016146ed565b5f805f80611eea6127f6565b5090505f80611ef88761294e565b91509150611f0887848484610e7c565b9550955095505050509193909250565b6003545f90611dc881611c57565b5f611f32848484613c0c565b90505b9392505050565b604080518082019091525f8082526020820152611f7660405180608001604052805f81526020015f81526020015f81526020015f81525090565b611f7e6127ce565b611f866127f6565b509150611f928361294e565b509050611f9e60015f55565b915091565b611fab6127ce565b335f81815260086020526040812091611fc390612504565b9050806001600160a01b03163b5f03611fee5760405162461bcd60e51b8152600401610a4c90614903565b81548511156120515760405162461bcd60e51b815260206004820152602960248201527f476f7665726e616e63653a20696e73756666696369656e742d756e616c6c6f63604482015268617465642d6c71747960b81b6064820152608401610a4c565b815485101561209957815460018301545f919061206e90886146d6565b6120789190614863565b905080836001015f82825461208d9190614677565b909155506120a0915050565b5f60018301555b84825f015f8282546120b29190614677565b9091555050604051633fdf42e360e01b81526004810186905284151560248201526001600160a01b0384811660448301525f9182918291829182918291881690633fdf42e39060640160c0604051808303815f875af1158015612117573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061213b9190614946565b955095509550955095509550336001600160a01b03167f2870fe177552976b0dbc3166ded2169d5d73275fc565df30894320fd3608f3398a88888888888860405161218c979695949392919061498c565b60405180910390a25050505050505050611daa60015f55565b5f805f6121b133612504565b9050806001600160a01b03163b5f036121dc5760405162461bcd60e51b8152600401610a4c90614903565b604051633fdf42e360e01b81525f60048201819052600160248301526001600160a01b0386811660448401529091829182918291861690633fdf42e39060640160c0604051808303815f875af1158015612238573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061225c9190614946565b604051929c509a50939750919550935090915033907f2870fe177552976b0dbc3166ded2169d5d73275fc565df30894320fd3608f339906122aa908b908890889088908e9089908f9061498c565b60405180910390a25050505050915091565b5f6122c56127ce565b5f6122ce6127f6565b5090505f806122dc8561294e565b915091505f806122ee87868686610e7c565b9193509091506003905082600681111561230a5761230a61437c565b1461231c575f955050505050506124f6565b6001612326611e7d565b6123309190614677565b8560200151146123425761234261468a565b600161234c611e7d565b6123569190614677565b6001600160a01b038881165f90815260096020526040808220600490810194909455516370a0823160e01b81523093810193909352917f000000000000000000000000b01dd87b29d187f3e3a4bf6cdaebfb97f3d9ab98909116906370a0823190602401602060405180830381865afa1580156123d5573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906123f99190614876565b905080821115612407578091505b61243b6001600160a01b037f000000000000000000000000b01dd87b29d187f3e3a4bf6cdaebfb97f3d9ab98168984613c3a565b5f61249489620557305f8a6020015187604051602401612465929190918252602082015260400190565b60408051601f198184030181529190526020810180516001600160e01b0316630f0c6f0d60e11b179052612a18565b9050886001600160a01b03167f023acf63ebe0add874abd78d1a9b96f3384a3128bfdb97bf6c23e91f3d2e6e5a848960200151846124d2575f6124d5565b60015b6040516124e4939291906149c7565b60405180910390a25090955050505050505b6124ff60015f55565b919050565b5f6125387f0000000000000000000000005e17a49ec5efea6e54dab5468eb649b1d3bb57286001600160a01b038416613c6b565b92915050565b61256560405180608001604052805f81526020015f81526020015f81526020015f81525090565b61256d613edd565b5f80612577611e7d565b905060055f866001600160a01b03166001600160a01b031681526020019081526020015f206040518060800160405290815f82015481526020016001820154815260200160028201548152602001600382015481525050935060095f866001600160a01b03166001600160a01b031681526020019081526020015f206040518060a00160405290815f8201548152602001600182015481526020016002820154815260200160038201548152602001600482015481525050925060018161263e9190614677565b846020015110156126a157600191505f612656610b9f565b90505f61266b855f0151838760200151611f26565b90505f6126818660400151848860600151611f26565b828852606088018190529050612698600185614677565b60208801525050505b509193909250565b6126b16127ce565b5f6126bb84612a79565b6040516338fb359960e01b81526004810186905233602482015284151560448201526001600160a01b0384811660648301529192505f91829182918291908616906338fb3599906084016080604051808303815f875af1158015612721573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061274591906147a4565b604080516001600160a01b038c168152602081018e9052908101859052606081018490526080810183905260a081018290529397509195509350915033907f5cd37895bb9287e542a426d6abbc93d7daca0979bd43264d320d4fd224a5fc5e9060c00160405180910390a25050505050611daa60015f55565b6127ca82825f33610c0e565b5050565b60025f54036127f057604051633ee5aeb560e01b815260040160405180910390fd5b60025f55565b604080518082019091525f8082526020820152604080518082019091525f80825260208201525f612825611dce565b919450925090508015612949578251600355602083015160049081556040516370a0823160e01b815230918101919091525f907f000000000000000000000000b01dd87b29d187f3e3a4bf6cdaebfb97f3d9ab986001600160a01b0316906370a0823190602401602060405180830381865afa1580156128a7573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906128cb9190614876565b90507f000000000000000000000000000000000000000000000000000000000000000081106128fa57806128fc565b5f5b6002819055845160208087015160408051938452918301528101919091527f977a95eb227039fce4f8fe452d2d625ab67187ac5363991816ed06f4e5eed5019060600160405180910390a1505b509091565b61297560405180608001604052805f81526020015f81526020015f81526020015f81525090565b61297d613edd565b5f6129878461253e565b919450925090508015612a12576001600160a01b0384165f81815260056020908152604091829020865180825587830151600183018190558885015160028401556060808a01516003909401849055855192835293820192909252928301527f27b60fbfc71b45e0696ef86e3c31ac7979d464c139f6de0709de61b6b78093c5910160405180910390a25b50915091565b5f612a25846103e8613cca565b612a645760405162461bcd60e51b815260206004820152601060248201526f4d7573742068617665206d696e47617360801b6044820152606401610a4c565b5f80835160208501868989f195945050505050565b5f808211612ac95760405162461bcd60e51b815260206004820152601c60248201527f476f7665726e616e63653a207a65726f2d6c7174792d616d6f756e74000000006044820152606401610a4c565b5f612ad333612504565b9050806001600160a01b03163b5f03612af057612aee611042565b505b335f90815260086020526040812080548392869291612b109084906146ed565b90915550612b20905084426146d6565b335f9081526008602052604081206001018054909190612b419084906146ed565b9091555090949350505050565b5f763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c175f526e5af43d82803e903d91602b57fd5bf38360781b1760205281603760095ff590506001600160a01b038116612538576040516330be1a3d60e21b815260040160405180910390fd5b6040516001600160a01b038481166024830152838116604483015260648201839052610d139186918216906323b872dd906084015b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050613ce7565b6001546040515f916001600160a01b0316907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600180546001600160a01b0319169055565b805f819003612c7757505050565b5f5b612c84600183614677565b811015610d13575f612c978260016146ed565b90505b82811015612d3d57848482818110612cb457612cb46147d7565b9050602002016020810190612cc9919061403c565b6001600160a01b0316858584818110612ce457612ce46147d7565b9050602002016020810190612cf9919061403c565b6001600160a01b031603612d355760405162461bcd60e51b815260206004820152600360248201526206475760ec1b6044820152606401610a4c565b600101612c9a565b50600101612c79565b80515f5b81811015611daa575f838281518110612d6557612d656147d7565b60200260200101511215612db05760405162461bcd60e51b815260206004820152601260248201527143616e6e6f74206265206e6567617469766560701b6044820152606401610a4c565b600101612d4a565b5f5b8251811015611daa575f838281518110612dd657612dd66147d7565b60200260200101511380612e0257505f828281518110612df857612df86147d7565b6020026020010151135b612e4e5760405162461bcd60e51b815260206004820152601a60248201527f476f7665726e616e63653a20766f74696e67206e6f7468696e670000000000006044820152606401610a4c565b600101612dba565b5f5b8251811015611daa57828181518110612e7357612e736147d7565b60200260200101515f1480612ea05750818181518110612e9557612e956147d7565b60200260200101515f145b612eec5760405162461bcd60e51b815260206004820152601960248201527f476f7665726e616e63653a20766f74652d616e642d7665746f000000000000006044820152606401610a4c565b600101612e58565b60605f826001600160401b03811115612f0f57612f0f6141e7565b604051908082528060200260200182016040528015612f7657816020015b612f636040518060a001604052805f6001600160a01b031681526020015f81526020015f81526020015f81526020015f81525090565b815260200190600190039081612f2d5790505b5090505f836001600160401b03811115612f9257612f926141e7565b604051908082528060200260200182016040528015612fbb578160200160208202803683370190505b5090505f846001600160401b03811115612fd757612fd76141e7565b604051908082528060200260200182016040528015613000578160200160208202803683370190505b5090505f856001600160401b0381111561301c5761301c6141e7565b604051908082528060200260200182016040528015613045578160200160208202803683370190505b5090505f866001600160401b03811115613061576130616141e7565b60405190808252806020026020018201604052801561308a578160200160208202803683370190505b5090505f5b8781101561331c57335f908152600a60205260408120818b8b858181106130b8576130b86147d7565b90506020020160208101906130cd919061403c565b6001600160a01b0316815260208082019290925260409081015f20815160a081018352815480825260018301549482019490945260028201549281019290925260038101546060830152600401546080820152915015158061313257505f8160400151115b61317e5760405162461bcd60e51b815260206004820152601c60248201527f476f7665726e616e63653a206e6f7468696e6720746f207265736574000000006044820152606401610a4c565b6040518060a001604052808b8b8581811061319b5761319b6147d7565b90506020020160208101906131b0919061403c565b6001600160a01b03168152602001825f01518152602001826040015181526020018260200151815260200182606001518152508783815181106131f5576131f56147d7565b6020026020010181905250868281518110613212576132126147d7565b602002602001015160200151613227906149e2565b868381518110613239576132396147d7565b602002602001018181525050868281518110613257576132576147d7565b60200260200101516040015161326c906149e2565b85838151811061327e5761327e6147d7565b60200260200101818152505086828151811061329c5761329c6147d7565b6020026020010151606001516132b1906149e2565b8483815181106132c3576132c36147d7565b6020026020010181815250508682815181106132e1576132e16147d7565b6020026020010151608001516132f6906149e2565b838381518110613308576133086147d7565b60209081029190910101525060010161308f565b5061335e8888808060200260200160405190810160405280939291908181526020018383602002808284375f920191909152508892508791508690508561336a565b50929695505050505050565b613372613f07565b61337a6127f6565b60208301528152613389611e7d565b60e0820152335f9081526008602090815260408083208151608081018352815481526001820154938101939093526002810154838301526003015460608301528301525b8651811015613ae5575f8782815181106133e9576133e96147d7565b60200260200101519050868281518110613405576134056147d7565b602002602001015183610100018181525050858281518110613429576134296147d7565b6020908102919091010151610120840152610100830151151580613451575061012083015115155b61345d5761345d61468a565b84828151811061346f5761346f6147d7565b602002602001015183610140018181525050838281518110613493576134936147d7565b6020026020010151836101600181815250506134ae8161294e565b608085018190526060850182905284515f926134cd9285929190610e7c565b505090505f84610100015113806134e857505f846101200151135b156135865760028160068111156135015761350161437c565b148061351e5750600381600681111561351c5761351c61437c565b145b8061353a575060048160068111156135385761353861437c565b145b6135865760405162461bcd60e51b815260206004820152601b60248201527f476f7665726e616e63653a206163746976652d766f74652d66736d00000000006044820152606401610a4c565b600681600681111561359a5761359a61437c565b036135fc575f846101000151131580156135b957505f84610120015113155b6135fc5760405162461bcd60e51b8152602060048201526014602482015273135d5cdd0818994818481dda5d1a191c985dd85b60621b6044820152606401610a4c565b6040805160a080820183526080808801805151845280516020908101519085015280518501519484019490945283516060908101519084015283518101519083015286015251516101008501516136539190613d48565b6080850180519190915251604001516101208501516136729190613d48565b6080850180516040019190915251602001516101408501516136949190613d48565b6080850180516020019190915251606001516101608501516136b69190613d48565b60808086018051606090810193909352516001600160a01b0385165f90815260096020908152604091829020835181559083015160018201559082015160028201559281015160038401550151600490910155600681600681111561371d5761371d61437c565b146137b75760a084015151602085015151101561373c5761373c61468a565b60a08401515160208501518051613754908390614677565b90525060a0840151602090810151818601519091018051613776908390614677565b905250608084015151602085015180516137919083906146ed565b90525060808401516020908101518186015190910180516137b39083906146ed565b9052505b335f908152600a602090815260408083206001600160a01b0386168452825291829020825160a0810184528154815260018201549281019283526002820154938101939093526003810154606084015260040154608083015260c0860191909152516101408501516138299190613d48565b60c08501805160200191909152516060015161016085015161384b9190613d48565b60c08501805160600191909152515161010085015161386a9190613d48565b60c0850180519190915251604001516101208501516138899190613d48565b60c0850180516040019190915260e085015181516080015251602081015190516138b49042906146d6565b10156138c2576138c261468a565b60c084015160608101516040909101516138dd9042906146d6565b10156138eb576138eb61468a565b60c0840151335f908152600a602090815260408083206001600160a01b03871684528252918290208351815590830151600182015581830151600282015560608301516003820155608090920151600490920191909155840151516101208501516101008601516139659291613960916149fc565b613d75565b60408501805191909152516020015161016085015161014086015161398e9291613960916149fc565b6040808601805160200192909252905101516101208501516101008601516139bf92916139ba916149fc565b613d48565b846040015160400181815250506139ee8460400151606001518561016001518661014001516139ba91906149fc565b846040015160600181815250505f8460c00151604001515f03613a8057613a6c83620557305f8860e00151338a604001518b60c001518c60800151604051602401613a3d959493929190614a23565b60408051601f198184030181529190526020810180516001600160e01b0316633064cba960e21b179052612a18565b613a76575f613a79565b60015b9050613a84565b5060025b61010085015161012086015160e08701516040516001600160a01b0387169333937f361311c5372a81d4ab4bce0bf1b7dbfc61da467667c2097deb69b4b24bc12d2893613ad2938890614ad3565b60405180910390a35050506001016133cd565b507f0000000000000000000000004f9fbb3f1e99b56e0fe2892e623ed36a76fc605d6001600160a01b03166316934fc4613b1e33612504565b6040516001600160e01b031960e084901b1681526001600160a01b039091166004820152602401602060405180830381865afa158015613b60573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190613b849190614876565b8160400151604001511115613bab5760405162461bcd60e51b8152600401610a4c906148b9565b6020808201518051600655810151600755604091820151335f90815260088352839020815181559181015160018301559182015160028201556060909101516003909101555050505050565b5f818311613c055781611f35565b5090919050565b5f80613c1884866146d6565b9050828111613c27575f613c31565b613c318382614677565b95945050505050565b6040516001600160a01b03838116602483015260448201839052611daa91859182169063a9059cbb90606401612bee565b6040513060388201526f5af43d82803e903d91602b57fd5bf3ff602482015260148101839052733d602d80600a3d3981f3363d3d373d3d3d363d738152605881018290526037600c820120607882015260556043909101205f90611f35565b5f80603f83619c4001026040850201603f5a021015949350505050565b5f613cfb6001600160a01b03841683613d9b565b905080515f14158015613d1f575080806020019051810190613d1d9190614af5565b155b15611daa57604051635274afe760e01b81526001600160a01b0384166004820152602401610a4c565b5f80821215613d6b57613d5a82613da8565b613d649084614677565b9050612538565b611f3582846146ed565b5f80821215613d9157613d8782613da8565b613d6490846146ed565b611f358284614677565b6060611f3583835f613dbf565b5f808212613db65781612538565b612538826149e2565b606081471015613de45760405163cd78605960e01b8152306004820152602401610a4c565b5f80856001600160a01b03168486604051613dff9190614b10565b5f6040518083038185875af1925050503d805f8114613e39576040519150601f19603f3d011682016040523d82523d5f602084013e613e3e565b606091505b5091509150613e4e868383613e58565b9695505050505050565b606082613e6d57613e6882613eb4565b611f35565b8151158015613e8457506001600160a01b0384163b155b15613ead57604051639996b31560e01b81526001600160a01b0385166004820152602401610a4c565b5080611f35565b805115613ec45780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b6040518060a001604052805f81526020015f81526020015f81526020015f81526020015f81525090565b604080516101c081019091525f61018082018181526101a083019190915281908152602001613f4760405180604001604052805f81526020015f81525090565b8152602001613f7360405180608001604052805f81526020015f81526020015f81526020015f81525090565b8152602001613f9f60405180608001604052805f81526020015f81526020015f81526020015f81525090565b8152602001613fac613edd565b8152602001613fb9613edd565b8152602001613feb6040518060a001604052805f81526020015f81526020015f81526020015f81526020015f81525090565b81526020015f81526020015f81526020015f81526020015f81526020015f81525090565b5f6020828403121561401f575f80fd5b5035919050565b80356001600160a01b03811681146124ff575f80fd5b5f6020828403121561404c575f80fd5b611f3582614026565b5f60e08284031215614065575f80fd5b50919050565b80151581146109a3575f80fd5b5f805f80610140858703121561408c575f80fd5b8435935061409d8660208701614055565b92506101008501356140ae8161406b565b91506140bd6101208601614026565b905092959194509250565b5f8083601f8401126140d8575f80fd5b5081356001600160401b038111156140ee575f80fd5b6020830191508360208260051b8501011115614108575f80fd5b9250929050565b5f8060208385031215614120575f80fd5b82356001600160401b03811115614135575f80fd5b614141858286016140c8565b90969095509350505050565b5f5b8381101561416757818101518382015260200161414f565b50505f910152565b5f602080830181845280855180835260408601915060408160051b87010192508387015f5b828110156141da57878503603f19018452815180518087526141bb818989018a850161414d565b601f01601f191695909501860194509285019290850190600101614194565b5092979650505050505050565b634e487b7160e01b5f52604160045260245ffd5b604080519081016001600160401b038111828210171561421d5761421d6141e7565b60405290565b604051608081016001600160401b038111828210171561421d5761421d6141e7565b604051601f8201601f191681016001600160401b038111828210171561426d5761426d6141e7565b604052919050565b5f60a08284031215614285575f80fd5b60405160a081018181106001600160401b03821117156142a7576142a76141e7565b806040525080915082358152602083013560208201526040830135604082015260608301356060820152608083013560808201525092915050565b5f805f808486036101808112156142f7575f80fd5b61430086614026565b94506040601f1982011215614313575f80fd5b61431b6141fb565b602087810135825260408801359082015293506080605f198201121561433f575f80fd5b50614348614223565b60608681013582526080870135602083015260a0870135604083015260c08701359082015291506140bd8660e08701614275565b634e487b7160e01b5f52602160045260245ffd5b60608101600785106143a4576143a461437c565b938152602081019290925260409091015290565b5f60208083850312156143c9575f80fd5b82356001600160401b03808211156143df575f80fd5b818501915085601f8301126143f2575f80fd5b813581811115614404576144046141e7565b8060051b9150614415848301614245565b818152918301840191848101908884111561442e575f80fd5b938501935b838510156144535761444485614026565b82529385019390850190614433565b98975050505050505050565b5f805f805f805f806080898b031215614476575f80fd5b88356001600160401b038082111561448c575f80fd5b6144988c838d016140c8565b909a50985060208b01359150808211156144b0575f80fd5b6144bc8c838d016140c8565b909850965060408b01359150808211156144d4575f80fd5b6144e08c838d016140c8565b909650945060608b01359150808211156144f8575f80fd5b506145058b828c016140c8565b999c989b5096995094979396929594505050565b5f805f6040848603121561452b575f80fd5b83356001600160401b03811115614540575f80fd5b61454c868287016140c8565b90945092505060208401356145608161406b565b809150509250925092565b5f806040838503121561457c575f80fd5b61458583614026565b915061459360208401614026565b90509250929050565b5f805f606084860312156145ae575f80fd5b505081359360208301359350604090920135919050565b825181526020808401519082015260c08101611f356040830184805182526020810151602083015260408101516040830152606081015160608301525050565b5f805f60608486031215614617575f80fd5b8335925060208401356146298161406b565b915061463760408501614026565b90509250925092565b5f806101008385031215614652575f80fd5b823591506145938460208501614055565b634e487b7160e01b5f52601160045260245ffd5b8181038181111561253857612538614663565b634e487b7160e01b5f52600160045260245ffd5b600381106146ae576146ae61437c565b9052565b6001600160a01b03841681526020810183905260608101611d17604083018461469e565b808202811582820484141761253857612538614663565b8082018082111561253857612538614663565b8581526001600160a01b0385811660208301526101608201908061472387614026565b1660408401528061473660208801614026565b1660608401525060408501356080830152606085013560a0830152608085013560ff81168114614764575f80fd5b60ff1660c08381019190915260a086013560e084015294909401356101008201529115156101208301526001600160a01b03166101409091015292915050565b5f805f80608085870312156147b7575f80fd5b505082516020840151604085015160609095015191969095509092509050565b634e487b7160e01b5f52603260045260245ffd5b5f808335601e19843603018112614800575f80fd5b8301803591506001600160401b03821115614819575f80fd5b602001915036819003821315614108575f80fd5b818382375f9101908152919050565b634e487b7160e01b5f52601260045260245ffd5b5f8261485e5761485e61483c565b500690565b5f826148715761487161483c565b500490565b5f60208284031215614886575f80fd5b5051919050565b6001600160a01b038581168252841660208201526040810183905260808101613c31606083018461469e565b6020808252602a908201527f476f7665726e616e63653a20696e73756666696369656e742d6f722d616c6c6f60408201526963617465642d6c71747960b01b606082015260800190565b60208082526023908201527f476f7665726e616e63653a20757365722d70726f78792d6e6f742d6465706c6f6040820152621e595960ea1b606082015260800190565b5f805f805f8060c0878903121561495b575f80fd5b865195506020870151945060408701519350606087015192506080870151915060a087015190509295509295509295565b6001600160a01b03979097168752602087019590955260408601939093526060850191909152608084015260a083015260c082015260e00190565b8381526020810183905260608101611d17604083018461469e565b5f600160ff1b82016149f6576149f6614663565b505f0390565b8082018281125f831280158216821582161715614a1b57614a1b614663565b505092915050565b8581526001600160a01b03851660208201526102008101614a686040830186805182526020810151602083015260408101516040830152606081015160608301525050565b835160c0830152602084015160e08301526040840151610100830152606084015161012083015260808401516101408301528251610160830152602083015161018083015260408301516101a083015260608301516101c083015260808301516101e0830152613e4e565b848152602081018490526040810183905260808101613c31606083018461469e565b5f60208284031215614b05575f80fd5b8151611f358161406b565b5f8251614b2181846020870161414d565b919091019291505056fea264697066735822122068679bfa2c2eee9124643e597f74c2abf6b9406391adb4f8cd43a688cc2b27df64736f6c63430008180033

Verified Source Code Full Match

Compiler: v0.8.24+commit.e11b9ed9 EVM: cancun Optimization: Yes (200 runs)
Governance.sol 942 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {IERC20} from "openzeppelin/contracts/interfaces/IERC20.sol";
import {SafeERC20} from "openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "openzeppelin/contracts/utils/ReentrancyGuard.sol";

import {IGovernance, UNREGISTERED_INITIATIVE} from "./interfaces/IGovernance.sol";
import {IInitiative} from "./interfaces/IInitiative.sol";
import {ILQTYStaking} from "./interfaces/ILQTYStaking.sol";

import {UserProxy} from "./UserProxy.sol";
import {UserProxyFactory} from "./UserProxyFactory.sol";

import {add, sub, max} from "./utils/Math.sol";
import {_requireNoDuplicates, _requireNoNegatives} from "./utils/UniqueArray.sol";
import {MultiDelegateCall} from "./utils/MultiDelegateCall.sol";
import {WAD, PermitParams} from "./utils/Types.sol";
import {safeCallWithMinGas} from "./utils/SafeCallMinGas.sol";
import {Ownable} from "./utils/Ownable.sol";
import {_lqtyToVotes} from "./utils/VotingPower.sol";

/// @title Governance: Modular Initiative based Governance
contract Governance is MultiDelegateCall, UserProxyFactory, ReentrancyGuard, Ownable, IGovernance {
    using SafeERC20 for IERC20;

    uint256 constant MIN_GAS_TO_HOOK = 350_000;

    /// Replace this to ensure hooks have sufficient gas

    /// @inheritdoc IGovernance
    ILQTYStaking public immutable stakingV1;
    /// @inheritdoc IGovernance
    IERC20 public immutable lqty;
    /// @inheritdoc IGovernance
    IERC20 public immutable bold;
    /// @inheritdoc IGovernance
    uint256 public immutable EPOCH_START;
    /// @inheritdoc IGovernance
    uint256 public immutable EPOCH_DURATION;
    /// @inheritdoc IGovernance
    uint256 public immutable EPOCH_VOTING_CUTOFF;
    /// @inheritdoc IGovernance
    uint256 public immutable MIN_CLAIM;
    /// @inheritdoc IGovernance
    uint256 public immutable MIN_ACCRUAL;
    /// @inheritdoc IGovernance
    uint256 public immutable REGISTRATION_FEE;
    /// @inheritdoc IGovernance
    uint256 public immutable REGISTRATION_THRESHOLD_FACTOR;
    /// @inheritdoc IGovernance
    uint256 public immutable UNREGISTRATION_THRESHOLD_FACTOR;
    /// @inheritdoc IGovernance
    uint256 public immutable UNREGISTRATION_AFTER_EPOCHS;
    /// @inheritdoc IGovernance
    uint256 public immutable VOTING_THRESHOLD_FACTOR;

    /// @inheritdoc IGovernance
    uint256 public boldAccrued;

    /// @inheritdoc IGovernance
    VoteSnapshot public votesSnapshot;
    /// @inheritdoc IGovernance
    mapping(address => InitiativeVoteSnapshot) public votesForInitiativeSnapshot;

    /// @inheritdoc IGovernance
    GlobalState public globalState;
    /// @inheritdoc IGovernance
    mapping(address => UserState) public userStates;
    /// @inheritdoc IGovernance
    mapping(address => InitiativeState) public initiativeStates;
    /// @inheritdoc IGovernance
    mapping(address => mapping(address => Allocation)) public lqtyAllocatedByUserToInitiative;
    /// @inheritdoc IGovernance
    mapping(address => uint256) public override registeredInitiatives;

    constructor(
        address _lqty,
        address _lusd,
        address _stakingV1,
        address _bold,
        Configuration memory _config,
        address _owner,
        address[] memory _initiatives
    ) UserProxyFactory(_lqty, _lusd, _stakingV1) Ownable(_owner) {
        stakingV1 = ILQTYStaking(_stakingV1);
        lqty = IERC20(_lqty);
        bold = IERC20(_bold);
        require(_config.minClaim <= _config.minAccrual, "Gov: min-claim-gt-min-accrual");
        REGISTRATION_FEE = _config.registrationFee;

        // Registration threshold must be below 100% of votes
        require(_config.registrationThresholdFactor < WAD, "Gov: registration-config");
        REGISTRATION_THRESHOLD_FACTOR = _config.registrationThresholdFactor;

        // Unregistration must be X times above the `votingThreshold`
        require(_config.unregistrationThresholdFactor > WAD, "Gov: unregistration-config");
        UNREGISTRATION_THRESHOLD_FACTOR = _config.unregistrationThresholdFactor;
        UNREGISTRATION_AFTER_EPOCHS = _config.unregistrationAfterEpochs;

        // Voting threshold must be below 100% of votes
        require(_config.votingThresholdFactor < WAD, "Gov: voting-config");
        VOTING_THRESHOLD_FACTOR = _config.votingThresholdFactor;

        MIN_CLAIM = _config.minClaim;
        MIN_ACCRUAL = _config.minAccrual;
        require(_config.epochStart <= block.timestamp, "Gov: cannot-start-in-future");
        EPOCH_START = _config.epochStart;
        require(_config.epochDuration > 0, "Gov: epoch-duration-zero");
        EPOCH_DURATION = _config.epochDuration;
        require(_config.epochVotingCutoff < _config.epochDuration, "Gov: epoch-voting-cutoff-gt-epoch-duration");
        EPOCH_VOTING_CUTOFF = _config.epochVotingCutoff;

        if (_initiatives.length > 0) {
            registerInitialInitiatives(_initiatives);
        }
    }

    function registerInitialInitiatives(address[] memory _initiatives) public onlyOwner {
        for (uint256 i = 0; i < _initiatives.length; i++) {
            // Register initial initiatives in the earliest possible epoch, which lets us make them votable immediately
            // post-deployment if we so choose, by backdating the first epoch at least EPOCH_DURATION in the past.
            registeredInitiatives[_initiatives[i]] = 1;

            bool success = safeCallWithMinGas(
                _initiatives[i], MIN_GAS_TO_HOOK, 0, abi.encodeCall(IInitiative.onRegisterInitiative, (1))
            );

            emit RegisterInitiative(_initiatives[i], msg.sender, 1, success ? HookStatus.Succeeded : HookStatus.Failed);
        }

        _renounceOwnership();
    }

    /*//////////////////////////////////////////////////////////////
                                STAKING
    //////////////////////////////////////////////////////////////*/

    function _increaseUserVoteTrackers(uint256 _lqtyAmount) private returns (UserProxy) {
        require(_lqtyAmount > 0, "Governance: zero-lqty-amount");

        address userProxyAddress = deriveUserProxyAddress(msg.sender);

        if (userProxyAddress.code.length == 0) {
            deployUserProxy();
        }

        UserProxy userProxy = UserProxy(payable(userProxyAddress));

        // update the vote power trackers
        userStates[msg.sender].unallocatedLQTY += _lqtyAmount;
        userStates[msg.sender].unallocatedOffset += block.timestamp * _lqtyAmount;

        return userProxy;
    }

    /// @inheritdoc IGovernance
    function depositLQTY(uint256 _lqtyAmount) external {
        depositLQTY(_lqtyAmount, false, msg.sender);
    }

    function depositLQTY(uint256 _lqtyAmount, bool _doSendRewards, address _recipient) public nonReentrant {
        UserProxy userProxy = _increaseUserVoteTrackers(_lqtyAmount);

        (uint256 lusdReceived, uint256 lusdSent, uint256 ethReceived, uint256 ethSent) =
            userProxy.stake(_lqtyAmount, msg.sender, _doSendRewards, _recipient);

        emit DepositLQTY(msg.sender, _recipient, _lqtyAmount, lusdReceived, lusdSent, ethReceived, ethSent);
    }

    /// @inheritdoc IGovernance
    function depositLQTYViaPermit(uint256 _lqtyAmount, PermitParams calldata _permitParams) external {
        depositLQTYViaPermit(_lqtyAmount, _permitParams, false, msg.sender);
    }

    function depositLQTYViaPermit(
        uint256 _lqtyAmount,
        PermitParams calldata _permitParams,
        bool _doSendRewards,
        address _recipient
    ) public nonReentrant {
        UserProxy userProxy = _increaseUserVoteTrackers(_lqtyAmount);

        (uint256 lusdReceived, uint256 lusdSent, uint256 ethReceived, uint256 ethSent) =
            userProxy.stakeViaPermit(_lqtyAmount, msg.sender, _permitParams, _doSendRewards, _recipient);

        emit DepositLQTY(msg.sender, _recipient, _lqtyAmount, lusdReceived, lusdSent, ethReceived, ethSent);
    }

    /// @inheritdoc IGovernance
    function withdrawLQTY(uint256 _lqtyAmount) external {
        withdrawLQTY(_lqtyAmount, true, msg.sender);
    }

    function withdrawLQTY(uint256 _lqtyAmount, bool _doSendRewards, address _recipient) public nonReentrant {
        UserState storage userState = userStates[msg.sender];

        UserProxy userProxy = UserProxy(payable(deriveUserProxyAddress(msg.sender)));
        require(address(userProxy).code.length != 0, "Governance: user-proxy-not-deployed");

        // check if user has enough unallocated lqty
        require(_lqtyAmount <= userState.unallocatedLQTY, "Governance: insufficient-unallocated-lqty");

        // Update the offset tracker
        if (_lqtyAmount < userState.unallocatedLQTY) {
            // The offset decrease is proportional to the partial lqty decrease
            uint256 offsetDecrease = _lqtyAmount * userState.unallocatedOffset / userState.unallocatedLQTY;
            userState.unallocatedOffset -= offsetDecrease;
        } else {
            // if _lqtyAmount == userState.unallocatedLqty, zero the offset tracker
            userState.unallocatedOffset = 0;
        }

        // Update the user's LQTY tracker
        userState.unallocatedLQTY -= _lqtyAmount;

        (
            uint256 lqtyReceived,
            uint256 lqtySent,
            uint256 lusdReceived,
            uint256 lusdSent,
            uint256 ethReceived,
            uint256 ethSent
        ) = userProxy.unstake(_lqtyAmount, _doSendRewards, _recipient);

        emit WithdrawLQTY(msg.sender, _recipient, lqtyReceived, lqtySent, lusdReceived, lusdSent, ethReceived, ethSent);
    }

    /// @inheritdoc IGovernance
    function claimFromStakingV1(address _rewardRecipient) external returns (uint256 lusdSent, uint256 ethSent) {
        address payable userProxyAddress = payable(deriveUserProxyAddress(msg.sender));
        require(userProxyAddress.code.length != 0, "Governance: user-proxy-not-deployed");

        uint256 lqtyReceived;
        uint256 lqtySent;
        uint256 lusdReceived;
        uint256 ethReceived;

        (lqtyReceived, lqtySent, lusdReceived, lusdSent, ethReceived, ethSent) =
            UserProxy(userProxyAddress).unstake(0, true, _rewardRecipient);

        emit WithdrawLQTY(
            msg.sender, _rewardRecipient, lqtyReceived, lqtySent, lusdReceived, lusdSent, ethReceived, ethSent
        );
    }

    /*//////////////////////////////////////////////////////////////
                                 VOTING
    //////////////////////////////////////////////////////////////*/

    /// @inheritdoc IGovernance
    function epoch() public view returns (uint256) {
        return ((block.timestamp - EPOCH_START) / EPOCH_DURATION) + 1;
    }

    /// @inheritdoc IGovernance
    function epochStart() public view returns (uint256) {
        return EPOCH_START + (epoch() - 1) * EPOCH_DURATION;
    }

    /// @inheritdoc IGovernance
    function secondsWithinEpoch() public view returns (uint256) {
        return (block.timestamp - EPOCH_START) % EPOCH_DURATION;
    }

    /// @inheritdoc IGovernance
    function lqtyToVotes(uint256 _lqtyAmount, uint256 _timestamp, uint256 _offset) public pure returns (uint256) {
        return _lqtyToVotes(_lqtyAmount, _timestamp, _offset);
    }

    /*//////////////////////////////////////////////////////////////
                                 SNAPSHOTS
    //////////////////////////////////////////////////////////////*/

    /// @inheritdoc IGovernance
    function getLatestVotingThreshold() public view returns (uint256) {
        uint256 snapshotVotes = votesSnapshot.votes;

        return calculateVotingThreshold(snapshotVotes);
    }

    /// @inheritdoc IGovernance
    function calculateVotingThreshold() public returns (uint256) {
        (VoteSnapshot memory snapshot,) = _snapshotVotes();

        return calculateVotingThreshold(snapshot.votes);
    }

    /// @inheritdoc IGovernance
    function calculateVotingThreshold(uint256 _votes) public view returns (uint256) {
        if (_votes == 0) return 0;

        uint256 minVotes; // to reach MIN_CLAIM: snapshotVotes * MIN_CLAIM / boldAccrued
        uint256 payoutPerVote = boldAccrued * WAD / _votes;
        if (payoutPerVote != 0) {
            minVotes = MIN_CLAIM * WAD / payoutPerVote;
        }
        return max(_votes * VOTING_THRESHOLD_FACTOR / WAD, minVotes);
    }

    // Snapshots votes at the end of the previous epoch
    // Accrues funds until the first activity of the current epoch, which are valid throughout all of the current epoch
    function _snapshotVotes() internal returns (VoteSnapshot memory snapshot, GlobalState memory state) {
        bool shouldUpdate;
        (snapshot, state, shouldUpdate) = getTotalVotesAndState();

        if (shouldUpdate) {
            votesSnapshot = snapshot;
            uint256 boldBalance = bold.balanceOf(address(this));
            boldAccrued = (boldBalance < MIN_ACCRUAL) ? 0 : boldBalance;
            emit SnapshotVotes(snapshot.votes, snapshot.forEpoch, boldAccrued);
        }
    }

    /// @inheritdoc IGovernance
    function getTotalVotesAndState()
        public
        view
        returns (VoteSnapshot memory snapshot, GlobalState memory state, bool shouldUpdate)
    {
        uint256 currentEpoch = epoch();
        snapshot = votesSnapshot;
        state = globalState;

        if (snapshot.forEpoch < currentEpoch - 1) {
            shouldUpdate = true;

            snapshot.votes = lqtyToVotes(state.countedVoteLQTY, epochStart(), state.countedVoteOffset);
            snapshot.forEpoch = currentEpoch - 1;
        }
    }

    // Snapshots votes for an initiative for the previous epoch
    function _snapshotVotesForInitiative(address _initiative)
        internal
        returns (InitiativeVoteSnapshot memory initiativeSnapshot, InitiativeState memory initiativeState)
    {
        bool shouldUpdate;
        (initiativeSnapshot, initiativeState, shouldUpdate) = getInitiativeSnapshotAndState(_initiative);

        if (shouldUpdate) {
            votesForInitiativeSnapshot[_initiative] = initiativeSnapshot;
            emit SnapshotVotesForInitiative(
                _initiative, initiativeSnapshot.votes, initiativeSnapshot.vetos, initiativeSnapshot.forEpoch
            );
        }
    }

    /// @inheritdoc IGovernance
    function getInitiativeSnapshotAndState(address _initiative)
        public
        view
        returns (
            InitiativeVoteSnapshot memory initiativeSnapshot,
            InitiativeState memory initiativeState,
            bool shouldUpdate
        )
    {
        // Get the storage data
        uint256 currentEpoch = epoch();
        initiativeSnapshot = votesForInitiativeSnapshot[_initiative];
        initiativeState = initiativeStates[_initiative];

        if (initiativeSnapshot.forEpoch < currentEpoch - 1) {
            shouldUpdate = true;

            uint256 start = epochStart();
            uint256 votes = lqtyToVotes(initiativeState.voteLQTY, start, initiativeState.voteOffset);
            uint256 vetos = lqtyToVotes(initiativeState.vetoLQTY, start, initiativeState.vetoOffset);
            initiativeSnapshot.votes = votes;
            initiativeSnapshot.vetos = vetos;

            initiativeSnapshot.forEpoch = currentEpoch - 1;
        }
    }

    /// @inheritdoc IGovernance
    function snapshotVotesForInitiative(address _initiative)
        external
        nonReentrant
        returns (VoteSnapshot memory voteSnapshot, InitiativeVoteSnapshot memory initiativeVoteSnapshot)
    {
        (voteSnapshot,) = _snapshotVotes();
        (initiativeVoteSnapshot,) = _snapshotVotesForInitiative(_initiative);
    }

    /*//////////////////////////////////////////////////////////////
                                 FSM
    //////////////////////////////////////////////////////////////*/

    /// @notice Given an inititive address, updates all snapshots and return the initiative state
    ///     See the view version of `getInitiativeState` for the underlying logic on Initatives FSM
    function getInitiativeState(address _initiative)
        public
        returns (InitiativeStatus status, uint256 lastEpochClaim, uint256 claimableAmount)
    {
        (VoteSnapshot memory votesSnapshot_,) = _snapshotVotes();
        (InitiativeVoteSnapshot memory votesForInitiativeSnapshot_, InitiativeState memory initiativeState) =
            _snapshotVotesForInitiative(_initiative);

        return getInitiativeState(_initiative, votesSnapshot_, votesForInitiativeSnapshot_, initiativeState);
    }

    /// @dev Given an initiative address and its snapshot, determines the current state for an initiative
    function getInitiativeState(
        address _initiative,
        VoteSnapshot memory _votesSnapshot,
        InitiativeVoteSnapshot memory _votesForInitiativeSnapshot,
        InitiativeState memory _initiativeState
    ) public view returns (InitiativeStatus status, uint256 lastEpochClaim, uint256 claimableAmount) {
        uint256 initiativeRegistrationEpoch = registeredInitiatives[_initiative];

        // == Non existent Condition == //
        if (initiativeRegistrationEpoch == 0) {
            return (InitiativeStatus.NONEXISTENT, 0, 0);
            /// By definition it has zero rewards
        }

        uint256 currentEpoch = epoch();

        // == Just Registered Condition == //
        if (initiativeRegistrationEpoch == currentEpoch) {
            return (InitiativeStatus.WARM_UP, 0, 0);
            /// Was registered this week, cannot have rewards
        }

        // Fetch last epoch at which we claimed
        lastEpochClaim = initiativeStates[_initiative].lastEpochClaim;

        // == Disabled Condition == //
        if (initiativeRegistrationEpoch == UNREGISTERED_INITIATIVE) {
            return (InitiativeStatus.DISABLED, lastEpochClaim, 0);
            /// By definition it has zero rewards
        }

        // == Already Claimed Condition == //
        if (lastEpochClaim >= currentEpoch - 1) {
            // early return, we have already claimed
            return (InitiativeStatus.CLAIMED, lastEpochClaim, claimableAmount);
        }

        // NOTE: Pass the snapshot value so we get accurate result
        uint256 votingTheshold = calculateVotingThreshold(_votesSnapshot.votes);

        // If it's voted and can get rewards
        // Votes > calculateVotingThreshold
        // == Rewards Conditions (votes can be zero, logic is the same) == //

        // By definition if _votesForInitiativeSnapshot.votes > 0 then _votesSnapshot.votes > 0
        if (
            _votesForInitiativeSnapshot.votes > votingTheshold
                && _votesForInitiativeSnapshot.votes > _votesForInitiativeSnapshot.vetos
        ) {
            uint256 claim = _votesForInitiativeSnapshot.votes * boldAccrued / _votesSnapshot.votes;

            return (InitiativeStatus.CLAIMABLE, lastEpochClaim, claim);
        }

        // == Unregister Condition == //
        // e.g. if `UNREGISTRATION_AFTER_EPOCHS` is 4, the initiative will become unregisterable after spending 4 epochs
        // while being in one of the following conditions:
        //  - in `SKIP` state (not having received enough votes to cross the voting threshold)
        //  - in `CLAIMABLE` state (having received enough votes to cross the voting threshold) but never being claimed
        if (
            (_initiativeState.lastEpochClaim + UNREGISTRATION_AFTER_EPOCHS < currentEpoch - 1)
                || _votesForInitiativeSnapshot.vetos > _votesForInitiativeSnapshot.votes
                    && _votesForInitiativeSnapshot.vetos > votingTheshold * UNREGISTRATION_THRESHOLD_FACTOR / WAD
        ) {
            return (InitiativeStatus.UNREGISTERABLE, lastEpochClaim, 0);
        }

        // == Not meeting threshold Condition == //
        return (InitiativeStatus.SKIP, lastEpochClaim, 0);
    }

    /// @inheritdoc IGovernance
    function registerInitiative(address _initiative) external nonReentrant {
        uint256 currentEpoch = epoch();
        require(currentEpoch > 2, "Governance: registration-not-yet-enabled");

        require(_initiative != address(0), "Governance: zero-address");
        (InitiativeStatus status,,) = getInitiativeState(_initiative);
        require(status == InitiativeStatus.NONEXISTENT, "Governance: initiative-already-registered");

        address userProxyAddress = deriveUserProxyAddress(msg.sender);
        (VoteSnapshot memory snapshot,) = _snapshotVotes();
        UserState memory userState = userStates[msg.sender];

        bold.safeTransferFrom(msg.sender, address(this), REGISTRATION_FEE);

        // an initiative can be registered if the registrant has more voting power (LQTY * age)
        // than the registration threshold derived from the previous epoch's total global votes

        uint256 upscaledSnapshotVotes = snapshot.votes;

        uint256 totalUserOffset = userState.allocatedOffset + userState.unallocatedOffset;
        require(
            // Check against the user's total voting power, so include both allocated and unallocated LQTY
            lqtyToVotes(stakingV1.stakes(userProxyAddress), epochStart(), totalUserOffset)
                >= upscaledSnapshotVotes * REGISTRATION_THRESHOLD_FACTOR / WAD,
            "Governance: insufficient-lqty"
        );

        registeredInitiatives[_initiative] = currentEpoch;

        /// This ensures that the initiatives has UNREGISTRATION_AFTER_EPOCHS even after the first epoch
        initiativeStates[_initiative].lastEpochClaim = currentEpoch - 1;

        // Replaces try / catch | Enforces sufficient gas is passed
        bool success = safeCallWithMinGas(
            _initiative, MIN_GAS_TO_HOOK, 0, abi.encodeCall(IInitiative.onRegisterInitiative, (currentEpoch))
        );

        emit RegisterInitiative(
            _initiative, msg.sender, currentEpoch, success ? HookStatus.Succeeded : HookStatus.Failed
        );
    }

    struct ResetInitiativeData {
        address initiative;
        int256 LQTYVotes;
        int256 LQTYVetos;
        int256 OffsetVotes;
        int256 OffsetVetos;
    }

    /// @dev Resets an initiative and return the previous votes
    /// NOTE: Technically we don't need vetos
    /// NOTE: Technically we want to populate the `ResetInitiativeData` only when `secondsWithinEpoch() > EPOCH_VOTING_CUTOFF`
    function _resetInitiatives(address[] calldata _initiativesToReset)
        internal
        returns (ResetInitiativeData[] memory)
    {
        ResetInitiativeData[] memory cachedData = new ResetInitiativeData[](_initiativesToReset.length);

        int256[] memory deltaLQTYVotes = new int256[](_initiativesToReset.length);
        int256[] memory deltaLQTYVetos = new int256[](_initiativesToReset.length);
        int256[] memory deltaOffsetVotes = new int256[](_initiativesToReset.length);
        int256[] memory deltaOffsetVetos = new int256[](_initiativesToReset.length);

        // Prepare reset data
        for (uint256 i; i < _initiativesToReset.length; i++) {
            Allocation memory alloc = lqtyAllocatedByUserToInitiative[msg.sender][_initiativesToReset[i]];
            require(alloc.voteLQTY > 0 || alloc.vetoLQTY > 0, "Governance: nothing to reset");

            // Cache, used to enforce limits later
            cachedData[i] = ResetInitiativeData({
                initiative: _initiativesToReset[i],
                LQTYVotes: int256(alloc.voteLQTY),
                LQTYVetos: int256(alloc.vetoLQTY),
                OffsetVotes: int256(alloc.voteOffset),
                OffsetVetos: int256(alloc.vetoOffset)
            });

            // -0 is still 0, so its fine to flip both
            deltaLQTYVotes[i] = -(cachedData[i].LQTYVotes);
            deltaLQTYVetos[i] = -(cachedData[i].LQTYVetos);
            deltaOffsetVotes[i] = -(cachedData[i].OffsetVotes);
            deltaOffsetVetos[i] = -(cachedData[i].OffsetVetos);
        }

        // RESET HERE || All initiatives will receive most updated data and 0 votes / vetos
        _allocateLQTY(_initiativesToReset, deltaLQTYVotes, deltaLQTYVetos, deltaOffsetVotes, deltaOffsetVetos);

        return cachedData;
    }

    /// @inheritdoc IGovernance
    function resetAllocations(address[] calldata _initiativesToReset, bool checkAll) external nonReentrant {
        _requireNoDuplicates(_initiativesToReset);
        _resetInitiatives(_initiativesToReset);

        // NOTE: In most cases, the check will pass
        // But if you allocate too many initiatives, we may run OOG
        // As such the check is optional here
        // All other calls to the system enforce this
        // So it's recommended that your last call to `resetAllocations` passes the check
        if (checkAll) {
            require(userStates[msg.sender].allocatedLQTY == 0, "Governance: must be a reset");
        }
    }

    /// @inheritdoc IGovernance
    function allocateLQTY(
        address[] calldata _initiativesToReset,
        address[] calldata _initiatives,
        int256[] calldata _absoluteLQTYVotes,
        int256[] calldata _absoluteLQTYVetos
    ) external nonReentrant {
        require(
            _initiatives.length == _absoluteLQTYVotes.length && _absoluteLQTYVotes.length == _absoluteLQTYVetos.length,
            "Governance: array-length-mismatch"
        );

        // To ensure the change is safe, enforce uniqueness
        _requireNoDuplicates(_initiativesToReset);
        _requireNoDuplicates(_initiatives);

        // Explicit >= 0 checks for all values since we reset values below
        _requireNoNegatives(_absoluteLQTYVotes);
        _requireNoNegatives(_absoluteLQTYVetos);
        // If the goal is to remove all votes from an initiative, including in _initiativesToReset is enough
        _requireNoNOP(_absoluteLQTYVotes, _absoluteLQTYVetos);
        _requireNoSimultaneousVoteAndVeto(_absoluteLQTYVotes, _absoluteLQTYVetos);

        // You MUST always reset
        ResetInitiativeData[] memory cachedData = _resetInitiatives(_initiativesToReset);

        /// Invariant, 0 allocated = 0 votes
        UserState memory userState = userStates[msg.sender];
        require(userState.allocatedLQTY == 0, "must be a reset");
        require(userState.unallocatedLQTY != 0, "Governance: insufficient-or-allocated-lqty"); // avoid div-by-zero

        // After cutoff you can only re-apply the same vote
        // Or vote less
        // Or abstain
        // You can always add a veto, hence we only validate the addition of Votes
        // And ignore the addition of vetos
        // Validate the data here to ensure that the voting is capped at the amount in the other case
        if (secondsWithinEpoch() > EPOCH_VOTING_CUTOFF) {
            // Cap the max votes to the previous cache value
            // This means that no new votes can happen here

            // Removing and VETOING is always accepted
            for (uint256 x; x < _initiatives.length; x++) {
                // If we find it, we ensure it cannot be an increase
                bool found;
                for (uint256 y; y < cachedData.length; y++) {
                    if (cachedData[y].initiative == _initiatives[x]) {
                        found = true;
                        require(_absoluteLQTYVotes[x] <= cachedData[y].LQTYVotes, "Cannot increase");
                        break;
                    }
                }

                // Else we assert that the change is a veto, because by definition the initiatives will have received zero votes past this line
                if (!found) {
                    require(_absoluteLQTYVotes[x] == 0, "Must be zero for new initiatives");
                }
            }
        }

        int256[] memory absoluteOffsetVotes = new int256[](_initiatives.length);
        int256[] memory absoluteOffsetVetos = new int256[](_initiatives.length);

        // Calculate the offset portions that correspond to each LQTY vote and veto portion
        // By recalculating `unallocatedLQTY` & `unallocatedOffset` after each step, we ensure that rounding error
        // doesn't accumulate in `unallocatedOffset`.
        // However, it should be noted that this makes the exact offset allocations dependent on the ordering of the
        // `_initiatives` array.
        for (uint256 x; x < _initiatives.length; x++) {
            // Either _absoluteLQTYVotes[x] or _absoluteLQTYVetos[x] is guaranteed to be zero
            (int256[] calldata lqtyAmounts, int256[] memory offsets) = _absoluteLQTYVotes[x] > 0
                ? (_absoluteLQTYVotes, absoluteOffsetVotes)
                : (_absoluteLQTYVetos, absoluteOffsetVetos);

            uint256 lqtyAmount = uint256(lqtyAmounts[x]);
            uint256 offset = userState.unallocatedOffset * lqtyAmount / userState.unallocatedLQTY;

            userState.unallocatedLQTY -= lqtyAmount;
            userState.unallocatedOffset -= offset;

            offsets[x] = int256(offset);
        }

        // Vote here, all values are now absolute changes
        _allocateLQTY(_initiatives, _absoluteLQTYVotes, _absoluteLQTYVetos, absoluteOffsetVotes, absoluteOffsetVetos);
    }

    // Avoid "stack too deep" by placing these variables in memory
    struct AllocateLQTYMemory {
        VoteSnapshot votesSnapshot_;
        GlobalState state;
        UserState userState;
        InitiativeVoteSnapshot votesForInitiativeSnapshot_;
        InitiativeState initiativeState;
        InitiativeState prevInitiativeState;
        Allocation allocation;
        uint256 currentEpoch;
        int256 deltaLQTYVotes;
        int256 deltaLQTYVetos;
        int256 deltaOffsetVotes;
        int256 deltaOffsetVetos;
    }

    /// @dev For each given initiative applies relative changes to the allocation
    /// @dev Assumes that all the input arrays are of equal length
    /// @dev NOTE: Given the current usage the function either: Resets the value to 0, or sets the value to a new value
    ///      Review the flows as the function could be used in many ways, but it ends up being used in just those 2 ways
    function _allocateLQTY(
        address[] memory _initiatives,
        int256[] memory _deltaLQTYVotes,
        int256[] memory _deltaLQTYVetos,
        int256[] memory _deltaOffsetVotes,
        int256[] memory _deltaOffsetVetos
    ) internal {
        AllocateLQTYMemory memory vars;
        (vars.votesSnapshot_, vars.state) = _snapshotVotes();
        vars.currentEpoch = epoch();
        vars.userState = userStates[msg.sender];

        for (uint256 i = 0; i < _initiatives.length; i++) {
            address initiative = _initiatives[i];
            vars.deltaLQTYVotes = _deltaLQTYVotes[i];
            vars.deltaLQTYVetos = _deltaLQTYVetos[i];
            assert(vars.deltaLQTYVotes != 0 || vars.deltaLQTYVetos != 0);

            vars.deltaOffsetVotes = _deltaOffsetVotes[i];
            vars.deltaOffsetVetos = _deltaOffsetVetos[i];

            /// === Check FSM === ///
            // Can vote positively in SKIP, CLAIMABLE and CLAIMED states
            // Force to remove votes if disabled
            // Can remove votes and vetos in every stage
            (vars.votesForInitiativeSnapshot_, vars.initiativeState) = _snapshotVotesForInitiative(initiative);

            (InitiativeStatus status,,) = getInitiativeState(
                initiative, vars.votesSnapshot_, vars.votesForInitiativeSnapshot_, vars.initiativeState
            );

            if (vars.deltaLQTYVotes > 0 || vars.deltaLQTYVetos > 0) {
                /// You cannot vote on `unregisterable` but a vote may have been there
                require(
                    status == InitiativeStatus.SKIP || status == InitiativeStatus.CLAIMABLE
                        || status == InitiativeStatus.CLAIMED,
                    "Governance: active-vote-fsm"
                );
            }

            if (status == InitiativeStatus.DISABLED) {
                require(vars.deltaLQTYVotes <= 0 && vars.deltaLQTYVetos <= 0, "Must be a withdrawal");
            }

            /// === UPDATE ACCOUNTING === ///
            // == INITIATIVE STATE == //

            // deep copy of the initiative's state before the allocation
            vars.prevInitiativeState = InitiativeState(
                vars.initiativeState.voteLQTY,
                vars.initiativeState.voteOffset,
                vars.initiativeState.vetoLQTY,
                vars.initiativeState.vetoOffset,
                vars.initiativeState.lastEpochClaim
            );

            // allocate the voting and vetoing LQTY to the initiative
            vars.initiativeState.voteLQTY = add(vars.initiativeState.voteLQTY, vars.deltaLQTYVotes);
            vars.initiativeState.vetoLQTY = add(vars.initiativeState.vetoLQTY, vars.deltaLQTYVetos);

            // Update the initiative's vote and veto offsets
            vars.initiativeState.voteOffset = add(vars.initiativeState.voteOffset, vars.deltaOffsetVotes);
            vars.initiativeState.vetoOffset = add(vars.initiativeState.vetoOffset, vars.deltaOffsetVetos);

            // update the initiative's state
            initiativeStates[initiative] = vars.initiativeState;

            // == GLOBAL STATE == //

            /// We update the state only for non-disabled initiatives
            /// Disabled initiatves have had their totals subtracted already
            if (status != InitiativeStatus.DISABLED) {
                assert(vars.state.countedVoteLQTY >= vars.prevInitiativeState.voteLQTY);

                // Remove old initative LQTY and offset from global count
                vars.state.countedVoteLQTY -= vars.prevInitiativeState.voteLQTY;
                vars.state.countedVoteOffset -= vars.prevInitiativeState.voteOffset;

                // Add new initative LQTY and offset to global count
                vars.state.countedVoteLQTY += vars.initiativeState.voteLQTY;
                vars.state.countedVoteOffset += vars.initiativeState.voteOffset;
            }

            // == USER ALLOCATION TO INITIATIVE == //

            // Record the vote and veto LQTY and offsets by user to initative
            vars.allocation = lqtyAllocatedByUserToInitiative[msg.sender][initiative];
            // Update offsets
            vars.allocation.voteOffset = add(vars.allocation.voteOffset, vars.deltaOffsetVotes);
            vars.allocation.vetoOffset = add(vars.allocation.vetoOffset, vars.deltaOffsetVetos);

            // Update votes and vetos
            vars.allocation.voteLQTY = add(vars.allocation.voteLQTY, vars.deltaLQTYVotes);
            vars.allocation.vetoLQTY = add(vars.allocation.vetoLQTY, vars.deltaLQTYVetos);

            vars.allocation.atEpoch = vars.currentEpoch;

            // Voting power allocated to initiatives should never be negative, else it might break reward allocation
            // schemes such as `BribeInitiative` which distribute rewards in proportion to voting power allocated.
            assert(vars.allocation.voteLQTY * block.timestamp >= vars.allocation.voteOffset);
            assert(vars.allocation.vetoLQTY * block.timestamp >= vars.allocation.vetoOffset);

            lqtyAllocatedByUserToInitiative[msg.sender][initiative] = vars.allocation;

            // == USER STATE == //

            // Remove from the user's unallocated LQTY and offset
            vars.userState.unallocatedLQTY =
                sub(vars.userState.unallocatedLQTY, (vars.deltaLQTYVotes + vars.deltaLQTYVetos));
            vars.userState.unallocatedOffset =
                sub(vars.userState.unallocatedOffset, (vars.deltaOffsetVotes + vars.deltaOffsetVetos));

            // Add to the user's allocated LQTY and offset
            vars.userState.allocatedLQTY =
                add(vars.userState.allocatedLQTY, (vars.deltaLQTYVotes + vars.deltaLQTYVetos));
            vars.userState.allocatedOffset =
                add(vars.userState.allocatedOffset, (vars.deltaOffsetVotes + vars.deltaOffsetVetos));

            HookStatus hookStatus;

            // See https://github.com/liquity/V2-gov/issues/125
            // A malicious initiative could try to dissuade voters from casting vetos by consuming as much gas as
            // possible in the `onAfterAllocateLQTY` hook when detecting vetos.
            // We deem that the risks of calling into malicous initiatives upon veto allocation far outweigh the
            // benefits of notifying benevolent initiatives of vetos.
            if (vars.allocation.vetoLQTY == 0) {
                // Replaces try / catch | Enforces sufficient gas is passed
                hookStatus = safeCallWithMinGas(
                    initiative,
                    MIN_GAS_TO_HOOK,
                    0,
                    abi.encodeCall(
                        IInitiative.onAfterAllocateLQTY,
                        (vars.currentEpoch, msg.sender, vars.userState, vars.allocation, vars.initiativeState)
                    )
                ) ? HookStatus.Succeeded : HookStatus.Failed;
            } else {
                hookStatus = HookStatus.NotCalled;
            }

            emit AllocateLQTY(
                msg.sender, initiative, vars.deltaLQTYVotes, vars.deltaLQTYVetos, vars.currentEpoch, hookStatus
            );
        }

        require(
            vars.userState.allocatedLQTY <= stakingV1.stakes(deriveUserProxyAddress(msg.sender)),
            "Governance: insufficient-or-allocated-lqty"
        );

        globalState = vars.state;
        userStates[msg.sender] = vars.userState;
    }

    /// @inheritdoc IGovernance
    function unregisterInitiative(address _initiative) external nonReentrant {
        /// Enforce FSM
        (VoteSnapshot memory votesSnapshot_, GlobalState memory state) = _snapshotVotes();
        (InitiativeVoteSnapshot memory votesForInitiativeSnapshot_, InitiativeState memory initiativeState) =
            _snapshotVotesForInitiative(_initiative);

        (InitiativeStatus status,,) =
            getInitiativeState(_initiative, votesSnapshot_, votesForInitiativeSnapshot_, initiativeState);
        require(status == InitiativeStatus.UNREGISTERABLE, "Governance: cannot-unregister-initiative");

        // Remove weight from current state
        uint256 currentEpoch = epoch();

        // NOTE: Safe to remove | See `check_claim_soundness`
        assert(initiativeState.lastEpochClaim < currentEpoch - 1);

        assert(state.countedVoteLQTY >= initiativeState.voteLQTY);
        assert(state.countedVoteOffset >= initiativeState.voteOffset);

        state.countedVoteLQTY -= initiativeState.voteLQTY;
        state.countedVoteOffset -= initiativeState.voteOffset;

        globalState = state;

        /// Epoch will never reach 2^256 - 1
        registeredInitiatives[_initiative] = UNREGISTERED_INITIATIVE;

        // Replaces try / catch | Enforces sufficient gas is passed
        bool success = safeCallWithMinGas(
            _initiative, MIN_GAS_TO_HOOK, 0, abi.encodeCall(IInitiative.onUnregisterInitiative, (currentEpoch))
        );

        emit UnregisterInitiative(_initiative, currentEpoch, success ? HookStatus.Succeeded : HookStatus.Failed);
    }

    /// @inheritdoc IGovernance
    function claimForInitiative(address _initiative) external nonReentrant returns (uint256) {
        // Accrue and update state
        (VoteSnapshot memory votesSnapshot_,) = _snapshotVotes();
        (InitiativeVoteSnapshot memory votesForInitiativeSnapshot_, InitiativeState memory initiativeState) =
            _snapshotVotesForInitiative(_initiative);

        // Compute values on accrued state
        (InitiativeStatus status,, uint256 claimableAmount) =
            getInitiativeState(_initiative, votesSnapshot_, votesForInitiativeSnapshot_, initiativeState);

        if (status != InitiativeStatus.CLAIMABLE) {
            return 0;
        }

        /// INVARIANT: You can only claim for previous epoch
        assert(votesSnapshot_.forEpoch == epoch() - 1);

        /// All unclaimed rewards are always recycled
        /// Invariant `lastEpochClaim` is < epoch() - 1; |
        /// If `lastEpochClaim` is older than epoch() - 1 it means the initiative couldn't claim any rewards this epoch
        initiativeStates[_initiative].lastEpochClaim = epoch() - 1;

        /// INVARIANT, because of rounding errors the system can overpay
        /// We upscale the timestamp to reduce the impact of the loss
        /// However this is still possible
        uint256 available = bold.balanceOf(address(this));
        if (claimableAmount > available) {
            claimableAmount = available;
        }

        bold.safeTransfer(_initiative, claimableAmount);

        // Replaces try / catch | Enforces sufficient gas is passed
        bool success = safeCallWithMinGas(
            _initiative,
            MIN_GAS_TO_HOOK,
            0,
            abi.encodeCall(IInitiative.onClaimForInitiative, (votesSnapshot_.forEpoch, claimableAmount))
        );

        emit ClaimForInitiative(
            _initiative, claimableAmount, votesSnapshot_.forEpoch, success ? HookStatus.Succeeded : HookStatus.Failed
        );

        return claimableAmount;
    }

    function _requireNoNOP(int256[] memory _absoluteLQTYVotes, int256[] memory _absoluteLQTYVetos) internal pure {
        for (uint256 i; i < _absoluteLQTYVotes.length; i++) {
            require(_absoluteLQTYVotes[i] > 0 || _absoluteLQTYVetos[i] > 0, "Governance: voting nothing");
        }
    }

    function _requireNoSimultaneousVoteAndVeto(int256[] memory _absoluteLQTYVotes, int256[] memory _absoluteLQTYVetos)
        internal
        pure
    {
        for (uint256 i; i < _absoluteLQTYVotes.length; i++) {
            require(_absoluteLQTYVotes[i] == 0 || _absoluteLQTYVetos[i] == 0, "Governance: vote-and-veto");
        }
    }
}
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";
SafeERC20.sol 118 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev An operation with an ERC20 token failed.
     */
    error SafeERC20FailedOperation(address token);

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

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    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.
     */
    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.
     */
    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 Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            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 silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    }
}
ReentrancyGuard.sol 84 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.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 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;
    }
}
IGovernance.sol 408 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IERC20} from "openzeppelin/contracts/interfaces/IERC20.sol";

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

import {PermitParams} from "../utils/Types.sol";

uint256 constant UNREGISTERED_INITIATIVE = type(uint256).max;

interface IGovernance {
    enum HookStatus {
        Failed,
        Succeeded,
        NotCalled
    }

    /// @notice Emitted when a user deposits LQTY
    /// @param user The account depositing LQTY
    /// @param rewardRecipient The account receiving the LUSD/ETH rewards earned from staking in V1, if claimed
    /// @param lqtyAmount The amount of LQTY being deposited
    /// @return lusdReceived Amount of LUSD tokens received as a side-effect of staking new LQTY
    /// @return lusdSent Amount of LUSD tokens sent to `rewardRecipient` (may include previously received LUSD)
    /// @return ethReceived Amount of ETH received as a side-effect of staking new LQTY
    /// @return ethSent Amount of ETH sent to `rewardRecipient` (may include previously received ETH)
    event DepositLQTY(
        address indexed user,
        address rewardRecipient,
        uint256 lqtyAmount,
        uint256 lusdReceived,
        uint256 lusdSent,
        uint256 ethReceived,
        uint256 ethSent
    );

    /// @notice Emitted when a user withdraws LQTY or claims V1 staking rewards
    /// @param user The account withdrawing LQTY or claiming V1 staking rewards
    /// @param recipient The account receiving the LQTY withdrawn, and if claimed, the LUSD/ETH rewards earned from staking in V1
    /// @return lqtyReceived Amount of LQTY tokens actually withdrawn (may be lower than the `_lqtyAmount` passed to `withdrawLQTY`)
    /// @return lqtySent Amount of LQTY tokens sent to `recipient` (may include LQTY sent to the user's proxy from sources other than V1 staking)
    /// @return lusdReceived Amount of LUSD tokens received as a side-effect of staking new LQTY
    /// @return lusdSent Amount of LUSD tokens sent to `recipient` (may include previously received LUSD)
    /// @return ethReceived Amount of ETH received as a side-effect of staking new LQTY
    /// @return ethSent Amount of ETH sent to `recipient` (may include previously received ETH)
    event WithdrawLQTY(
        address indexed user,
        address recipient,
        uint256 lqtyReceived,
        uint256 lqtySent,
        uint256 lusdReceived,
        uint256 lusdSent,
        uint256 ethReceived,
        uint256 ethSent
    );

    event SnapshotVotes(uint256 votes, uint256 forEpoch, uint256 boldAccrued);
    event SnapshotVotesForInitiative(address indexed initiative, uint256 votes, uint256 vetos, uint256 forEpoch);

    event RegisterInitiative(address initiative, address registrant, uint256 atEpoch, HookStatus hookStatus);
    event UnregisterInitiative(address initiative, uint256 atEpoch, HookStatus hookStatus);

    event AllocateLQTY(
        address indexed user,
        address indexed initiative,
        int256 deltaVoteLQTY,
        int256 deltaVetoLQTY,
        uint256 atEpoch,
        HookStatus hookStatus
    );
    event ClaimForInitiative(address indexed initiative, uint256 bold, uint256 forEpoch, HookStatus hookStatus);

    struct Configuration {
        uint256 registrationFee;
        uint256 registrationThresholdFactor;
        uint256 unregistrationThresholdFactor;
        uint256 unregistrationAfterEpochs;
        uint256 votingThresholdFactor;
        uint256 minClaim;
        uint256 minAccrual;
        uint256 epochStart;
        uint256 epochDuration;
        uint256 epochVotingCutoff;
    }

    function registerInitialInitiatives(address[] memory _initiatives) external;

    /// @notice Address of the LQTY StakingV1 contract
    /// @return stakingV1 Address of the LQTY StakingV1 contract
    function stakingV1() external view returns (ILQTYStaking stakingV1);
    /// @notice Address of the LQTY token
    /// @return lqty Address of the LQTY token
    function lqty() external view returns (IERC20 lqty);
    /// @notice Address of the BOLD token
    /// @return bold Address of the BOLD token
    function bold() external view returns (IERC20 bold);
    /// @notice Timestamp at which the first epoch starts
    /// @return epochStart Timestamp at which the first epoch starts
    function EPOCH_START() external view returns (uint256 epochStart);
    /// @notice Duration of an epoch in seconds (e.g. 1 week)
    /// @return epochDuration Epoch duration
    function EPOCH_DURATION() external view returns (uint256 epochDuration);
    /// @notice Voting period of an epoch in seconds (e.g. 6 days)
    /// @return epochVotingCutoff Epoch voting cutoff
    function EPOCH_VOTING_CUTOFF() external view returns (uint256 epochVotingCutoff);
    /// @notice Minimum BOLD amount that has to be claimed, if an initiative doesn't have enough votes to meet the
    /// criteria then it's votes a excluded from the vote count and distribution
    /// @return minClaim Minimum claim amount
    function MIN_CLAIM() external view returns (uint256 minClaim);
    /// @notice Minimum amount of BOLD that have to be accrued for an epoch, otherwise accrual will be skipped for
    /// that epoch
    /// @return minAccrual Minimum amount of BOLD
    function MIN_ACCRUAL() external view returns (uint256 minAccrual);
    /// @notice Amount of BOLD to be paid in order to register a new initiative
    /// @return registrationFee Registration fee
    function REGISTRATION_FEE() external view returns (uint256 registrationFee);
    /// @notice Share of all votes that are necessary to register a new initiative
    /// @return registrationThresholdFactor Threshold factor
    function REGISTRATION_THRESHOLD_FACTOR() external view returns (uint256 registrationThresholdFactor);
    /// @notice Multiple of the voting threshold in vetos that are necessary to unregister an initiative
    /// @return unregistrationThresholdFactor Unregistration threshold factor
    function UNREGISTRATION_THRESHOLD_FACTOR() external view returns (uint256 unregistrationThresholdFactor);
    /// @notice Number of epochs an initiative has to be inactive before it can be unregistered
    /// @return unregistrationAfterEpochs Number of epochs
    function UNREGISTRATION_AFTER_EPOCHS() external view returns (uint256 unregistrationAfterEpochs);
    /// @notice Share of all votes that are necessary for an initiative to be included in the vote count
    /// @return votingThresholdFactor Voting threshold factor
    function VOTING_THRESHOLD_FACTOR() external view returns (uint256 votingThresholdFactor);

    /// @notice Returns the amount of BOLD accrued since last epoch (last snapshot)
    /// @return boldAccrued BOLD accrued
    function boldAccrued() external view returns (uint256 boldAccrued);

    struct VoteSnapshot {
        uint256 votes; // Votes at epoch transition
        uint256 forEpoch; // Epoch for which the votes are counted
    }

    struct InitiativeVoteSnapshot {
        uint256 votes; // Votes at epoch transition
        uint256 forEpoch; // Epoch for which the votes are counted
        uint256 lastCountedEpoch; // Epoch at which which the votes where counted last in the global snapshot
        uint256 vetos; // Vetos at epoch transition
    }

    /// @notice Returns the vote count snapshot of the previous epoch
    /// @return votes Number of votes
    /// @return forEpoch Epoch for which the votes are counted
    function votesSnapshot() external view returns (uint256 votes, uint256 forEpoch);
    /// @notice Returns the vote count snapshot for an initiative of the previous epoch
    /// @param _initiative Address of the initiative
    /// @return votes Number of votes
    /// @return forEpoch Epoch for which the votes are counted
    /// @return lastCountedEpoch Epoch at which which the votes where counted last in the global snapshot
    function votesForInitiativeSnapshot(address _initiative)
        external
        view
        returns (uint256 votes, uint256 forEpoch, uint256 lastCountedEpoch, uint256 vetos);

    struct Allocation {
        uint256 voteLQTY; // LQTY allocated vouching for the initiative
        uint256 voteOffset; // Offset associated with LQTY vouching for the initiative
        uint256 vetoLQTY; // LQTY vetoing the initiative
        uint256 vetoOffset; // Offset associated with LQTY vetoing the initiative
        uint256 atEpoch; // Epoch at which the allocation was last updated
    }

    struct UserState {
        uint256 unallocatedLQTY; // LQTY deposited and unallocated
        uint256 unallocatedOffset; // The offset sum corresponding to the unallocated LQTY
        uint256 allocatedLQTY; // LQTY allocated by the user to initatives
        uint256 allocatedOffset; // The offset sum corresponding to the allocated LQTY
    }

    struct InitiativeState {
        uint256 voteLQTY; // LQTY allocated vouching for the initiative
        uint256 voteOffset; // Offset associated with LQTY vouching for to the initative
        uint256 vetoLQTY; // LQTY allocated vetoing the initiative
        uint256 vetoOffset; // Offset associated with LQTY veoting the initative
        uint256 lastEpochClaim;
    }

    struct GlobalState {
        uint256 countedVoteLQTY; // Total LQTY that is included in vote counting
        uint256 countedVoteOffset; // Offset associated with the counted vote LQTY
    }

    /// @notice Returns the user's state
    /// @return unallocatedLQTY LQTY deposited and unallocated
    /// @return unallocatedOffset Offset associated with unallocated LQTY
    /// @return allocatedLQTY allocated by the user to initatives
    /// @return allocatedOffset Offset associated with allocated LQTY
    function userStates(address _user)
        external
        view
        returns (uint256 unallocatedLQTY, uint256 unallocatedOffset, uint256 allocatedLQTY, uint256 allocatedOffset);
    /// @notice Returns the initiative's state
    /// @param _initiative Address of the initiative
    /// @return voteLQTY LQTY allocated vouching for the initiative
    /// @return voteOffset Offset associated with voteLQTY
    /// @return vetoLQTY LQTY allocated vetoing the initiative
    /// @return vetoOffset Offset associated with vetoLQTY
    /// @return lastEpochClaim // Last epoch at which rewards were claimed
    function initiativeStates(address _initiative)
        external
        view
        returns (uint256 voteLQTY, uint256 voteOffset, uint256 vetoLQTY, uint256 vetoOffset, uint256 lastEpochClaim);
    /// @notice Returns the global state
    /// @return countedVoteLQTY Total LQTY that is included in vote counting
    /// @return countedVoteOffset Offset associated with countedVoteLQTY
    function globalState() external view returns (uint256 countedVoteLQTY, uint256 countedVoteOffset);
    /// @notice Returns the amount of voting and vetoing LQTY a user allocated to an initiative
    /// @param _user Address of the user
    /// @param _initiative Address of the initiative
    /// @return voteLQTY LQTY allocated vouching for the initiative
    /// @return voteOffset The offset associated with voteLQTY
    /// @return vetoLQTY allocated vetoing the initiative
    /// @return vetoOffset the offset associated with vetoLQTY
    /// @return atEpoch Epoch at which the allocation was last updated
    function lqtyAllocatedByUserToInitiative(address _user, address _initiative)
        external
        view
        returns (uint256 voteLQTY, uint256 voteOffset, uint256 vetoLQTY, uint256 vetoOffset, uint256 atEpoch);

    /// @notice Returns when an initiative was registered
    /// @param _initiative Address of the initiative
    /// @return atEpoch If `_initiative` is an active initiative, returns the epoch at which it was registered.
    ///                 If `_initiative` hasn't been registered, returns 0.
    ///                 If `_initiative` has been unregistered, returns `UNREGISTERED_INITIATIVE`.
    function registeredInitiatives(address _initiative) external view returns (uint256 atEpoch);

    /*//////////////////////////////////////////////////////////////
                                STAKING
    //////////////////////////////////////////////////////////////*/

    /// @notice Deposits LQTY
    /// @dev The caller has to approve their `UserProxy` address to spend the LQTY tokens
    /// @param _lqtyAmount Amount of LQTY to deposit
    function depositLQTY(uint256 _lqtyAmount) external;

    /// @notice Deposits LQTY
    /// @dev The caller has to approve their `UserProxy` address to spend the LQTY tokens
    /// @param _lqtyAmount Amount of LQTY to deposit
    /// @param _doSendRewards If true, send rewards claimed from LQTY staking
    /// @param _recipient Address to which the tokens should be sent
    function depositLQTY(uint256 _lqtyAmount, bool _doSendRewards, address _recipient) external;

    /// @notice Deposits LQTY via Permit
    /// @param _lqtyAmount Amount of LQTY to deposit
    /// @param _permitParams Permit parameters
    function depositLQTYViaPermit(uint256 _lqtyAmount, PermitParams calldata _permitParams) external;

    /// @notice Deposits LQTY via Permit
    /// @param _lqtyAmount Amount of LQTY to deposit
    /// @param _permitParams Permit parameters
    /// @param _doSendRewards If true, send rewards claimed from LQTY staking
    /// @param _recipient Address to which the tokens should be sent
    function depositLQTYViaPermit(
        uint256 _lqtyAmount,
        PermitParams calldata _permitParams,
        bool _doSendRewards,
        address _recipient
    ) external;

    /// @notice Withdraws LQTY and claims any accrued LUSD and ETH rewards from StakingV1
    /// @param _lqtyAmount Amount of LQTY to withdraw
    function withdrawLQTY(uint256 _lqtyAmount) external;

    /// @notice Withdraws LQTY and claims any accrued LUSD and ETH rewards from StakingV1
    /// @param _lqtyAmount Amount of LQTY to withdraw
    /// @param _doSendRewards If true, send rewards claimed from LQTY staking
    /// @param _recipient Address to which the tokens should be sent
    function withdrawLQTY(uint256 _lqtyAmount, bool _doSendRewards, address _recipient) external;

    /// @notice Claims staking rewards from StakingV1 without unstaking
    /// @dev Note: in the unlikely event that the caller's `UserProxy` holds any LQTY tokens, they will also be sent to `_rewardRecipient`
    /// @param _rewardRecipient Address that will receive the rewards
    /// @return lusdSent Amount of LUSD tokens sent to `_rewardRecipient` (may include previously received LUSD)
    /// @return ethSent Amount of ETH sent to `_rewardRecipient` (may include previously received ETH)
    function claimFromStakingV1(address _rewardRecipient) external returns (uint256 lusdSent, uint256 ethSent);

    /*//////////////////////////////////////////////////////////////
                                 VOTING
    //////////////////////////////////////////////////////////////*/

    /// @notice Returns the current epoch number
    /// @return epoch Current epoch
    function epoch() external view returns (uint256 epoch);
    /// @notice Returns the timestamp at which the current epoch started
    /// @return epochStart Epoch start of the current epoch
    function epochStart() external view returns (uint256 epochStart);
    /// @notice Returns the number of seconds that have gone by since the current epoch started
    /// @return secondsWithinEpoch Seconds within the current epoch
    function secondsWithinEpoch() external view returns (uint256 secondsWithinEpoch);

    /// @notice Returns the voting power for an entity (i.e. user or initiative) at a given timestamp
    /// @param _lqtyAmount Amount of LQTY associated with the entity
    /// @param _timestamp Timestamp at which to calculate voting power
    /// @param _offset The entity's offset sum
    /// @return votes Number of votes
    function lqtyToVotes(uint256 _lqtyAmount, uint256 _timestamp, uint256 _offset) external pure returns (uint256);

    /// @dev Returns the most up to date voting threshold
    /// In contrast to `getLatestVotingThreshold` this function updates the snapshot
    /// This ensures that the value returned is always the latest
    function calculateVotingThreshold() external returns (uint256);

    /// @dev Utility function to compute the threshold votes without recomputing the snapshot
    /// Note that `boldAccrued` is a cached value, this function works correctly only when called after an accrual
    function calculateVotingThreshold(uint256 _votes) external view returns (uint256);

    /// @notice Return the most up to date global snapshot and state as well as a flag to notify whether the state can be updated
    /// This is a convenience function to always retrieve the most up to date state values
    function getTotalVotesAndState()
        external
        view
        returns (VoteSnapshot memory snapshot, GlobalState memory state, bool shouldUpdate);

    /// @dev Given an initiative address, return it's most up to date snapshot and state as well as a flag to notify whether the state can be updated
    /// This is a convenience function to always retrieve the most up to date state values
    function getInitiativeSnapshotAndState(address _initiative)
        external
        view
        returns (
            InitiativeVoteSnapshot memory initiativeSnapshot,
            InitiativeState memory initiativeState,
            bool shouldUpdate
        );

    /// @notice Voting threshold is the max. of either:
    ///   - 4% of the total voting LQTY in the previous epoch
    ///   - or the minimum number of votes necessary to claim at least MIN_CLAIM BOLD
    /// This value can be offsynch, use the non view `calculateVotingThreshold` to always retrieve the most up to date value
    /// @return votingThreshold Voting threshold
    function getLatestVotingThreshold() external view returns (uint256 votingThreshold);

    /// @notice Snapshots votes for the previous epoch and accrues funds for the current epoch
    /// @param _initiative Address of the initiative
    /// @return voteSnapshot Vote snapshot
    /// @return initiativeVoteSnapshot Vote snapshot of the initiative
    function snapshotVotesForInitiative(address _initiative)
        external
        returns (VoteSnapshot memory voteSnapshot, InitiativeVoteSnapshot memory initiativeVoteSnapshot);

    /*//////////////////////////////////////////////////////////////
                                 FSM
    //////////////////////////////////////////////////////////////*/

    enum InitiativeStatus {
        NONEXISTENT,
        /// This Initiative Doesn't exist | This is never returned
        WARM_UP,
        /// This epoch was just registered
        SKIP,
        /// This epoch will result in no rewards and no unregistering
        CLAIMABLE,
        /// This epoch will result in claiming rewards
        CLAIMED,
        /// The rewards for this epoch have been claimed
        UNREGISTERABLE,
        /// Can be unregistered
        DISABLED // It was already Unregistered

    }

    function getInitiativeState(address _initiative)
        external
        returns (InitiativeStatus status, uint256 lastEpochClaim, uint256 claimableAmount);

    function getInitiativeState(
        address _initiative,
        VoteSnapshot memory _votesSnapshot,
        InitiativeVoteSnapshot memory _votesForInitiativeSnapshot,
        InitiativeState memory _initiativeState
    ) external view returns (InitiativeStatus status, uint256 lastEpochClaim, uint256 claimableAmount);

    /// @notice Registers a new initiative
    /// @param _initiative Address of the initiative
    function registerInitiative(address _initiative) external;
    // /// @notice Unregisters an initiative if it didn't receive enough votes in the last 4 epochs
    // /// or if it received more vetos than votes and the number of vetos are greater than 3 times the voting threshold
    // /// @param _initiative Address of the initiative
    function unregisterInitiative(address _initiative) external;

    /// @notice Allocates the user's LQTY to initiatives
    /// @dev The user can only allocate to active initiatives (older than 1 epoch) and has to have enough unallocated
    /// LQTY available, the initiatives listed must be unique, and towards the end of the epoch a user can only maintain or reduce their votes
    /// @param _initiativesToReset Addresses of the initiatives the caller was previously allocated to, must be reset to prevent desynch of voting power
    /// @param _initiatives Addresses of the initiatives to allocate to, can match or be different from `_resetInitiatives`
    /// @param _absoluteLQTYVotes LQTY to allocate to the initiatives as votes
    /// @param _absoluteLQTYVetos LQTY to allocate to the initiatives as vetos
    function allocateLQTY(
        address[] calldata _initiativesToReset,
        address[] memory _initiatives,
        int256[] memory _absoluteLQTYVotes,
        int256[] memory _absoluteLQTYVetos
    ) external;
    /// @notice Deallocates the user's LQTY from initiatives
    /// @param _initiativesToReset Addresses of initiatives to deallocate LQTY from
    /// @param _checkAll When true, the call will revert if there is still some allocated LQTY left after deallocating
    ///                  from all the addresses in `_initiativesToReset`
    function resetAllocations(address[] calldata _initiativesToReset, bool _checkAll) external;

    /// @notice Splits accrued funds according to votes received between all initiatives
    /// @param _initiative Addresse of the initiative
    /// @return claimed Amount of BOLD claimed
    function claimForInitiative(address _initiative) external returns (uint256 claimed);
}
IInitiative.sol 34 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

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

interface IInitiative {
    /// @notice Callback hook that is called by Governance after the initiative was successfully registered
    /// @param _atEpoch Epoch at which the initiative is registered
    function onRegisterInitiative(uint256 _atEpoch) external;

    /// @notice Callback hook that is called by Governance after the initiative was unregistered
    /// @param _atEpoch Epoch at which the initiative is unregistered
    function onUnregisterInitiative(uint256 _atEpoch) external;

    /// @notice Callback hook that is called by Governance after the LQTY allocation is updated by a user
    /// @param _currentEpoch Epoch at which the LQTY allocation is updated
    /// @param _user Address of the user that updated their LQTY allocation
    /// @param _userState User state
    /// @param _allocation Allocation state from user to initiative
    /// @param _initiativeState Initiative state
    function onAfterAllocateLQTY(
        uint256 _currentEpoch,
        address _user,
        IGovernance.UserState calldata _userState,
        IGovernance.Allocation calldata _allocation,
        IGovernance.InitiativeState calldata _initiativeState
    ) external;

    /// @notice Callback hook that is called by Governance after the claim for the last epoch was distributed
    /// to the initiative
    /// @param _claimEpoch Epoch at which the claim was distributed
    /// @param _bold Amount of BOLD that was distributed
    function onClaimForInitiative(uint256 _claimEpoch, uint256 _bold) external;
}
ILQTYStaking.sol 46 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface ILQTYStaking {
    // --- Events --

    event LQTYTokenAddressSet(address _lqtyTokenAddress);
    event LUSDTokenAddressSet(address _lusdTokenAddress);
    event TroveManagerAddressSet(address _troveManager);
    event BorrowerOperationsAddressSet(address _borrowerOperationsAddress);
    event ActivePoolAddressSet(address _activePoolAddress);

    event StakeChanged(address indexed staker, uint256 newStake);
    event StakingGainsWithdrawn(address indexed staker, uint256 LUSDGain, uint256 ETHGain);
    event F_ETHUpdated(uint256 _F_ETH);
    event F_LUSDUpdated(uint256 _F_LUSD);
    event TotalLQTYStakedUpdated(uint256 _totalLQTYStaked);
    event EtherSent(address _account, uint256 _amount);
    event StakerSnapshotsUpdated(address _staker, uint256 _F_ETH, uint256 _F_LUSD);

    // --- Functions ---

    function setAddresses(
        address _lqtyTokenAddress,
        address _lusdTokenAddress,
        address _troveManagerAddress,
        address _borrowerOperationsAddress,
        address _activePoolAddress
    ) external;

    function stake(uint256 _LQTYamount) external;

    function unstake(uint256 _LQTYamount) external;

    function increaseF_ETH(uint256 _ETHFee) external;

    function increaseF_LUSD(uint256 _LQTYFee) external;

    function getPendingETHGain(address _user) external view returns (uint256);

    function getPendingLUSDGain(address _user) external view returns (uint256);

    function stakes(address _user) external view returns (uint256);

    function totalLQTYStaked() external view returns (uint256);
}
UserProxy.sol 128 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {IERC20} from "openzeppelin/contracts/interfaces/IERC20.sol";
import {IERC20Permit} from "openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";

import {IUserProxy} from "./interfaces/IUserProxy.sol";
import {ILQTYStaking} from "./interfaces/ILQTYStaking.sol";
import {PermitParams} from "./utils/Types.sol";

contract UserProxy is IUserProxy {
    /// @inheritdoc IUserProxy
    IERC20 public immutable lqty;
    /// @inheritdoc IUserProxy
    IERC20 public immutable lusd;

    /// @inheritdoc IUserProxy
    ILQTYStaking public immutable stakingV1;
    /// @inheritdoc IUserProxy
    address public immutable stakingV2;

    constructor(address _lqty, address _lusd, address _stakingV1) {
        lqty = IERC20(_lqty);
        lusd = IERC20(_lusd);
        stakingV1 = ILQTYStaking(_stakingV1);
        stakingV2 = msg.sender;
    }

    modifier onlyStakingV2() {
        require(msg.sender == stakingV2, "UserProxy: caller-not-stakingV2");
        _;
    }

    /// @inheritdoc IUserProxy
    function stake(uint256 _amount, address _lqtyFrom, bool _doSendRewards, address _recipient)
        public
        onlyStakingV2
        returns (uint256 lusdReceived, uint256 lusdSent, uint256 ethReceived, uint256 ethSent)
    {
        uint256 initialLUSDAmount = lusd.balanceOf(address(this));
        uint256 initialETHAmount = address(this).balance;

        lqty.transferFrom(_lqtyFrom, address(this), _amount);
        stakingV1.stake(_amount);

        uint256 lusdAmount = lusd.balanceOf(address(this));
        uint256 ethAmount = address(this).balance;

        lusdReceived = lusdAmount - initialLUSDAmount;
        ethReceived = ethAmount - initialETHAmount;

        if (_doSendRewards) (lusdSent, ethSent) = _sendRewards(_recipient, lusdAmount, ethAmount);
    }

    /// @inheritdoc IUserProxy
    function stakeViaPermit(
        uint256 _amount,
        address _lqtyFrom,
        PermitParams calldata _permitParams,
        bool _doSendRewards,
        address _recipient
    ) external onlyStakingV2 returns (uint256 lusdReceived, uint256 lusdSent, uint256 ethReceived, uint256 ethSent) {
        require(_lqtyFrom == _permitParams.owner, "UserProxy: owner-not-sender");

        try IERC20Permit(address(lqty)).permit(
            _permitParams.owner,
            _permitParams.spender,
            _permitParams.value,
            _permitParams.deadline,
            _permitParams.v,
            _permitParams.r,
            _permitParams.s
        ) {} catch {}

        return stake(_amount, _lqtyFrom, _doSendRewards, _recipient);
    }

    /// @inheritdoc IUserProxy
    function unstake(uint256 _amount, bool _doSendRewards, address _recipient)
        external
        onlyStakingV2
        returns (
            uint256 lqtyReceived,
            uint256 lqtySent,
            uint256 lusdReceived,
            uint256 lusdSent,
            uint256 ethReceived,
            uint256 ethSent
        )
    {
        uint256 initialLQTYAmount = lqty.balanceOf(address(this));
        uint256 initialLUSDAmount = lusd.balanceOf(address(this));
        uint256 initialETHAmount = address(this).balance;

        stakingV1.unstake(_amount);

        lqtySent = lqty.balanceOf(address(this));
        uint256 lusdAmount = lusd.balanceOf(address(this));
        uint256 ethAmount = address(this).balance;

        lqtyReceived = lqtySent - initialLQTYAmount;
        lusdReceived = lusdAmount - initialLUSDAmount;
        ethReceived = ethAmount - initialETHAmount;

        if (lqtySent > 0) lqty.transfer(_recipient, lqtySent);
        if (_doSendRewards) (lusdSent, ethSent) = _sendRewards(_recipient, lusdAmount, ethAmount);
    }

    function _sendRewards(address _recipient, uint256 _lusdAmount, uint256 _ethAmount)
        internal
        returns (uint256 lusdSent, uint256 ethSent)
    {
        if (_lusdAmount > 0) lusd.transfer(_recipient, _lusdAmount);
        if (_ethAmount > 0) {
            (bool success,) = payable(_recipient).call{value: _ethAmount}("");
            require(success, "UserProxy: eth-fail");
        }

        return (_lusdAmount, _ethAmount);
    }

    /// @inheritdoc IUserProxy
    function staked() external view returns (uint256) {
        return stakingV1.stakes(address(this));
    }

    receive() external payable {}
}
UserProxyFactory.sol 31 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {Clones} from "openzeppelin/contracts/proxy/Clones.sol";

import {IUserProxyFactory} from "./interfaces/IUserProxyFactory.sol";
import {UserProxy} from "./UserProxy.sol";

contract UserProxyFactory is IUserProxyFactory {
    /// @inheritdoc IUserProxyFactory
    address public immutable userProxyImplementation;

    constructor(address _lqty, address _lusd, address _stakingV1) {
        userProxyImplementation = address(new UserProxy(_lqty, _lusd, _stakingV1));
    }

    /// @inheritdoc IUserProxyFactory
    function deriveUserProxyAddress(address _user) public view returns (address) {
        return Clones.predictDeterministicAddress(userProxyImplementation, bytes32(uint256(uint160(_user))));
    }

    /// @inheritdoc IUserProxyFactory
    function deployUserProxy() public returns (address) {
        // reverts if the user already has a proxy
        address userProxy = Clones.cloneDeterministic(userProxyImplementation, bytes32(uint256(uint160(msg.sender))));

        emit DeployUserProxy(msg.sender, userProxy);

        return userProxy;
    }
}
Math.sol 24 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

function add(uint256 a, int256 b) pure returns (uint256) {
    if (b < 0) {
        return a - abs(b);
    }
    return a + uint256(b);
}

function sub(uint256 a, int256 b) pure returns (uint256) {
    if (b < 0) {
        return a + abs(b);
    }
    return a - uint256(b);
}

function max(uint256 a, uint256 b) pure returns (uint256) {
    return a > b ? a : b;
}

function abs(int256 a) pure returns (uint256) {
    return a < 0 ? uint256(-int256(a)) : uint256(a);
}
UniqueArray.sol 32 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/// @dev Checks that there's no duplicate addresses
/// @param arr - List to check for dups
function _requireNoDuplicates(address[] calldata arr) pure {
    uint256 arrLength = arr.length;
    if (arrLength == 0) return;

    // only up to len - 1 (no j to check if i == len - 1)
    for (uint i; i < arrLength - 1;) {
        for (uint j = i + 1; j < arrLength;) {
            require(arr[i] != arr[j], "dup");

            unchecked {
                ++j;
            }
        }

        unchecked {
            ++i;
        }
    }
}

function _requireNoNegatives(int256[] memory vals) pure {
    uint256 arrLength = vals.length;

    for (uint i; i < arrLength; i++) {
        require(vals[i] >= 0, "Cannot be negative");
    }
}
MultiDelegateCall.sol 27 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

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

contract MultiDelegateCall is IMultiDelegateCall {
    /// @inheritdoc IMultiDelegateCall
    function multiDelegateCall(bytes[] calldata inputs) external returns (bytes[] memory returnValues) {
        returnValues = new bytes[](inputs.length);

        for (uint256 i; i < inputs.length; ++i) {
            (bool success, bytes memory returnData) = address(this).delegatecall(inputs[i]);

            if (!success) {
                // Bubble up the revert
                assembly {
                    revert(
                        add(32, returnData), // offset (skip first 32 bytes, where the size of the array is stored)
                        mload(returnData) // size
                    )
                }
            }

            returnValues[i] = returnData;
        }
    }
}
Types.sol 14 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

struct PermitParams {
    address owner;
    address spender;
    uint256 value;
    uint256 deadline;
    uint8 v;
    bytes32 r;
    bytes32 s;
}

uint256 constant WAD = 1e18;
SafeCallMinGas.sol 42 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/// @notice Given the gas requirement, ensures that the current context has sufficient gas to perform a call + a fixed buffer
/// @dev Credits: https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/libraries/SafeCall.sol#L100-L107
function hasMinGas(uint256 _minGas, uint256 _reservedGas) view returns (bool) {
    bool _hasMinGas;
    assembly {
        // Equation: gas × 63 ≥ minGas × 64 + 63(40_000 + reservedGas)
        _hasMinGas := iszero(lt(mul(gas(), 63), add(mul(_minGas, 64), mul(add(40000, _reservedGas), 63))))
    }
    return _hasMinGas;
}

/// @dev Performs a call ignoring the recipient existing or not, passing the exact gas value, ignoring any return value
function safeCallWithMinGas(address _target, uint256 _gas, uint256 _value, bytes memory _calldata)
    returns (bool success)
{
    /// This is not necessary
    /// But this is basically a worst case estimate of mem exp cost + operations before the call
    require(hasMinGas(_gas, 1_000), "Must have minGas");

    // dispatch message to recipient
    // by assembly calling "handle" function
    // we call via assembly to avoid memcopying a very large returndata
    // returned by a malicious contract
    assembly {
        success :=
            call(
                _gas, // gas
                _target, // recipient
                _value, // ether value
                add(_calldata, 0x20), // inloc
                mload(_calldata), // inlen
                0, // outloc
                0 // outlen
            )

        // Ignore all return values
    }
    return (success);
}
Ownable.sol 66 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.24;

/**
 * Based on OpenZeppelin's Ownable contract:
 * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.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.
 *
 * 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.
 */
contract Ownable {
    address private _owner;

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

    /**
     * @dev Initializes the contract setting `initialOwner` as the initial owner.
     */
    constructor(address initialOwner) {
        _owner = initialOwner;
        emit OwnershipTransferred(address(0), initialOwner);
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(isOwner(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Returns true if the caller is the current owner.
     */
    function isOwner() public view returns (bool) {
        return msg.sender == _owner;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     *
     * NOTE: This function is not safe, as it doesn’t check owner is calling it.
     * Make sure you check it before calling it.
     */
    function _renounceOwnership() internal {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }
}
VotingPower.sol 7 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

function _lqtyToVotes(uint256 _lqtyAmount, uint256 _timestamp, uint256 _offset) pure returns (uint256) {
    uint256 prod = _lqtyAmount * _timestamp;
    return prod > _offset ? prod - _offset : 0;
}
IERC20.sol 79 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}
IERC20Permit.sol 90 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     *
     * CAUTION: See Security Considerations above.
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}
Address.sol 159 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)

pragma solidity ^0.8.20;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error AddressInsufficientBalance(address account);

    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

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

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert AddressInsufficientBalance(address(this));
        }

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

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {FailedInnerCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert AddressInsufficientBalance(address(this));
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

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

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

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

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

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
     */
    function _revert(bytes memory returndata) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert FailedInnerCall();
        }
    }
}
IUserProxy.sol 80 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IERC20} from "openzeppelin/contracts/interfaces/IERC20.sol";

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

import {PermitParams} from "../utils/Types.sol";

interface IUserProxy {
    /// @notice Address of the LQTY token
    /// @return lqty Address of the LQTY token
    function lqty() external view returns (IERC20 lqty);
    /// @notice Address of the LUSD token
    /// @return lusd Address of the LUSD token
    function lusd() external view returns (IERC20 lusd);
    /// @notice Address of the V1 LQTY staking contract
    /// @return stakingV1 Address of the V1 LQTY staking contract
    function stakingV1() external view returns (ILQTYStaking stakingV1);
    /// @notice Address of the V2 LQTY staking contract
    /// @return stakingV2 Address of the V2 LQTY staking contract
    function stakingV2() external view returns (address stakingV2);

    /// @notice Stakes a given amount of LQTY tokens in the V1 staking contract
    /// @dev The LQTY tokens must be approved for transfer by the user
    /// @param _amount Amount of LQTY tokens to stake
    /// @param _lqtyFrom Address from which to transfer the LQTY tokens
    /// @param _doSendRewards If true, send rewards claimed from LQTY staking
    /// @param _recipient Address to which the tokens should be sent
    /// @return lusdReceived Amount of LUSD tokens received as a side-effect of staking new LQTY
    /// @return lusdSent Amount of LUSD tokens sent to `_recipient` (may include previously received LUSD)
    /// @return ethReceived Amount of ETH received as a side-effect of staking new LQTY
    /// @return ethSent Amount of ETH sent to `_recipient` (may include previously received ETH)
    function stake(uint256 _amount, address _lqtyFrom, bool _doSendRewards, address _recipient)
        external
        returns (uint256 lusdReceived, uint256 lusdSent, uint256 ethReceived, uint256 ethSent);

    /// @notice Stakes a given amount of LQTY tokens in the V1 staking contract using a permit
    /// @param _amount Amount of LQTY tokens to stake
    /// @param _lqtyFrom Address from which to transfer the LQTY tokens
    /// @param _permitParams Parameters for the permit data
    /// @param _doSendRewards If true, send rewards claimed from LQTY staking
    /// @param _recipient Address to which the tokens should be sent
    /// @return lusdReceived Amount of LUSD tokens received as a side-effect of staking new LQTY
    /// @return lusdSent Amount of LUSD tokens sent to `_recipient` (may include previously received LUSD)
    /// @return ethReceived Amount of ETH received as a side-effect of staking new LQTY
    /// @return ethSent Amount of ETH sent to `_recipient` (may include previously received ETH)
    function stakeViaPermit(
        uint256 _amount,
        address _lqtyFrom,
        PermitParams calldata _permitParams,
        bool _doSendRewards,
        address _recipient
    ) external returns (uint256 lusdReceived, uint256 lusdSent, uint256 ethReceived, uint256 ethSent);

    /// @notice Unstakes a given amount of LQTY tokens from the V1 staking contract and claims the accrued rewards
    /// @param _amount Amount of LQTY tokens to unstake
    /// @param _doSendRewards If true, send rewards claimed from LQTY staking
    /// @param _recipient Address to which the tokens should be sent
    /// @return lqtyReceived Amount of LQTY tokens actually unstaked (may be lower than `_amount`)
    /// @return lqtySent Amount of LQTY tokens sent to `_recipient` (may include LQTY sent to the proxy from sources other than V1 staking)
    /// @return lusdReceived Amount of LUSD tokens received as a side-effect of staking new LQTY
    /// @return lusdSent Amount of LUSD tokens claimed (may include previously received LUSD)
    /// @return ethReceived Amount of ETH received as a side-effect of staking new LQTY
    /// @return ethSent Amount of ETH claimed (may include previously received ETH)
    function unstake(uint256 _amount, bool _doSendRewards, address _recipient)
        external
        returns (
            uint256 lqtyReceived,
            uint256 lqtySent,
            uint256 lusdReceived,
            uint256 lusdSent,
            uint256 ethReceived,
            uint256 ethSent
        );

    /// @notice Returns the current amount LQTY staked by a user in the V1 staking contract
    /// @return staked Amount of LQTY tokens staked
    function staked() external view returns (uint256);
}
Clones.sol 95 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/Clones.sol)

pragma solidity ^0.8.20;

/**
 * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
 * deploying minimal proxy contracts, also known as "clones".
 *
 * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
 * > a minimal bytecode implementation that delegates all calls to a known, fixed address.
 *
 * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
 * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
 * deterministic method.
 */
library Clones {
    /**
     * @dev A clone instance deployment failed.
     */
    error ERC1167FailedCreateClone();

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create opcode, which should never revert.
     */
    function clone(address implementation) internal returns (address instance) {
        /// @solidity memory-safe-assembly
        assembly {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create(0, 0x09, 0x37)
        }
        if (instance == address(0)) {
            revert ERC1167FailedCreateClone();
        }
    }

    /**
     * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
     *
     * This function uses the create2 opcode and a `salt` to deterministically deploy
     * the clone. Using the same `implementation` and `salt` multiple time will revert, since
     * the clones cannot be deployed twice at the same address.
     */
    function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
        /// @solidity memory-safe-assembly
        assembly {
            // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
            // of the `implementation` address with the bytecode before the address.
            mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
            // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
            mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
            instance := create2(0, 0x09, 0x37, salt)
        }
        if (instance == address(0)) {
            revert ERC1167FailedCreateClone();
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt,
        address deployer
    ) internal pure returns (address predicted) {
        /// @solidity memory-safe-assembly
        assembly {
            let ptr := mload(0x40)
            mstore(add(ptr, 0x38), deployer)
            mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
            mstore(add(ptr, 0x14), implementation)
            mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
            mstore(add(ptr, 0x58), salt)
            mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
            predicted := keccak256(add(ptr, 0x43), 0x55)
        }
    }

    /**
     * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
     */
    function predictDeterministicAddress(
        address implementation,
        bytes32 salt
    ) internal view returns (address predicted) {
        return predictDeterministicAddress(implementation, salt, address(this));
    }
}
IUserProxyFactory.sol 19 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface IUserProxyFactory {
    event DeployUserProxy(address indexed user, address indexed userProxy);

    /// @notice Address of the UserProxy implementation contract
    /// @return implementation Address of the UserProxy implementation contract
    function userProxyImplementation() external view returns (address implementation);

    /// @notice Derive the address of a user's proxy contract
    /// @param _user Address of the user
    /// @return userProxyAddress Address of the user's proxy contract
    function deriveUserProxyAddress(address _user) external view returns (address userProxyAddress);

    /// @notice Deploy a new UserProxy contract for the sender
    /// @return userProxyAddress Address of the deployed UserProxy contract
    function deployUserProxy() external returns (address userProxyAddress);
}
IMultiDelegateCall.sol 9 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface IMultiDelegateCall {
    /// @notice Call multiple functions of the contract while preserving `msg.sender`
    /// @param inputs Function calls to perform, encoded using `abi.encodeCall()` or equivalent
    /// @return returnValues Raw data returned by each call
    function multiDelegateCall(bytes[] calldata inputs) external returns (bytes[] memory returnValues);
}

Read Contract

EPOCH_DURATION 0xa70b9f0c → uint256
EPOCH_START 0x46d62a63 → uint256
EPOCH_VOTING_CUTOFF 0x0df9049b → uint256
MIN_ACCRUAL 0xe772b765 → uint256
MIN_CLAIM 0x1cd2a05d → uint256
REGISTRATION_FEE 0x64b4f751 → uint256
REGISTRATION_THRESHOLD_FACTOR 0x2591003a → uint256
UNREGISTRATION_AFTER_EPOCHS 0xe4a61d95 → uint256
UNREGISTRATION_THRESHOLD_FACTOR 0x6bca7c55 → uint256
VOTING_THRESHOLD_FACTOR 0x8a00b67a → uint256
bold 0x727d0f35 → address
boldAccrued 0xa5e8455d → uint256
calculateVotingThreshold 0x8777e095 → uint256
deriveUserProxyAddress 0xcc9baa12 → address
epoch 0x900cf0cf → uint256
epochStart 0x15e5a1e5 → uint256
getInitiativeSnapshotAndState 0xcf84a88c → tuple, tuple, bool
getInitiativeState 0xa0d17540 → uint8, uint256, uint256
getLatestVotingThreshold 0xac85912e → uint256
getTotalVotesAndState 0x8eb9399e → tuple, tuple, bool
globalState 0xe76c01e4 → uint256, uint256
initiativeStates 0x524fcc80 → uint256, uint256, uint256, uint256, uint256
isOwner 0x8f32d59b → bool
lqty 0x7f6ec455 → address
lqtyAllocatedByUserToInitiative 0x91050f40 → uint256, uint256, uint256, uint256, uint256
lqtyToVotes 0xadfcdb9d → uint256
owner 0x8da5cb5b → address
registeredInitiatives 0x38f3e285 → uint256
secondsWithinEpoch 0x50283275 → uint256
stakingV1 0xf556a79c → address
userProxyImplementation 0x8d4f0b6c → address
userStates 0x0ecc535f → uint256, uint256, uint256, uint256
votesForInitiativeSnapshot 0x53905402 → uint256, uint256, uint256, uint256
votesSnapshot 0xa9952a0c → uint256, uint256

Write Contract 18 functions

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

allocateLQTY 0x82a15be1
address[] _initiativesToReset
address[] _initiatives
int256[] _absoluteLQTYVotes
int256[] _absoluteLQTYVetos
calculateVotingThreshold 0x8b7d38a1
No parameters
returns: uint256
claimForInitiative 0xc20fb59e
address _initiative
returns: uint256
claimFromStakingV1 0xbfd79cae
address _rewardRecipient
returns: uint256, uint256
deployUserProxy 0x5c79696c
No parameters
returns: address
depositLQTY 0x52d420b9
uint256 _lqtyAmount
depositLQTY 0xecf22e32
uint256 _lqtyAmount
bool _doSendRewards
address _recipient
depositLQTYViaPermit 0x012bebe4
uint256 _lqtyAmount
tuple _permitParams
bool _doSendRewards
address _recipient
depositLQTYViaPermit 0xa0bbc066
uint256 _lqtyAmount
tuple _permitParams
getInitiativeState 0x972e6d66
address _initiative
returns: uint8, uint256, uint256
multiDelegateCall 0x415c2d96
bytes[] inputs
returns: bytes[]
registerInitialInitiatives 0x8113630d
address[] _initiatives
registerInitiative 0x78a1bdd4
address _initiative
resetAllocations 0x88edf9be
address[] _initiativesToReset
bool checkAll
snapshotVotesForInitiative 0xb1d8f274
address _initiative
returns: tuple, tuple
unregisterInitiative 0x0f2f6aa5
address _initiative
withdrawLQTY 0x070f418a
uint256 _lqtyAmount
withdrawLQTY 0xb285a870
uint256 _lqtyAmount
bool _doSendRewards
address _recipient

Recent Transactions

No transactions found for this address