Address Contract Partially Verified
Address
0x3feB1e09b4bb0E7f0387CeE092a52e85797ab889
Balance
0 ETH
Nonce
1
Code Size
23839 bytes
Creator
0x1bFd4555...17cf at tx 0x437288ed...4784f9
Indexed Transactions
0
Contract Bytecode
23839 bytes
0x60806040526004361015610013575b600080fd5b60003560e01c8063049b2ca0146104065780630641bdd8146103fd5780630fbc8f5b146103f4578063165d35e1146103eb578063181f5a77146103e25780631a9d4c7c146103d95780631ddb5552146103d057806322f3e2d4146103c75780632def6620146103be57806332e28850146103b557806338adb6f0146103ac57806349590657146103a35780634a4e3bd51461039a57806351858e271461039157806359f01879146103885780635aa6e0131461037f5780635c975abb1461029e5780635e8b40d7146103765780635fec60f81461036d57806363b2c85a146103645780636d70f7ae1461035b578063741040021461035257806374de4ec41461034957806374f237c41461034057806379ba5097146103375780637a7664601461032e5780637cb64759146103255780637e1a37861461031c5780638019e7d01461031357806383db28a01461030a57806387e900b1146103015780638856398f146102f85780638932a90d146102ef5780638a44f337146102e65780638da5cb5b146102dd5780638fb4b573146102d45780639a109bc2146102cb5780639d0a3864146102c2578063a07aea1c146102b9578063a4c0ed36146102b0578063a7a2f5aa146102a7578063b187bd261461029e578063bfbd9b1b14610295578063c1852f581461028c578063d365a37714610283578063da9c732f1461027a578063e0974ea514610271578063e5f9297314610268578063e937fdaa1461025f578063ebdb56f3146102565763f2fde38b1461024e57600080fd5b61000e613173565b5061000e6130e1565b5061000e612fe2565b5061000e612e50565b5061000e612e34565b5061000e612a3f565b5061000e61255d565b5061000e612535565b5061000e6122e6565b5061000e610bfe565b5061000e6122ca565b5061000e612244565b5061000e611e8f565b5061000e611d96565b5061000e611c91565b5061000e611b17565b5061000e611ae2565b5061000e611728565b5061000e61151a565b5061000e6114d0565b5061000e6114a8565b5061000e611456565b5061000e611437565b5061000e61140c565b5061000e6113bf565b5061000e611362565b5061000e611235565b5061000e611176565b5061000e610fe4565b5061000e610f9f565b5061000e610f4e565b5061000e610cfa565b5061000e610cab565b5061000e610c25565b5061000e610a3d565b5061000e610a0c565b5061000e610977565b5061000e61089f565b5061000e610880565b5061000e610864565b5061000e610842565b5061000e6106c4565b5061000e61069e565b5061000e610669565b5061000e6105cc565b5061000e61056d565b5061000e6104bd565b5061000e610490565b5061000e610436565b503461000e57600060031936011261000e5760206bffffffffffffffffffffffff60045460101c16604051908152f35b503461000e57600060031936011261000e57600554604080517f0000000000000000000000000000000000000000000000000de0b6b3a7640000815260609290921c69ffffffffffffffffffff16602083015290f35b0390f35b503461000e57600060031936011261000e5760206bffffffffffffffffffffffff60055416604051908152f35b503461000e57600060031936011261000e57602060405173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca168152f35b919082519283825260005b8481106105595750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8460006020809697860101520116010190565b60208183018101518483018201520161051a565b503461000e57600060031936011261000e5761048c60405161058e81611d12565b600d81527f5374616b696e6720302e312e3000000000000000000000000000000000000000602082015260405191829160208352602083019061050f565b503461000e57600060031936011261000e5760206105e8615ab9565b64e8d4a5100061063c6105f9614dad565b926bffffffffffffffffffffffff600a541693810390811161065c575b61063761062161393a565b9169ffffffffffffffffffff60095416906134e6565b6134e6565b04810390811161064f575b604051908152f35b6106576133cf565b610647565b6106646133cf565b610616565b503461000e57600060031936011261000e57602073ffffffffffffffffffffffffffffffffffffffff600f5416604051908152f35b503461000e57600060031936011261000e5760206106ba614b09565b6040519015158152f35b503461000e5760008060031936011261083f576106df614b09565b6108075761077e602061073e6107396106f733615648565b92917f204fccf0d92ed8d48f204adb39b2e81e92bad0dedb93f5716ca9478cfb57de0060806040513381528389820152846040820152866060820152a161356d565b61356d565b6040517fa9059cbb000000000000000000000000000000000000000000000000000000008152336004820152602481019190915291829081906044820190565b03818573ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca165af180156107fa575b6107cc575b50604051f35b6107ec9060203d81116107f3575b6107e48183611d2e565b8101906139da565b50386107c6565b503d6107da565b6108026139f2565b6107c1565b604490604051907fa30a70c2000000000000000000000000000000000000000000000000000000008252600160048301526024820152fd5b80fd5b503461000e57600060031936011261000e57602060ff60085416604051908152f35b503461000e57600060031936011261000e576020610647615ab9565b503461000e57600060031936011261000e576020601154604051908152f35b503461000e57600060031936011261000e576108b96132a6565b60015460ff8160a01c1615610919577fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff166001557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6020604051338152a1005b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5061757361626c653a206e6f74207061757365640000000000000000000000006044820152fd5b503461000e57600060031936011261000e576109916132a6565b740100000000000000000000000000000000000000007fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff6001546109db60ff8260a01c161561409e565b16176001557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586020604051338152a1005b503461000e57600060031936011261000e57600c54600b546040805163ffffffff9093168352602083019190915290f35b503461000e5760008060031936011261083f57610a58614b09565b61080757610ab2610aa1610a8c3373ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b5460701c6bffffffffffffffffffffffff1690565b6bffffffffffffffffffffffff1690565b8015610bc957602081610ad2610acd61077e9460065461348b565b600655565b610b27610aff3373ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b7fffffffffffff000000000000000000000000ffffffffffffffffffffffffffff8154169055565b7f204fccf0d92ed8d48f204adb39b2e81e92bad0dedb93f5716ca9478cfb57de0060405180610b86843383606090600092949373ffffffffffffffffffffffffffffffffffffffff608083019616825260208201528260408201520152565b0390a16040517fa9059cbb000000000000000000000000000000000000000000000000000000008152336004820152602481019190915291829081906044820190565b6040517fe4adde72000000000000000000000000000000000000000000000000000000008152336004820152602490fd5b0390fd5b503461000e57600060031936011261000e57602060ff60015460a01c166040519015158152f35b503461000e57600060031936011261000e5760206040517f00000000000000000000000000000000000000000000000000000000000000148152f35b90815180825260208080930193019160005b828110610c81575050505090565b835173ffffffffffffffffffffffffffffffffffffffff1685529381019392810192600101610c73565b503461000e57600060031936011261000e5761048c610cc8614103565b604051918291602083526020830190610c61565b73ffffffffffffffffffffffffffffffffffffffff81160361000e57565b503461000e57602060031936011261000e57600435610d1881610cdc565b610d206132a6565b803b158015610f2f575b8015610f0b575b8015610ee7575b8015610e39575b610e0f57610e0a7f5c74c441be501340b2713817a6c6975e6f3d4a4ae39fa1ac0bf75d3c54a0cad391610d957fffffffffffffffffffffffff0000000000000000000000000000000000000000600f5416600f55565b610dda8173ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff0000000000000000000000000000000000000000600d541617600d55565b610de342600e55565b60405173ffffffffffffffffffffffffffffffffffffffff90911681529081906020820190565b0390a1005b60046040517f367a1038000000000000000000000000000000000000000000000000000000008152fd5b506040517f01ffc9a70000000000000000000000000000000000000000000000000000000081527fa4c0ed3600000000000000000000000000000000000000000000000000000000600482015260208160248173ffffffffffffffffffffffffffffffffffffffff86165afa908115610eda575b600091610ebc575b5015610d3f565b610ed4915060203d81116107f3576107e48183611d2e565b38610eb5565b610ee26139f2565b610ead565b50600f5473ffffffffffffffffffffffffffffffffffffffff828116911614610d38565b50600d5473ffffffffffffffffffffffffffffffffffffffff828116911614610d31565b503073ffffffffffffffffffffffffffffffffffffffff821614610d2a565b503461000e57602060031936011261000e5773ffffffffffffffffffffffffffffffffffffffff600435610f8181610cdc565b166000526002602052602060ff604060002054166040519015158152f35b503461000e57600060031936011261000e576020610fbb614dad565b64e8d4a5100061063c6bffffffffffffffffffffffff600a5460601c169261063761062161393a565b503461000e57602060031936011261000e577fde88a922e0d3b88b24e9623efeb464919c6bf9f66857a65e2bfcf2ce87a9433d610e0a6004356110256132a6565b61102d6159c3565b6040517f23b872dd0000000000000000000000000000000000000000000000000000000081523360048201523060248201526044810182905260208160648160007f000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca73ffffffffffffffffffffffffffffffffffffffff165af18015611169575b61114b575b5061113b6110d1610aa16005546bffffffffffffffffffffffff1690565b6110d9615ab9565b906111016110f260095469ffffffffffffffffffff1690565b69ffffffffffffffffffff1690565b9061110a614b20565b91611113614dad565b937f0000000000000000000000000000000000000000000000000000000000278d009261357a565b6040519081529081906020820190565b6111629060203d81116107f3576107e48183611d2e565b50386110b3565b6111716139f2565b6110ae565b503461000e57602060031936011261000e576004356111936132a6565b61119b6159c3565b801561000e5760207f1e3be2efa25bca5bff2215c7b30b31086e703d6aa7d9b9a1f8ba62c5291219ad9161122c6111d0614dad565b6111d981613c86565b6111e1613bb5565b6bffffffffffffffffffffffff600554166111fa615ab9565b9084611204614b20565b927f0000000000000000000000000000000000000000000000000000000000278d009261357a565b604051908152a1005b503461000e5760008060031936011261083f5773ffffffffffffffffffffffffffffffffffffffff8060015416330361130457815473ffffffffffffffffffffffffffffffffffffffff16600080547fffffffffffffffffffffffff000000000000000000000000000000000000000016331790556112d77fffffffffffffffffffffffff000000000000000000000000000000000000000060015416600155565b604051913391167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08484a3f35b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4d7573742062652070726f706f736564206f776e6572000000000000000000006044820152fd5b503461000e57602060031936011261000e5773ffffffffffffffffffffffffffffffffffffffff60043561139581610cdc565b16600052600260205260206bffffffffffffffffffffffff60406000205460101c16604051908152f35b503461000e57602060031936011261000e577f1b930366dfeaa7eb3b325021e4ae81e36527063452ee55b86c95f85b36f4c31c60206004356113ff6132a6565b80601155604051908152a1005b503461000e57600060031936011261000e57602069ffffffffffffffffffff60095416604051908152f35b503461000e57600060031936011261000e576020600654604051908152f35b503461000e57600060031936011261000e57602060405173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000005f4ec3df9cbd43714fe2740f5e3616155c5b8419168152f35b503461000e57602060031936011261000e5760206106476004356114cb81610cdc565b614d24565b503461000e57600060031936011261000e57600554604080517f00000000000000000000000000000000000000000000003635c9adc5dea00000815260b09290921c602083015290f35b503461000e57602060031936011261000e5767ffffffffffffffff60043581811161000e573660238201121561000e57806004013591821161000e576024810190602483369201011161000e5761156f614b09565b6116f05773ffffffffffffffffffffffffffffffffffffffff91826115a9600f5473ffffffffffffffffffffffffffffffffffffffff1690565b1615610e0f576116989260209260006107396116626116236115ca33615648565b9194907f667838b33bdc898470de09e0e746990f2adc11b965b7fe6828e502ebc39e0434604051806116018d8c88878d33876141c5565b0390a1600f5473ffffffffffffffffffffffffffffffffffffffff169561356d565b93611636604051978892338b8501614204565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101875286611d2e565b604051968795869485937f4000aea000000000000000000000000000000000000000000000000000000000855260048501614231565b03927f000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca165af180156116e3575b6116cc575b005b6116ca9060203d81116107f3576107e48183611d2e565b6116eb6139f2565b6116c5565b60446040517fa30a70c20000000000000000000000000000000000000000000000000000000081526001600482015260006024820152fd5b503461000e57606060031936011261000e576044356004803560243561174c6132a6565b6117546159c3565b818411611aaa576005546bffffffffffffffffffffffff811690838211611a75578269ffffffffffffffffffff8260601c1611611a4057859060b01c11611a075761179d6159d2565b936117df6117bc876106376117b660208a015160ff1690565b60ff1690565b6117d9610aa16040809901516bffffffffffffffffffffffff1690565b9061356d565b84106119d35750938291611839936000960361195e575b806118136110f260055469ffffffffffffffffffff9060601c1690565b036118cd575b508061182a6110f260055460b01c90565b0361183c575b506110d9615ab9565b51f35b6118c4816118b561186d7f816587cb2e773af4f3689a03d7520fabff3462605ded374b485b13994c0d7b5294613980565b75ffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffff000000000000000000000000000000000000000000006005549260b01b16911617600555565b85519081529081906020820190565b0390a138611830565b611955816119466118fe7fb5f554e5ef00806bace1edbb84186512ebcefa2af7706085143f501f29314df794613980565b7fffffffffffffffffffff00000000000000000000ffffffffffffffffffffffff75ffffffffffffffffffff0000000000000000000000006005549260601b16911617600555565b86519081529081906020820190565b0390a138611819565b6119a361196a846139c1565b6bffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffff0000000000000000000000006005541617600555565b84518381527f7f4f497e086b2eb55f8a9885ba00d33399bbe0ebcb92ea092834386435a1b9c090602090a16117f6565b84517fbc91aa3300000000000000000000000000000000000000000000000000000000815290810186815281906020010390fd5b50506040517fbc91aa33000000000000000000000000000000000000000000000000000000008152918201928352509081906020010390fd5b6040517fbc91aa3300000000000000000000000000000000000000000000000000000000815280860184815281906020010390fd5b6040517f1f3c387600000000000000000000000000000000000000000000000000000000815280860185815281906020010390fd5b50506040517fbc91aa330000000000000000000000000000000000000000000000000000000081529081019182529081906020010390fd5b503461000e57600060031936011261000e57602073ffffffffffffffffffffffffffffffffffffffff60005416604051908152f35b503461000e57604060031936011261000e57611b316132a6565b60115415611c6757611b627f0000000000000000000000000000000000000000000000000000000000000032615a1b565b6040517f23b872dd0000000000000000000000000000000000000000000000000000000081523360048083019190915230602483015235604482015260208160648160007f000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca73ffffffffffffffffffffffffffffffffffffffff165af18015611c5a575b611c3c575b6116ca611c08610aa16005546bffffffffffffffffffffffff1690565b611c10614b20565b907f0000000000000000000000000000000000000000000000000000000000278d0090602435906139ff565b611c539060203d81116107f3576107e48183611d2e565b5038611beb565b611c626139f2565b611be6565b60046040517f9f8a28f2000000000000000000000000000000000000000000000000000000008152fd5b503461000e57602060031936011261000e576020610647600435611cb481610cdc565b614c08565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6080810190811067ffffffffffffffff821117611d0557604052565b611d0d611cb9565b604052565b6040810190811067ffffffffffffffff821117611d0557604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff821117611d0557604052565b60209067ffffffffffffffff8111611d89575b60051b0190565b611d91611cb9565b611d82565b503461000e57604060031936011261000e57600435611db481610cdc565b6024359067ffffffffffffffff821161000e573660238301121561000e578160040135611de081611d6f565b92611dee6040519485611d2e565b81845260209160248386019160051b8301019136831161000e57602401905b828210611e335761048c611e218787613325565b60405190151581529081906020820190565b81358152908301908301611e0d565b90602060031983011261000e5760043567ffffffffffffffff9283821161000e578060238301121561000e57816004013593841161000e5760248460051b8301011161000e576024019190565b503461000e57611e9e36611e42565b90611ea76132a6565b63ffffffff600c54161515806121ec575b6121b457611ed4611ece6110f260055460b01c90565b836134e6565b611edc615ae7565b80821161217957505060005b828110611f49576116ca611f1584611f0f611f0960045460ff9060081c1690565b91614090565b90615401565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff61ff006004549260081b16911617600455565b611f8f611f88611f62611f5d848787613fdf565b613ff7565b73ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b5460ff1690565b61211c57611fbf610aa1611faa611f62611f5d858888613fdf565b5460101c6bffffffffffffffffffffffff1690565b6120be57611fda610aa1610a8c611f62611f5d858888613fdf565b6120605780612021611ff6611f62611f5d61205b958888613fdf565b60017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00825416179055565b7fac6fa858e9350a46cec16539926e0fde25b7629f84b5a72bffaae4df888ae86d612053610de3611f5d848888613fdf565b0390a16133ff565b611ee8565b611f5d90610bfa9361207193613fdf565b6040517f7a378b9c00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff90911660048201529081906024820190565b611f5d90610bfa936120cf93613fdf565b6040517f602d4d1100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff90911660048201529081906024820190565b611f5d90610bfa9361212d93613fdf565b6040517ea5216600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff90911660048201529081906024820190565b6040517f35cf446b00000000000000000000000000000000000000000000000000000000815260048101919091526024810191909152604490fd5b60446040517fa30a70c20000000000000000000000000000000000000000000000000000000081526000600482015260016024820152fd5b506121f5614b09565b15611eb8565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f60209267ffffffffffffffff8111612237575b01160190565b61223f611cb9565b612231565b503461000e57606060031936011261000e5760043561226281610cdc565b6044359067ffffffffffffffff821161000e573660238301121561000e5781600401359061228f826121fb565b9161229d6040519384611d2e565b808352366024828601011161000e5760208160009260246116ca9701838701378401015260243590614ded565b503461000e57600060031936011261000e576020610647614dad565b503461000e576122f536611e42565b6122fd6132a6565b60005b6003548110156123725780612368612340611f6261232061236d95615b42565b905473ffffffffffffffffffffffffffffffffffffffff9160031b1c1690565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff8154169055565b6133ff565b612300565b5061237b615b87565b60005b8181106123c15750816123b2827f40aed8e423b39a56b445ae160f4c071fc2cfb48ee0b6dcd5ffeb6bc5b18d10d094615bda565b610e0a60405192839283615cb7565b6123cf611f5d828486613fdf565b6124036123ff611f888373ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b1590565b6124ec5761243e6124348273ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b5460081c60ff1690565b6124a4579061236861247361249f9373ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b6101007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff825416179055565b61237e565b6040517ea5216600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff919091166004820152602490fd5b6040517feac13dcd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff919091166004820152602490fd5b503461000e57602060031936011261000e5760206106ba60043561255881610cdc565b61496a565b503461000e5761256c36611e42565b906125756132a6565b61257d6159c3565b61258d612588614dad565b613c86565b60005b8281106125b4576116ca611f156125a685614090565b60045460081c60ff1661407c565b6125c2611f5d828585613fdf565b6125f46125ef8273ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b614001565b906126026123ff8351151590565b6124ec5760208201516129f6576126df92917f2360404a74478febece1a14f11275f22ada88d19ef96f7d785913010bfff447991612053612656610aa16040809501516bffffffffffffffffffffffff1690565b92836126e4575b6126b261268a8473ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008154169055565b51928392836020909392919373ffffffffffffffffffffffffffffffffffffffff60408201951681520152565b612590565b6127f66127ac6126fb6126f686614c08565b6139c1565b61275861271f600a9261271a84546bffffffffffffffffffffffff1690565b613dcf565b6bffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffff000000000000000000000000600a541617600a55565b61278761271f6127726126f661276c61393a565b8b6138d2565b83546bffffffffffffffffffffffff16613dcf565b61271a6127966126f688614d24565b915460601c6bffffffffffffffffffffffff1690565b7fffffffffffffffff000000000000000000000000ffffffffffffffffffffffff77ffffffffffffffffffffffff000000000000000000000000600a549260601b16911617600a55565b61283b61280d61280860085460ff1690565b61404b565b60ff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff006008541617600855565b6128906128688473ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b7fffffffffffffffffffffffffffffffffffff000000000000000000000000ffff8154169055565b61299c610acd6bffffffffffffffffffffffff6128ac876139c1565b61291a6128ce8261271a6004546bffffffffffffffffffffffff9060701c1690565b7fffffffffffff000000000000000000000000ffffffffffffffffffffffffffff79ffffffffffffffffffffffff00000000000000000000000000006004549260701b16911617600455565b612993816129488973ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b907fffffffffffff000000000000000000000000ffffffffffffffffffffffffffff79ffffffffffffffffffffffff000000000000000000000000000083549260701b169116179055565b1660065461356d565b6129f16129c98473ffffffffffffffffffffffffffffffffffffffff166000526007602052604060002090565b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000008154169055565b61265d565b6040517fded6031900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff919091166004820152602490fd5b503461000e5760008060031936011261083f57612a5a6159c3565b612a8a610aa1611faa3373ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b8015612e0a5760409081517ffeaf968c00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff9160a082600481867f0000000000000000000000005f4ec3df9cbd43714fe2740f5e3616155c5b8419165afa918215612dfd575b85908693612dbd575b5069ffffffffffffffffffff16916010548314612d8c57612b4b7f0000000000000000000000000000000000000000000000000000000000002a308261356d565b4210612d2d57612b7c907f0000000000000000000000000000000000000000000000000000000000002ee09061356d565b4210908180612d56575b612d2d5792827fd2720e8f454493f612cc97499fe8cbce7fa4d4c18d346fe7104e9042df1c1edd612bc6612c21948997612bc1602098601055565b61596b565b875133815260208101939093526040830181905291606090a185517fa9059cbb000000000000000000000000000000000000000000000000000000008152336004820152602481019190915293849283919082906044820190565b03927f000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca165af18015612d20575b612d02575b50612cae612c5f614dad565b612c67614103565b7f000000000000000000000000000000000000000000000000000000000076a7007f00000000000000000000000000000000000000000000003635c9adc5dea00000614366565b611839612ccb610aa16005546bffffffffffffffffffffffff1690565b612cd3615ab9565b612ceb6110f260095469ffffffffffffffffffff1690565b612cf3614b20565b91612cfc614dad565b936136c4565b612d199060203d81116107f3576107e48183611d2e565b5038612c53565b612d286139f2565b612c4e565b600485517ffc53c50a000000000000000000000000000000000000000000000000000000008152fd5b50612d876123ff611f883373ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b612b86565b84517ff3553c2200000000000000000000000000000000000000000000000000000000815260048101849052602490fd5b69ffffffffffffffffffff9350612deb915060a03d8111612df6575b612de38183611d2e565b81019061427a565b50949150612b0a9050565b503d612dd9565b612e056139f2565b612b01565b60046040517fef67f5d8000000000000000000000000000000000000000000000000000000008152fd5b503461000e57600060031936011261000e576020610647614b20565b503461000e57600060031936011261000e57612e6a6132a6565b612e726159c3565b612e7a615ab9565b612f437fffffffffffffffff00000000000000000000000000000000000000000000000077ffffffffffffffffffffffff000000000000000000000000612f73612f27612f21612f1b612ecb614dad565b97612ed589613c86565b612edd613bb5565b888103908111612fd5575b610637612ef361393a565b9169ffffffffffffffffffff60095416948591612f1b8561063764e8d4a51000998a946134e6565b046139c1565b9a6134e6565b600a54956bffffffffffffffffffffffff958691828916613dcf565b1694857fffffffffffffffffffffffffffffffffffffffff00000000000000000000000088161760601c16613dcf565b60601b1692161717600a5542600b557fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff006004541660045560006040517ff7d0e0f15586495da8c687328ead30fb829d9da55538cb0ef73dd229e517cdb88282a1f35b612fdd6133cf565b612ee8565b503461000e57600060031936011261000e57612ffc6132a6565b600d5473ffffffffffffffffffffffffffffffffffffffff168015610e0f57613026600e5461355d565b4210612e0a576130719073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffff0000000000000000000000000000000000000000600f541617600f55565b61309e7fffffffffffffffffffffffff0000000000000000000000000000000000000000600d5416600d55565b7ffa33c052bbee754f3c0482a89962daffe749191fa33c696a61e947fbfd68bd84610e0a610de3600f5473ffffffffffffffffffffffffffffffffffffffff1690565b503461000e5760008060031936011261083f576130fc6132a6565b613104614b09565b6108075761077e6020613141613118614b20565b600a54906131366bffffffffffffffffffffffff918284169061348b565b9160601c169061348b565b6040518181527f150a6ec0e6f4e9ddcaaaa1674f157d91165a42d60653016f87a9fc870a39f050908060208101610b86565b503461000e57602060031936011261000e5773ffffffffffffffffffffffffffffffffffffffff6004356131a681610cdc565b6131ae6132a6565b1633811461324857806000917fffffffffffffffffffffffff0000000000000000000000000000000000000000600154161760015561321d613204835473ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff1690565b90604051917fed8889f560326eb138920d842192f0eb3dd22b4f139c87a2c57538e05bae12788484a3f35b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f43616e6e6f74207472616e7366657220746f2073656c660000000000000000006044820152fd5b73ffffffffffffffffffffffffffffffffffffffff6000541633036132c757565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4f6e6c792063616c6c61626c65206279206f776e6572000000000000000000006044820152fd5b90601154801561336c576133699260405173ffffffffffffffffffffffffffffffffffffffff60208201921682526020815261336081611d12565b51902091613374565b90565b505050600190565b929091906000915b84518310156133c75761338f8386613469565b51908181116133b2576000526020526133ac6040600020926133ff565b9161337c565b906000526020526133ac6040600020926133ff565b915092501490565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6001907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff811461342d570190565b6134356133cf565b0190565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b602091815181101561347e575b60051b010190565b613486613439565b613476565b9190820391821161349857565b6134a06133cf565b565b64e8d4a5100090807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048211811515166134da570290565b6134e26133cf565b0290565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048211811515166134da570290565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b8115613551570490565b613559613517565b0490565b9062093a80820180921161349857565b9190820180921161349857565b929394916135df906135d961358f85856138f5565b956135d36135b061359f88613957565b8b8a829d039081116136b75761348b565b6135c86110f260095469ffffffffffffffffffff1690565b850361366f576134a2565b926134e6565b90613547565b938410613645576136409464e8d4a51000613633866106378661361e61271f6126f661363a9b6117d9876136196127ac9e6126f69e61348b565b6138d2565b69ffffffffffffffffffff60095416906134e6565b049061356d565b4261356d565b600b55565b60046040517fda056d00000000000000000000000000000000000000000000000000000000008152fd5b6136b261367b86613980565b69ffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffffffff000000000000000000006009541617600955565b6134a2565b6136bf6133cf565b61348b565b928491926136d283856138f5565b906137156136df85613957565b96838503948511613826575b878503948511613819575b6135d369ffffffffffffffffffff958660095416850361366f576134a2565b90811561380c575b0461372a8197829661348b565b90613734916138d2565b61373d9161356d565b613746906139c1565b613783906bffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffff000000000000000000000000600a541617600a55565b60095416613790916134e6565b9061379a916134e6565b64e8d4a5100090046137ab9161356d565b6137b4906139c1565b613802907fffffffffffffffff000000000000000000000000ffffffffffffffffffffffff77ffffffffffffffffffffffff000000000000000000000000600a549260601b16911617600a55565b613640904261356d565b613814613517565b61371d565b6138216133cf565b6136f6565b61382e6133cf565b6136eb565b6136bf93916135d9613899926135d3600a5469ffffffffffffffffffff61388c6bffffffffffffffffffffffff988984169b8c9a61386f61393a565b506009549560601c169a8b92600b544281116138b6575b5061348b565b9116850361366f576134a2565b918210613645576127ac61363a916126f661271f613640966139c1565b428103116138c5575b38613886565b6138cd6133cf565b6138bf565b64e8d4a51000916106376135599269ffffffffffffffffffff60095416906134e6565b61392564e8d4a51000916bffffffffffffffffffffffff600a541693810390811161065c5761063761062161393a565b0481039081116139325790565b6133696133cf565b600b5442811161394a5750600090565b4281039081116139325790565b64e8d4a510006139256bffffffffffffffffffffffff600a5460601c169261063761062161393a565b69ffffffffffffffffffff90818111613997571690565b60046040517f811752de000000000000000000000000000000000000000000000000000000008152fd5b6bffffffffffffffffffffffff90818111613997571690565b9081602091031261000e5751801515810361000e5790565b506040513d6000823e3d90fd5b600c5463ffffffff949385821661000e5783613b3f93613b3a7f125fc8494f786b470e3c39d0932a62e9e09e291ebd81ea19c57604f6d2b1d16798889569ffffffffffffffffffff613a5086613980565b167fffffffffffffffffffffffffffffffffffffffffffff0000000000000000000060095416176009557fffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000613aa442613b84565b928316911617600c556008547fffffffffffffffffffffffffffffff00000000ffffffffffffffffffffffffff70ffffffff000000000000000000000000008360681b169116176008557fffffffffffff00000000ffffffffffffffffffffffffffffffffffffffffffff79ffffffff000000000000000000000000000000000000000000006009549260b01b16911617600955565b613833565b613b7f613b51600c5463ffffffff1690565b600b5490604051948594859094939263ffffffff906060936080840197845260208401521660408201520152565b0390a1565b63ffffffff90818111613997571690565b9190916bffffffffffffffffffffffff8080941691160191821161349857565b613bc2600b544290613fcd565b600954907fffffffffffff00000000000000000000000000000000ffffffffffffffffffff79ffffffff00000000000000000000000000000000000000000000613c6c75ffffffffffffffffffffffff00000000000000000000613c62613c4b6126f663ffffffff8960b01c168803888111613c79575b69ffffffffffffffffffff8a166134e6565b6bffffffffffffffffffffffff8860501c16613b95565b60501b1693613b84565b60b01b1692161717600955565b613c816133cf565b613c39565b6126f6613c9291613cfd565b6008547fffffffffffffffffffffffffffffff00000000000000000000000000000000ff6cffffffffffffffffffffffff0070ffffffff00000000000000000000000000613ceb613ce6600b544290613fcd565b613b84565b60681b169360081b1691161717600855565b61336990613d0d600b5442101590565b15613d82576bffffffffffffffffffffffff613d7864e8d4a51000613d6b613d4f600b54613d4963ffffffff60085460681c1663ffffffff1690565b9061348b565b6106376008549669ffffffffffffffffffff60095416906134e6565b046135d960ff8516613dbe565b9160081c1661356d565b6bffffffffffffffffffffffff613d7864e8d4a51000613d6b63ffffffff60085460681c16420342811115613d4f57613db96133cf565b613d4f565b60018110613dc95790565b50600190565b6bffffffffffffffffffffffff918216908216039190821161349857565b6001906bffffffffffffffffffffffff80911690811461342d570190565b6127ac906134a092613e1b61393a565b69ffffffffffffffffffff6009541691610637613e5984612f1b85610637613e528a612f1b64e8d4a510009a8b998a9889946134e6565b98876134e6565b9406613ecd575b06613eb8575b613ea7907fffffffffffffffffffffffffffffffffffffffff000000000000000000000000600a54916bffffffffffffffffffffffff938491828516613b95565b1691161780600a5560601c16613b95565b90613ec5613ea791613ded565b919050613e66565b91613ed790613ded565b91613e60565b613f556134a09164e8d4a51000613f1181612f1b613ef961393a565b61063769ffffffffffffffffffff60095416876134e6565b9106613fbf575b600a547fffffffffffffffffffffffffffffffffffffffff0000000000000000000000006bffffffffffffffffffffffff80948194828516613b95565b1691161780600a5560601c16908111613fb2577fffffffffffffffff000000000000000000000000ffffffffffffffffffffffff77ffffffffffffffffffffffff000000000000000000000000600a549260601b16911617600a55565b613fba6133cf565b6127ac565b613fc890613ded565b613f18565b9080821015613fda575090565b905090565b9190811015613fef5760051b0190565b611d91613439565b3561336981610cdc565b9060405161400e81611ce9565b606081935460ff81161515835260ff8160081c16151560208401526bffffffffffffffffffffffff90818160101c16604085015260701c16910152565b60ff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9116019060ff821161349857565b9060ff8091169116039060ff821161349857565b60ff81116139975760ff1690565b156140a557565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a20706175736564000000000000000000000000000000006044820152fd5b60405190600354808352826020918282019060036000527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b936000905b828210614156575050506134a092500383611d2e565b855473ffffffffffffffffffffffffffffffffffffffff1684526001958601958895509381019390910190614140565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938186528686013760008582860101520116010190565b919260a09373ffffffffffffffffffffffffffffffffffffffff6133699896931684526020840152604083015260608201528160808201520191614186565b60409073ffffffffffffffffffffffffffffffffffffffff61336995931681528160208201520191614186565b613369939273ffffffffffffffffffffffffffffffffffffffff6060931682526020820152816040820152019061050f565b519069ffffffffffffffffffff8216820361000e57565b908160a091031261000e5761428e81614263565b91602082015191604081015191613369608060608401519301614263565b906142b682611d6f565b6142c36040519182611d2e565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe06142f18294611d6f565b0190602036910137565b90815180825260208080930193019160005b82811061431b575050505090565b83518552938101939281019260010161430d565b916143589061434a6133699593606086526060860190610c61565b9084820360208601526142fb565b9160408184039101526142fb565b939291909360ff61437960085460ff1690565b1615614522578264e8d4a510006143a6876106376143ad9569ffffffffffffffffffff60095416906134e6565b0495614529565b91600091826143bc82516142ac565b946143c783516142ac565b92855b81518710156144a1576143fa6143e08884613469565b5173ffffffffffffffffffffffffffffffffffffffff1690565b8861442b610aa1611faa8473ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b8c811561449257898461447a946144578e61445161448b9a976144859a61445e986145b5565b92613469565b5288614723565b6144688b8a613469565b526144738a8c613469565b519061356d565b946144738988613469565b966133ff565b95926143ca565b50505050929561448b906133ff565b7e635ea9da6e262e92bb713d71840af7c567807ff35bf73e927490c6128324809899506144fd919650613b7f95506127ac9250926126f661271f6144e7614516966139c1565b600a546bffffffffffffffffffffffff16613dcf565b600a5460601c6bffffffffffffffffffffffff16613dcf565b6040519384938461432f565b5050509050565b64e8d4a5100061459960ff92610637604051956060870187811067ffffffffffffffff8211176145a8575b60405263ffffffff60085487811689526bffffffffffffffffffffffff8160081c1660208a015260681c16604088015269ffffffffffffffffffff60095416906134e6565b04915116908115613551570490565b6145b0611cb9565b614554565b916146b061467361336993946145cd600b5442101590565b156146f45761463761462c6145f0600b5463ffffffff60095460b01c169061348b565b955b61462661460e6009549869ffffffffffffffffffff8a166134e6565b6bffffffffffffffffffffffff809960501c1661356d565b906134e6565b64e8d4a51000900490565b90846146638873ffffffffffffffffffffffffffffffffffffffff166000526007602052604060002090565b541682039182116146e757613fcd565b936146a7614680866139c1565b9173ffffffffffffffffffffffffffffffffffffffff166000526007602052604060002090565b92835416613b95565b6bffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffff000000000000000000000000825416179055565b6146ef6133cf565b613fcd565b61463761462c63ffffffff60095460b01c164203428111614716575b956145f2565b61471e6133cf565b614710565b916148066147ed613369939461473b600b5442101590565b1561484e576147ae61479664e8d4a5100061478961476d600b54613d4963ffffffff60085460681c1663ffffffff1690565b6106376008549969ffffffffffffffffffff60095416906134e6565b046135d960ff8816613dbe565b6bffffffffffffffffffffffff809660081c1661356d565b90846147da8873ffffffffffffffffffffffffffffffffffffffff166000526007602052604060002090565b5460601c1682039182116146e757613fcd565b936147fa614680866139c1565b92835460601c16613b95565b7fffffffffffffffff000000000000000000000000ffffffffffffffffffffffff77ffffffffffffffffffffffff00000000000000000000000083549260601b169116179055565b6147ae61479664e8d4a5100061478963ffffffff60085460681c1642034281111561476d5761487b6133cf565b61476d565b600b546133699161462c914281116148f45763ffffffff60095460b01c1681039081116148e7575b905b6148d56009549269ffffffffffffffffffff6bffffffffffffffffffffffff8560501c1694166134e6565b8201809211156134e6576106376133cf565b6148ef6133cf565b6148a8565b5063ffffffff60095460b01c164203428111614911575b906148aa565b6149196133cf565b61490b565b9061493d73ffffffffffffffffffffffffffffffffffffffff91613cfd565b911660005260076020526bffffffffffffffffffffffff60406000205460601c1681039081116139325790565b61499a610aa1611faa8373ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b15614b03576149aa6123ff614b09565b614b03576040517ffeaf968c00000000000000000000000000000000000000000000000000000000815260a08160048173ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000005f4ec3df9cbd43714fe2740f5e3616155c5b8419165afa908115614af6575b6000908192614ad1575b5069ffffffffffffffffffff601054911614614aca57614a657f0000000000000000000000000000000000000000000000000000000000002a308261356d565b4210614aca57614a96907f0000000000000000000000000000000000000000000000000000000000002ee09061356d565b421015613dc957611f886133699173ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b5050600090565b9050614aeb915060a03d8111612df657612de38183611d2e565b509291505038614a25565b614afe6139f2565b614a1b565b50600090565b60ff6004541680614b175790565b50600b54421090565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260208160248173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca165afa908115614bfb575b600091614bca575b50614ba4615ab9565b8103908111614bbd575b60065481039081116139325790565b614bc56133cf565b614bae565b906020823d8211614bf3575b81614be360209383611d2e565b8101031261083f57505138614b9b565b3d9150614bd6565b614c036139f2565b614b93565b73ffffffffffffffffffffffffffffffffffffffff81169060009180835260026020526bffffffffffffffffffffffff80604085205460101c16938415614cf95760ff604082205416614cd357505050613d49610aa1614cc1614c9a614c9561336996613d497f000000000000000000000000000000000000000000000000000000000000001482613547565b614880565b9373ffffffffffffffffffffffffffffffffffffffff166000526007602052604060002090565b546bffffffffffffffffffffffff1690565b909250614ce260409294614880565b938152600760205220541681039081116139325790565b935050505090565b908015614d17575b810481039081116139325790565b614d1f613517565b614d09565b73ffffffffffffffffffffffffffffffffffffffff81166000526002602052604060002060405190614d5582611ce9565b549060ff821615908115815260ff8360081c161515602082015260606bffffffffffffffffffffffff808560101c169485604085015260701c16910152614aca5715614b035761336990614da7614dad565b9061491e565b6bffffffffffffffffffffffff60045460101c167f0000000000000000000000000000000000000000000000000000000000000014908115613551570490565b9173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca169182330361502657614e3f60ff60015460a01c161561409e565b614e476159c3565b809264e8d4a51000808310614ff05782069182614f3f575b505050614e8f611f888473ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b15614e9e57506134a091615415565b6011549081614eb3575b50506134a0916150ca565b805115612e0a57614f3491614ed4826020806123ff95518301019101615050565b90604051602081019061336081614f088a8591909173ffffffffffffffffffffffffffffffffffffffff6020820193169052565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611d2e565b612e0a573880614ea8565b829450602091614f54600094614fae9361348b565b956040519485809481937fa9059cbb0000000000000000000000000000000000000000000000000000000083528b600484016020909392919373ffffffffffffffffffffffffffffffffffffffff60408201951681520152565b03925af18015614fe3575b614fc5575b8080614e5f565b614fdc9060203d81116107f3576107e48183611d2e565b5038614fbe565b614feb6139f2565b614fb9565b6040517f1d820b1700000000000000000000000000000000000000000000000000000000815264e8d4a510006004820152602490fd5b60046040517f4d695438000000000000000000000000000000000000000000000000000000008152fd5b602090818184031261000e5780519067ffffffffffffffff821161000e57019180601f8401121561000e57825161508681611d6f565b936150946040519586611d2e565b818552838086019260051b82010192831161000e578301905b8282106150bb575050505090565b815181529083019083016150ad565b91906150fc610aa1611faa8573ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b92615107828561356d565b937f0000000000000000000000000000000000000000000000000de0b6b3a76400008086106153bc575060055461514c9060601c69ffffffffffffffffffff166110f2565b80861161537857505061515d615ae7565b8083116153455750613b7f7f1449c6dd7851abc30abf37f57715f492010519147cc2652fbc38202c18a6ee909394615196612588614dad565b6152357f000000000000000000000000000000000000000000000000000000000000001461522f6151d06151ca8389613547565b8861348b565b916152296151e06126f685614880565b6146b061520d8a73ffffffffffffffffffffffffffffffffffffffff166000526007602052604060002090565b9161522483546bffffffffffffffffffffffff1690565b613b95565b87613547565b90613e0b565b61529d61525d615244866139c1565b60045460101c6bffffffffffffffffffffffff16613b95565b7fffffffffffffffffffffffffffffffffffff000000000000000000000000ffff6dffffffffffffffffffffffff00006004549260101b16911617600455565b6153126152a9826139c1565b6152d38573ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b907fffffffffffffffffffffffffffffffffffff000000000000000000000000ffff6dffffffffffffffffffffffff000083549260101b169116179055565b6040519384938460409194939273ffffffffffffffffffffffffffffffffffffffff606083019616825260208201520152565b6040517fb94339d80000000000000000000000000000000000000000000000000000000081526004810191909152602490fd5b610bfa916153859161348b565b6040519182917fb94339d8000000000000000000000000000000000000000000000000000000008352600483019190602083019252565b6040517f1d820b170000000000000000000000000000000000000000000000000000000081526004810191909152602490fd5b60ff60019116019060ff821161349857565b9060ff8091169116019060ff821161349857565b9190615447610aa1611faa8573ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b92615452828561356d565b937f00000000000000000000000000000000000000000000003635c9adc5dea000008086106153bc575061548b6110f260055460b01c90565b80861161537857507f1449c6dd7851abc30abf37f57715f492010519147cc2652fbc38202c18a6ee909394613b7f911561552d575b6154fc6154cf6126f686614880565b6146b061520d8673ffffffffffffffffffffffffffffffffffffffff166000526007602052604060002090565b6155246128ce61550b866139c1565b60045460701c6bffffffffffffffffffffffff16613b95565b61529d84613edd565b615538612588614dad565b61555361280d60085460ff81169081156155e2575b506153ef565b600880546155dd911c6bffffffffffffffffffffffff166155948573ffffffffffffffffffffffffffffffffffffffff166000526007602052604060002090565b907fffffffffffffffff000000000000000000000000ffffffffffffffffffffffff77ffffffffffffffffffffffff00000000000000000000000083549260601b169116179055565b6154c0565b6127ac6bffffffffffffffffffffffff6156159260081c1661271a600a546bffffffffffffffffffffffff9060601c1690565b6156427fffffffffffffffffffffffffffffffffffffff000000000000000000000000ff60085416600855565b3861554d565b6156756125ef8273ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b9160408301926bffffffffffffffffffffffff90816156a086516bffffffffffffffffffffffff1690565b16156158cc576156c1906156b660045460ff1690565b615887575b51151590565b156157ed57506156fc6128ce6156e385516bffffffffffffffffffffffff1690565b60045460701c6bffffffffffffffffffffffff16613dcf565b6157e8610aa16157248461571f610aa188516bffffffffffffffffffffffff1690565b615913565b946157b861286861579161574c610aa16008546bffffffffffffffffffffffff9060081c1690565b613d49610aa161577c8b73ffffffffffffffffffffffffffffffffffffffff166000526007602052604060002090565b5460601c6bffffffffffffffffffffffff1690565b9673ffffffffffffffffffffffffffffffffffffffff166000526002602052604060002090565b6157c761271f6144e7886139c1565b6157d66127ac6144fd876139c1565b516bffffffffffffffffffffffff1690565b929190565b836158716128686157918661571f610aa19661583961525d6158206158809b9c9d516bffffffffffffffffffffffff1690565b60045460101c6bffffffffffffffffffffffff16613dcf565b86516bffffffffffffffffffffffff167f00000000000000000000000000000000000000000000000000000000000000149116614d01565b6157d661271f6144e7876139c1565b9190600090565b615892612588614dad565b61589a613bb5565b6158c77fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0060045416600455565b6156bb565b6040517fe4adde7200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85166004820152602490fd5b73ffffffffffffffffffffffffffffffffffffffff64e8d4a5100061594d6bffffffffffffffffffffffff938460095460501c16906134e6565b04921660005260076020526040600020541681039081116139325790565b9061599e576133699060057f00000000000000000000000000000000000000000000017b7883c069166000009104613fcd565b507f00000000000000000000000000000000000000000000017b7883c0691660000090565b6159cb614b09565b156121b457565b604051906159df82611ce9565b81606060045460ff81161515835260ff8160081c1660208401526bffffffffffffffffffffffff90818160101c16604085015260701c16910152565b6004549060ff8260081c1690808210615a825750507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0060019116176004557fded6ebf04e261e1eb2f3e3b268a2e6aee5b478c15b341eba5cf18b9bc80c2e636000604051a1565b60449250604051917fe709379900000000000000000000000000000000000000000000000000000000835260048301526024820152fd5b615ac16159d2565b6bffffffffffffffffffffffff6060816040840151169201511681018091116139325790565b615aef6159d2565b6005549060406bffffffffffffffffffffffff91615b1c8385169460ff6020840151169060b01c906134e6565b8403938411615b35575b01511681039081116139325790565b615b3d6133cf565b615b26565b600354811015615b7a575b60036000527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b0190600090565b615b82613439565b615b4d565b60035460008060035581615b99575050565b600381527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b918201915b828110615bcf57505050565b818155600101615bc3565b67ffffffffffffffff8211615caa575b680100000000000000008211615c9d575b60035482600355808310615c5c575b50600360005260005b828110615c1f57505050565b60019060208335615c2f81610cdc565b930192817fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b015501615c13565b827fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b91820191015b818110615c915750615c0a565b60008155600101615c84565b615ca5611cb9565b615bfb565b615cb2611cb9565b615bea565b90916040602092828482018583525201929160005b828110615cda575050505090565b90919293828060019273ffffffffffffffffffffffffffffffffffffffff8835615d0381610cdc565b16815201950193929101615ccc56fea164736f6c6343000810000a
Verified Source Code Partial Match
Compiler: v0.8.16+commit.07a7930e
EVM: london
Optimization: Yes (75000 runs)
StakingPoolLib.sol 295 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {SafeCast} from './SafeCast.sol';
library StakingPoolLib {
using SafeCast for uint256;
/// @notice This event is emitted when the staking pool is opened for stakers
event PoolOpened();
/// @notice This event is emitted when the staking pool is concluded
event PoolConcluded();
/// @notice This event is emitted when the staking pool's maximum size is
/// increased
/// @param maxPoolSize the new maximum pool size
event PoolSizeIncreased(uint256 maxPoolSize);
/// @notice This event is emitted when the maximum stake amount
// for community stakers is increased
/// @param maxStakeAmount the new maximum stake amount
event MaxCommunityStakeAmountIncreased(uint256 maxStakeAmount);
/// @notice This event is emitted when the maximum stake amount for node
/// operators is increased
/// @param maxStakeAmount the new maximum stake amount
event MaxOperatorStakeAmountIncreased(uint256 maxStakeAmount);
/// @notice This event is emitted when an operator is added
/// @param operator address of the operator that was added to the staking pool
event OperatorAdded(address operator);
/// @notice This event is emitted when an operator is removed
/// @param operator address of the operator that was removed from the staking pool
/// @param amount principal amount that will be available for withdrawal when staking ends
event OperatorRemoved(address operator, uint256 amount);
/// @notice This event is emitted when the contract owner sets the list
/// of feed operators subject to slashing
/// @param feedOperators new list of feed operator staking addresses
event FeedOperatorsSet(address[] feedOperators);
/// @notice Surfaces the required pool status to perform an operation
/// @param currentStatus current status of the pool (true if open / false if closed)
/// @param requiredStatus required status of the pool to proceed
/// (true if pool must be open / false if pool must be closed)
error InvalidPoolStatus(bool currentStatus, bool requiredStatus);
/// @notice This error is raised when attempting to decrease maximum pool size
/// @param maxPoolSize the current maximum pool size
error InvalidPoolSize(uint256 maxPoolSize);
/// @notice This error is raised when attempting to decrease maximum stake amount
/// for community stakers or node operators
/// @param maxStakeAmount the current maximum stake amount
error InvalidMaxStakeAmount(uint256 maxStakeAmount);
/// @notice This error is raised when attempting to add more node operators without
/// sufficient available pool space to reserve their allocations.
/// @param remainingPoolSize the remaining pool space available to reserve
/// @param requiredPoolSize the required reserved pool space to add new node operators
error InsufficientRemainingPoolSpace(
uint256 remainingPoolSize,
uint256 requiredPoolSize
);
/// @param requiredAmount minimum required stake amount
error InsufficientStakeAmount(uint256 requiredAmount);
/// @notice This error is raised when stakers attempt to stake past pool limits
/// @param remainingAmount maximum remaining amount that can be staked. This is
/// the difference between the existing staked amount and the individual and global limits.
error ExcessiveStakeAmount(uint256 remainingAmount);
/// @notice This error is raised when stakers attempt to exit the pool
/// @param staker address of the staker who attempted to withdraw funds
error StakeNotFound(address staker);
/// @notice This error is raised when addresses with existing stake is added as an operator
/// @param staker address of the staker who is being added as an operator
error ExistingStakeFound(address staker);
/// @notice This error is raised when an address is duplicated in the supplied list of operators.
/// This can happen in addOperators and setFeedOperators functions.
/// @param operator address of the operator
error OperatorAlreadyExists(address operator);
/// @notice This error is thrown when the owner attempts to remove an on-feed operator.
/// @dev The owner must remove the operator from the on-feed list first.
error OperatorIsAssignedToFeed(address operator);
/// @notice This error is raised when removing an operator in removeOperators
/// and setFeedOperators
/// @param operator address of the operator
error OperatorDoesNotExist(address operator);
/// @notice This error is raised when operator has been removed from the pool
/// and is attempted to be readded
/// @param operator address of the locked operator
error OperatorIsLocked(address operator);
/// @notice This error is raised when attempting to start staking with less
/// than the minimum required node operators
/// @param currentOperatorsCount The current number of operators in the staking pool
/// @param minInitialOperatorsCount The minimum required number of operators
/// in the staking pool before opening
error InadequateInitialOperatorsCount(
uint256 currentOperatorsCount,
uint256 minInitialOperatorsCount
);
struct PoolLimits {
// The max amount of staked LINK allowed in the pool
uint96 maxPoolSize;
// The max amount of LINK a community staker can stake
uint80 maxCommunityStakeAmount;
// The max amount of LINK a Node Op can stake
uint80 maxOperatorStakeAmount;
}
struct PoolState {
// Flag that signals if the staking pool is open for staking
bool isOpen;
// Total number of operators added to the staking pool
uint8 operatorsCount;
// Total amount of LINK staked by community stakers
uint96 totalCommunityStakedAmount;
// Total amount of LINK staked by operators
uint96 totalOperatorStakedAmount;
}
struct Staker {
// Flag that signals whether a staker is an operator
bool isOperator;
// Flag that signals whether a staker is an on-feed operator
bool isFeedOperator;
// Amount of LINK staked by a staker
uint96 stakedAmount;
// Amount of LINK staked by a removed operator that can be withdrawn
// Removed operators can only withdraw at the end of staking.
// Used to know which operators have been removed.
uint96 removedStakeAmount;
}
struct Pool {
mapping(address => Staker) stakers;
address[] feedOperators;
PoolState state;
PoolLimits limits;
// Sum of removed operator principals that have not been withdrawn.
// Used to make sure that contract's balance is correct.
// total staked amount + total removed amount + available rewards = current balance
uint256 totalOperatorRemovedAmount;
}
/// @notice Sets staking pool parameters
/// @param maxPoolSize Maximum total stake amount across all stakers
/// @param maxCommunityStakeAmount Maximum stake amount for a single community staker
/// @param maxOperatorStakeAmount Maximum stake amount for a single node operator
function _setConfig(
Pool storage pool,
uint256 maxPoolSize,
uint256 maxCommunityStakeAmount,
uint256 maxOperatorStakeAmount
) internal {
if (maxOperatorStakeAmount > maxPoolSize)
revert InvalidMaxStakeAmount(maxOperatorStakeAmount);
if (pool.limits.maxPoolSize > maxPoolSize)
revert InvalidPoolSize(maxPoolSize);
if (pool.limits.maxCommunityStakeAmount > maxCommunityStakeAmount)
revert InvalidMaxStakeAmount(maxCommunityStakeAmount);
if (pool.limits.maxOperatorStakeAmount > maxOperatorStakeAmount)
revert InvalidMaxStakeAmount(maxOperatorStakeAmount);
PoolState memory poolState = pool.state;
if (
maxPoolSize <
(poolState.operatorsCount * maxOperatorStakeAmount) +
poolState.totalCommunityStakedAmount
) revert InvalidMaxStakeAmount(maxOperatorStakeAmount);
if (pool.limits.maxPoolSize != maxPoolSize) {
pool.limits.maxPoolSize = maxPoolSize._toUint96();
emit PoolSizeIncreased(maxPoolSize);
}
if (pool.limits.maxCommunityStakeAmount != maxCommunityStakeAmount) {
pool.limits.maxCommunityStakeAmount = maxCommunityStakeAmount._toUint80();
emit MaxCommunityStakeAmountIncreased(maxCommunityStakeAmount);
}
if (pool.limits.maxOperatorStakeAmount != maxOperatorStakeAmount) {
pool.limits.maxOperatorStakeAmount = maxOperatorStakeAmount._toUint80();
emit MaxOperatorStakeAmountIncreased(maxOperatorStakeAmount);
}
}
/// @notice Opens the staking pool
function _open(Pool storage pool, uint256 minInitialOperatorCount) internal {
if (uint256(pool.state.operatorsCount) < minInitialOperatorCount)
revert InadequateInitialOperatorsCount(
pool.state.operatorsCount,
minInitialOperatorCount
);
pool.state.isOpen = true;
emit PoolOpened();
}
/// @notice Closes the staking pool
function _close(Pool storage pool) internal {
pool.state.isOpen = false;
emit PoolConcluded();
}
/// @notice Returns true if a supplied staker address is in the operators list
/// @param staker Address of a staker
/// @return bool
function _isOperator(Pool storage pool, address staker)
internal
view
returns (bool)
{
return pool.stakers[staker].isOperator;
}
/// @notice Returns the sum of all principal staked in the pool
/// @return totalStakedAmount
function _getTotalStakedAmount(Pool storage pool)
internal
view
returns (uint256)
{
StakingPoolLib.PoolState memory poolState = pool.state;
return
uint256(poolState.totalCommunityStakedAmount) +
uint256(poolState.totalOperatorStakedAmount);
}
/// @notice Returns the amount of remaining space available in the pool for
/// community stakers. Community stakers can only stake up to this amount
/// even if they are within their individual limits.
/// @return remainingPoolSpace
function _getRemainingPoolSpace(Pool storage pool)
internal
view
returns (uint256)
{
StakingPoolLib.PoolState memory poolState = pool.state;
return
uint256(pool.limits.maxPoolSize) -
(uint256(poolState.operatorsCount) *
uint256(pool.limits.maxOperatorStakeAmount)) -
uint256(poolState.totalCommunityStakedAmount);
}
/// @dev Required conditions for adding operators:
/// - Operators can only been added to the pool if they have no prior stake.
/// - Operators can only been readded to the pool if they have no removed
/// stake.
/// - Operators cannot be added to the pool after staking ends (either through
/// conclusion or through reward expiry).
function _addOperators(Pool storage pool, address[] calldata operators)
internal
{
uint256 requiredReservedPoolSpace = operators.length *
uint256(pool.limits.maxOperatorStakeAmount);
uint256 remainingPoolSpace = _getRemainingPoolSpace(pool);
if (requiredReservedPoolSpace > remainingPoolSpace)
revert InsufficientRemainingPoolSpace(
remainingPoolSpace,
requiredReservedPoolSpace
);
for (uint256 i; i < operators.length; i++) {
if (pool.stakers[operators[i]].isOperator)
revert OperatorAlreadyExists(operators[i]);
if (pool.stakers[operators[i]].stakedAmount > 0)
revert ExistingStakeFound(operators[i]);
// Avoid edge-cases where we attempt to add an operator that has
// locked principal (this means that the operator was previously removed).
if (pool.stakers[operators[i]].removedStakeAmount > 0)
revert OperatorIsLocked(operators[i]);
pool.stakers[operators[i]].isOperator = true;
emit OperatorAdded(operators[i]);
}
// Safely update operators count with respect to the maximum of 255 operators
pool.state.operatorsCount =
pool.state.operatorsCount +
operators.length._toUint8();
}
/// @notice Helper function to set the list of on-feed Operator addresses
/// @param operators List of Operator addresses
function _setFeedOperators(Pool storage pool, address[] calldata operators)
internal
{
for (uint256 i; i < pool.feedOperators.length; i++) {
delete pool.stakers[pool.feedOperators[i]].isFeedOperator;
}
delete pool.feedOperators;
for (uint256 i; i < operators.length; i++) {
address newFeedOperator = operators[i];
if (!_isOperator(pool, newFeedOperator))
revert OperatorDoesNotExist(newFeedOperator);
if (pool.stakers[newFeedOperator].isFeedOperator)
revert OperatorAlreadyExists(newFeedOperator);
pool.stakers[newFeedOperator].isFeedOperator = true;
}
pool.feedOperators = operators;
emit FeedOperatorsSet(operators);
}
}
LinkTokenInterface.sol 36 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface LinkTokenInterface {
function allowance(address owner, address spender) external view returns (uint256 remaining);
function approve(address spender, uint256 value) external returns (bool success);
function balanceOf(address owner) external view returns (uint256 balance);
function decimals() external view returns (uint8 decimalPlaces);
function decreaseApproval(address spender, uint256 addedValue) external returns (bool success);
function increaseApproval(address spender, uint256 subtractedValue) external;
function name() external view returns (string memory tokenName);
function symbol() external view returns (string memory tokenSymbol);
function totalSupply() external view returns (uint256 totalTokensIssued);
function transfer(address to, uint256 value) external returns (bool success);
function transferAndCall(
address to,
uint256 value,
bytes calldata data
) external returns (bool success);
function transferFrom(
address from,
address to,
uint256 value
) external returns (bool success);
}
SafeCast.sol 35 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
library SafeCast {
error CastError();
/// @notice This is used to safely case timestamps to uint8
uint256 private constant MAX_UINT_8 = type(uint8).max;
/// @notice This is used to safely case timestamps to uint32
uint256 private constant MAX_UINT_32 = type(uint32).max;
/// @notice This is used to safely case timestamps to uint80
uint256 private constant MAX_UINT_80 = type(uint80).max;
/// @notice This is used to safely case timestamps to uint96
uint256 private constant MAX_UINT_96 = type(uint96).max;
function _toUint8(uint256 value) internal pure returns (uint8) {
if (value > MAX_UINT_8) revert CastError();
return uint8(value);
}
function _toUint32(uint256 value) internal pure returns (uint32) {
if (value > MAX_UINT_32) revert CastError();
return uint32(value);
}
function _toUint80(uint256 value) internal pure returns (uint80) {
if (value > MAX_UINT_80) revert CastError();
return uint80(value);
}
function _toUint96(uint256 value) internal pure returns (uint96) {
if (value > MAX_UINT_96) revert CastError();
return uint96(value);
}
}
IERC165.sol 25 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
Math.sol 43 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a >= b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a / b + (a % b == 0 ? 0 : 1);
}
}
Context.sol 24 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
OwnableInterface.sol 10 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface OwnableInterface {
function owner() external returns (address);
function transferOwnership(address recipient) external;
function acceptOwnership() external;
}
RewardLib.sol 643 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {SafeCast} from './SafeCast.sol';
import {StakingPoolLib} from './StakingPoolLib.sol';
import {Math} from '@openzeppelin/contracts/utils/math/Math.sol';
library RewardLib {
using SafeCast for uint256;
/// @notice emitted when the reward is initialized for the first time
/// @param rate the reward rate
/// @param available the amount of rewards available for distribution in the
/// staking pool
/// @param startTimestamp the start timestamp when rewards are started
/// @param endTimestamp the timestamp when the reward will run out
event RewardInitialized(
uint256 rate,
uint256 available,
uint256 startTimestamp,
uint256 endTimestamp
);
/// @notice emitted when owner changes the reward rate
/// @param rate the new reward rate
event RewardRateChanged(uint256 rate);
/// @notice emitted when owner adds more rewards to the pool
/// @param amountAdded the amount of LINK rewards added to the pool
event RewardAdded(uint256 amountAdded);
/// @notice emitted when owner withdraws unreserved rewards
/// @param amount the amount of rewards withdrawn
event RewardWithdrawn(uint256 amount);
/// @notice emitted when an on feed operator gets slashed.
/// Node operators are not slashed more than the amount of rewards they
/// have earned. This means that a node operator that has not
/// accumulated at least two weeks of rewards will be slashed
/// less than an operator that has accumulated at least
/// two weeks of rewards.
event RewardSlashed(
address[] operator,
uint256[] slashedBaseRewards,
uint256[] slashedDelegatedRewards
);
/// @notice This error is thrown when the updated reward duration is less than a month
error RewardDurationTooShort();
/// @notice This is the reward calculation precision variable. LINK token has the
/// 1e18 multiplier which means that rewards are floored after 6 decimals
/// points. Micro LINK is the smallest unit that is eligible for rewards.
uint256 internal constant REWARD_PRECISION = 1e12;
struct DelegatedRewards {
// Count of delegates who are eligible for a share of a reward
// This is always going to be less or equal to operatorsCount
uint8 delegatesCount;
// Tracks base reward amounts that goes to an operator as delegation rewards.
// Used to correctly account for any changes in operator count, delegated amount, or reward rate.
// Formula: duration * rate * amount
uint96 cumulativePerDelegate;
// Timestamp of the last time accumulate was called
// `startTimestamp` <= `delegated.lastAccumulateTimestamp`
uint32 lastAccumulateTimestamp;
}
struct BaseRewards {
// Reward rate expressed in juels per second per micro LINK
uint80 rate;
// The cumulative LINK accrued per stake from past reward rates
// expressed in juels per micro LINK
// Formula: sum of (previousRate * elapsedDurationSinceLastAccumulate)
uint96 cumulativePerMicroLINK;
// Timestamp of the last time the base reward rate was accumulated
uint32 lastAccumulateTimestamp;
}
struct MissedRewards {
// Tracks missed base rewards that are deducted from late stakers
uint96 base;
// Tracks missed delegation rewards that are deducted from late delegates
uint96 delegated;
}
struct ReservedRewards {
// Tracks base reward amount reserved for stakers. This can be used after
// `endTimestamp` to calculate unused amount.
// This amount accumulates as the reward is utilized.
// Formula: duration * rate * amount
uint96 base;
// Tracks delegated reward amount reserved for node operators. This can
// be used after `endTimestamp` to calculate unused amount.
// This amount accumulates as the reward is utilized.
// Formula: duration * rate * amount
uint96 delegated;
}
struct Reward {
mapping(address => MissedRewards) missed;
DelegatedRewards delegated;
BaseRewards base;
ReservedRewards reserved;
// Timestamp when the reward stops accumulating. Has to support a very long
// duration for scenarios with low reward rate.
// `endTimestamp` >= `startTimestamp`
uint256 endTimestamp;
// Timestamp when the reward comes into effect
// `startTimestamp` <= `endTimestamp`
uint32 startTimestamp;
}
/// @notice initializes the reward with the defined parameters
/// @param maxPoolSize maximum pool size that the reward is initialized with
/// @param rate reward rate
/// @param minRewardDuration the minimum duration rewards need to last for
/// @param availableReward available reward amount
/// @dev can only be called once. Any future reward changes have to be done
/// using specific functions.
function _initialize(
Reward storage reward,
uint256 maxPoolSize,
uint256 rate,
uint256 minRewardDuration,
uint256 availableReward
) internal {
if (reward.startTimestamp != 0) revert();
reward.base.rate = rate._toUint80();
uint32 blockTimestamp = block.timestamp._toUint32();
reward.startTimestamp = blockTimestamp;
reward.delegated.lastAccumulateTimestamp = blockTimestamp;
reward.base.lastAccumulateTimestamp = blockTimestamp;
_updateDuration(
reward,
maxPoolSize,
0,
rate,
minRewardDuration,
availableReward,
0
);
emit RewardInitialized(
rate,
availableReward,
reward.startTimestamp,
reward.endTimestamp
);
}
/// @return bool true if the reward is expired (end <= now)
function _isDepleted(Reward storage reward) internal view returns (bool) {
return reward.endTimestamp <= block.timestamp;
}
/// @notice Helper function to accumulate base rewards
/// Accumulate reward per micro LINK before changing reward rate.
/// This keeps rewards prior to rate change unaffected.
function _accumulateBaseRewards(Reward storage reward) internal {
uint256 cappedTimestamp = _getCappedTimestamp(reward);
reward.base.cumulativePerMicroLINK += (uint256(reward.base.rate) *
(cappedTimestamp - uint256(reward.base.lastAccumulateTimestamp)))
._toUint96();
reward.base.lastAccumulateTimestamp = cappedTimestamp._toUint32();
}
/// @notice Helper function to accumulate delegation rewards
/// @dev This function is necessary to correctly account for any changes in
/// eligible operators, delegated amount or reward rate.
function _accumulateDelegationRewards(
Reward storage reward,
uint256 delegatedAmount
) internal {
reward.delegated.cumulativePerDelegate = _calculateAccruedDelegatedRewards(
reward,
delegatedAmount
)._toUint96();
reward.delegated.lastAccumulateTimestamp = _getCappedTimestamp(reward)
._toUint32();
}
/// @notice Helper function to calculate rewards
/// @param amount a staked amount to calculate rewards for
/// @param duration a duration that the specified amount receives rewards for
/// @return rewardsAmount
function _calculateReward(
Reward storage reward,
uint256 amount,
uint256 duration
) internal view returns (uint256) {
return (amount * uint256(reward.base.rate) * duration) / REWARD_PRECISION;
}
/// @notice Calculates the amount of delegated rewards accumulated so far.
/// @dev This function takes into account the amount of delegated
/// rewards accumulated from previous delegate counts and amounts and
/// the latest additional value.
function _calculateAccruedDelegatedRewards(
Reward storage reward,
uint256 totalDelegatedAmount
) internal view returns (uint256) {
uint256 elapsedDurationSinceLastAccumulate = _isDepleted(reward)
? uint256(reward.endTimestamp) -
uint256(reward.delegated.lastAccumulateTimestamp)
: block.timestamp - uint256(reward.delegated.lastAccumulateTimestamp);
return
uint256(reward.delegated.cumulativePerDelegate) +
_calculateReward(
reward,
totalDelegatedAmount,
elapsedDurationSinceLastAccumulate
) /
// We are doing this to keep track of delegated rewards prior to the
// first operator staking.
Math.max(uint256(reward.delegated.delegatesCount), 1);
}
/// @notice Calculates the amount of rewards accrued so far.
/// @dev This function takes into account the amount of
/// rewards accumulated from previous rates in addition to
/// the rewards that will be accumulated based off the current rate
/// over a given duration.
function _calculateAccruedBaseRewards(Reward storage reward, uint256 amount)
internal
view
returns (uint256)
{
uint256 elapsedDurationSinceLastAccumulate = _isDepleted(reward)
? (uint256(reward.endTimestamp) -
uint256(reward.base.lastAccumulateTimestamp))
: block.timestamp - uint256(reward.base.lastAccumulateTimestamp);
return
(amount *
(uint256(reward.base.cumulativePerMicroLINK) +
uint256(reward.base.rate) *
elapsedDurationSinceLastAccumulate)) / REWARD_PRECISION;
}
/// @notice We use a simplified reward calculation formula because we know that
/// the reward is expired. We accumulate reward per micro LINK
/// before concluding the pool so we can avoid reading additional storage
/// variables.
function _calculateConcludedBaseRewards(
Reward storage reward,
uint256 amount,
address staker
) internal view returns (uint256) {
return
(amount * uint256(reward.base.cumulativePerMicroLINK)) /
REWARD_PRECISION -
uint256(reward.missed[staker].base);
}
/// @notice Reserves staker rewards. This is necessary to make sure that
/// there are always enough available LINK tokens for all stakers until the
/// reward end timestamp. The amount is calculated for the remaining reward
/// duration using the current reward rate.
/// @param baseRewardAmount The amount of base rewards to reserve
/// or unreserve for
/// @param delegatedRewardAmount The amount of delegated rewards to reserve
/// or unreserve for
/// @param isReserving true if function should reserve more rewards. false will
/// unreserve and deduct from the reserved total
function _updateReservedRewards(
Reward storage reward,
uint256 baseRewardAmount,
uint256 delegatedRewardAmount,
bool isReserving
) private {
uint256 duration = _getRemainingDuration(reward);
uint96 deltaBaseReward = _calculateReward(
reward,
baseRewardAmount,
duration
)._toUint96();
uint96 deltaDelegatedReward = _calculateReward(
reward,
delegatedRewardAmount,
duration
)._toUint96();
// add if is reserving, subtract otherwise
if (isReserving) {
// We round up (by adding an extra juels) if the amount includes an
// increment below REWARD_PRECISION. We always need to reserve more than
// the user will earn. The consequence of this is that we’ll have dust
// LINK amounts left over in the contract after stakers exit. The amount
// will be approximately 1 juels for every call to reserve function,
// which translates to <1 LINK for the duration of staking v0.1 contract.
if (baseRewardAmount % REWARD_PRECISION > 0) deltaBaseReward++;
if (delegatedRewardAmount % REWARD_PRECISION > 0) deltaDelegatedReward++;
reward.reserved.base += deltaBaseReward;
reward.reserved.delegated += deltaDelegatedReward;
} else {
reward.reserved.base -= deltaBaseReward;
reward.reserved.delegated -= deltaDelegatedReward;
}
}
/// @notice Increase reserved staker rewards.
/// @param baseRewardAmount The amount of base rewards to reserve
/// or unreserve for
/// @param delegatedRewardAmount The amount of delegated rewards to reserve
/// or unreserve for
function _reserve(
Reward storage reward,
uint256 baseRewardAmount,
uint256 delegatedRewardAmount
) internal {
_updateReservedRewards(
reward,
baseRewardAmount,
delegatedRewardAmount,
true
);
}
/// @notice Decrease reserved staker rewards.
/// @param baseRewardAmount The amount of base rewards to reserve
/// or unreserve for
/// @param delegatedRewardAmount The amount of delegated rewards to reserve
/// or unreserve for
function _unreserve(
Reward storage reward,
uint256 baseRewardAmount,
uint256 delegatedRewardAmount
) internal {
_updateReservedRewards(
reward,
baseRewardAmount,
delegatedRewardAmount,
false
);
}
/// @notice function does multiple things:
/// - Unreserves future staking rewards to make them available for withdrawal;
/// - Expires the reward to stop rewards from accumulating;
function _release(
Reward storage reward,
uint256 amount,
uint256 delegatedAmount
) internal {
// Accumulate base and delegation rewards before unreserving rewards to save gas costs.
// We can use the accumulated reward per micro LINK and accumulated delegation reward
// to simplify reward calculations in migrate() and unstake() instead of recalculating.
_accumulateDelegationRewards(reward, delegatedAmount);
_accumulateBaseRewards(reward);
_unreserve(reward, amount - delegatedAmount, delegatedAmount);
reward.endTimestamp = block.timestamp;
}
/// @notice calculates an amount that community stakers have to delegate to operators
/// @param amount base staked amount to calculate delegated amount against
/// @param delegationRateDenominator Delegation rate used to calculate delegated stake amount
function _getDelegatedAmount(
uint256 amount,
uint256 delegationRateDenominator
) internal pure returns (uint256) {
return amount / delegationRateDenominator;
}
/// @notice calculates the amount of stake that remains after accounting for delegation requirement
/// @param amount base staked amount to calculate non-delegated amount against
/// @param delegationRateDenominator Delegation rate used to calculate delegated stake amount
function _getNonDelegatedAmount(
uint256 amount,
uint256 delegationRateDenominator
) internal pure returns (uint256) {
return amount - _getDelegatedAmount(amount, delegationRateDenominator);
}
/// @return uint256 the remaining reward duration (time until end), or 0 if expired/ended.
function _getRemainingDuration(Reward storage reward)
internal
view
returns (uint256)
{
return _isDepleted(reward) ? 0 : reward.endTimestamp - block.timestamp;
}
/// @notice This function is called when the staking pool is initialized,
/// pool size is changed, reward rates are changed, rewards are added, and an alert is raised
/// @param maxPoolSize Current maximum staking pool size
/// @param totalStakedAmount Currently staked amount across community stakers and operators
/// @param newRate New reward rate if it needs to be changed
/// @param minRewardDuration The minimum duration rewards need to last for
/// @param availableReward available reward amount
/// @param totalDelegatedAmount total delegated amount delegated by community stakers
function _updateDuration(
Reward storage reward,
uint256 maxPoolSize,
uint256 totalStakedAmount,
uint256 newRate,
uint256 minRewardDuration,
uint256 availableReward,
uint256 totalDelegatedAmount
) internal {
uint256 earnedBaseRewards = _getEarnedBaseRewards(
reward,
totalStakedAmount,
totalDelegatedAmount
);
uint256 earnedDelegationRewards = _getEarnedDelegationRewards(
reward,
totalDelegatedAmount
);
uint256 remainingRewards = availableReward -
earnedBaseRewards -
earnedDelegationRewards;
if (newRate != uint256(reward.base.rate)) {
reward.base.rate = newRate._toUint80();
}
uint256 availableRewardDuration = (remainingRewards * REWARD_PRECISION) /
(newRate * maxPoolSize);
// Validate that the new reward duration is at least the min reward duration.
// This is a safety mechanism to guard against operational mistakes.
if (availableRewardDuration < minRewardDuration)
revert RewardDurationTooShort();
// Because we utilize unreserved rewards we need to update reserved amounts as well.
// Reserved amounts are set to currently earned rewards plus new future rewards
// based on the available reward duration.
reward.reserved.base = (earnedBaseRewards +
// Future base rewards for currently staked amounts based on the new duration
_calculateReward(
reward,
totalStakedAmount - totalDelegatedAmount,
availableRewardDuration
))._toUint96();
reward.reserved.delegated = (earnedDelegationRewards +
// Future delegation rewards for currently staked amounts based on the new duration
_calculateReward(reward, totalDelegatedAmount, availableRewardDuration))
._toUint96();
reward.endTimestamp = block.timestamp + availableRewardDuration;
}
/// @return The total amount of base rewards earned by all stakers
function _getEarnedBaseRewards(
Reward storage reward,
uint256 totalStakedAmount,
uint256 totalDelegatedAmount
) internal view returns (uint256) {
return
reward.reserved.base -
_calculateReward(
reward,
totalStakedAmount - totalDelegatedAmount,
_getRemainingDuration(reward)
);
}
/// @return The total amount of delegated rewards earned by all node operators
function _getEarnedDelegationRewards(
Reward storage reward,
uint256 totalDelegatedAmount
) internal view returns (uint256) {
return
reward.reserved.delegated -
_calculateReward(
reward,
totalDelegatedAmount,
_getRemainingDuration(reward)
);
}
/// @notice Slashes all on feed node operators.
/// Node operators are slashed the minimum of either the
/// amount of rewards they have earned or the amount
/// of rewards earned by the minimum operator stake amount
/// over the slashable duration.
function _slashOnFeedOperators(
Reward storage reward,
uint256 minOperatorStakeAmount,
uint256 slashableDuration,
address[] memory feedOperators,
mapping(address => StakingPoolLib.Staker) storage stakers,
uint256 totalDelegatedAmount
) internal {
if (reward.delegated.delegatesCount == 0) return; // Skip slashing if there are no staking operators
uint256 slashableBaseRewards = _getSlashableBaseRewards(
reward,
minOperatorStakeAmount,
slashableDuration
);
uint256 slashableDelegatedRewards = _getSlashableDelegatedRewards(
reward,
slashableDuration,
totalDelegatedAmount
);
uint256 totalSlashedBaseReward;
uint256 totalSlashedDelegatedReward;
uint256[] memory slashedBaseAmounts = new uint256[](feedOperators.length);
uint256[] memory slashedDelegatedAmounts = new uint256[](
feedOperators.length
);
for (uint256 i; i < feedOperators.length; i++) {
address operator = feedOperators[i];
uint256 operatorStakedAmount = stakers[operator].stakedAmount;
if (operatorStakedAmount == 0) continue;
slashedBaseAmounts[i] = _slashOperatorBaseRewards(
reward,
slashableBaseRewards,
operator,
operatorStakedAmount
);
slashedDelegatedAmounts[i] = _slashOperatorDelegatedRewards(
reward,
slashableDelegatedRewards,
operator,
totalDelegatedAmount
);
totalSlashedBaseReward += slashedBaseAmounts[i];
totalSlashedDelegatedReward += slashedDelegatedAmounts[i];
}
reward.reserved.base -= totalSlashedBaseReward._toUint96();
reward.reserved.delegated -= totalSlashedDelegatedReward._toUint96();
emit RewardSlashed(
feedOperators,
slashedBaseAmounts,
slashedDelegatedAmounts
);
}
/// @return The amount of base rewards to slash
/// @notice The amount of rewards accrued over the slashable duration for a
/// minimum node operator stake amount
function _getSlashableBaseRewards(
Reward storage reward,
uint256 minOperatorStakeAmount,
uint256 slashableDuration
) private view returns (uint256) {
return _calculateReward(reward, minOperatorStakeAmount, slashableDuration);
}
/// @return The amount of delegated rewards to slash
/// @dev The amount of delegated rewards accrued over the slashable duration
function _getSlashableDelegatedRewards(
Reward storage reward,
uint256 slashableDuration,
uint256 totalDelegatedAmount
) private view returns (uint256) {
DelegatedRewards memory delegatedRewards = reward.delegated;
return
_calculateReward(reward, totalDelegatedAmount, slashableDuration) /
// We don't validate for delegatedRewards.delegatesCount to be a
// non-zero value as this is already checked in _slashOnFeedOperators.
uint256(delegatedRewards.delegatesCount);
}
/// @notice Slashes an on feed node operator the minimum of
/// either the total amount of base rewards they have
/// earned or the amount of rewards earned by the
/// minimum operator stake amount over the slashable duration.
function _slashOperatorBaseRewards(
Reward storage reward,
uint256 slashableRewards,
address operator,
uint256 operatorStakedAmount
) private returns (uint256) {
uint256 earnedRewards = _getOperatorEarnedBaseRewards(
reward,
operator,
operatorStakedAmount
);
uint256 slashedRewards = Math.min(slashableRewards, earnedRewards); // max capped by earnings
reward.missed[operator].base += slashedRewards._toUint96();
return slashedRewards;
}
/// @notice Slashes an on feed node operator the minimum of
/// either the total amount of delegated rewards they have
/// earned or the amount of delegated rewards they have
/// earned over the slashable duration.
function _slashOperatorDelegatedRewards(
Reward storage reward,
uint256 slashableRewards,
address operator,
uint256 totalDelegatedAmount
) private returns (uint256) {
uint256 earnedRewards = _getOperatorEarnedDelegatedRewards(
reward,
operator,
totalDelegatedAmount
);
uint256 slashedRewards = Math.min(slashableRewards, earnedRewards); // max capped by earnings
reward.missed[operator].delegated += slashedRewards._toUint96();
return slashedRewards;
}
/// @return The amount of base rewards an operator
/// has earned.
function _getOperatorEarnedBaseRewards(
Reward storage reward,
address operator,
uint256 operatorStakedAmount
) internal view returns (uint256) {
return
_calculateAccruedBaseRewards(reward, operatorStakedAmount) -
uint256(reward.missed[operator].base);
}
/// @return The amount of delegated rewards an operator
/// has earned.
function _getOperatorEarnedDelegatedRewards(
Reward storage reward,
address operator,
uint256 totalDelegatedAmount
) internal view returns (uint256) {
return
_calculateAccruedDelegatedRewards(reward, totalDelegatedAmount) -
uint256(reward.missed[operator].delegated);
}
/// @return The current timestamp or, if the current timestamp has passed reward
/// end timestamp, reward end timestamp.
/// @dev This is necessary to ensure that rewards are calculated correctly
/// after the reward is depleted.
function _getCappedTimestamp(Reward storage reward)
internal
view
returns (uint256)
{
return Math.min(uint256(reward.endTimestamp), block.timestamp);
}
}
IStaking.sol 129 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
interface IStaking {
/// @notice This event is emitted when a staker adds stake to the pool.
/// @param staker Staker address
/// @param newStake New principal amount staked
/// @param totalStake Total principal amount staked
event Staked(address staker, uint256 newStake, uint256 totalStake);
/// @notice This event is emitted when a staker exits the pool.
/// @param staker Staker address
/// @param principal Principal amount staked
/// @param baseReward base reward earned
/// @param delegationReward delegation reward earned, if any
event Unstaked(
address staker,
uint256 principal,
uint256 baseReward,
uint256 delegationReward
);
/// @notice This error is thrown whenever the sender is not the LINK token
error SenderNotLinkToken();
/// @notice This error is thrown whenever an address does not have access
/// to successfully execute a transaction
error AccessForbidden();
/// @notice This error is thrown whenever a zero-address is supplied when
/// a non-zero address is required
error InvalidZeroAddress();
/// @notice This function allows stakers to exit the pool after it has been
/// concluded. It returns the principal as well as base and delegation
/// rewards.
function unstake() external;
/// @notice This function allows removed operators to withdraw their original
/// principal. Operators can only withdraw after the pool is closed, like
/// every other staker.
function withdrawRemovedStake() external;
/// @return address LINK token contract's address that is used by the pool
function getChainlinkToken() external view returns (address);
/// @param staker address
/// @return uint256 staker's staked principal amount
function getStake(address staker) external view returns (uint256);
/// @notice Returns true if an address is an operator
function isOperator(address staker) external view returns (bool);
/// @notice The staking pool starts closed and only allows
/// stakers to stake once it's opened
/// @return bool pool status
function isActive() external view returns (bool);
/// @return uint256 current maximum staking pool size
function getMaxPoolSize() external view returns (uint256);
/// @return uint256 minimum amount that can be staked by a community staker
/// @return uint256 maximum amount that can be staked by a community staker
function getCommunityStakerLimits() external view returns (uint256, uint256);
/// @return uint256 minimum amount that can be staked by an operator
/// @return uint256 maximum amount that can be staked by an operator
function getOperatorLimits() external view returns (uint256, uint256);
/// @return uint256 reward initialization timestamp
/// @return uint256 reward expiry timestamp
function getRewardTimestamps() external view returns (uint256, uint256);
/// @return uint256 current reward rate, expressed in juels per second per micro LINK
function getRewardRate() external view returns (uint256);
/// @return uint256 current delegation rate
function getDelegationRateDenominator() external view returns (uint256);
/// @return uint256 total amount of LINK tokens made available for rewards in
/// Juels
/// @dev This reflects how many rewards were made available over the
/// lifetime of the staking pool. This is not updated when the rewards are
/// unstaked or migrated by the stakers. This means that the contract balance
/// will dip below available amount when the reward expires and users start
/// moving their rewards.
function getAvailableReward() external view returns (uint256);
/// @return uint256 amount of base rewards earned by a staker in Juels
function getBaseReward(address) external view returns (uint256);
/// @return uint256 amount of delegation rewards earned by an operator in Juels
function getDelegationReward(address) external view returns (uint256);
/// @notice Total delegated amount is calculated by dividing the total
/// community staker staked amount by the delegation rate, i.e.
/// totalDelegatedAmount = pool.totalCommunityStakedAmount / delegationRateDenominator
/// @return uint256 staked amount that is used when calculating delegation rewards in Juels
function getTotalDelegatedAmount() external view returns (uint256);
/// @notice Delegates count increases after an operator is added to the list
/// of operators and stakes the minimum required amount.
/// @return uint256 number of staking operators that are eligible for delegation rewards
function getDelegatesCount() external view returns (uint256);
/// @return uint256 total amount of base rewards earned by all stakers in Juels
function getEarnedBaseRewards() external view returns (uint256);
/// @return uint256 total amount of delegated rewards earned by all node operators in Juels
function getEarnedDelegationRewards() external view returns (uint256);
/// @return uint256 total amount staked by community stakers and operators in Juels
function getTotalStakedAmount() external view returns (uint256);
/// @return uint256 total amount staked by community stakers in Juels
function getTotalCommunityStakedAmount() external view returns (uint256);
/// @return uint256 the sum of removed operator principals that have not been
/// withdrawn from the staking pool in Juels.
/// @dev Used to make sure that contract's balance is correct.
/// total staked amount + total removed amount + available rewards = current balance
function getTotalRemovedAmount() external view returns (uint256);
/// @notice This function returns the pause state
/// @return bool whether or not the pool is paused
function isPaused() external view returns (bool);
/// @return address The address of the feed being monitored to raise alerts for
function getMonitoredFeed() external view returns (address);
}
Pausable.sol 91 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
bool private _paused;
/**
* @dev Initializes the contract in unpaused state.
*/
constructor() {
_paused = false;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
require(!paused(), "Pausable: paused");
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
require(paused(), "Pausable: not paused");
_;
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
MerkleProof.sol 65 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (utils/cryptography/MerkleProof.sol)
pragma solidity ^0.8.0;
/**
* @dev These functions deal with verification of Merkle Trees proofs.
*
* The proofs can be generated using the JavaScript library
* https://github.com/miguelmota/merkletreejs[merkletreejs].
* Note: the hashing algorithm should be keccak256 and pair sorting should be enabled.
*
* See `test/utils/cryptography/MerkleProof.test.js` for some examples.
*
* WARNING: You should avoid using leaf values that are 64 bytes long prior to
* hashing, or use a hash function other than keccak256 for hashing leaves.
* This is because the concatenation of a sorted pair of internal nodes in
* the merkle tree could be reinterpreted as a leaf value.
*/
library MerkleProof {
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*/
function verify(
bytes32[] memory proof,
bytes32 root,
bytes32 leaf
) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leafs & pre-images are assumed to be sorted.
*
* _Available since v4.4._
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
bytes32 proofElement = proof[i];
if (computedHash <= proofElement) {
// Hash(current computed hash + current element of the proof)
computedHash = _efficientHash(computedHash, proofElement);
} else {
// Hash(current element of the proof + current computed hash)
computedHash = _efficientHash(proofElement, computedHash);
}
}
return computedHash;
}
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
TypeAndVersionInterface.sol 6 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
abstract contract TypeAndVersionInterface {
function typeAndVersion() external pure virtual returns (string memory);
}
IERC165.sol 6 lines
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (interfaces/IERC165.sol) pragma solidity ^0.8.0; import "../utils/introspection/IERC165.sol";
IStakingOwner.sol 101 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
/// @notice Owner functions restricted to the setup and maintenance
/// of the staking contract by the owner.
interface IStakingOwner {
/// @notice This error is thrown when an zero delegation rate is supplied
error InvalidDelegationRate();
/// @notice This error is thrown when an invalid regular period threshold is supplied
error InvalidRegularPeriodThreshold();
/// @notice This error is thrown when an invalid min operator stake amount is
/// supplied
error InvalidMinOperatorStakeAmount();
/// @notice This error is thrown when an invalid min community stake amount
/// is supplied
error InvalidMinCommunityStakeAmount();
/// @notice This error is thrown when an invalid max alerting reward is
/// supplied
error InvalidMaxAlertingRewardAmount();
/// @notice This error is thrown when the pool is started with an empty
/// merkle root
error MerkleRootNotSet();
/// @notice Adds one or more operators to a list of operators
/// @dev Should only callable by the Owner
/// @param operators A list of operator addresses to add
function addOperators(address[] calldata operators) external;
/// @notice Removes one or more operators from a list of operators. When an
/// operator is removed, we store their principal in a separate mapping to
/// prevent immediate withdrawals. This is so that the removed operator can
/// only unstake at the same time as every other staker.
/// @dev Should only be callable by the owner when the pool is open.
/// When an operator is removed they can stake as a community staker.
/// We allow that because the alternative (checking for removed stake before
/// staking) is going to unnecessarily increase gas costs in 99.99% of the
/// cases.
/// @param operators A list of operator addresses to remove
function removeOperators(address[] calldata operators) external;
/// @notice Allows the contract owner to set the list of on-feed operator addresses who are subject to slashing
/// @dev Existing feed operators are cleared before setting the new operators.
/// @param operators New list of on-feed operator staker addresses
function setFeedOperators(address[] calldata operators) external;
/// @return List of the ETH-USD feed node operators' staking addresses
function getFeedOperators() external view returns (address[] memory);
/// @notice This function can be called to change the reward rate for the pool.
/// This change only affects future rewards, i.e. rewards earned at a previous
/// rate are unaffected.
/// @dev Should only be callable by the owner. The rate can be increased or decreased.
/// The new rate cannot be 0.
/// @param rate The new reward rate
function changeRewardRate(uint256 rate) external;
/// @notice This function can be called to add rewards to the pool
/// @dev Should only be callable by the owner
/// @param amount The amount of rewards to add to the pool
function addReward(uint256 amount) external;
/// @notice This function can be called to withdraw unused reward amount from
/// the staking pool. It can be called before the pool is initialized, after
/// the pool is concluded or when the reward expires.
/// @dev Should only be callable by the owner when the pool is closed
function withdrawUnusedReward() external;
/// @notice Set the pool config
/// @param maxPoolSize The max amount of staked LINK allowed in the pool
/// @param maxCommunityStakeAmount The max amount of LINK a community staker can stake
/// @param maxOperatorStakeAmount The max amount of LINK a Node Op can stake
function setPoolConfig(
uint256 maxPoolSize,
uint256 maxCommunityStakeAmount,
uint256 maxOperatorStakeAmount
) external;
/// @notice Transfers LINK tokens and initializes the reward
/// @dev Uses ERC20 approve + transferFrom flow
/// @param amount rewards amount in LINK
/// @param initialRewardRate The amount of LINK earned per second for
/// each LINK staked.
function start(uint256 amount, uint256 initialRewardRate) external;
/// @notice Closes the pool, unreserving future staker rewards, expires the
/// reward and releases unreserved rewards
function conclude() external;
/// @notice This function pauses staking
/// @dev Sets the pause flag to true
function emergencyPause() external;
/// @notice This function unpauses staking
/// @dev Sets the pause flag to false
function emergencyUnpause() external;
}
IAlertsController.sol 26 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
interface IAlertsController {
/// @param alerter The address of an alerter
/// @param roundId The feed's round ID that an alert has been raised for
/// @param rewardAmount The amount of LINK rewarded to the alerter
/// @notice Emitted when a valid alert is raised for a feed round
event AlertRaised(address alerter, uint256 roundId, uint256 rewardAmount);
/// @param roundId The feed's round ID that the alerter is trying to raise an alert for
/// @notice This error is thrown when an alerter tries to raise an
// alert for a round that has already been alerted.
error AlertAlreadyExists(uint256 roundId);
/// @notice This error is thrown when alerting conditions are not met and the
/// alert is invalid.
error AlertInvalid();
/// @notice This function creates an alert for a stalled feed
function raiseAlert() external;
/// @notice This function checks to see whether the alerter may raise an alert
/// to claim rewards
function canAlert(address alerter) external view returns (bool);
}
IMigratable.sol 43 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
interface IMigratable {
/// @notice This event is emitted when a migration target is proposed by the contract owner.
/// @param migrationTarget Contract address to migrate stakes to.
event MigrationTargetProposed(address migrationTarget);
/// @notice This event is emitted after a 7 day period has passed since a migration target is proposed, and the target is accepted.
/// @param migrationTarget Contract address to migrate stakes to.
event MigrationTargetAccepted(address migrationTarget);
/// @notice This event is emitted when a staker migrates their stake to the migration target.
/// @param staker Staker address
/// @param principal Principal amount deposited
/// @param baseReward Amount of base rewards withdrawn
/// @param delegationReward Amount of delegation rewards withdrawn (if applicable)
/// @param data Migration payload
event Migrated(
address staker,
uint256 principal,
uint256 baseReward,
uint256 delegationReward,
bytes data
);
/// @notice This error is raised when the contract owner supplies a non-contract migration target.
error InvalidMigrationTarget();
/// @notice This function returns the migration target contract address
function getMigrationTarget() external view returns (address);
/// @notice This function allows the contract owner to set a proposed
/// migration target address. If the migration target is valid it renounces
/// the previously accepted migration target (if any).
/// @param migrationTarget Contract address to migrate stakes to.
function proposeMigrationTarget(address migrationTarget) external;
/// @notice This function allows the contract owner to accept a proposed migration target address after a waiting period.
function acceptMigrationTarget() external;
/// @notice This function allows stakers to migrate funds to a new staking pool.
/// @param data Migration path details
function migrate(bytes calldata data) external;
}
IMerkleAccessController.sol 26 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
interface IMerkleAccessController {
/// @notice Emitted when the contract owner updates the staking allowlist
/// @param newMerkleRoot The root of a new Staking allowlist merkle tree
event MerkleRootChanged(bytes32 newMerkleRoot);
/// @notice Validates if a community staker has access to the private staking pool
/// @param staker The community staker's address
/// @param proof Merkle proof for the community staker's allowlist
function hasAccess(address staker, bytes32[] calldata proof)
external
view
returns (bool);
/// @notice This function is called to update the staking allowlist in a private staking pool
/// @dev Only callable by the contract owner
/// @param newMerkleRoot Merkle Tree root, used to prove access for community stakers
/// will be required at start but can be removed at any time by the owner when
/// staking access will be granted to the public.
function setMerkleRoot(bytes32 newMerkleRoot) external;
/// @return The current root of the Staking allowlist merkle tree
function getMerkleRoot() external view returns (bytes32);
}
Staking.sol 1096 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {LinkTokenInterface} from '@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol';
import {TypeAndVersionInterface} from '@chainlink/contracts/src/v0.8/interfaces/TypeAndVersionInterface.sol';
import {AggregatorV3Interface} from '@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol';
import {ConfirmedOwner} from '@chainlink/contracts/src/v0.8/ConfirmedOwner.sol';
import {Pausable} from '@openzeppelin/contracts/security/Pausable.sol';
import {MerkleProof} from '@openzeppelin/contracts/utils/cryptography/MerkleProof.sol';
import {IERC165} from '@openzeppelin/contracts/interfaces/IERC165.sol';
import {Math} from '@openzeppelin/contracts/utils/math/Math.sol';
import {IStaking} from './interfaces/IStaking.sol';
import {IStakingOwner} from './interfaces/IStakingOwner.sol';
import {IMerkleAccessController} from './interfaces/IMerkleAccessController.sol';
import {IAlertsController} from './interfaces/IAlertsController.sol';
import {IMigratable} from './interfaces/IMigratable.sol';
import {StakingPoolLib} from './StakingPoolLib.sol';
import {RewardLib, SafeCast} from './RewardLib.sol';
contract Staking is
IStaking,
IStakingOwner,
IMigratable,
IMerkleAccessController,
IAlertsController,
ConfirmedOwner,
TypeAndVersionInterface,
Pausable
{
using StakingPoolLib for StakingPoolLib.Pool;
using RewardLib for RewardLib.Reward;
using SafeCast for uint256;
/// @notice This struct defines the params required by the Staking contract's
/// constructor.
struct PoolConstructorParams {
/// @notice The LINK Token
LinkTokenInterface LINKAddress;
/// @notice The feed being monitored when raising alerts
AggregatorV3Interface monitoredFeed;
/// @notice The initial maximum total stake amount across all stakers
uint256 initialMaxPoolSize;
/// @notice The initial maximum stake amount for a single community staker
uint256 initialMaxCommunityStakeAmount;
/// @notice The initial maximum stake amount for a single node operator
uint256 initialMaxOperatorStakeAmount;
/// @notice The minimum stake amount that a community staker can stake
uint256 minCommunityStakeAmount;
/// @notice The minimum stake amount that an operator can stake
uint256 minOperatorStakeAmount;
/// @notice The number of seconds until the feed is considered stale
/// and the priority period begins.
uint256 priorityPeriodThreshold;
/// @notice The number of seconds until the priority period ends
/// and the regular period begins.
uint256 regularPeriodThreshold;
/// @notice The amount of LINK to reward an operator who
/// raises an alert in the priority period.
uint256 maxAlertingRewardAmount;
/// @notice The minimum number of node operators required to initialize the
/// staking pool.
uint256 minInitialOperatorCount;
/// @notice The minimum reward duration after pool config updates and pool
/// reward extensions
uint256 minRewardDuration;
/// @notice The duration of earned rewards to slash when an alert is raised
uint256 slashableDuration;
/// @notice Used to calculate delegated stake amount
/// = amount / delegation rate denominator = 100% / 100 = 1%
uint256 delegationRateDenominator;
}
/// @notice The amount to divide an alerter's stake amount when
/// calculating their reward for raising an alert.
uint256 private constant ALERTING_REWARD_STAKED_AMOUNT_DENOMINATOR = 5;
LinkTokenInterface private immutable i_LINK;
StakingPoolLib.Pool private s_pool;
RewardLib.Reward private s_reward;
/// @notice The ETH USD feed that alerters can raise alerts for.
AggregatorV3Interface private immutable i_monitoredFeed;
/// @notice The proposed address stakers will migrate funds to
address private s_proposedMigrationTarget;
/// @notice The timestamp of when the migration target was proposed at
uint256 private s_proposedMigrationTargetAt;
/// @notice The address stakers can migrate their funds to
address private s_migrationTarget;
/// @notice The round ID of the last feed round an alert was raised
uint256 private s_lastAlertedRoundId;
/// @notice The merkle root of the merkle tree generated from the list
/// of staker addresses with early acccess.
bytes32 private s_merkleRoot;
/// @notice The number of seconds until the feed is considered stale
/// and the priority period begins.
uint256 private immutable i_priorityPeriodThreshold;
/// @notice The number of seconds until the priority period ends
/// and the regular period begins.
uint256 private immutable i_regularPeriodThreshold;
/// @notice The amount of LINK to reward an operator who
/// raises an alert in the priority period.
uint256 private immutable i_maxAlertingRewardAmount;
/// @notice The minimum stake amount that a node operator can stake
uint256 private immutable i_minOperatorStakeAmount;
/// @notice The minimum stake amount that a community staker can stake
uint256 private immutable i_minCommunityStakeAmount;
/// @notice The minimum number of node operators required to initialize the
/// staking pool.
uint256 private immutable i_minInitialOperatorCount;
/// @notice The minimum reward duration after pool config updates and pool
/// reward extensions
uint256 private immutable i_minRewardDuration;
/// @notice The duration of earned rewards to slash when an alert is raised
uint256 private immutable i_slashableDuration;
/// @notice Used to calculate delegated stake amount
/// = amount / delegation rate denominator = 100% / 100 = 1%
uint256 private immutable i_delegationRateDenominator;
constructor(PoolConstructorParams memory params) ConfirmedOwner(msg.sender) {
if (address(params.LINKAddress) == address(0)) revert InvalidZeroAddress();
if (address(params.monitoredFeed) == address(0))
revert InvalidZeroAddress();
if (params.delegationRateDenominator == 0) revert InvalidDelegationRate();
if (RewardLib.REWARD_PRECISION % params.delegationRateDenominator > 0)
revert InvalidDelegationRate();
if (params.regularPeriodThreshold <= params.priorityPeriodThreshold)
revert InvalidRegularPeriodThreshold();
if (params.minOperatorStakeAmount == 0)
revert InvalidMinOperatorStakeAmount();
if (params.minOperatorStakeAmount > params.initialMaxOperatorStakeAmount)
revert InvalidMinOperatorStakeAmount();
if (params.minCommunityStakeAmount > params.initialMaxCommunityStakeAmount)
revert InvalidMinCommunityStakeAmount();
if (params.maxAlertingRewardAmount > params.initialMaxOperatorStakeAmount)
revert InvalidMaxAlertingRewardAmount();
s_pool._setConfig(
params.initialMaxPoolSize,
params.initialMaxCommunityStakeAmount,
params.initialMaxOperatorStakeAmount
);
i_LINK = params.LINKAddress;
i_monitoredFeed = params.monitoredFeed;
i_priorityPeriodThreshold = params.priorityPeriodThreshold;
i_regularPeriodThreshold = params.regularPeriodThreshold;
i_maxAlertingRewardAmount = params.maxAlertingRewardAmount;
i_minOperatorStakeAmount = params.minOperatorStakeAmount;
i_minCommunityStakeAmount = params.minCommunityStakeAmount;
i_minInitialOperatorCount = params.minInitialOperatorCount;
i_minRewardDuration = params.minRewardDuration;
i_slashableDuration = params.slashableDuration;
i_delegationRateDenominator = params.delegationRateDenominator;
}
// =======================
// TypeAndVersionInterface
// =======================
/// @inheritdoc TypeAndVersionInterface
function typeAndVersion() external pure override returns (string memory) {
return 'Staking 0.1.0';
}
// =================
// IMerkleAccessController
// =================
/// @inheritdoc IMerkleAccessController
function hasAccess(address staker, bytes32[] memory proof)
external
view
override
returns (bool)
{
if (s_merkleRoot == bytes32(0)) return true;
return
MerkleProof.verify(proof, s_merkleRoot, keccak256(abi.encode(staker)));
}
/// @inheritdoc IMerkleAccessController
function setMerkleRoot(bytes32 newMerkleRoot) external override onlyOwner {
s_merkleRoot = newMerkleRoot;
emit MerkleRootChanged(newMerkleRoot);
}
/// @inheritdoc IMerkleAccessController
function getMerkleRoot() external view override returns (bytes32) {
return s_merkleRoot;
}
// =============
// IStakingOwner
// =============
/// @inheritdoc IStakingOwner
function setPoolConfig(
uint256 maxPoolSize,
uint256 maxCommunityStakeAmount,
uint256 maxOperatorStakeAmount
) external override(IStakingOwner) onlyOwner whenActive {
s_pool._setConfig(
maxPoolSize,
maxCommunityStakeAmount,
maxOperatorStakeAmount
);
s_reward._updateDuration(
maxPoolSize,
s_pool._getTotalStakedAmount(),
uint256(s_reward.base.rate),
i_minRewardDuration,
getAvailableReward(),
getTotalDelegatedAmount()
);
}
/// @inheritdoc IStakingOwner
function setFeedOperators(address[] calldata operators)
external
override(IStakingOwner)
onlyOwner
{
s_pool._setFeedOperators(operators);
}
/// @inheritdoc IStakingOwner
function start(uint256 amount, uint256 initialRewardRate)
external
override(IStakingOwner)
onlyOwner
{
if (s_merkleRoot == bytes32(0)) revert MerkleRootNotSet();
s_pool._open(i_minInitialOperatorCount);
// We need to transfer LINK balance before we initialize the reward to
// calculate the new reward expiry timestamp.
i_LINK.transferFrom(msg.sender, address(this), amount);
s_reward._initialize(
uint256(s_pool.limits.maxPoolSize),
initialRewardRate,
i_minRewardDuration,
getAvailableReward()
);
}
/// @inheritdoc IStakingOwner
function conclude() external override(IStakingOwner) onlyOwner whenActive {
s_reward._release(
s_pool._getTotalStakedAmount(),
getTotalDelegatedAmount()
);
s_pool._close();
}
/// @inheritdoc IStakingOwner
function addReward(uint256 amount)
external
override(IStakingOwner)
onlyOwner
whenActive
{
// We need to transfer LINK balance before we recalculate the reward expiry
// timestamp so the new amount is accounted for.
i_LINK.transferFrom(msg.sender, address(this), amount);
s_reward._updateDuration(
uint256(s_pool.limits.maxPoolSize),
s_pool._getTotalStakedAmount(),
uint256(s_reward.base.rate),
i_minRewardDuration,
getAvailableReward(),
getTotalDelegatedAmount()
);
emit RewardLib.RewardAdded(amount);
}
/// @inheritdoc IStakingOwner
function withdrawUnusedReward()
external
override(IStakingOwner)
onlyOwner
whenInactive
{
uint256 unusedRewards = getAvailableReward() -
uint256(s_reward.reserved.base) -
uint256(s_reward.reserved.delegated);
emit RewardLib.RewardWithdrawn(unusedRewards);
// msg.sender is the owner address as only the owner can call this function
i_LINK.transfer(msg.sender, unusedRewards);
}
/// @dev Required conditions for adding operators:
/// - Operators can only be added to the pool if they have no prior stake.
/// - Operators can only be readded to the pool if they have no removed
/// stake.
/// - Operators cannot be added to the pool after staking ends (either through
/// conclusion or through reward expiry).
/// @inheritdoc IStakingOwner
function addOperators(address[] calldata operators)
external
override(IStakingOwner)
onlyOwner
{
// If reward was initialized (meaning the pool was active) but the pool is
// no longer active we want to prevent adding new operators.
if (s_reward.startTimestamp > 0 && !isActive())
revert StakingPoolLib.InvalidPoolStatus(false, true);
s_pool._addOperators(operators);
}
/// @inheritdoc IStakingOwner
function removeOperators(address[] calldata operators)
external
override(IStakingOwner)
onlyOwner
whenActive
{
// Accumulate delegation rewards before removing operators as this affects
// rewards that are distributed to remaining operators.
s_reward._accumulateDelegationRewards(getTotalDelegatedAmount());
for (uint256 i; i < operators.length; i++) {
address operator = operators[i];
StakingPoolLib.Staker memory staker = s_pool.stakers[operator];
if (!staker.isOperator)
revert StakingPoolLib.OperatorDoesNotExist(operator);
// Operator must not be on the feed
if (staker.isFeedOperator)
revert StakingPoolLib.OperatorIsAssignedToFeed(operator);
uint256 principal = staker.stakedAmount;
// An operator with stake is a delegate
if (principal > 0) {
// The operator's rewards are forfeited when they are removed
// Unreserve operator's earned base reward
s_reward.reserved.base -= getBaseReward(operator)._toUint96();
// Unreserve operator's future base reward
s_reward.reserved.base -= s_reward
._calculateReward(principal, s_reward._getRemainingDuration())
._toUint96();
// Unreserve operator's earned delegation reward. We don't need to
// unreserve future delegation rewards because they will be split by
// other operators.
s_reward.reserved.delegated -= getDelegationReward(operator)
._toUint96();
s_reward.delegated.delegatesCount -= 1;
delete s_pool.stakers[operator].stakedAmount;
uint96 castPrincipal = principal._toUint96();
s_pool.state.totalOperatorStakedAmount -= castPrincipal;
// Only the operator's principal is withdrawable after they are removed
s_pool.stakers[operator].removedStakeAmount = castPrincipal;
s_pool.totalOperatorRemovedAmount += castPrincipal;
// We need to reset operator's missed base rewards in case they decide
// to stake as a community staker using the same address. It's fine to
// not reset missed delegated rewards, because a removed operator
// cannot be re-added as operator again.
delete s_reward.missed[operator].base;
}
s_pool.stakers[operator].isOperator = false;
emit StakingPoolLib.OperatorRemoved(operator, principal);
}
s_pool.state.operatorsCount -= operators.length._toUint8();
}
/// @inheritdoc IStakingOwner
function changeRewardRate(uint256 newRate)
external
override
onlyOwner
whenActive
{
if (newRate == 0) revert();
uint256 totalDelegatedAmount = getTotalDelegatedAmount();
s_reward._accumulateDelegationRewards(totalDelegatedAmount);
s_reward._accumulateBaseRewards();
s_reward._updateDuration(
uint256(s_pool.limits.maxPoolSize),
s_pool._getTotalStakedAmount(),
newRate,
i_minRewardDuration,
getAvailableReward(),
totalDelegatedAmount
);
emit RewardLib.RewardRateChanged(newRate);
}
/// @inheritdoc IStakingOwner
function emergencyPause() external override(IStakingOwner) onlyOwner {
_pause();
}
/// @inheritdoc IStakingOwner
function emergencyUnpause() external override(IStakingOwner) onlyOwner {
_unpause();
}
/// @inheritdoc IStakingOwner
function getFeedOperators()
external
view
override(IStakingOwner)
returns (address[] memory)
{
return s_pool.feedOperators;
}
// ===========
// IMigratable
// ===========
/// @inheritdoc IMigratable
function getMigrationTarget()
external
view
override(IMigratable)
returns (address)
{
return s_migrationTarget;
}
/// @inheritdoc IMigratable
function proposeMigrationTarget(address migrationTarget)
external
override(IMigratable)
onlyOwner
{
if (
migrationTarget.code.length == 0 ||
migrationTarget == address(this) ||
s_proposedMigrationTarget == migrationTarget ||
s_migrationTarget == migrationTarget ||
!IERC165(migrationTarget).supportsInterface(this.onTokenTransfer.selector)
) revert InvalidMigrationTarget();
s_migrationTarget = address(0);
s_proposedMigrationTarget = migrationTarget;
s_proposedMigrationTargetAt = block.timestamp;
emit MigrationTargetProposed(migrationTarget);
}
/// @inheritdoc IMigratable
function acceptMigrationTarget() external override(IMigratable) onlyOwner {
if (s_proposedMigrationTarget == address(0))
revert InvalidMigrationTarget();
if (block.timestamp < (uint256(s_proposedMigrationTargetAt) + 7 days))
revert AccessForbidden();
s_migrationTarget = s_proposedMigrationTarget;
s_proposedMigrationTarget = address(0);
emit MigrationTargetAccepted(s_migrationTarget);
}
/// @inheritdoc IMigratable
function migrate(bytes calldata data)
external
override(IMigratable)
whenInactive
{
if (s_migrationTarget == address(0)) revert InvalidMigrationTarget();
(uint256 amount, uint256 baseReward, uint256 delegationReward) = _exit(
msg.sender
);
emit Migrated(msg.sender, amount, baseReward, delegationReward, data);
i_LINK.transferAndCall(
s_migrationTarget,
uint256(amount + baseReward + delegationReward),
abi.encode(msg.sender, data)
);
}
// =================
// IAlertsController
// =================
/// @inheritdoc IAlertsController
function raiseAlert() external override(IAlertsController) whenActive {
uint256 stakedAmount = getStake(msg.sender);
if (stakedAmount == 0) revert AccessForbidden();
(uint256 roundId, , , uint256 lastFeedUpdatedAt, ) = i_monitoredFeed
.latestRoundData();
if (roundId == s_lastAlertedRoundId) revert AlertAlreadyExists(roundId);
if (block.timestamp < lastFeedUpdatedAt + i_priorityPeriodThreshold)
revert AlertInvalid();
bool isInPriorityPeriod = block.timestamp <
lastFeedUpdatedAt + i_regularPeriodThreshold;
if (isInPriorityPeriod && !s_pool._isOperator(msg.sender))
revert AlertInvalid();
s_lastAlertedRoundId = roundId;
// There is a risk that this might get us below the total amount of
// reserved if the reward amount slashed is greater than LINK
// balance in the pool. This is an extreme edge case that will only occur
/// if an alert is raised many times such that it completely depletes the
// available rewards in the pool. As this is an unlikely scenario, the
// contract avoids adding an extra check to minimize gas costs.
// There is a similar edge case when the total slashed amount is less than
// the alerting reward. This can happen because slashed amounts are capped to
// earned rewards so far. The result is a net outflow of rewards from the
// staking pool up to the max alerting reward amount in the worst case.
// This is acceptable and in practice has little to no impact to staking.
uint256 rewardAmount = _calculateAlertingRewardAmount(
stakedAmount,
isInPriorityPeriod
);
emit AlertRaised(msg.sender, roundId, rewardAmount);
// We need to transfer the rewards out before recalculating the new reward
// expiry timestamp
i_LINK.transfer(msg.sender, rewardAmount);
s_reward._slashOnFeedOperators(
i_minOperatorStakeAmount,
i_slashableDuration,
s_pool.feedOperators,
s_pool.stakers,
getTotalDelegatedAmount()
);
s_reward._updateDuration(
uint256(s_pool.limits.maxPoolSize),
s_pool._getTotalStakedAmount(),
uint256(s_reward.base.rate),
0,
getAvailableReward(),
getTotalDelegatedAmount()
);
}
/// @inheritdoc IAlertsController
function canAlert(address alerter)
external
view
override(IAlertsController)
returns (bool)
{
if (getStake(alerter) == 0) return false;
if (!isActive()) return false;
(uint256 roundId, , , uint256 updatedAt, ) = i_monitoredFeed
.latestRoundData();
if (roundId == s_lastAlertedRoundId) return false;
// nobody can (feed is not stale)
if (block.timestamp < updatedAt + i_priorityPeriodThreshold) return false;
// all stakers can (regular alerters)
if (block.timestamp >= updatedAt + i_regularPeriodThreshold) return true;
return s_pool._isOperator(alerter); // only operators can (priority alerters)
}
// ========
// IStaking
// ========
/// @inheritdoc IStaking
function unstake() external override(IStaking) whenInactive {
(uint256 amount, uint256 baseReward, uint256 delegationReward) = _exit(
msg.sender
);
emit Unstaked(msg.sender, amount, baseReward, delegationReward);
i_LINK.transfer(msg.sender, amount + baseReward + delegationReward);
}
/// @inheritdoc IStaking
function withdrawRemovedStake() external override(IStaking) whenInactive {
uint256 amount = s_pool.stakers[msg.sender].removedStakeAmount;
if (amount == 0) revert StakingPoolLib.StakeNotFound(msg.sender);
s_pool.totalOperatorRemovedAmount -= amount;
delete s_pool.stakers[msg.sender].removedStakeAmount;
emit Unstaked(msg.sender, amount, 0, 0);
i_LINK.transfer(msg.sender, amount);
}
/// @inheritdoc IStaking
function getStake(address staker)
public
view
override(IStaking)
returns (uint256)
{
return s_pool.stakers[staker].stakedAmount;
}
/// @inheritdoc IStaking
function isOperator(address staker)
external
view
override(IStaking)
returns (bool)
{
return s_pool._isOperator(staker);
}
/// @inheritdoc IStaking
function isActive() public view override(IStaking) returns (bool) {
return s_pool.state.isOpen && !s_reward._isDepleted();
}
/// @inheritdoc IStaking
function getMaxPoolSize() external view override(IStaking) returns (uint256) {
return uint256(s_pool.limits.maxPoolSize);
}
/// @inheritdoc IStaking
function getCommunityStakerLimits()
external
view
override(IStaking)
returns (uint256, uint256)
{
return (
i_minCommunityStakeAmount,
uint256(s_pool.limits.maxCommunityStakeAmount)
);
}
/// @inheritdoc IStaking
function getOperatorLimits()
external
view
override(IStaking)
returns (uint256, uint256)
{
return (
i_minOperatorStakeAmount,
uint256(s_pool.limits.maxOperatorStakeAmount)
);
}
/// @inheritdoc IStaking
function getRewardTimestamps()
external
view
override(IStaking)
returns (uint256, uint256)
{
return (uint256(s_reward.startTimestamp), uint256(s_reward.endTimestamp));
}
/// @inheritdoc IStaking
function getRewardRate() external view override(IStaking) returns (uint256) {
return uint256(s_reward.base.rate);
}
/// @inheritdoc IStaking
function getDelegationRateDenominator()
external
view
override(IStaking)
returns (uint256)
{
return i_delegationRateDenominator;
}
/// @inheritdoc IStaking
function getAvailableReward()
public
view
override(IStaking)
returns (uint256)
{
return
i_LINK.balanceOf(address(this)) -
s_pool._getTotalStakedAmount() -
s_pool.totalOperatorRemovedAmount;
}
/// @inheritdoc IStaking
function getBaseReward(address staker)
public
view
override(IStaking)
returns (uint256)
{
uint256 stake = s_pool.stakers[staker].stakedAmount;
if (stake == 0) return 0;
if (s_pool._isOperator(staker)) {
return s_reward._getOperatorEarnedBaseRewards(staker, stake);
}
return
s_reward._calculateAccruedBaseRewards(
RewardLib._getNonDelegatedAmount(stake, i_delegationRateDenominator)
) - uint256(s_reward.missed[staker].base);
}
/// @inheritdoc IStaking
function getDelegationReward(address staker)
public
view
override(IStaking)
returns (uint256)
{
StakingPoolLib.Staker memory stakerAccount = s_pool.stakers[staker];
if (!stakerAccount.isOperator) return 0;
if (stakerAccount.stakedAmount == 0) return 0;
return
s_reward._getOperatorEarnedDelegatedRewards(
staker,
getTotalDelegatedAmount()
);
}
/// @inheritdoc IStaking
function getTotalDelegatedAmount()
public
view
override(IStaking)
returns (uint256)
{
return
RewardLib._getDelegatedAmount(
s_pool.state.totalCommunityStakedAmount,
i_delegationRateDenominator
);
}
/// @inheritdoc IStaking
function getDelegatesCount()
external
view
override(IStaking)
returns (uint256)
{
return uint256(s_reward.delegated.delegatesCount);
}
/// @inheritdoc IStaking
function getTotalStakedAmount()
external
view
override(IStaking)
returns (uint256)
{
return s_pool._getTotalStakedAmount();
}
/// @inheritdoc IStaking
function getTotalCommunityStakedAmount()
external
view
override(IStaking)
returns (uint256)
{
return s_pool.state.totalCommunityStakedAmount;
}
/// @inheritdoc IStaking
function getTotalRemovedAmount()
external
view
override(IStaking)
returns (uint256)
{
return s_pool.totalOperatorRemovedAmount;
}
/// @inheritdoc IStaking
function getEarnedBaseRewards()
external
view
override(IStaking)
returns (uint256)
{
return
s_reward._getEarnedBaseRewards(
s_pool._getTotalStakedAmount(),
getTotalDelegatedAmount()
);
}
/// @inheritdoc IStaking
function getEarnedDelegationRewards()
external
view
override(IStaking)
returns (uint256)
{
return s_reward._getEarnedDelegationRewards(getTotalDelegatedAmount());
}
/// @inheritdoc IStaking
function isPaused() external view override(IStaking) returns (bool) {
return paused();
}
/// @inheritdoc IStaking
function getChainlinkToken()
public
view
override(IStaking)
returns (address)
{
return address(i_LINK);
}
/// @inheritdoc IStaking
function getMonitoredFeed() external view override returns (address) {
return address(i_monitoredFeed);
}
/**
* @notice Called when LINK is sent to the contract via `transferAndCall`
* @param sender Address of the sender
* @param amount Amount of LINK sent (specified in wei)
* @param data Optional payload containing a Staking Allowlist Merkle proof
*/
function onTokenTransfer(
address sender,
uint256 amount,
bytes memory data
) external validateFromLINK whenNotPaused whenActive {
if (amount < RewardLib.REWARD_PRECISION)
revert StakingPoolLib.InsufficientStakeAmount(RewardLib.REWARD_PRECISION);
// TL;DR: Reward calculation and delegation logic requires precise numbers
// to avoid cumulative rounding errors.
// Long explanation:
// When users stake amounts that are rounded down to 0 after dividing
// by the delegation rate denominator, not enough rewards are reserved for
// the user. When the user then stakes enough times, small rounding errors
// accumulate. This causes an integer underflow when unreserving rewards because
// the total delegated amount returns a larger number than what individual
// reserved amounts sum up to.
uint256 remainder = amount % RewardLib.REWARD_PRECISION;
if (remainder > 0) {
amount -= remainder;
i_LINK.transfer(sender, remainder);
}
if (s_pool._isOperator(sender)) {
_stakeAsOperator(sender, amount);
} else {
// If a Merkle root is set, the sender should
// prove that they are part of the merkle tree
if (s_merkleRoot != bytes32(0)) {
if (data.length == 0) revert AccessForbidden();
if (
!MerkleProof.verify(
abi.decode(data, (bytes32[])),
s_merkleRoot,
keccak256(abi.encode(sender))
)
) revert AccessForbidden();
}
_stakeAsCommunityStaker(sender, amount);
}
}
// =======
// Private
// =======
/// @notice Helper function for when a community staker enters the pool
/// @param staker The staker address
/// @param amount The amount of principal staked
/// @dev When an operator is removed they can stake as a community staker.
/// We allow that because the alternative (checking for removed stake before
/// staking) is going to unnecessarily increase gas costs in 99.99% of the
/// cases.
function _stakeAsCommunityStaker(address staker, uint256 amount) private {
uint256 currentStakedAmount = s_pool.stakers[staker].stakedAmount;
uint256 newStakedAmount = currentStakedAmount + amount;
// Check that the amount is greater than or equal to the minimum required
if (newStakedAmount < i_minCommunityStakeAmount)
revert StakingPoolLib.InsufficientStakeAmount(i_minCommunityStakeAmount);
// Check that the amount is less than or equal to the maximum allowed
uint256 maxCommunityStakeAmount = uint256(
s_pool.limits.maxCommunityStakeAmount
);
if (newStakedAmount > maxCommunityStakeAmount)
revert StakingPoolLib.ExcessiveStakeAmount(
maxCommunityStakeAmount - currentStakedAmount
);
// Check if the amount supplied increases the total staked amount above
// the maximum pool size
uint256 remainingPoolSpace = s_pool._getRemainingPoolSpace();
if (amount > remainingPoolSpace)
revert StakingPoolLib.ExcessiveStakeAmount(remainingPoolSpace);
s_reward._accumulateDelegationRewards(getTotalDelegatedAmount());
uint256 extraNonDelegatedAmount = RewardLib._getNonDelegatedAmount(
amount,
i_delegationRateDenominator
);
s_reward.missed[staker].base += s_reward
._calculateAccruedBaseRewards(extraNonDelegatedAmount)
._toUint96();
s_reward._reserve(
extraNonDelegatedAmount,
RewardLib._getDelegatedAmount(amount, i_delegationRateDenominator)
);
s_pool.state.totalCommunityStakedAmount += amount._toUint96();
s_pool.stakers[staker].stakedAmount = newStakedAmount._toUint96();
emit Staked(staker, amount, newStakedAmount);
}
/// @notice Helper function for when an operator enters the pool
/// @dev Function skips validating whether or not the operator stake
/// amount will cause the total stake amount to exceed the maximum pool size.
/// This is because the pool already reserves a fixed amount of space
/// for each operator meaning that an operator staking cannot cause the
/// total stake amount to exceed the maximum pool size. Each operator
/// receives a reserved stake amount equal to the maxOperatorStakeAmount.
/// This is done by deducting operatorCount * maxOperatorStakeAmount from the
/// remaining pool space available for staking.
/// @param staker The staker address
/// @param amount The amount of principal staked
function _stakeAsOperator(address staker, uint256 amount) private {
StakingPoolLib.Staker storage operator = s_pool.stakers[staker];
uint256 currentStakedAmount = operator.stakedAmount;
uint256 newStakedAmount = currentStakedAmount + amount;
// Check that the amount is greater than or equal to the minimum required
if (newStakedAmount < i_minOperatorStakeAmount)
revert StakingPoolLib.InsufficientStakeAmount(i_minOperatorStakeAmount);
// Check that the amount is less than or equal to the maximum allowed
uint256 maxOperatorStakeAmount = uint256(
s_pool.limits.maxOperatorStakeAmount
);
if (newStakedAmount > maxOperatorStakeAmount)
revert StakingPoolLib.ExcessiveStakeAmount(
maxOperatorStakeAmount - currentStakedAmount
);
// On first stake
if (currentStakedAmount == 0) {
s_reward._accumulateDelegationRewards(getTotalDelegatedAmount());
uint8 delegatesCount = s_reward.delegated.delegatesCount;
// We are doing this check to unreserve any unused delegated rewards
// prior to the first operator staking. After the rewards are unreserved
// we reset the accumulated value so it doesn't count towards missed
// rewards.
// There is a known edge-case where, if no operator stakes throughout the
// duration of the pool, we wouldn't unreserve unused delegation rewards.
// In practice this shouldn't happen and, if it does, there are
// operational workarounds to unreserve those rewards.
if (delegatesCount == 0) {
s_reward.reserved.delegated -= s_reward.delegated.cumulativePerDelegate;
delete s_reward.delegated.cumulativePerDelegate;
}
s_reward.delegated.delegatesCount = delegatesCount + 1;
s_reward.missed[staker].delegated = s_reward
.delegated
.cumulativePerDelegate;
}
s_reward.missed[staker].base += s_reward
._calculateAccruedBaseRewards(amount)
._toUint96();
s_pool.state.totalOperatorStakedAmount += amount._toUint96();
s_reward._reserve(amount, 0);
s_pool.stakers[staker].stakedAmount = newStakedAmount._toUint96();
emit Staked(staker, amount, newStakedAmount);
}
/// @notice Helper function when staker exits the pool
/// @param staker The staker address
function _exit(address staker)
private
returns (
uint256,
uint256,
uint256
)
{
StakingPoolLib.Staker memory stakerAccount = s_pool.stakers[staker];
if (stakerAccount.stakedAmount == 0)
revert StakingPoolLib.StakeNotFound(staker);
// If the pool isOpen that means that we haven't concluded it and stakers
// got here because the reward depleted. In that case, the first user to
// unstake will accumulate delegation and base rewards to save on cost for
// others.
if (s_pool.state.isOpen) {
// Accumulate base and delegation rewards before unreserving rewards to
// save gas costs. We can use the accumulated reward per micro LINK and
// accumulated delegation reward to simplify reward calculations.
s_reward._accumulateDelegationRewards(getTotalDelegatedAmount());
s_reward._accumulateBaseRewards();
delete s_pool.state.isOpen;
}
if (stakerAccount.isOperator) {
s_pool.state.totalOperatorStakedAmount -= stakerAccount.stakedAmount;
uint256 baseReward = s_reward._calculateConcludedBaseRewards(
stakerAccount.stakedAmount,
staker
);
uint256 delegationReward = uint256(
s_reward.delegated.cumulativePerDelegate
) - uint256(s_reward.missed[staker].delegated);
delete s_pool.stakers[staker].stakedAmount;
s_reward.reserved.base -= baseReward._toUint96();
s_reward.reserved.delegated -= delegationReward._toUint96();
return (stakerAccount.stakedAmount, baseReward, delegationReward);
} else {
s_pool.state.totalCommunityStakedAmount -= stakerAccount.stakedAmount;
uint256 baseReward = s_reward._calculateConcludedBaseRewards(
RewardLib._getNonDelegatedAmount(
stakerAccount.stakedAmount,
i_delegationRateDenominator
),
staker
);
delete s_pool.stakers[staker].stakedAmount;
s_reward.reserved.base -= baseReward._toUint96();
return (stakerAccount.stakedAmount, baseReward, 0);
}
}
/// @notice Calculates the reward amount an alerter will receive for raising
/// a successful alert in the current alerting period.
/// @param stakedAmount Amount of LINK staked by the alerter
/// @param isInPriorityPeriod True if it is currently in the priority period
/// @return rewardAmount Amount of LINK rewards to be paid to the alerter
function _calculateAlertingRewardAmount(
uint256 stakedAmount,
bool isInPriorityPeriod
) private view returns (uint256) {
if (isInPriorityPeriod) return i_maxAlertingRewardAmount;
return
Math.min(
stakedAmount / ALERTING_REWARD_STAKED_AMOUNT_DENOMINATOR,
i_maxAlertingRewardAmount
);
}
// =========
// Modifiers
// =========
/// @dev Having a private function for the modifer saves on the contract size
function _isActive() private view {
if (!isActive()) revert StakingPoolLib.InvalidPoolStatus(false, true);
}
/// @dev Reverts if the staking pool is inactive (not open for staking or
/// expired)
modifier whenActive() {
_isActive();
_;
}
/// @dev Reverts if the staking pool is active (open for staking)
modifier whenInactive() {
if (isActive()) revert StakingPoolLib.InvalidPoolStatus(true, false);
_;
}
/// @dev Reverts if not sent from the LINK token
modifier validateFromLINK() {
if (msg.sender != getChainlinkToken()) revert SenderNotLinkToken();
_;
}
}
ConfirmedOwner.sol 12 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./ConfirmedOwnerWithProposal.sol";
/**
* @title The ConfirmedOwner contract
* @notice A contract with helpers for basic contract ownership.
*/
contract ConfirmedOwner is ConfirmedOwnerWithProposal {
constructor(address newOwner) ConfirmedOwnerWithProposal(newOwner, address(0)) {}
}
AggregatorV3Interface.sol 35 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface AggregatorV3Interface {
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
// getRoundData and latestRoundData should both raise "No data present"
// if they do not have data to report, instead of returning unset values
// which could be misinterpreted as actual reported values.
function getRoundData(uint80 _roundId)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
ConfirmedOwnerWithProposal.sol 79 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./interfaces/OwnableInterface.sol";
/**
* @title The ConfirmedOwner contract
* @notice A contract with helpers for basic contract ownership.
*/
contract ConfirmedOwnerWithProposal is OwnableInterface {
address private s_owner;
address private s_pendingOwner;
event OwnershipTransferRequested(address indexed from, address indexed to);
event OwnershipTransferred(address indexed from, address indexed to);
constructor(address newOwner, address pendingOwner) {
require(newOwner != address(0), "Cannot set owner to zero");
s_owner = newOwner;
if (pendingOwner != address(0)) {
_transferOwnership(pendingOwner);
}
}
/**
* @notice Allows an owner to begin transferring ownership to a new address,
* pending.
*/
function transferOwnership(address to) public override onlyOwner {
_transferOwnership(to);
}
/**
* @notice Allows an ownership transfer to be completed by the recipient.
*/
function acceptOwnership() external override {
require(msg.sender == s_pendingOwner, "Must be proposed owner");
address oldOwner = s_owner;
s_owner = msg.sender;
s_pendingOwner = address(0);
emit OwnershipTransferred(oldOwner, msg.sender);
}
/**
* @notice Get the current owner
*/
function owner() public view override returns (address) {
return s_owner;
}
/**
* @notice validate, transfer ownership, and emit relevant events
*/
function _transferOwnership(address to) private {
require(to != msg.sender, "Cannot transfer to self");
s_pendingOwner = to;
emit OwnershipTransferRequested(s_owner, to);
}
/**
* @notice validate access
*/
function _validateOwnership() internal view {
require(msg.sender == s_owner, "Only callable by owner");
}
/**
* @notice Reverts if called by anyone other than the contract owner.
*/
modifier onlyOwner() {
_validateOwnership();
_;
}
}
Read Contract
canAlert 0xc1852f58 → bool
getAvailableReward 0xe0974ea5 → uint256
getBaseReward 0x9a109bc2 → uint256
getChainlinkToken 0x165d35e1 → address
getCommunityStakerLimits 0x0641bdd8 → uint256, uint256
getDelegatesCount 0x32e28850 → uint256
getDelegationRateDenominator 0x5e8b40d7 → uint256
getDelegationReward 0x87e900b1 → uint256
getEarnedBaseRewards 0x1a9d4c7c → uint256
getEarnedDelegationRewards 0x74104002 → uint256
getFeedOperators 0x5fec60f8 → address[]
getMaxPoolSize 0x0fbc8f5b → uint256
getMerkleRoot 0x49590657 → bytes32
getMigrationTarget 0x1ddb5552 → address
getMonitoredFeed 0x83db28a0 → address
getOperatorLimits 0x8856398f → uint256, uint256
getRewardRate 0x7e1a3786 → uint256
getRewardTimestamps 0x59f01879 → uint256, uint256
getStake 0x7a766460 → uint256
getTotalCommunityStakedAmount 0x049b2ca0 → uint256
getTotalDelegatedAmount 0xa7a2f5aa → uint256
getTotalRemovedAmount 0x8019e7d0 → uint256
getTotalStakedAmount 0x38adb6f0 → uint256
hasAccess 0x9d0a3864 → bool
isActive 0x22f3e2d4 → bool
isOperator 0x6d70f7ae → bool
isPaused 0xb187bd26 → bool
owner 0x8da5cb5b → address
paused 0x5c975abb → bool
typeAndVersion 0x181f5a77 → string
Write Contract 21 functions
These functions modify contract state and require a wallet transaction to execute.
acceptMigrationTarget 0xe937fdaa
No parameters
acceptOwnership 0x79ba5097
No parameters
addOperators 0xa07aea1c
address[] operators
addReward 0x74de4ec4
uint256 amount
changeRewardRate 0x74f237c4
uint256 newRate
conclude 0xe5f92973
No parameters
emergencyPause 0x51858e27
No parameters
emergencyUnpause 0x4a4e3bd5
No parameters
migrate 0x8932a90d
bytes data
onTokenTransfer 0xa4c0ed36
address sender
uint256 amount
bytes data
proposeMigrationTarget 0x63b2c85a
address migrationTarget
raiseAlert 0xda9c732f
No parameters
removeOperators 0xd365a377
address[] operators
setFeedOperators 0xbfbd9b1b
address[] operators
setMerkleRoot 0x7cb64759
bytes32 newMerkleRoot
setPoolConfig 0x8a44f337
uint256 maxPoolSize
uint256 maxCommunityStakeAmount
uint256 maxOperatorStakeAmount
start 0x8fb4b573
uint256 amount
uint256 initialRewardRate
transferOwnership 0xf2fde38b
address to
unstake 0x2def6620
No parameters
withdrawRemovedStake 0x5aa6e013
No parameters
withdrawUnusedReward 0xebdb56f3
No parameters
Token Balances (2)
View Transfers →Recent Transactions
No transactions found for this address