diff --git a/SECURITY.md b/SECURITY.md index 5ed9da5d5..48068daa9 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -45,8 +45,7 @@ Vulnerabilities contingent upon the occurrence of any of the following also are Sablier Lockup has been developed with a number of technical assumptions in mind. For a disclosure to qualify as a vulnerability, it must adhere to these assumptions as well: -- The immutable variables `MAX_SEGMENT_COUNT` and `MAX_TRANCHE_COUNT` have values that cannot lead to an overflow of the - block gas limit. +- The immutable `MAX_COUNT` has value that cannot lead to an overflow of the block gas limit. - The total supply of any ERC-20 token remains below 2128 - 1, i.e., `type(uint128).max`. - The `transfer` and `transferFrom` methods of any ERC-20 token strictly reduce the sender's balance by the transfer amount and increase the recipient's balance by the same amount. In other words, tokens that charge fees on transfers diff --git a/foundry.toml b/foundry.toml index 0b65c10c5..562d35df1 100644 --- a/foundry.toml +++ b/foundry.toml @@ -70,9 +70,7 @@ [profile.smt.model_checker.contracts] "src/core/LockupNFTDescriptor.sol" = ["LockupNFTDescriptor"] - "src/core/SablierLockupDynamic.sol" = ["SablierLockupDynamic"] - "src/core/SablierLockupLinear.sol" = ["SablierLockupLinear"] - "src/core/SablierLockupTranched.sol" = ["SablierLockupTranched"] + "src/core/SablierLockup.sol" = ["SablierLockup"] # Test the optimized contracts without re-compiling them [profile.test-optimized] @@ -100,7 +98,7 @@ scroll = { key = "${SCROLLSCAN_API_KEY}" } sepolia = { key = "${SEPOLIASCAN_KEY}" } taiko_mainnet = { key = "${TAIKO_MAINNET_API_KEY}" } - + [fmt] bracket_spacing = true int_types = "long" @@ -134,4 +132,4 @@ sei_testnet = "https://evm-rpc.arctic-1.seinetwork.io" sepolia = "${SEPOLIA_RPC_URL}" taiko_hekla = "https://rpc.hekla.taiko.xyz" - taiko_mainnet = "https://rpc.mainnet.taiko.xyz" \ No newline at end of file + taiko_mainnet = "https://rpc.mainnet.taiko.xyz" diff --git a/precompiles/Precompiles.sol b/precompiles/Precompiles.sol index 691814156..8a90f3539 100644 --- a/precompiles/Precompiles.sol +++ b/precompiles/Precompiles.sol @@ -3,9 +3,7 @@ pragma solidity >=0.8.22; import { ILockupNFTDescriptor } from "./../src/core/interfaces/ILockupNFTDescriptor.sol"; -import { ISablierLockupDynamic } from "./../src/core/interfaces/ISablierLockupDynamic.sol"; -import { ISablierLockupLinear } from "./../src/core/interfaces/ISablierLockupLinear.sol"; -import { ISablierLockupTranched } from "./../src/core/interfaces/ISablierLockupTranched.sol"; +import { ISablierLockup } from "./../src/core/interfaces/ISablierLockup.sol"; import { LockupNFTDescriptor } from "./../src/core/LockupNFTDescriptor.sol"; import { ISablierBatchLockup } from "./../src/periphery/interfaces/ISablierBatchLockup.sol"; import { ISablierMerkleFactory } from "./../src/periphery/interfaces/ISablierMerkleFactory.sol"; @@ -21,153 +19,61 @@ contract Precompiles { //////////////////////////////////////////////////////////////////////////*/ bytes public constant BYTECODE_BATCH_LOCKUP = - hex"60808060405234601557611b67908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c806337266dd31461108957806349a32c4014610d7b578063606ef87514610a635780639e743f291461072b578063a514f83e146103e85763f7ca34eb1461005b575f80fd5b3461032a57610069366113d4565b91909282156103c0575f905f5b84811061039557506001600160a01b036100939116918383611827565b61009c83611584565b926001600160a01b035f9316925b8181106100c357604051806100bf878261142b565b0390f35b6100ce8183886117e1565b6100d7906115b6565b90826100e482828a6117e1565b6020016100f0906115b6565b6100fb83838b6117e1565b604001610107906114b0565b93896101148585836117e1565b606001610120906115ca565b61012b8686846117e1565b608001610137906115ca565b6101428787856117e1565b60a00161014e90611804565b918761015b8189876117e1565b60c081016101689161170e565b986101749291966117e1565b60e001936040519561018587611511565b6001600160a01b0316865260208601966001600160a01b0316875260408601996001600160801b03168a5260608601978d895260808701921515835260a08701931515845260c087019464ffffffffff16855236906101e392611744565b9360e08601948552366101f5916116b0565b966101008601978852604051998a977f31df3d480000000000000000000000000000000000000000000000000000000089526004890160209052610164890197516001600160a01b031660248a0152516001600160a01b03166044890152516001600160801b03166064880152516001600160a01b0316608487015251151560a486015251151560c48501525164ffffffffff1660e48401525190610104830161014090528151809152610184830191602001905f905b808210610341575050925180516001600160a01b03166101248401526020908101516101448401529250819003815f885af18015610336575f90610300575b600192506102f982886116e9565b52016100aa565b506020823d821161032e575b816103196020938361154a565b8101031261032a57600191516102eb565b5f80fd5b3d915061030c565b6040513d5f823e3d90fd5b91949350916020606082610386600194895164ffffffffff604080926001600160801b03815116855267ffffffffffffffff6020820151166020860152015116910152565b019501920186939492916102ac565b916001906001600160801b036103b760406103b1878a8c6117e1565b016114b0565b16019201610076565b7f36186274000000000000000000000000000000000000000000000000000000005f5260045ffd5b3461032a57606036600319011261032a57610401611464565b61040961138d565b906044359167ffffffffffffffff831161032a573660238401121561032a5782600401359267ffffffffffffffff841161032a57602481019060243691610140870201011161032a5783156103c05790915f9190825b85811061070257506001600160a01b0361047c9116928484611827565b61048584611584565b926001600160a01b03165f5b8581106104a657604051806100bf878261142b565b6104b96104b4828886611816565b6115b6565b906104d060206104ca838a88611816565b016115b6565b87856104e260406103b1868585611816565b6104f860606104f2878686611816565b016115ca565b906101006105228761051b818861051560806104f284848d611816565b98611816565b968c611816565b01906001600160a01b03604051986105398a6114c4565b1688526001600160a01b0360208901961686526001600160801b036040890191168152606088019189835260808901931515845260a0890194151585526060609f19873603011261032a57604051956105918761152e565b61059d60a08201611621565b87526105ab60c08201611621565b602088015260e0016105bc90611621565b604087015260c08901958652366105d2916116b0565b9560e08901968752604051987f53b15727000000000000000000000000000000000000000000000000000000008a52516001600160a01b031660048a0152516001600160a01b03166024890152516001600160801b03166044880152516001600160a01b03166064870152511515608486015251151560a485015251805164ffffffffff1660c4850152602081015164ffffffffff1660e48501526040015164ffffffffff166101048401525161012483016106a091602080916001600160a01b0381511684520151910152565b8180865a925f61016492602095f18015610336575f906106d0575b600192506106c982886116e9565b5201610491565b506020823d82116106fa575b816106e96020938361154a565b8101031261032a57600191516106bb565b3d91506106dc565b93926001906001600160801b0361071f60406103b1898b89611816565b1601940193929361045f565b3461032a57610739366113d4565b91909282156103c0575f905f5b848110610a3e57506001600160a01b036107639116918383611827565b61076c83611584565b926001600160a01b035f9316925b81811061078f57604051806100bf878261142b565b61079a8183886117e1565b6107a3906115b6565b90826107b082828a6117e1565b6020016107bc906115b6565b6107c783838b6117e1565b6040016107d3906114b0565b93896107e08585836117e1565b6060016107ec906115ca565b6107f78686846117e1565b608001610803906115ca565b61080e8787856117e1565b60a00161081a90611804565b91876108278189876117e1565b60c08101610834916115d7565b986108409291966117e1565b60e001936040519561085187611511565b6001600160a01b0316865260208601966001600160a01b0316875260408601996001600160801b03168a5260608601978d895260808701921515835260a08701931515845260c087019464ffffffffff16855236906108af92611633565b9360e08601948552366108c1916116b0565b966101008601978852604051998a977f32fbe22b0000000000000000000000000000000000000000000000000000000089526004890160209052610164890197516001600160a01b031660248a0152516001600160a01b03166044890152516001600160801b03166064880152516001600160a01b0316608487015251151560a486015251151560c48501525164ffffffffff1660e48401525190610104830161014090528151809152610184830191602001905f905b8082106109fe575050925180516001600160a01b03166101248401526020908101516101448401529250819003815f885af18015610336575f906109cc575b600192506109c582886116e9565b520161077a565b506020823d82116109f6575b816109e56020938361154a565b8101031261032a57600191516109b7565b3d91506109d8565b91949350916020604082610a2f600194895164ffffffffff602080926001600160801b038151168552015116910152565b01950192018693949291610978565b916001906001600160801b03610a5a60406103b1878a8c6117e1565b16019201610746565b3461032a57610a71366113d4565b91909282156103c0575f905f5b848110610d5657506001600160a01b03610a9b9116918383611827565b610aa483611584565b926001600160a01b035f9316925b818110610ac757604051806100bf878261142b565b610ad281838861147a565b610adb906115b6565b9082610ae882828a61147a565b602001610af4906115b6565b610aff83838b61147a565b604001610b0b906114b0565b9389610b1885858361147a565b606001610b24906115ca565b610b2f86868461147a565b608001610b3b906115ca565b9086610b4881888661147a565b60a08101610b559161170e565b97610b6192919561147a565b60c0019260405194610b72866114c4565b6001600160a01b0316855260208501956001600160a01b0316865260408501986001600160801b0316895260608501968c885260808601921515835260a0860193151584523690610bc292611744565b9260c0850193845236610bd4916116b0565b9560e085019687526040519889967f54c022920000000000000000000000000000000000000000000000000000000088526004880160209052610144880196516001600160a01b03166024890152516001600160a01b03166044880152516001600160801b03166064870152516001600160a01b0316608486015251151560a485015251151560c4840152519060e4830161012090528151809152610164830191602001905f905b808210610d02575050925180516001600160a01b03166101048401526020908101516101248401529250819003815f885af18015610336575f90610cd0575b60019250610cc982886116e9565b5201610ab2565b506020823d8211610cfa575b81610ce96020938361154a565b8101031261032a5760019151610cbb565b3d9150610cdc565b91949350916020606082610d47600194895164ffffffffff604080926001600160801b03815116855267ffffffffffffffff6020820151166020860152015116910152565b01950192018693949291610c7c565b916001906001600160801b03610d7260406103b1878a8c61147a565b16019201610a7e565b3461032a57606036600319011261032a57610d94611464565b610d9c61138d565b906044359167ffffffffffffffff831161032a573660238401121561032a5782600401359267ffffffffffffffff841161032a57602481019060243691610120870201011161032a5783156103c05790915f9190825b85811061106057506001600160a01b03610e0f9116928484611827565b610e1884611584565b926001600160a01b03165f5b858110610e3957604051806100bf878261142b565b610e476104b48288866116fd565b90610e5860206104ca838a886116fd565b8785610e6a60406103b18685856116fd565b610e7a60606104f28786866116fd565b9060e0610ea387610e9c8188610e9660806104f284848d6116fd565b986116fd565b968c6116fd565b01906001600160a01b0360405198610eba8a6114c4565b1688526001600160a01b0360208901961686526001600160801b036040890191168152606088019189835260808901931515845260a0890194151585526040609f19873603011261032a5760405195610f12876114f5565b610f1e60a08201611621565b875260c001610f2c90611621565b602087015260c0890195865236610f42916116b0565b9560e08901968752604051987fab167ccc000000000000000000000000000000000000000000000000000000008a52516001600160a01b031660048a0152516001600160a01b03166024890152516001600160801b03166044880152516001600160a01b03166064870152511515608486015251151560a485015251805164ffffffffff1660c48501526020015164ffffffffff1660e4840152516101048301610ffe91602080916001600160a01b0381511684520151910152565b8180865a925f61014492602095f18015610336575f9061102e575b6001925061102782886116e9565b5201610e24565b506020823d8211611058575b816110476020938361154a565b8101031261032a5760019151611019565b3d915061103a565b93926001906001600160801b0361107d60406103b1898b896116fd565b16019401939293610df2565b3461032a57611097366113d4565b91909282156103c0575f905f5b84811061136857506001600160a01b036110c19116918383611827565b6110ca83611584565b926001600160a01b035f9316925b8181106110ed57604051806100bf878261142b565b6110f881838861147a565b611101906115b6565b908261110e82828a61147a565b60200161111a906115b6565b61112583838b61147a565b604001611131906114b0565b938961113e85858361147a565b60600161114a906115ca565b61115586868461147a565b608001611161906115ca565b908661116e81888661147a565b60a0810161117b916115d7565b9761118792919561147a565b60c0019260405194611198866114c4565b6001600160a01b0316855260208501956001600160a01b0316865260408501986001600160801b0316895260608501968c885260808601921515835260a08601931515845236906111e892611633565b9260c08501938452366111fa916116b0565b9560e085019687526040519889967f897f362b0000000000000000000000000000000000000000000000000000000088526004880160209052610144880196516001600160a01b03166024890152516001600160a01b03166044880152516001600160801b03166064870152516001600160a01b0316608486015251151560a485015251151560c4840152519060e4830161012090528151809152610164830191602001905f905b808210611328575050925180516001600160a01b03166101048401526020908101516101248401529250819003815f885af18015610336575f906112f6575b600192506112ef82886116e9565b52016110d8565b506020823d8211611320575b8161130f6020938361154a565b8101031261032a57600191516112e1565b3d9150611302565b91949350916020604082611359600194895164ffffffffff602080926001600160801b038151168552015116910152565b019501920186939492916112a2565b916001906001600160801b0361138460406103b1878a8c61147a565b160192016110a4565b602435906001600160a01b038216820361032a57565b9181601f8401121561032a5782359167ffffffffffffffff831161032a576020808501948460051b01011161032a57565b606060031982011261032a576004356001600160a01b038116810361032a57916024356001600160a01b038116810361032a57916044359067ffffffffffffffff821161032a57611427916004016113a3565b9091565b60206040818301928281528451809452019201905f5b81811061144e5750505090565b8251845260209384019390920191600101611441565b600435906001600160a01b038216820361032a57565b919081101561149c5760051b8101359060fe198136030182121561032a570190565b634e487b7160e01b5f52603260045260245ffd5b356001600160801b038116810361032a5790565b610100810190811067ffffffffffffffff8211176114e157604052565b634e487b7160e01b5f52604160045260245ffd5b6040810190811067ffffffffffffffff8211176114e157604052565b610120810190811067ffffffffffffffff8211176114e157604052565b6060810190811067ffffffffffffffff8211176114e157604052565b90601f8019910116810190811067ffffffffffffffff8211176114e157604052565b67ffffffffffffffff81116114e15760051b60200190565b9061158e8261156c565b61159b604051918261154a565b82815280926115ac601f199161156c565b0190602036910137565b356001600160a01b038116810361032a5790565b35801515810361032a5790565b903590601e198136030182121561032a570180359067ffffffffffffffff821161032a57602001918160061b3603831361032a57565b35906001600160801b038216820361032a57565b359064ffffffffff8216820361032a57565b92919261163f8261156c565b9361164d604051958661154a565b602085848152019260061b82019181831161032a57925b8284106116715750505050565b60408483031261032a576020604091825161168b816114f5565b6116948761160d565b81526116a1838801611621565b83820152815201930192611664565b919082604091031261032a576040516116c8816114f5565b809280356001600160a01b038116810361032a578252602090810135910152565b805182101561149c5760209160051b010190565b919081101561149c57610120020190565b903590601e198136030182121561032a570180359067ffffffffffffffff821161032a5760200191606082023603831361032a57565b9291926117508261156c565b9361175e604051958661154a565b606060208685815201930282019181831161032a57925b8284106117825750505050565b60608483031261032a57604051906117998261152e565b6117a28561160d565b825260208501359067ffffffffffffffff8216820361032a57826020928360609501526117d160408801611621565b6040820152815201930192611775565b919081101561149c5760051b8101359061011e198136030182121561032a570190565b3564ffffffffff8116810361032a5790565b919081101561149c57610140020190565b919061187c6040517f23b872dd0000000000000000000000000000000000000000000000000000000060208201523360248201523060448201528360648201526064815261187660848261154a565b82611a0a565b6001600160a01b0381166001600160a01b03604051947fdd62ed3e0000000000000000000000000000000000000000000000000000000086523060048701521693846024820152602081604481855afa80156103365784915f916119bd575b50106118e8575b50505050565b5f8060405194602086019063095ea7b360e01b825287602488015260448701526044865261191760648761154a565b85519082855af190611927611a8f565b8261198b575b5081611980575b5015611941575b806118e2565b611973611978936040519063095ea7b360e01b602083015260248201525f60448201526044815261187660648261154a565b611a0a565b5f808061193b565b90503b15155f611934565b805191925081159182156119a3575b5050905f61192d565b6119b692506020809183010191016119f2565b5f8061199a565b9150506020813d6020116119ea575b816119d96020938361154a565b8101031261032a578390515f6118db565b3d91506119cc565b9081602091031261032a5751801515810361032a5790565b5f806001600160a01b03611a3393169360208151910182865af1611a2c611a8f565b9083611ace565b8051908115159182611a74575b5050611a495750565b7f5274afe7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b611a8792506020809183010191016119f2565b155f80611a40565b3d15611ac9573d9067ffffffffffffffff82116114e15760405191611abe601f8201601f19166020018461154a565b82523d5f602084013e565b606090565b90611b0b5750805115611ae357805190602001fd5b7f1425ea42000000000000000000000000000000000000000000000000000000005f5260045ffd5b81511580611b51575b611b1c575090565b6001600160a01b03907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b15611b1456fea164736f6c634300081a000a"; - bytes public constant BYTECODE_LOCKUP_DYNAMIC = + hex"60808060405234601557611888908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c806337266dd314610e1357806349a32c4014610b81578063606ef875146109205780639e743f2914610649578063a514f83e146103875763f7ca34eb1461005b575f80fd5b346102e157610069366110a0565b92839291921561035f579092905f90815b83811061032e57506001600160a01b036100979116918583611548565b6100a082611212565b926001600160a01b035f9516945b8381106100c757604051806100c3878261111c565b0390f35b6100df6100d582868561145c565b60c08101906113e2565b6100ed6100d584888761145c565b5f190191821015905061031a576060020160400161010a9061147f565b9061011681868561145c565b61011f90611244565b9161012b82878661145c565b60200161013790611244565b9061014383888761145c565b60400161014f90611177565b61015a84898861145c565b60600161016690611258565b610171858a8961145c565b60800161017d90611258565b90610189868b8a61145c565b60a0016101959061147f565b926101a1878c8b61145c565b60e00195604051986101b28a6111bb565b6001600160a01b031689526001600160a01b031660208901526001600160801b031660408801528b606088015215156080870152151560a086015264ffffffffff1660c085015264ffffffffff1660e08401523661020f91611265565b61010083015261022081868561145c565b60c0810161022d916113e2565b9290836040519485937f53ac8bc30000000000000000000000000000000000000000000000000000000085526101648501906004860161026c91611491565b61016061014486015252610184830191905f905b8082106102f857505050908060209203815f885af180156102ed575f906102b7575b600192506102b082886113bd565b52016100ae565b506020823d82116102e5575b816102d0602093836111d8565b810103126102e157600191516102a2565b5f80fd5b3d91506102c3565b6040513d5f823e3d90fd5b919350916060808261030c60019488611418565b019401920185939291610280565b634e487b7160e01b5f52603260045260245ffd5b916001906001600160801b03610353604061034d87898b9c9a9c61145c565b01611177565b1601920194929461007a565b7f36186274000000000000000000000000000000000000000000000000000000005f5260045ffd5b346102e15760603660031901126102e1576103a0611074565b6103a861108a565b6044359067ffffffffffffffff82116102e157366023830112156102e157816004013567ffffffffffffffff81116102e15760248301926024369161014084020101116102e157801561035f57905f935f5b83811061061e57506001600160a01b036104179116948286611548565b61042082611212565b926001600160a01b035f9216915b83811061044357604051806100c3878261111c565b61044e818584611537565b61045790611244565b90610463818685611537565b60200161046f90611244565b61047a828786611537565b60400161048690611177565b610491838887611537565b60600161049d90611258565b6104a8848988611537565b6080016104b490611258565b6104bf858a89611537565b60a0016104cb9061147f565b916104d7868b8a611537565b60e0016104e39061147f565b936104ef878c8b611537565b6101000195604051986105018a6111bb565b6001600160a01b031689526001600160a01b031660208901526001600160801b0316604088015288606088015215156080870152151560a086015264ffffffffff1660c085015264ffffffffff1660e08401523661055e91611265565b61010083015261056f818685611537565b60c00161057b9061147f565b604051927f9e7f5dbb000000000000000000000000000000000000000000000000000000008452600484016105af91611491565b64ffffffffff166101448301528180885a925f61016492602095f180156102ed575f906105ec575b600192506105e582886113bd565b520161042e565b506020823d8211610616575b81610605602093836111d8565b810103126102e157600191516105d7565b3d91506105f8565b946001906001600160801b0361063d604061034d8a898b9a999a611537565b160195019291926103fa565b346102e157610657366110a0565b92839291921561035f579092905f90815b8381106108f557506001600160a01b036106859116918583611548565b61068e82611212565b926001600160a01b035f9516945b8381106106b157604051806100c3878261111c565b6106c96106bf82868561145c565b60c08101906112af565b6106d76106bf84888761145c565b5f190191821015905061031a5760061b016020016106f49061147f565b9061070081868561145c565b61070990611244565b9161071582878661145c565b60200161072190611244565b9061072d83888761145c565b60400161073990611177565b61074484898861145c565b60600161075090611258565b61075b858a8961145c565b60800161076790611258565b90610773868b8a61145c565b60a00161077f9061147f565b9261078b878c8b61145c565b60e001956040519861079c8a6111bb565b6001600160a01b031689526001600160a01b031660208901526001600160801b031660408801528b606088015215156080870152151560a086015264ffffffffff1660c085015264ffffffffff1660e0840152366107f991611265565b61010083015261080a81868561145c565b60c08101610817916112af565b9290836040519485937f56b955e80000000000000000000000000000000000000000000000000000000085526101648501906004860161085691611491565b61016061014486015252610184830191905f905b8082106108d357505050908060209203815f885af180156102ed575f906108a1575b6001925061089a82886113bd565b520161069c565b506020823d82116108cb575b816108ba602093836111d8565b810103126102e1576001915161088c565b3d91506108ad565b91935091604080826108e760019488611390565b01940192018593929161086a565b916001906001600160801b03610914604061034d87898b9c9a9c61145c565b16019201949294610668565b346102e15761092e366110a0565b92839291921561035f579092905f90815b838110610b5657506001600160a01b0361095c9116918583611548565b61096582611212565b926001600160a01b035f9516945b83811061098857604051806100c3878261111c565b610993818584611155565b61099c90611244565b906109a8818685611155565b6020016109b490611244565b6109bf828786611155565b6040016109cb90611177565b6109d6838887611155565b6060016109e290611258565b6109ed848988611155565b6080016109f990611258565b91610a05858a89611155565b60c0019360405196610a168861118b565b6001600160a01b031687526001600160a01b031660208701526001600160801b0316604086015289606086015215156080850152151560a084015236610a5b91611265565b60c0830152610a6b818685611155565b60a08101610a78916113e2565b9290836040519485937f42e94a7b00000000000000000000000000000000000000000000000000000000855261012485019060048601610ab7916112e5565b61012061010486015252610144830191905f905b808210610b3457505050908060209203815f885af180156102ed575f90610b02575b60019250610afb82886113bd565b5201610973565b506020823d8211610b2c575b81610b1b602093836111d8565b810103126102e15760019151610aed565b3d9150610b0e565b9193509160608082610b4860019488611418565b019401920185939291610acb565b916001906001600160801b03610b75604061034d87898b9c9a9c611155565b1601920194929461093f565b346102e15760603660031901126102e157610b9a611074565b610ba261108a565b6044359067ffffffffffffffff82116102e157366023830112156102e157816004013567ffffffffffffffff81116102e15760248301926024369161012084020101116102e157801561035f57905f935f5b838110610de857506001600160a01b03610c119116948286611548565b610c1a82611212565b926001600160a01b035f9216915b838110610c3d57604051806100c3878261111c565b610c488185846113d1565b610c5190611244565b90610c5d8186856113d1565b602001610c6990611244565b610c748287866113d1565b604001610c8090611177565b610c8b8388876113d1565b606001610c9790611258565b610ca28489886113d1565b608001610cae90611258565b91610cba858a896113d1565b60e0019360405196610ccb8861118b565b6001600160a01b031687526001600160a01b031660208701526001600160801b0316604086015286606086015215156080850152151560a084015236610d1091611265565b60c0830152610d208186856113d1565b604051927f8222b69400000000000000000000000000000000000000000000000000000000845260048401610d54916112e5565b610d6060a0820161137e565b64ffffffffff1661010484015260c001610d799061137e565b64ffffffffff166101248301528180885a925f61014492602095f180156102ed575f90610db6575b60019250610daf82886113bd565b5201610c28565b506020823d8211610de0575b81610dcf602093836111d8565b810103126102e15760019151610da1565b3d9150610dc2565b946001906001600160801b03610e07604061034d8a898b9a999a6113d1565b16019501929192610bf4565b346102e157610e21366110a0565b92839291921561035f579092905f90815b83811061104957506001600160a01b03610e4f9116918583611548565b610e5882611212565b926001600160a01b035f9516945b838110610e7b57604051806100c3878261111c565b610e86818584611155565b610e8f90611244565b90610e9b818685611155565b602001610ea790611244565b610eb2828786611155565b604001610ebe90611177565b610ec9838887611155565b606001610ed590611258565b610ee0848988611155565b608001610eec90611258565b91610ef8858a89611155565b60c0019360405196610f098861118b565b6001600160a01b031687526001600160a01b031660208701526001600160801b0316604086015289606086015215156080850152151560a084015236610f4e91611265565b60c0830152610f5e818685611155565b60a08101610f6b916112af565b9290836040519485937f6611ceab00000000000000000000000000000000000000000000000000000000855261012485019060048601610faa916112e5565b61012061010486015252610144830191905f905b80821061102757505050908060209203815f885af180156102ed575f90610ff5575b60019250610fee82886113bd565b5201610e66565b506020823d821161101f575b8161100e602093836111d8565b810103126102e15760019151610fe0565b3d9150611001565b919350916040808261103b60019488611390565b019401920185939291610fbe565b916001906001600160801b03611068604061034d87898b9c9a9c611155565b16019201949294610e32565b600435906001600160a01b03821682036102e157565b602435906001600160a01b03821682036102e157565b9060606003198301126102e1576004356001600160a01b03811681036102e157916024356001600160a01b03811681036102e1579160443567ffffffffffffffff81116102e15760040182601f820112156102e15780359267ffffffffffffffff84116102e1576020808301928560051b0101116102e1579190565b60206040818301928281528451809452019201905f5b81811061113f5750505090565b8251845260209384019390920191600101611132565b919081101561031a5760051b8101359060fe19813603018212156102e1570190565b356001600160801b03811681036102e15790565b60e0810190811067ffffffffffffffff8211176111a757604052565b634e487b7160e01b5f52604160045260245ffd5b610120810190811067ffffffffffffffff8211176111a757604052565b90601f8019910116810190811067ffffffffffffffff8211176111a757604052565b67ffffffffffffffff81116111a75760051b60200190565b9061121c826111fa565b61122960405191826111d8565b828152809261123a601f19916111fa565b0190602036910137565b356001600160a01b03811681036102e15790565b3580151581036102e15790565b91908260409103126102e1576040516040810181811067ffffffffffffffff8211176111a757604052809280356001600160a01b03811681036102e1578252602090810135910152565b903590601e19813603018212156102e1570180359067ffffffffffffffff82116102e157602001918160061b360383136102e157565b9060c080611368936001600160a01b0381511684526001600160a01b0360208201511660208501526001600160801b0360408201511660408501526001600160a01b03606082015116606085015260808101511515608085015260a0810151151560a08501520151910190602080916001600160a01b0381511684520151910152565b565b35906001600160801b03821682036102e157565b359064ffffffffff821682036102e157565b64ffffffffff6113b7602080936001600160801b036113ae8261136a565b1686520161137e565b16910152565b805182101561031a5760209160051b010190565b919081101561031a57610120020190565b903590601e19813603018212156102e1570180359067ffffffffffffffff82116102e1576020019160608202360383136102e157565b6001600160801b036114298261136a565b168252602081013567ffffffffffffffff81168091036102e1576113b76040809364ffffffffff9360208701520161137e565b919081101561031a5760051b8101359061011e19813603018212156102e1570190565b3564ffffffffff811681036102e15790565b9061010080611368936001600160a01b0381511684526001600160a01b0360208201511660208501526001600160801b0360408201511660408501526001600160a01b03606082015116606085015260808101511515608085015260a0810151151560a085015264ffffffffff60c08201511660c085015264ffffffffff60e08201511660e08501520151910190602080916001600160a01b0381511684520151910152565b919081101561031a57610140020190565b919061159d6040517f23b872dd000000000000000000000000000000000000000000000000000000006020820152336024820152306044820152836064820152606481526115976084826111d8565b8261172b565b6001600160a01b0381166001600160a01b03604051947fdd62ed3e0000000000000000000000000000000000000000000000000000000086523060048701521693846024820152602081604481855afa80156102ed5784915f916116de575b5010611609575b50505050565b5f8060405194602086019063095ea7b360e01b82528760248801526044870152604486526116386064876111d8565b85519082855af1906116486117b0565b826116ac575b50816116a1575b5015611662575b80611603565b611694611699936040519063095ea7b360e01b602083015260248201525f6044820152604481526115976064826111d8565b61172b565b5f808061165c565b90503b15155f611655565b805191925081159182156116c4575b5050905f61164e565b6116d79250602080918301019101611713565b5f806116bb565b9150506020813d60201161170b575b816116fa602093836111d8565b810103126102e1578390515f6115fc565b3d91506116ed565b908160209103126102e1575180151581036102e15790565b5f806001600160a01b0361175493169360208151910182865af161174d6117b0565b90836117ef565b8051908115159182611795575b505061176a5750565b7f5274afe7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b6117a89250602080918301019101611713565b155f80611761565b3d156117ea573d9067ffffffffffffffff82116111a757604051916117df601f8201601f1916602001846111d8565b82523d5f602084013e565b606090565b9061182c575080511561180457805190602001fd5b7f1425ea42000000000000000000000000000000000000000000000000000000005f5260045ffd5b81511580611872575b61183d575090565b6001600160a01b03907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b1561183556fea164736f6c634300081a000a"; + bytes public constant BYTECODE_LOCKUP = hex"60c0604052346103e157615a506060813803918261001c816103e5565b9384928339810103126103e15780516001600160a01b038116908190036103e15760208201516001600160a01b03811692908390036103e1576040015161006360406103e5565b92601a84527f5361626c696572204c6f636b75702044796e616d6963204e4654000000000000602085015261009860406103e5565b600e81526d29a0a116a627a1a5aaa816a22ca760911b602082015230608052845190946001600160401b0382116102e45760015490600182811c921680156103d7575b60208310146102c65781601f849311610369575b50602090601f8311600114610303575f926102f8575b50508160011b915f199060031b1c1916176001555b83516001600160401b0381116102e457600254600181811c911680156102da575b60208210146102c657601f8111610263575b50602094601f8211600114610200579481929394955f926101f5575b50508160011b915f199060031b1c1916176002555b5f80546001600160a01b031990811685178255600880549091169290921790915560405192907fbdd36143ee09de60bdefca70680e0f71189b2ed7acee364b53917ad433fdaf808180a360a0526001600755615645908161040b823960805181613938015260a051818181610bcc0152613a020152f35b015190505f80610169565b601f1982169560025f52805f20915f5b88811061024b57508360019596979810610233575b505050811b0160025561017e565b01515f1960f88460031b161c191690555f8080610225565b91926020600181928685015181550194019201610210565b60025f527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace601f830160051c810191602084106102bc575b601f0160051c01905b8181106102b1575061014d565b5f81556001016102a4565b909150819061029b565b634e487b7160e01b5f52602260045260245ffd5b90607f169061013b565b634e487b7160e01b5f52604160045260245ffd5b015190505f80610105565b60015f9081528281209350601f198516905b8181106103515750908460019594939210610339575b505050811b0160015561011a565b01515f1960f88460031b161c191690555f808061032b565b92936020600181928786015181550195019301610315565b60015f529091507fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6601f840160051c810191602085106103cd575b90601f859493920160051c01905b8181106103bf57506100ef565b5f81558493506001016103b2565b90915081906103a4565b91607f16916100db565b5f80fd5b6040519190601f01601f191682016001600160401b038111838210176102e45760405256fe6080806040526004361015610012575f80fd5b5f3560e01c90816301ffc9a7146127c957508063027b6744146127a757806306fdde03146126ec578063081812fc146126ce578063095ea7b3146125c95780631400ecec146125185780631c1cdd4c146124b45780631e99d5691461249757806323b872dd14612480578063303acc851461244357806331df3d4814612330578063406887cb146121c157806340e58ee514611ea8578063425d30dd14611e5857806342842e0e14611e2f57806342966c6814611c6b5780634426757014611c455780634857501f14611bd45780634869e12d14611b9a5780634cc55e1114611aa257806354c02292146117f157806357404b12146117635780636352211e146117345780636d0cee751461173457806370a08231146116ca57806375829def1461165c5780637cad6cd11461156b5780637de6b1db146113ec5780638659c27014610fed578063894e9a0d14610cbe5780638f69b99314610c3e5780639067b67714610bef5780639188ec8414610bb557806395d89b4114610aad578063a22cb465146109f9578063a80fc071146109a8578063ad35efd414610949578063b2564569146108f9578063b637b865146108a0578063b88d4fde14610818578063b8a3be66146107e3578063b971302a14610795578063bc2be1be14610746578063c156a11d1461060f578063c87b56dd146104f9578063d4dbd20b146104a8578063d511609f1461045d578063d975dfed14610423578063e985e9c5146103ca578063ea5ead1914610386578063eac8f5b814610335578063f590c176146102d9578063f851a440146102b45763fdd46d601461026e575f80fd5b346102b05760603660031901126102b0576102876128f6565b6044356001600160801b03811681036102b0576102ae916102a661392e565b6004356133e2565b005b5f80fd5b346102b0575f3660031901126102b05760206001600160a01b035f5416604051908152f35b346102b05760203660031901126102b057600435805f52600a60205260ff600160405f20015460a81c1615610323575f52600a602052602060405f205460f81c6040519015158152f35b63699d2de960e01b5f5260045260245ffd5b346102b05760203660031901126102b057600435805f52600a60205260ff600160405f20015460a81c1615610323575f52600a60205260206001600160a01b03600160405f20015416604051908152f35b346102b05760403660031901126102b05760206004356103b96103a76128f6565b916103b181614209565b928391613085565b6001600160801b0360405191168152f35b346102b05760403660031901126102b0576103e36128e0565b6001600160a01b036103f36128f6565b91165f5260066020526001600160a01b0360405f2091165f52602052602060ff60405f2054166040519015158152f35b346102b05760203660031901126102b057600435805f52600a60205260ff600160405f20015460a81c1615610323576103b9602091614209565b346102b05760203660031901126102b057600435805f52600a60205260ff600160405f20015460a81c1615610323575f52600a6020526020600260405f20015460801c604051908152f35b346102b05760203660031901126102b057600435805f52600a60205260ff600160405f20015460a81c1615610323575f52600a60205260206001600160801b03600360405f20015416604051908152f35b346102b05760203660031901126102b057600435610516816136b0565b505f6001600160a01b0360085416916044604051809481937fe9dc637500000000000000000000000000000000000000000000000000000000835230600484015260248301525afa8015610604575f90610587575b610583906040519182916020835260208301906128bb565b0390f35b503d805f833e6105978183612a79565b8101906020818303126102b05780519067ffffffffffffffff82116102b057019080601f830112156102b0578151916105cf83612a9b565b916105dd6040519384612a79565b838352602084830101116102b057610583926105ff916020808501910161289a565b61056b565b6040513d5f823e3d90fd5b346102b05760403660031901126102b05760043561062b6128f6565b61063361392e565b815f52600a60205260ff600160405f20015460a81c161561073357815f5260036020526001600160a01b0360405f205416908133036107135761067583614209565b6001600160801b0381169182610703575b6001600160a01b038116156106f0576106a7856001600160a01b03926137f4565b1692836106c15784637e27328960e01b5f5260045260245ffd5b80859185036106d557602084604051908152f35b8492506364283d7b60e01b5f5260045260245260445260645ffd5b633250574960e11b5f525f60045260245ffd5b61070e828587613085565b610686565b82632082501160e01b5f526004526001600160a01b03331660245260445ffd5b5063699d2de960e01b5f5260045260245ffd5b346102b05760203660031901126102b057600435805f52600a60205260ff600160405f20015460a81c1615610323575f52600a602052602064ffffffffff60405f205460a01c16604051908152f35b346102b05760203660031901126102b057600435805f52600a60205260ff600160405f20015460a81c1615610323575f52600a60205260206001600160a01b0360405f205416604051908152f35b346102b05760203660031901126102b0576004355f52600a602052602060ff600160405f20015460a81c166040519015158152f35b346102b05760803660031901126102b0576108316128e0565b6108396128f6565b6064359167ffffffffffffffff83116102b057366023840112156102b05782600401359161086683612a9b565b926108746040519485612a79565b80845236602482870101116102b0576020815f9260246102ae9801838801378501015260443591612f95565b346102b05760203660031901126102b057600435805f52600a60205260ff600160405f20015460a81c1615610323575f52600b6020526105836108e560405f20612f0e565b60405191829160208352602083019061298b565b346102b05760203660031901126102b057600435805f52600a60205260ff600160405f20015460a81c1615610323575f52600a602052602060ff600160405f20015460b01c166040519015158152f35b346102b05760203660031901126102b057600435805f52600a60205260ff600160405f20015460a81c16156103235761098190613760565b6040516005821015610994576020918152f35b634e487b7160e01b5f52602160045260245ffd5b346102b05760203660031901126102b057600435805f52600a60205260ff600160405f20015460a81c1615610323575f52600a60205260206001600160801b03600260405f20015416604051908152f35b346102b05760403660031901126102b057610a126128e0565b602435908115158092036102b0576001600160a01b0316908115610a8157335f52600660205260405f20825f5260205260405f2060ff1981541660ff83161790556040519081527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3160203392a3005b507f5b08ba18000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b346102b0575f3660031901126102b0576040515f6002548060011c90600181168015610bab575b602083108114610b9757828552908115610b735750600114610b15575b61058383610b0181850382612a79565b6040519182916020835260208301906128bb565b91905060025f527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace915f905b808210610b5957509091508101602001610b01610af1565b919260018160209254838588010152019101909291610b41565b60ff191660208086019190915291151560051b84019091019150610b019050610af1565b634e487b7160e01b5f52602260045260245ffd5b91607f1691610ad4565b346102b0575f3660031901126102b05760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346102b05760203660031901126102b057600435805f52600a60205260ff600160405f20015460a81c1615610323575f52600a602052602064ffffffffff60405f205460c81c16604051908152f35b346102b05760203660031901126102b057600435805f52600a60205260ff600160405f20015460a81c161561032357610c7690613760565b6005811015806109945760028214908115610cb2575b8115610ca0575b6020826040519015158152f35b90506109945760046020911482610c93565b5050600381145f610c8c565b346102b05760203660031901126102b057600435604051610180810181811067ffffffffffffffff821117610fd957606091610160916040525f81525f60208201525f60408201525f838201525f60808201525f60a08201525f60c08201525f60e08201525f6101008201525f610120820152610d39612ebe565b6101408201520152805f52600a60205260ff600160405f20015460a81c161561032357805f52600a60205260405f20604051610d7481612a5c565b8154906001600160a01b0382168152602081019264ffffffffff8360a01c1684526040820164ffffffffff8460c81c168152606083019160ff8560f01c1615158352608084019460f81c1515855260018101549160a08501946001600160a01b038416865260c0810160ff8560a01c1615158152610e13600260e084019560ff8860a81c161515875260ff61010086019860b01c161515885201612edc565b61012083019081526002610e268c613760565b610e2f816129fd565b14610fd1575b5196516001600160a01b0316925164ffffffffff1695511515905115159351151594511515958a5f52600360205260405f20546001600160a01b03169a5f52600b60205260405f2092516001600160a01b0316995164ffffffffff1698511515926040519a610ea66101808d612a79565b8b5260208b019b8c5260408b01998a5260608b0191825260808b0192835260a08b0193845260c08b0194855260e08b019586526101008b019687526101208b019788526101408b01988952610efa90612f0e565b986101608b01998a526040519b8c9b60208d52516001600160a01b031660208d0152516001600160a01b031660408c01525164ffffffffff1660608b01525164ffffffffff1660808a015251151560a089015251151560c0880152516001600160a01b031660e08701525115156101008601525115156101208501525115156101408401525180516001600160801b031661016084015260208101516001600160801b0316610180840152604001516001600160801b03166101a0830152516101c082016101c090526101e082016105839161298b565b5f8752610e35565b634e487b7160e01b5f52604160045260245ffd5b346102b05760203660031901126102b05760043567ffffffffffffffff81116102b05761101e90369060040161295a565b9061102761392e565b5f915b80831061103357005b61103e838284612e4d565b359261104861392e565b835f52600a60205260ff600160405f20015460a81c16156113d857835f52600a60205260ff600160405f20015460a01c165f1461109257836315efa0f360e11b5f5260045260245ffd5b909192805f52600a60205260405f205460f81c6113c6576110c7815f52600a6020526001600160a01b0360405f205416331490565b156113b0576110d5816136d1565b90805f52600a6020526110ed600260405f2001612edc565b916001600160801b038351166001600160801b038216101561139c57815f52600a60205260ff60405f205460f01c161561138857806001600160801b03602081611141948188511603169501511690612aed565b5f828152600a6020526040902080547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b179055916001600160801b038316908115611363575b825f52600a602052600360405f20016001600160801b0382166fffffffffffffffffffffffffffffffff19825416179055825f52600a6020526001600160a01b0360405f205416835f5260036020526001600160a01b0360405f20541694845f52600a60205285827f5edb27d6c1a327513b90a792050debf074b7194444885e3144d4decc5caaaa5061125c6001600160a01b03600160405f2001541694611234888588614663565b604080518b81526001600160801b03808b166020830152909216908201529081906060820190565b0390a47ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051868152a160ff6112a6866001600160a01b03165f52600960205260405f2090565b54166112bc575b5050505050600101919061102a565b60405193630d4af11f60e31b855260048501526024840152604483015260648201526020816084815f865af190811561060457630d4af11f60e31b916001600160e01b0319915f91611335575b50160361131957808080806112ad565b636ade251160e01b5f526001600160a01b03602491166004525ffd5b611356915060203d811161135c575b61134e8183612a79565b810190613690565b87611309565b503d611344565b825f52600a602052600160405f2001600160a01b60ff60a01b1982541617905561118b565b50635dd950cb60e11b5f526024906004525ffd5b506308aca53f60e21b5f526024906004525ffd5b632082501160e01b5f526004523360245260445ffd5b63d0a172b360e01b5f5260045260245ffd5b8363699d2de960e01b5f526024906004525ffd5b346102b05760203660031901126102b05760043561140861392e565b805f52600a60205260ff600160405f20015460a81c16156103235761142c81613760565b611435816129fd565b6004810361145057506315efa0f360e11b5f5260045260245ffd5b611459816129fd565b60038103611474575063d0a172b360e01b5f5260045260245ffd5b600290611480816129fd565b14611559576114a3815f52600a6020526001600160a01b0360405f205416331490565b1561153a57805f52600a60205260ff60405f205460f01c1615611528576020817ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce7925f52600a825260405f2060ff60f01b19815416905560405190807f0eb069207093cd3e51cd1370d2d369770057fbe29947e577e5fb428c6c6fc78f5f80a28152a1005b635dd950cb60e11b5f5260045260245ffd5b632082501160e01b5f526004526001600160a01b03331660245260445ffd5b6308aca53f60e21b5f5260045260245ffd5b346102b05760203660031901126102b0576004356001600160a01b0381168091036102b0576001600160a01b035f5416338103611646575060085490806001600160a01b03198316176008556001600160a01b036040519216825260208201527fa2548bd4b805e907c1558a47b5858324fe8bb4a2e1ddfca647eecbf65610eebc60403392a26007545f1981019081116116325760407f6bd5c950a8d8df17f772f5af37cb3655737899cbf903264b9795592da439661c91815190600182526020820152a1005b634e487b7160e01b5f52601160045260245ffd5b6331b339a960e21b5f526004523360245260445ffd5b346102b05760203660031901126102b0576116756128e0565b5f546001600160a01b03811633810361164657506001600160a01b036001600160a01b0319921691829116175f55337fbdd36143ee09de60bdefca70680e0f71189b2ed7acee364b53917ad433fdaf805f80a3005b346102b05760203660031901126102b0576001600160a01b036116eb6128e0565b168015611708575f526004602052602060405f2054604051908152f35b7f89c62b64000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b346102b05760203660031901126102b05760206117526004356136b0565b6001600160a01b0360405191168152f35b346102b05760203660031901126102b05760043561177f612ea6565b50805f52600a60205260ff600160405f20015460a81c1615610323575f908152600a6020526040908190205481519064ffffffffff60c882901c81169160a01c166117c983612a40565b825260208201526117ef8251809264ffffffffff60208092828151168552015116910152565bf35b346102b05760203660031901126102b05760043567ffffffffffffffff81116102b0578036036101206003198201126102b05761182c61392e565b60c482013590602219018112156102b057810160048101359067ffffffffffffffff82116102b05760240160608202360381136102b05761186e913691612d69565b9081519161187b83612d51565b926118896040519485612a79565b808452601f1961189882612d51565b015f5b818110611a8b57505064ffffffffff4216916001600160801b036118be82613988565b51511667ffffffffffffffff60206118d584613988565b5101511664ffffffffff8060406118eb86613988565b51015116860116906040519261190084612a24565b83526020830152604082015261191586613988565b5261191f85613988565b5060015b8281106119f75750505061193982600401612e85565b9261194660248401612e85565b9261195360448201612e71565b916064820135936001600160a01b0385168095036102b0576020966119ef966119af966001600160801b036119e4976001600160a01b0361199660848a01612e99565b94816119a460a48c01612e99565b976040519d8e612a07565b168c52168c8b0152166040890152606088015215156080870152151560a086015260c085015260e084015260e4369101612e06565b6101008201526139a9565b604051908152f35b806001600160801b03611a0c60019385613995565b51511667ffffffffffffffff6020611a248487613995565b5101511664ffffffffff806040611a3e5f1987018d613995565b51015116816040611a4f878a613995565b5101511601169060405192611a6384612a24565b835260208301526040820152611a798289613995565b52611a848188613995565b5001611923565b602090611a96612ebe565b8282890101520161189b565b346102b05760403660031901126102b05760043567ffffffffffffffff81116102b057611ad390369060040161295a565b9060243567ffffffffffffffff81116102b057611af490369060040161295a565b9091611afe61392e565b818403611b6a575f5b848110611b1057005b80611b64611b216001938886612e4d565b35611b2d838987612e4d565b355f5260036020526001600160a01b0360405f205416611b56611b5185898b612e4d565b612e71565b91611b5f61392e565b6133e2565b01611b07565b50827fa5ed43e6000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b346102b05760203660031901126102b057600435805f52600a60205260ff600160405f20015460a81c1615610323576103b9602091614159565b346102b05760203660031901126102b057600435805f52600a60205260ff600160405f20015460a81c1615610323575f611c0d82613760565b600581101561099457600203611c2b575b6020906040519015158152f35b505f52600a602052602060ff60405f205460f01c16611c1e565b346102b0575f3660031901126102b05760206001600160a01b0360085416604051908152f35b346102b05760203660031901126102b057600435611c8761392e565b805f52600a60205260ff600160405f20015460a81c161561032357805f52600a60205260ff600160405f20015460a01c1615611e0457611cc6816140e7565b156113b057805f5260036020526001600160a01b0360405f205416151580611dfd575b80611de0575b611dce577ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051838152a1805f5260036020526001600160a01b0360405f2054168015908115611d97575b825f52600360205260405f206001600160a01b03198154169055825f827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8280a450611d8557005b637e27328960e01b5f5260045260245ffd5b611db6835f52600560205260405f206001600160a01b03198154169055565b805f52600460205260405f205f198154019055611d3d565b634274c8e160e11b5f5260045260245ffd5b50805f52600a60205260ff600160405f20015460b01c1615611cef565b505f611ce9565b7f6121eb36000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b346102b0576102ae611e4036612920565b9060405192611e50602085612a79565b5f8452612f95565b346102b05760203660031901126102b057600435805f52600a60205260ff600160405f20015460a81c1615610323575f52600a602052602060ff600160405f20015460a01c166040519015158152f35b346102b05760203660031901126102b057600435611ec461392e565b805f52600a60205260ff600160405f20015460a81c161561032357805f52600a60205260ff600160405f20015460a01c165f14611f0d576315efa0f360e11b5f5260045260245ffd5b805f52600a60205260405f205460f81c6113c657611f3f815f52600a6020526001600160a01b0360405f205416331490565b1561153a57611f4d816136d1565b90805f52600a602052611f65600260405f2001612edc565b916001600160801b038351166001600160801b03821610156121ae57815f52600a60205260ff60405f205460f01c161561219b57806001600160801b03602081611fb9948188511603169501511690612aed565b5f828152600a6020526040902080547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b179055916001600160801b038316908115612176575b825f52600a602052600360405f20016001600160801b0382166fffffffffffffffffffffffffffffffff19825416179055825f52600a6020526001600160a01b0360405f205416835f5260036020526001600160a01b0360405f20541694845f52600a60205285827f5edb27d6c1a327513b90a792050debf074b7194444885e3144d4decc5caaaa506120ac6001600160a01b03600160405f2001541694611234888588614663565b0390a47ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051868152a1845f52600960205260ff60405f2054166120ef57005b60405193630d4af11f60e31b855260048501526024840152604483015260648201526020816084815f865af190811561060457630d4af11f60e31b916001600160e01b0319915f91612157575b50160361214557005b636ade251160e01b5f5260045260245ffd5b612170915060203d60201161135c5761134e8183612a79565b8461213c565b825f52600a602052600160405f2001600160a01b60ff60a01b19825416179055612003565b50635dd950cb60e11b5f5260045260245ffd5b506308aca53f60e21b5f5260045260245ffd5b346102b05760203660031901126102b0576121da6128e0565b6001600160a01b035f54169033820361231957806001600160a01b03913b156122ed57166040516301ffc9a760e01b81527ff8ee98d3000000000000000000000000000000000000000000000000000000006004820152602081602481855afa908115610604575f916122be575b501561229357805f52600960205260405f20600160ff198254161790556040519081527fb4378d4e289cb3f40f4f75a99c9cafa76e3df1c4dc31309babc23dc91bd7280160203392a2005b7ff1dc125d000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b6122e0915060203d6020116122e6575b6122d88183612a79565b810190612e35565b82612248565b503d6122ce565b7f295097c8000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b506331b339a960e21b5f526004523360245260445ffd5b346102b05760203660031901126102b05760043567ffffffffffffffff81116102b05761014060031982360301126102b05761236a61392e565b6040519061237782612a07565b6123838160040161290c565b82526123916024820161290c565b60208301526123a260448201612ab7565b604083015260648101356001600160a01b03811681036102b05760608301526123cd608482016129f0565b60808301526123de60a482016129f0565b60a08301526123ef60c48201612d3f565b60c083015260e481013567ffffffffffffffff81116102b057810191366023840112156102b0576119e46119ef926124336020953690602460048201359101612d69565b60e0840152610104369101612e06565b346102b05760203660031901126102b0576001600160a01b036124646128e0565b165f526009602052602060ff60405f2054166040519015158152f35b346102b0576102ae61249136612920565b91612b0d565b346102b0575f3660031901126102b0576020600754604051908152f35b346102b05760203660031901126102b057600435805f52600a60205260ff600160405f20015460a81c1615610323576124ec90613760565b600581101561099457806020911590811561250d575b506040519015158152f35b600191501482612502565b346102b05760203660031901126102b057600435805f52600a60205260ff600160405f20015460a81c1615610323576020905f90805f52600a835260ff60405f205460f01c16806125ad575b61257b575b506001600160801b0360405191168152f35b6125a79150805f52600a83526125a16001600160801b03600260405f20015416916136d1565b90612aed565b82612569565b50805f52600a835260ff600160405f20015460a01c1615612564565b346102b05760403660031901126102b0576125e26128e0565b6024356125ee816136b0565b331515806126bb575b80612688575b61265c5781906001600160a01b0380851691167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9255f80a45f5260056020526001600160a01b0360405f2091166001600160a01b03198254161790555f80f35b7fa9fbf51f000000000000000000000000000000000000000000000000000000005f523360045260245ffd5b506001600160a01b0381165f52600660205260405f206001600160a01b0333165f5260205260ff60405f205416156125fd565b50336001600160a01b03821614156125f7565b346102b05760203660031901126102b0576020611752600435612acb565b346102b0575f3660031901126102b0576040515f6001548060011c9060018116801561279d575b602083108114610b9757828552908115610b73575060011461273f5761058383610b0181850382612a79565b91905060015f527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6915f905b80821061278357509091508101602001610b01610af1565b91926001816020925483858801015201910190929161276b565b91607f1691612713565b346102b0575f3660031901126102b057602060405167016345785d8a00008152f35b346102b05760203660031901126102b057600435906001600160e01b031982168092036102b057817f490649060000000000000000000000000000000000000000000000000000000060209314908115612825575b5015158152f35b7f80ac58cd00000000000000000000000000000000000000000000000000000000811491508115612870575b811561285f575b508361281e565b6301ffc9a760e01b91501483612858565b7f5b5e139f0000000000000000000000000000000000000000000000000000000081149150612851565b5f5b8381106128ab5750505f910152565b818101518382015260200161289c565b906020916128d48151809281855285808601910161289a565b601f01601f1916010190565b600435906001600160a01b03821682036102b057565b602435906001600160a01b03821682036102b057565b35906001600160a01b03821682036102b057565b60609060031901126102b0576004356001600160a01b03811681036102b057906024356001600160a01b03811681036102b0579060443590565b9181601f840112156102b05782359167ffffffffffffffff83116102b0576020808501948460051b0101116102b057565b90602080835192838152019201905f5b8181106129a85750505090565b9091926020606060019264ffffffffff604088516001600160801b03815116845267ffffffffffffffff8682015116868501520151166040820152019401910191909161299b565b359081151582036102b057565b6005111561099457565b610120810190811067ffffffffffffffff821117610fd957604052565b6060810190811067ffffffffffffffff821117610fd957604052565b6040810190811067ffffffffffffffff821117610fd957604052565b610140810190811067ffffffffffffffff821117610fd957604052565b90601f8019910116810190811067ffffffffffffffff821117610fd957604052565b67ffffffffffffffff8111610fd957601f01601f191660200190565b35906001600160801b03821682036102b057565b612ad4816136b0565b505f5260056020526001600160a01b0360405f20541690565b906001600160801b03809116911603906001600160801b03821161163257565b91906001600160a01b031680156106f057815f5260036020526001600160a01b0360405f205416151580612d37575b80612d1a575b612d07577ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051848152a1815f5260036020526001600160a01b0360405f20541692823315159283612c52575b6001600160a01b03935085612c1b575b805f52600460205260405f2060018154019055815f52600360205260405f20816001600160a01b0319825416179055857fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef5f80a416808303612c0357505050565b6364283d7b60e01b5f5260045260245260445260645ffd5b612c3a825f52600560205260405f206001600160a01b03198154169055565b855f52600460205260405f205f198154019055612ba2565b9192905080612cb0575b15612c6957828291612b92565b8284612c8157637e27328960e01b5f5260045260245ffd5b7f177e802f000000000000000000000000000000000000000000000000000000005f523360045260245260445ffd5b503384148015612cde575b80612c5c5750825f526005602052336001600160a01b0360405f20541614612c5c565b50835f52600660205260405f206001600160a01b0333165f5260205260ff60405f205416612cbb565b50634274c8e160e11b5f5260045260245ffd5b50815f52600a60205260ff600160405f20015460b01c1615612b42565b506001612b3c565b359064ffffffffff821682036102b057565b67ffffffffffffffff8111610fd95760051b60200190565b929192612d7582612d51565b93612d836040519586612a79565b60606020868581520193028201918183116102b057925b828410612da75750505050565b6060848303126102b05760405190612dbe82612a24565b612dc785612ab7565b825260208501359067ffffffffffffffff821682036102b05782602092836060950152612df660408801612d3f565b6040820152815201930192612d9a565b91908260409103126102b057604051612e1e81612a40565b6020808294612e2c8161290c565b84520135910152565b908160209103126102b0575180151581036102b05790565b9190811015612e5d5760051b0190565b634e487b7160e01b5f52603260045260245ffd5b356001600160801b03811681036102b05790565b356001600160a01b03811681036102b05790565b3580151581036102b05790565b60405190612eb382612a40565b5f6020838281520152565b60405190612ecb82612a24565b5f6040838281528260208201520152565b90604051612ee981612a24565b60406001600160801b03600183958054838116865260801c6020860152015416910152565b908154612f1a81612d51565b92612f286040519485612a79565b81845260208401905f5260205f205f915b838310612f465750505050565b600160208192604051612f5881612a24565b64ffffffffff86546001600160801b038116835267ffffffffffffffff8160801c168584015260c01c166040820152815201920192019190612f39565b90612fa1838284612b0d565b803b612fae575b50505050565b602091612ff46001600160a01b03809316956040519586948594630a85bd0160e11b865233600487015216602485015260448401526080606484015260848301906128bb565b03815f865af15f9181613064575b5061303057506130106141da565b8051908161302b5782633250574960e11b5f5260045260245ffd5b602001fd5b6001600160e01b0319630a85bd0160e11b91160361305257505f808080612fa8565b633250574960e11b5f5260045260245ffd5b61307e91925060203d60201161135c5761134e8183612a79565b905f613002565b9061308e61392e565b815f52600a60205260ff600160405f20015460a81c161561073357815f52600a60205260ff600160405f20015460a01c166133cf576001600160a01b03811680156133a3576001600160801b03841691821561337757835f5260036020526001600160a01b0360405f205416948583141580613367575b613333576001600160801b0361311a86614209565b16808511613300575061313f90855f52600a602052600260405f20015460801c61422f565b5f858152600a6020526040902060020180546001600160801b031660809290921b6fffffffffffffffffffffffffffffffff191691909117815561318290612edc565b6001600160801b036131a68160208401511692826040818351169201511690612aed565b1611156132ce575b835f52600a6020526131d2836001600160a01b03600160405f200154169283614663565b81847f40b88e5c41c5a97ffb7b6ef88a0a2d505aa0c634cf8a0275cb236ea7dd87ed4d6020604051878152a47ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051858152a183331415806132b8575b61323c5750505050565b604051926392b9102b60e01b84526004840152336024840152604483015260648201526020816084815f865af1908115610604576392b9102b60e01b916001600160e01b0319915f91613299575b50160361214557808080612fa8565b6132b2915060203d60201161135c5761134e8183612a79565b5f61328a565b50835f52600960205260ff60405f205416613232565b5f848152600a6020526040902060018101805460ff60a01b1916600160a01b179055805460ff60f01b191690556131ae565b84867f066920d7000000000000000000000000000000000000000000000000000000005f5260045260245260445260645ffd5b82857f6a166406000000000000000000000000000000000000000000000000000000005f526004523360245260445260645ffd5b50613371856140e7565b15613105565b837fb2ae7633000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b827f2da33e5b000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b506315efa0f360e11b5f5260045260245ffd5b90815f52600a60205260ff600160405f20015460a81c161561073357815f52600a60205260ff600160405f20015460a01c166133cf576001600160a01b03811680156133a3576001600160801b03841691821561337757835f5260036020526001600160a01b0360405f205416948583141580613680575b613333576001600160801b0361346f86614209565b16808511613300575061349490855f52600a602052600260405f20015460801c61422f565b5f858152600a6020526040902060020180546001600160801b031660809290921b6fffffffffffffffffffffffffffffffff19169190911781556134d790612edc565b6001600160801b036134fb8160208401511692826040818351169201511690612aed565b16111561364e575b835f52600a602052613527836001600160a01b03600160405f200154169283614663565b81847f40b88e5c41c5a97ffb7b6ef88a0a2d505aa0c634cf8a0275cb236ea7dd87ed4d6020604051878152a47ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051858152a18333141580613629575b6135915750505050565b604051926392b9102b60e01b84526004840152336024840152604483015260648201526020816084815f865af1908115610604576392b9102b60e01b916001600160e01b0319915f9161360a575b5016036135ee57808080612fa8565b6001600160a01b0390636ade251160e01b5f521660045260245ffd5b613623915060203d60201161135c5761134e8183612a79565b5f6135df565b5060ff613647856001600160a01b03165f52600960205260405f2090565b5416613587565b5f848152600a6020526040902060018101805460ff60a01b1916600160a01b179055805460ff60f01b19169055613503565b5061368a856140e7565b1561345a565b908160209103126102b057516001600160e01b0319811681036102b05790565b805f5260036020526001600160a01b0360405f205416908115611d85575090565b64ffffffffff4216815f52600a6020528064ffffffffff60405f205460a01c16101561375a57815f52600a60205264ffffffffff60405f205460c81c16111561373f57805f52600b602052600160405f2054115f14613736576137339061430f565b90565b6137339061424f565b5f52600a6020526001600160801b03600260405f2001541690565b50505f90565b805f52600a60205260ff600160405f20015460a01c165f146137825750600490565b805f52600a60205260405f205460f81c6137ee57805f52600a60205264ffffffffff60405f205460a01c1642106137e9576137bc816136d1565b905f52600a6020526001600160801b0380600260405f200154169116105f146137e457600190565b600290565b505f90565b50600390565b90805f5260036020526001600160a01b0360405f20541615158061391c575b806138ff575b611dce577ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051838152a1805f5260036020526001600160a01b038060405f20541692836138c8575b16806138b0575b815f52600360205260405f20816001600160a01b0319825416179055827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef5f80a490565b805f52600460205260405f206001815401905561386c565b6138e7835f52600560205260405f206001600160a01b03198154169055565b835f52600460205260405f205f198154019055613865565b50805f52600a60205260ff600160405f20015460b01c1615613819565b506001600160a01b0382161515613813565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016300361396057565b7fa1c0d6e5000000000000000000000000000000000000000000000000000000005f5260045ffd5b805115612e5d5760200190565b8051821015612e5d5760209160051b010190565b906139cb6001600160801b036040840151166020610100850151015190614525565b916001600160801b038351169060e08101519160c082019264ffffffffff84511682156140bf578015614097578151801561406f577f00000000000000000000000000000000000000000000000000000000000000008111614044575064ffffffffff6040613a3984613988565b5101511681101561400057505f905f905f81515f905b808210613f78575050505064ffffffffff80421691169081811015613f4a5750506001600160801b031690818103613f1c57505060075493845f52600a60205260405f20916001600160801b038251166001600160801b036002850191166fffffffffffffffffffffffffffffffff198254161790556001600160a01b03606082015116916001600160a01b036001850193166001600160a01b031984541617835560808201948551151560ff60f01b197eff00000000000000000000000000000000000000000000000000000000000087549260f01b169116178555835493750100000000000000000000000000000000000000000060a08501957fffffffffffffffffff0000ffffffffffffffffffffffffffffffffffffffffff76ff000000000000000000000000000000000000000000008851151560b01b169116171790556001600160a01b0380845116166001600160a01b03198654161785555184549060e0840151917fffff00000000000000000000ffffffffffffffffffffffffffffffffffffffff78ffffffffff00000000000000000000000000000000000000007dffffffffff000000000000000000000000000000000000000000000000006040613c248751975f19890190613995565b51015160c81b169360a01b169116171785555f5b818110613e0e575050600187016007556001600160a01b0360208301511680156106f057613c6e886001600160a01b03926137f4565b16613de2578682613cbc6001600160a01b0360607f33eb09bbf19ea3fb22c760d5164234f8bf62ca07dcf5a437ad389e96b0bd6443960151166001600160801b038551169030903390614602565b6001600160801b0360208401511680613db2575b506001600160a01b0381511694613da7613d896001600160a01b03602085015116986001600160a01b036060860151169a511515935115156001600160a01b0361010060e088015193549764ffffffffff60405199613d2e8b612a40565b818160a01c168b5260c81c1660208a015201515116946001600160801b0360206040519a8b9a8b5233828c01528281511660408c01520151166060890152608088015260a087015261014060c087015261014086019061298b565b9260e085019064ffffffffff60208092828151168552015116910152565b6101208301520390a4565b613ddc906001600160a01b036060840151166001600160a01b036101008501515116903390614602565b5f613cd0565b7f73c6ac6e000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b885f52600b60205260405f2090613e298160e0870151613995565b51825468010000000000000000811015610fd95760018101808555811015612e5d576001935f5260205f2001906001600160801b0380825116166fffffffffffffffffffffffffffffffff198354161782556020810151907fffffff00000000000000000000000000ffffffffffffffffffffffffffffffff7cffffffffff000000000000000000000000000000000000000000000000604077ffffffffffffffff0000000000000000000000000000000086549560801b1693847fffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff8716178755015160c01b1692161717905501613c38565b7f0fa99c11000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b7f879842de000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b9193509193613f9c906001600160801b03613f938588613995565b5151169061422f565b9364ffffffffff806040613fb08685613995565b51015116941680851115613fcc57506001849301909291613a4f565b8490847f4b90504d000000000000000000000000000000000000000000000000000000005f5260045260245260445260645ffd5b64ffffffffff604061401184613988565b51015116907f93c315e6000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b7f099c714b000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f5fbab612000000000000000000000000000000000000000000000000000000005f5260045ffd5b7feaa6c316000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f779f8816000000000000000000000000000000000000000000000000000000005f5260045ffd5b805f5260036020526001600160a01b0360405f2054169081331491821561412d575b508115614114575090565b90506001600160a01b036141283392612acb565b161490565b9091505f52600660205260405f206001600160a01b0333165f5260205260ff60405f205416905f614109565b805f52600a602052614170600260405f2001612edc565b90805f52600a60205260ff600160405f20015460a01c165f1461419e5750602001516001600160801b031690565b90815f52600a60205260405f205460f81c6141bd5750613733906136d1565b61373391506001600160801b036040818351169201511690612aed565b3d15614204573d906141eb82612a9b565b916141f96040519384612a79565b82523d5f602084013e565b606090565b6137339061421681614159565b905f52600a602052600260405f20015460801c90612aed565b906001600160801b03809116911601906001600160801b03821161163257565b5f818152600a60205260409020546142869064ffffffffff60a082901c811660c89290921c811682900381169142821603166146b3565b90805f52600b60205260405f20805415612e5d575f526142db67ffffffffffffffff60205f205460801c1692825f52600a6020526142d66001600160801b03600260405f20015416948592614793565b614806565b9182136142f857506142f46001600160801b03916148e1565b1690565b90505f52600a602052600260405f20015460801c90565b9064ffffffffff421691805f52600a60205260405f20906040519061433382612a5c565b6101206143c660028554956001600160a01b0387168652602086019664ffffffffff8160a01c16885264ffffffffff8160c81c16604088015260ff8160f01c161515606088015260f81c1515608087015260ff60018201546001600160a01b03811660a0890152818160a01c16151560c0890152818160a81c16151560e089015260b01c16151561010087015201612edc565b92019182525f52600b6020526143de60405f20612f0e565b915f9264ffffffffff60406143f283613988565b510151168664ffffffffff5f925b16106144e6578161447764ffffffffff9697988784816001600160801b0361442f6142d69861447c9b9a613995565b5151169a8b9867ffffffffffffffff602061444a868b613995565b510151169782604061445c8784613995565b5101511694806144c9575050511680925b03169203166146b3565b614793565b91821361449d5750906001600160801b0361449781936148e1565b16011690565b6001600160801b03915060209051015116806001600160801b038316115f146144c4575090565b905090565b60409250906144db915f190190613995565b51015116809261446d565b936001600160801b03600191816144fd8886613995565b51511601169401958064ffffffffff8060406145198b87613995565b51015116989298614400565b91909160405161453481612a40565b5f81525f6020820152926001600160801b0382169081156145e55767016345785d8a000081116145ae576145706001600160801b0391836154fe565b166020850191818352111561459a576001600160801b03918261459592511690612aed565b168252565b634e487b7160e01b5f52600160045260245ffd5b7ffc8a7df4000000000000000000000000000000000000000000000000000000005f5260045267016345785d8a000060245260445ffd5b50505090506040516145f681612a40565b5f81525f602082015290565b9091926001600160a01b036146619481604051957f23b872dd00000000000000000000000000000000000000000000000000000000602088015216602486015216604484015260648301526064825261465c608483612a79565b614916565b565b614661926001600160a01b03604051937fa9059cbb00000000000000000000000000000000000000000000000000000000602086015216602484015260448301526044825261465c606483612a79565b600160ff1b81148015614786575b61475e575f811215614755576146e5815f035b5f84121561474e57835f039061499b565b917f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff831161471f575f199118131561471a5790565b5f0390565b907fd49c26b3000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b839061499b565b6146e5816146d4565b7f9fe2b450000000000000000000000000000000000000000000000000000000005f5260045ffd5b50600160ff1b82146146c1565b806147ad57506147a957670de0b6b3a764000090565b5f90565b90670de0b6b3a764000082146147f857806147d0575050670de0b6b3a764000090565b670de0b6b3a764000081146147f4576147ef906142d661373393614aa1565b614bf8565b5090565b5050670de0b6b3a764000090565b600160ff1b811480156148d4575b6148ac575f8112156148a357614838815f035b5f84121561489c57835f03906154fe565b917f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff831161486d575f199118131561471a5790565b907f120b5b43000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b83906154fe565b61483881614827565b7fa6070c25000000000000000000000000000000000000000000000000000000005f5260045ffd5b50600160ff1b8214614814565b5f81126148eb5790565b7f2463f3d5000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b5f806001600160a01b0361493f93169360208151910182865af16149386141da565b90836155ac565b8051908115159182614980575b50506149555750565b7f5274afe7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b6149939250602080918301019101612e35565b155f8061494c565b5f19670de0b6b3a7640000820991670de0b6b3a7640000820291828085109403938085039414614a665781841015614a2c57670de0b6b3a7640000829109600182190182168092046002816003021880820260020302808202600203028082026002030280820260020302808202600203028091026002030293600183805f03040190848311900302920304170290565b7f63a05778000000000000000000000000000000000000000000000000000000005f52600452670de0b6b3a764000060245260445260645ffd5b5091508115614a73570490565b634e487b7160e01b5f52601260045260245ffd5b8015614a73576ec097ce7bc90715b34b9f10000000000590565b805f811315614bcd57670de0b6b3a76400008112614bad57506001905b670de0b6b3a764000081056001600160801b03811160071b90811c67ffffffffffffffff811160061b90811c63ffffffff811160051b90811c61ffff811160041b90811c9060ff821160031b91821c92600f841160021b93841c94600160038711811b96871c119617171717171717670de0b6b3a7640000810291811d90670de0b6b3a76400008214614b9a57506706f05b59d3b20000905b5f8213614b645750500290565b80670de0b6b3a764000091020590671bc16d674ec80000821215614b8c575b60011d90614b57565b809192019160011d90614b83565b9050670de0b6b3a7640000929150020290565b5f1991508015614a73576ec097ce7bc90715b34b9f100000000005614abe565b7f059b101b000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b5f811215614c255768033dd1780914b971141981126137e957614c1c905f03614bf8565b61373390614a87565b680a688906bd8affffff81136154d357670de0b6b3a76400009060401b057780000000000000000000000000000000000000000000000067ff00000000000000821661539e575b670de0b6b3a76400009066ff000000000000831661528e575b65ff00000000008316615186575b64ff000000008316615086575b63ff0000008316614f8e575b62ff00008316614e9e575b61ff008316614db6575b60ff8316614cd6575b029060401c60bf031c90565b60808316614da3575b60408316614d90575b60208316614d7d575b60108316614d6a575b60088316614d57575b60048316614d44575b60028316614d31575b6001831615614cca57680100000000000000010260401c614cca565b680100000000000000010260401c614d15565b680100000000000000030260401c614d0c565b680100000000000000060260401c614d03565b6801000000000000000b0260401c614cfa565b680100000000000000160260401c614cf1565b6801000000000000002c0260401c614ce8565b680100000000000000590260401c614cdf565b6180008316614e8b575b6140008316614e78575b6120008316614e65575b6110008316614e52575b6108008316614e3f575b6104008316614e2c575b6102008316614e19575b610100831615614cc157680100000000000000b10260401c614cc1565b680100000000000001630260401c614dfc565b680100000000000002c60260401c614df2565b6801000000000000058c0260401c614de8565b68010000000000000b170260401c614dde565b6801000000000000162e0260401c614dd4565b68010000000000002c5d0260401c614dca565b680100000000000058b90260401c614dc0565b628000008316614f7b575b624000008316614f68575b622000008316614f55575b621000008316614f42575b620800008316614f2f575b620400008316614f1c575b620200008316614f09575b62010000831615614cb7576801000000000000b1720260401c614cb7565b680100000000000162e40260401c614eeb565b6801000000000002c5c80260401c614ee0565b68010000000000058b910260401c614ed5565b680100000000000b17210260401c614eca565b68010000000000162e430260401c614ebf565b680100000000002c5c860260401c614eb4565b6801000000000058b90c0260401c614ea9565b63800000008316615073575b63400000008316615060575b6320000000831661504d575b6310000000831661503a575b63080000008316615027575b63040000008316615014575b63020000008316615001575b6301000000831615614cac5768010000000000b172180260401c614cac565b6801000000000162e4300260401c614fe2565b68010000000002c5c8600260401c614fd6565b680100000000058b90c00260401c614fca565b6801000000000b17217f0260401c614fbe565b680100000000162e42ff0260401c614fb2565b6801000000002c5c85fe0260401c614fa6565b68010000000058b90bfc0260401c614f9a565b6480000000008316615173575b6440000000008316615160575b642000000000831661514d575b641000000000831661513a575b6408000000008316615127575b6404000000008316615114575b6402000000008316615101575b640100000000831615614ca057680100000000b17217f80260401c614ca0565b68010000000162e42ff10260401c6150e1565b680100000002c5c85fe30260401c6150d4565b6801000000058b90bfce0260401c6150c7565b68010000000b17217fbb0260401c6150ba565b6801000000162e42fff00260401c6150ad565b68010000002c5c8601cc0260401c6150a0565b680100000058b90c0b490260401c615093565b65800000000000831661527b575b654000000000008316615268575b652000000000008316615255575b651000000000008316615242575b65080000000000831661522f575b65040000000000831661521c575b650200000000008316615209575b65010000000000831615614c93576801000000b1721835510260401c614c93565b680100000162e430e5a20260401c6151e8565b6801000002c5c863b73f0260401c6151da565b68010000058b90cf1e6e0260401c6151cc565b680100000b1721bcfc9a0260401c6151be565b68010000162e43f4f8310260401c6151b0565b680100002c5c89d5ec6d0260401c6151a2565b6801000058b91b5bc9ae0260401c615194565b6680000000000000831661538b575b66400000000000008316615378575b66200000000000008316615365575b66100000000000008316615352575b6608000000000000831661533f575b6604000000000000831661532c575b66020000000000008316615319575b6601000000000000831615614c855768010000b17255775c040260401c614c85565b6801000162e525ee05470260401c6152f7565b68010002c5cc37da94920260401c6152e8565b680100058ba01fb9f96d0260401c6152d9565b6801000b175effdc76ba0260401c6152ca565b680100162f3904051fa10260401c6152bb565b6801002c605e2e8cec500260401c6152ac565b68010058c86da1c09ea20260401c61529d565b67800000000000000082166154b4575b670de0b6b3a76400009067400000000000000083166154a1575b672000000000000000831661548e575b671000000000000000831661547b575b6708000000000000008316615468575b6704000000000000008316615455575b6702000000000000008316615442575b670100000000000000831661542f575b9050614c6c565b680100b1afa5abcbed610260401c615428565b68010163da9fb33356d80260401c615418565b680102c9a3e778060ee70260401c615408565b6801059b0d31585743ae0260401c6153f8565b68010b5586cf9890f62a0260401c6153e8565b6801172b83c7d517adce0260401c6153d8565b6801306fe0a31b7152df0260401c6153c8565b5077b504f333f9de6484800000000000000000000000000000006153ae565b7f0360d028000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b9091905f198382098382029182808310920391808303921461559b57670de0b6b3a764000082101561556b577faccb18165bd6fe31ae1cf318dc5b51eee0e1ba569b88cd74c1773b91fac106699394670de0b6b3a7640000910990828211900360ee1b910360121c170290565b84907f5173648d000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b5050670de0b6b3a764000090049150565b906155e957508051156155c157805190602001fd5b7f1425ea42000000000000000000000000000000000000000000000000000000005f5260045ffd5b8151158061562f575b6155fa575090565b6001600160a01b03907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b156155f256fea164736f6c634300081a000a"; - bytes public constant BYTECODE_LOCKUP_LINEAR = - hex"60a0604052346103bc57614b716040813803918261001c816103c0565b9384928339810103126103bc5780516001600160a01b03811691908290036103bc57602001516001600160a01b038116908190036103bc5761005e60406103c0565b91601983527f5361626c696572204c6f636b7570204c696e656172204e465400000000000000602084015261009360406103c0565b600e81526d29a0a116a627a1a5aaa816a624a760911b60208201523060805283519092906001600160401b0381116102cd57600154600181811c911680156103b2575b60208210146102af57601f811161034f575b50602094601f82116001146102ec579481929394955f926102e1575b50508160011b915f199060031b1c1916176001555b82516001600160401b0381116102cd57600254600181811c911680156102c3575b60208210146102af57601f811161024c575b506020601f82116001146101e957819293945f926101de575b50508160011b915f199060031b1c1916176002555b5f80546001600160a01b031990811684178255600880549091169290921790915560405191907fbdd36143ee09de60bdefca70680e0f71189b2ed7acee364b53917ad433fdaf808180a3600160075561478b90816103e6823960805181613adf0152f35b015190505f80610165565b601f1982169060025f52805f20915f5b8181106102345750958360019596971061021c575b505050811b0160025561017a565b01515f1960f88460031b161c191690555f808061020e565b9192602060018192868b0151815501940192016101f9565b60025f527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace601f830160051c810191602084106102a5575b601f0160051c01905b81811061029a575061014c565b5f815560010161028d565b9091508190610284565b634e487b7160e01b5f52602260045260245ffd5b90607f169061013a565b634e487b7160e01b5f52604160045260245ffd5b015190505f80610104565b601f1982169560015f52805f20915f5b8881106103375750836001959697981061031f575b505050811b01600155610119565b01515f1960f88460031b161c191690555f8080610311565b919260206001819286850151815501940192016102fc565b60015f527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6601f830160051c810191602084106103a8575b601f0160051c01905b81811061039d57506100e8565b5f8155600101610390565b9091508190610387565b90607f16906100d6565b5f80fd5b6040519190601f01601f191682016001600160401b038111838210176102cd5760405256fe6080806040526004361015610012575f80fd5b5f3560e01c90816301ffc9a71461313b57508063027b67441461311957806306fdde031461305e578063081812fc14613040578063095ea7b314612f3b5780631400ecec14612e8a5780631c1cdd4c14612e265780631e99d56914612e0957806323b872dd14612df2578063303acc8514612db5578063406887cb14612c4657806340e58ee51461296f578063425d30dd1461291f57806342842e0e146128f657806342966c6814612732578063442675701461270c5780634857501f1461269b5780634869e12d146126615780634cc55e11146122ba57806353b157271461218f57806357404b12146120c95780636352211e1461209a5780636d0cee751461209a57806370a082311461203057806375829def14611fc2578063780a82c814611f765780637cad6cd114611e995780637de6b1db14611d4c5780638659c27014611994578063894e9a0d146116ac5780638f69b9931461162c5780639067b677146115dd57806395d89b41146114d5578063a22cb46514611421578063a80fc071146113d0578063ab167ccc1461125f578063ad35efd414611200578063b2564569146111b0578063b88d4fde14611126578063b8a3be66146110f1578063b971302a146110a3578063bc2be1be14611054578063c156a11d14610c3e578063c87b56dd14610b33578063d4dbd20b14610ae2578063d511609f14610a97578063d975dfed14610a4c578063e985e9c5146109f3578063ea5ead19146106ae578063eac8f5b81461065d578063f590c17614610601578063f851a440146105dc5763fdd46d6014610263575f80fd5b346105d85760603660031901126105d85760043561027f613268565b906102886133ca565b610290613ad5565b815f52600a60205260ff600160405f20015460a81c16156105c557815f52600a60205260ff600160405f20015460a01c166105b2576001600160a01b03831690811561059f576001600160801b031690811561058c57825f5260036020526001600160a01b0360405f20541693848214158061057c575b610561576001600160801b0361031c8561431f565b168084116105475750835f52600a60205282600260405f20015460801c016001600160801b0381116105335761037b90855f52600a602052600260405f2001906001600160801b036001600160801b031983549260801b169116179055565b835f52600a602052610392600260405f20016136ae565b6001600160801b036103b68160208401511692826040818351169201511690613402565b161115610501575b835f52600a6020526103e2836001600160a01b03600160405f200154169283614345565b81847f40b88e5c41c5a97ffb7b6ef88a0a2d505aa0c634cf8a0275cb236ea7dd87ed4d6020604051878152a47ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051858152a183331415806104eb575b61044857005b604051926392b9102b60e01b84526004840152336024840152604483015260648201526020816084815f865af19081156104e0576392b9102b60e01b916001600160e01b0319915f916104b1575b50160361049f57005b636ade251160e01b5f5260045260245ffd5b6104d3915060203d6020116104d9575b6104cb818361338c565b8101906137e4565b5f610496565b503d6104c1565b6040513d5f823e3d90fd5b50835f52600960205260ff60405f205416610442565b5f848152600a6020526040902060018101805460ff60a01b1916600160a01b179055805460ff60f01b191690556103be565b634e487b7160e01b5f52601160045260245ffd5b838563066920d760e01b5f5260045260245260445260645ffd5b508263350b320360e11b5f526004523360245260445260645ffd5b5061058684613b2f565b15610307565b8263b2ae763360e01b5f5260045260245ffd5b82632da33e5b60e01b5f5260045260245ffd5b506315efa0f360e11b5f5260045260245ffd5b5063699d2de960e01b5f5260045260245ffd5b5f80fd5b346105d8575f3660031901126105d85760206001600160a01b035f5416604051908152f35b346105d85760203660031901126105d857600435805f52600a60205260ff600160405f20015460a81c161561064b575f52600a602052602060405f205460f81c6040519015158152f35b63699d2de960e01b5f5260045260245ffd5b346105d85760203660031901126105d857600435805f52600a60205260ff600160405f20015460a81c161561064b575f52600a60205260206001600160a01b03600160405f20015416604051908152f35b346105d85760403660031901126105d8576004356106ca613268565b906106d48161431f565b906106dd613ad5565b805f52600a60205260ff600160405f20015460a81c161561064b57805f52600a60205260ff600160405f20015460a01c166109e1576001600160a01b0383169182156109ce576001600160801b03169182156109bb57815f5260036020526001600160a01b0360405f2054169384821415806109ab575b610990576001600160801b036107698461431f565b168085116109765750825f52600a60205283600260405f20015460801c016001600160801b038111610533576107c890845f52600a602052600260405f2001906001600160801b036001600160801b031983549260801b169116179055565b825f52600a6020526107df600260405f20016136ae565b6001600160801b036108038160208401511692826040818351169201511690613402565b161115610944575b825f52600a60205261082f846001600160a01b03600160405f200154169283614345565b81837f40b88e5c41c5a97ffb7b6ef88a0a2d505aa0c634cf8a0275cb236ea7dd87ed4d6020604051888152a47ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051848152a1833314158061092e575b61089f575b602083604051908152f35b604051916392b9102b60e01b8352600483015233602483015260448201528160648201526020816084815f875af19081156104e0576392b9102b60e01b916001600160e01b0319915f9161090f575b5016036108fc578180610894565b50636ade251160e01b5f5260045260245ffd5b610928915060203d6020116104d9576104cb818361338c565b856108ee565b50835f52600960205260ff60405f20541661088f565b5f838152600a6020526040902060018101805460ff60a01b1916600160a01b179055805460ff60f01b1916905561080b565b848463066920d760e01b5f5260045260245260445260645ffd5b509063350b320360e11b5f526004523360245260445260645ffd5b506109b583613b2f565b15610754565b5063b2ae763360e01b5f5260045260245ffd5b50632da33e5b60e01b5f5260045260245ffd5b6315efa0f360e11b5f5260045260245ffd5b346105d85760403660031901126105d857610a0c613252565b6001600160a01b03610a1c613268565b91165f5260066020526001600160a01b0360405f2091165f52602052602060ff60405f2054166040519015158152f35b346105d85760203660031901126105d857600435805f52600a60205260ff600160405f20015460a81c161561064b57610a8660209161431f565b6001600160801b0360405191168152f35b346105d85760203660031901126105d857600435805f52600a60205260ff600160405f20015460a81c161561064b575f52600a6020526020600260405f20015460801c604051908152f35b346105d85760203660031901126105d857600435805f52600a60205260ff600160405f20015460a81c161561064b575f52600a60205260206001600160801b03600360405f20015416604051908152f35b346105d85760203660031901126105d857600435610b5081613804565b505f6001600160a01b0360085416916044604051809481937fe9dc637500000000000000000000000000000000000000000000000000000000835230600484015260248301525afa80156104e0575f90610bc1575b610bbd9060405191829160208352602083019061322d565b0390f35b503d805f833e610bd1818361338c565b8101906020818303126105d85780519067ffffffffffffffff82116105d857019080601f830112156105d857815191610c09836133ae565b91610c17604051938461338c565b838352602084830101116105d857610bbd92610c39916020808501910161320c565b610ba5565b346105d85760403660031901126105d857600435610c5a613268565b610c62613ad5565b815f52600a60205260ff600160405f20015460a81c16156105c557815f5260036020526001600160a01b0360405f2054169081330361103d576001600160801b03610cac8461431f565b169081158015610d35575b506001600160a01b03811615610d2257610cd9846001600160a01b039261399b565b169182610cf35783637e27328960e01b5f5260045260245ffd5b8084918403610d0757602083604051908152f35b9091506364283d7b60e01b5f5260045260245260445260645ffd5b633250574960e11b5f525f60045260245ffd5b610d3d613ad5565b845f52600a60205260ff600160405f20015460a81c161561102a57845f52600a60205260ff600160405f20015460a01c1661101757831561100457610ff157835f5260036020526001600160a01b0360405f2054168084141580610fe1575b610fc6576001600160801b03610db18661431f565b16808411610fac5750845f52600a60205282600260405f20015460801c016001600160801b03811161053357610e1090865f52600a602052600260405f2001906001600160801b036001600160801b031983549260801b169116179055565b845f52600a602052610e27600260405f20016136ae565b6001600160801b03610e4b8160208401511692826040818351169201511690613402565b161115610f7a575b845f52600a6020526001600160a01b03600160405f20015416610e77848683614345565b84867f40b88e5c41c5a97ffb7b6ef88a0a2d505aa0c634cf8a0275cb236ea7dd87ed4d6020604051888152a47ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051878152a18033141580610f64575b15610cb7576040516392b9102b60e01b81528560048201523360248201528460448201528360648201526020816084815f865af19081156104e0576392b9102b60e01b916001600160e01b0319915f91610f45575b501614610cb757636ade251160e01b5f5260045260245ffd5b610f5e915060203d6020116104d9576104cb818361338c565b88610f2c565b50805f52600960205260ff60405f205416610ed7565b5f858152600a6020526040902060018101805460ff60a01b1916600160a01b179055805460ff60f01b19169055610e53565b838663066920d760e01b5f5260045260245260445260645ffd5b838563350b320360e11b5f526004523360245260445260645ffd5b50610feb85613b2f565b15610d9c565b8363b2ae763360e01b5f5260045260245ffd5b84632da33e5b60e01b5f5260045260245ffd5b846315efa0f360e11b5f5260045260245ffd5b8463699d2de960e01b5f5260045260245ffd5b82632082501160e01b5f526004523360245260445ffd5b346105d85760203660031901126105d857600435805f52600a60205260ff600160405f20015460a81c161561064b575f52600a602052602064ffffffffff60405f205460a01c16604051908152f35b346105d85760203660031901126105d857600435805f52600a60205260ff600160405f20015460a81c161561064b575f52600a60205260206001600160a01b0360405f205416604051908152f35b346105d85760203660031901126105d8576004355f52600a602052602060ff600160405f20015460a81c166040519015158152f35b346105d85760803660031901126105d85761113f613252565b611147613268565b6064359167ffffffffffffffff83116105d857366023840112156105d857826004013591611174836133ae565b92611182604051948561338c565b80845236602482870101116105d8576020815f9260246111ae98018388013785010152604435916136f4565b005b346105d85760203660031901126105d857600435805f52600a60205260ff600160405f20015460a81c161561064b575f52600a602052602060ff600160405f20015460b01c166040519015158152f35b346105d85760203660031901126105d857600435805f52600a60205260ff600160405f20015460a81c161561064b5761123890613907565b604051600582101561124b576020918152f35b634e487b7160e01b5f52602160045260245ffd5b346105d8576101403660031901126105d857611279613ad5565b611281613690565b64ffffffffff421680825264ffffffffff61129a6136e0565b166113b5575b60e43564ffffffffff811681036105d85764ffffffffff9101166040820152600435906001600160a01b038216918281036105d857506024356001600160a01b038116908181036105d857506044356001600160801b038116908181036105d857506064356001600160a01b0381168091036105d85760843591821515928381036105d8575060a43593841515948581036105d8575060405196611343886132e9565b8752602087015260408601526060850152608084015260a083015260c08201526040610103193601126105d8576040519061137d82613370565b61010435906001600160a01b03821682036105d857826113ad9260209452610124358482015260e0820152613c25565b604051908152f35b64ffffffffff6113c36136e0565b82011660208301526112a0565b346105d85760203660031901126105d857600435805f52600a60205260ff600160405f20015460a81c161561064b575f52600a60205260206001600160801b03600260405f20015416604051908152f35b346105d85760403660031901126105d85761143a613252565b602435908115158092036105d8576001600160a01b03169081156114a957335f52600660205260405f20825f5260205260405f2060ff1981541660ff83161790556040519081527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3160203392a3005b507f5b08ba18000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b346105d8575f3660031901126105d8576040515f6002548060011c906001811680156115d3575b6020831081146115bf5782855290811561159b575060011461153d575b610bbd836115298185038261338c565b60405191829160208352602083019061322d565b91905060025f527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace915f905b80821061158157509091508101602001611529611519565b919260018160209254838588010152019101909291611569565b60ff191660208086019190915291151560051b840190910191506115299050611519565b634e487b7160e01b5f52602260045260245ffd5b91607f16916114fc565b346105d85760203660031901126105d857600435805f52600a60205260ff600160405f20015460a81c161561064b575f52600a602052602064ffffffffff60405f205460c81c16604051908152f35b346105d85760203660031901126105d857600435805f52600a60205260ff600160405f20015460a81c161561064b5761166490613907565b60058110158061124b57600282149081156116a0575b811561168e575b6020826040519015158152f35b905061124b5760046020911482611681565b5050600381145f61167a565b346105d85760203660031901126105d8576004355f6101606040516116d081613336565b8281528260208201528260408201528260608201528260808201528260a08201528260c08201528260e08201528261010082015282610120820152611713613690565b6101408201520152805f52600a60205260ff600160405f20015460a81c161561064b57805f52600a60205260405f2060405161174e81613353565b81546001600160a01b0381168252602082019364ffffffffff8260a01c168552604083019364ffffffffff8360c81c1685526060840160ff8460f01c1615158152608085019360f81c1515845260018201549360a08601956001600160a01b038616875260c081019560ff8160a01c16151587526117ed600260e084019660ff8460a81c161515885260ff61010086019460b01c1615158452016136ae565b61012083019081526117fe87613907565b600581101561124b5760021461198c575b5197516001600160a01b031692865f52600b60205260405f205464ffffffffff16995164ffffffffff1694511515915115159751151595511515965f52600360205260405f20546001600160a01b031692516001600160a01b03169a5164ffffffffff16905115159260405161188481613336565b8c81526020810191825260408101928352606081019384526080810194855260a0810195865260c0810196875260e0810197885261010081019889526101208101998a5261014081019a8b52610160019a8b526040519b8c52516001600160a01b031660208c01525164ffffffffff1660408b015251151560608a01525115156080890152516001600160a01b031660a08801525164ffffffffff1660c087015251151560e08601525115156101008501525115156101208401525180516001600160801b031661014084015260208101516001600160801b0316610160840152604001516001600160801b03166101808301525164ffffffffff166101a08201526101c090f35b5f855261180f565b346105d85760203660031901126105d85760043567ffffffffffffffff81116105d8576119c59036906004016132b8565b906119ce613ad5565b5f915b8083106119da57005b6119e583828461366c565b35926119ef613ad5565b835f52600a60205260ff600160405f20015460a81c1615611d3957835f52600a60205260ff600160405f20015460a01c165f14611a3957836315efa0f360e11b5f5260045260245ffd5b909192805f52600a60205260405f205460f81c611d2757611a6e815f52600a6020526001600160a01b0360405f205416331490565b15611d1157611a7c81613825565b90805f52600a602052611a94600260405f20016136ae565b916001600160801b038351166001600160801b0382161015611cfe57815f52600a60205260ff60405f205460f01c1615611ceb57806001600160801b03602081611ae8948188511603169501511690613402565b5f828152600a6020526040902080547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b179055916001600160801b038316908115611cc6575b825f52600a602052600360405f20016001600160801b0382166001600160801b0319825416179055825f52600a6020526001600160a01b0360405f205416835f5260036020526001600160a01b0360405f20541694845f52600a60205285827f5edb27d6c1a327513b90a792050debf074b7194444885e3144d4decc5caaaa50611bfa6001600160a01b03600160405f2001541694611bd2888588614345565b604080518b81526001600160801b03808b166020830152909216908201529081906060820190565b0390a47ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051868152a1845f52600960205260ff60405f205416611c4b575b505050505060010191906119d1565b60405193630d4af11f60e31b855260048501526024840152604483015260648201526020816084815f865af19081156104e057630d4af11f60e31b916001600160e01b0319915f91611ca8575b50160361049f5780808080611c3c565b611cc0915060203d81116104d9576104cb818361338c565b87611c98565b825f52600a602052600160405f2001600160a01b60ff60a01b19825416179055611b32565b50635dd950cb60e11b5f5260045260245ffd5b506308aca53f60e21b5f5260045260245ffd5b632082501160e01b5f526004523360245260445ffd5b63d0a172b360e01b5f5260045260245ffd5b8363699d2de960e01b5f5260045260245ffd5b346105d85760203660031901126105d857600435611d68613ad5565b805f52600a60205260ff600160405f20015460a81c161561064b57611d8c81613907565b600581101561124b5760048103611db057506315efa0f360e11b5f5260045260245ffd5b60038103611dcb575063d0a172b360e01b5f5260045260245ffd5b600214611e8757611df0815f52600a6020526001600160a01b0360405f205416331490565b15611d1157805f52600a60205260ff60405f205460f01c1615611e75576020817ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce7925f52600a825260405f2060ff60f01b19815416905560405190807f0eb069207093cd3e51cd1370d2d369770057fbe29947e577e5fb428c6c6fc78f5f80a28152a1005b635dd950cb60e11b5f5260045260245ffd5b6308aca53f60e21b5f5260045260245ffd5b346105d85760203660031901126105d8576004356001600160a01b0381168091036105d8576001600160a01b035f5416338103611f60575060085490806001600160a01b03198316176008556001600160a01b036040519216825260208201527fa2548bd4b805e907c1558a47b5858324fe8bb4a2e1ddfca647eecbf65610eebc60403392a26007545f1981019081116105335760407f6bd5c950a8d8df17f772f5af37cb3655737899cbf903264b9795592da439661c91815190600182526020820152a1005b6331b339a960e21b5f526004523360245260445ffd5b346105d85760203660031901126105d857600435805f52600a60205260ff600160405f20015460a81c161561064b575f52600b602052602064ffffffffff60405f205416604051908152f35b346105d85760203660031901126105d857611fdb613252565b5f546001600160a01b038116338103611f6057506001600160a01b036001600160a01b0319921691829116175f55337fbdd36143ee09de60bdefca70680e0f71189b2ed7acee364b53917ad433fdaf805f80a3005b346105d85760203660031901126105d8576001600160a01b03612051613252565b16801561206e575f526004602052602060405f2054604051908152f35b7f89c62b64000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b346105d85760203660031901126105d85760206120b8600435613804565b6001600160a01b0360405191168152f35b346105d85760203660031901126105d8576004356120e5613690565b50805f52600a60205260ff600160405f20015460a81c161561064b57806060915f52600a60205264ffffffffff60405f205460a01c1690805f52600b60205264ffffffffff60405f205416905f52600a60205264ffffffffff60405f205460c81c1690604051926121558461331a565b83526020830152604082015261218d604051809264ffffffffff60408092828151168552826020820151166020860152015116910152565bf35b346105d8576101603660031901126105d8576121a9613ad5565b6040516121b5816132e9565b6121bd613252565b81526121c7613268565b60208201526121d46133ca565b60408201526064356001600160a01b03811681036105d857606082015260843580151581036105d857608082015260a43580151581036105d85760a082015260603660c31901126105d85760405161222b8161331a565b60c43564ffffffffff811681036105d857815260e43564ffffffffff811681036105d85760208201526101043564ffffffffff811681036105d857604082015260c08201526040610123193601126105d8576040519061228a82613370565b61012435906001600160a01b03821682036105d857826113ad9260209452610144358482015260e0820152613c25565b346105d85760403660031901126105d85760043567ffffffffffffffff81116105d8576122eb9036906004016132b8565b60243567ffffffffffffffff81116105d85761230b9036906004016132b8565b612316939193613ad5565b808303612632575f5b83811061232857005b61233381858561366c565b3561233f82868661366c565b355f5260036020526001600160a01b0360405f2054169061236183858961366c565b356001600160801b038116908181036105d8575061237d613ad5565b815f52600a60205260ff600160405f20015460a81c16156105c557815f52600a60205260ff600160405f20015460a01c166105b25782156109ce5780156109bb57815f5260036020526001600160a01b0360405f205416928381141580612622575b612608576001600160801b036123f48461431f565b168083116125ee5750825f52600a60205281600260405f20015460801c016001600160801b0381116105335761245390845f52600a602052600260405f2001906001600160801b036001600160801b031983549260801b169116179055565b825f52600a60205261246a600260405f20016136ae565b6001600160801b0361248e8160208401511692826040818351169201511690613402565b1611156125bc575b825f52600a6020526001600160a01b03600160405f200154166124ba838383614345565b81847f40b88e5c41c5a97ffb7b6ef88a0a2d505aa0c634cf8a0275cb236ea7dd87ed4d6020604051878152a47ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051858152a183331415806125a6575b61252b575b5050505060010161231f565b604051926392b9102b60e01b84526004840152336024840152604483015260648201526020816084815f865af19081156104e0576392b9102b60e01b916001600160e01b0319915f91612588575b50160361049f5780808061251f565b6125a0915060203d81116104d9576104cb818361338c565b89612579565b50835f52600960205260ff60405f20541661251a565b5f838152600a6020526040902060018101805460ff60a01b1916600160a01b179055805460ff60f01b19169055612496565b828463066920d760e01b5f5260045260245260445260645ffd5b8263350b320360e11b5f526004523360245260445260645ffd5b5061262c83613b2f565b156123df565b827fa5ed43e6000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b346105d85760203660031901126105d857600435805f52600a60205260ff600160405f20015460a81c161561064b57610a86602091613ba1565b346105d85760203660031901126105d857600435805f52600a60205260ff600160405f20015460a81c161561064b575f6126d482613907565b600581101561124b576002036126f2575b6020906040519015158152f35b505f52600a602052602060ff60405f205460f01c166126e5565b346105d8575f3660031901126105d85760206001600160a01b0360085416604051908152f35b346105d85760203660031901126105d85760043561274e613ad5565b805f52600a60205260ff600160405f20015460a81c161561064b57805f52600a60205260ff600160405f20015460a01c16156128cb5761278d81613b2f565b15611d1157805f5260036020526001600160a01b0360405f2054161515806128c4575b806128a7575b612895577ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051838152a1805f5260036020526001600160a01b0360405f205416801590811561285e575b825f52600360205260405f206001600160a01b03198154169055825f827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8280a45061284c57005b637e27328960e01b5f5260045260245ffd5b61287d835f52600560205260405f206001600160a01b03198154169055565b805f52600460205260405f205f198154019055612804565b634274c8e160e11b5f5260045260245ffd5b50805f52600a60205260ff600160405f20015460b01c16156127b6565b505f6127b0565b7f6121eb36000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b346105d8576111ae6129073661327e565b906040519261291760208561338c565b5f84526136f4565b346105d85760203660031901126105d857600435805f52600a60205260ff600160405f20015460a81c161561064b575f52600a602052602060ff600160405f20015460a01c166040519015158152f35b346105d85760203660031901126105d85760043561298b613ad5565b805f52600a60205260ff600160405f20015460a81c161561064b57805f52600a60205260ff600160405f20015460a01c165f146129d4576315efa0f360e11b5f5260045260245ffd5b805f52600a60205260405f205460f81c611d2757612a06815f52600a6020526001600160a01b0360405f205416331490565b15611d1157612a1481613825565b90805f52600a602052612a2c600260405f20016136ae565b916001600160801b038351166001600160801b0382161015611cfe57815f52600a60205260ff60405f205460f01c1615611ceb57806001600160801b03602081612a80948188511603169501511690613402565b5f828152600a6020526040902080547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b179055916001600160801b038316908115612c21575b825f52600a602052600360405f20016001600160801b0382166001600160801b0319825416179055825f52600a6020526001600160a01b0360405f205416835f5260036020526001600160a01b0360405f20541694845f52600a60205285827f5edb27d6c1a327513b90a792050debf074b7194444885e3144d4decc5caaaa50612b6a6001600160a01b03600160405f2001541694611bd2888588614345565b0390a47ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051868152a1845f52600960205260ff60405f205416612bad57005b60405193630d4af11f60e31b855260048501526024840152604483015260648201526020816084815f865af19081156104e057630d4af11f60e31b916001600160e01b0319915f91612c025750160361049f57005b612c1b915060203d6020116104d9576104cb818361338c565b84610496565b825f52600a602052600160405f2001600160a01b60ff60a01b19825416179055612aca565b346105d85760203660031901126105d857612c5f613252565b6001600160a01b035f541690338203612d9e57806001600160a01b03913b15612d7257166040516301ffc9a760e01b81527ff8ee98d3000000000000000000000000000000000000000000000000000000006004820152602081602481855afa9081156104e0575f91612d43575b5015612d1857805f52600960205260405f20600160ff198254161790556040519081527fb4378d4e289cb3f40f4f75a99c9cafa76e3df1c4dc31309babc23dc91bd7280160203392a2005b7ff1dc125d000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b612d65915060203d602011612d6b575b612d5d818361338c565b810190613654565b82612ccd565b503d612d53565b7f295097c8000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b506331b339a960e21b5f526004523360245260445ffd5b346105d85760203660031901126105d8576001600160a01b03612dd6613252565b165f526009602052602060ff60405f2054166040519015158152f35b346105d8576111ae612e033661327e565b91613422565b346105d8575f3660031901126105d8576020600754604051908152f35b346105d85760203660031901126105d857600435805f52600a60205260ff600160405f20015460a81c161561064b57612e5e90613907565b600581101561124b578060209115908115612e7f575b506040519015158152f35b600191501482612e74565b346105d85760203660031901126105d857600435805f52600a60205260ff600160405f20015460a81c161561064b576020905f90805f52600a835260ff60405f205460f01c1680612f1f575b612eed575b506001600160801b0360405191168152f35b612f199150805f52600a8352612f136001600160801b03600260405f2001541691613825565b90613402565b82612edb565b50805f52600a835260ff600160405f20015460a01c1615612ed6565b346105d85760403660031901126105d857612f54613252565b602435612f6081613804565b3315158061302d575b80612ffa575b612fce5781906001600160a01b0380851691167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9255f80a45f5260056020526001600160a01b0360405f2091166001600160a01b03198254161790555f80f35b7fa9fbf51f000000000000000000000000000000000000000000000000000000005f523360045260245ffd5b506001600160a01b0381165f52600660205260405f206001600160a01b0333165f5260205260ff60405f20541615612f6f565b50336001600160a01b0382161415612f69565b346105d85760203660031901126105d85760206120b86004356133e0565b346105d8575f3660031901126105d8576040515f6001548060011c9060018116801561310f575b6020831081146115bf5782855290811561159b57506001146130b157610bbd836115298185038261338c565b91905060015f527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6915f905b8082106130f557509091508101602001611529611519565b9192600181602092548385880101520191019092916130dd565b91607f1691613085565b346105d8575f3660031901126105d857602060405167016345785d8a00008152f35b346105d85760203660031901126105d857600435906001600160e01b031982168092036105d857817f490649060000000000000000000000000000000000000000000000000000000060209314908115613197575b5015158152f35b7f80ac58cd000000000000000000000000000000000000000000000000000000008114915081156131e2575b81156131d1575b5083613190565b6301ffc9a760e01b915014836131ca565b7f5b5e139f00000000000000000000000000000000000000000000000000000000811491506131c3565b5f5b83811061321d5750505f910152565b818101518382015260200161320e565b906020916132468151809281855285808601910161320c565b601f01601f1916010190565b600435906001600160a01b03821682036105d857565b602435906001600160a01b03821682036105d857565b60609060031901126105d8576004356001600160a01b03811681036105d857906024356001600160a01b03811681036105d8579060443590565b9181601f840112156105d85782359167ffffffffffffffff83116105d8576020808501948460051b0101116105d857565b610100810190811067ffffffffffffffff82111761330657604052565b634e487b7160e01b5f52604160045260245ffd5b6060810190811067ffffffffffffffff82111761330657604052565b610180810190811067ffffffffffffffff82111761330657604052565b610140810190811067ffffffffffffffff82111761330657604052565b6040810190811067ffffffffffffffff82111761330657604052565b90601f8019910116810190811067ffffffffffffffff82111761330657604052565b67ffffffffffffffff811161330657601f01601f191660200190565b604435906001600160801b03821682036105d857565b6133e981613804565b505f5260056020526001600160a01b0360405f20541690565b906001600160801b03809116911603906001600160801b03821161053357565b91906001600160a01b03168015610d2257815f5260036020526001600160a01b0360405f20541615158061364c575b8061362f575b61361c577ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051848152a1815f5260036020526001600160a01b0360405f20541692823315159283613567575b6001600160a01b03935085613530575b805f52600460205260405f2060018154019055815f52600360205260405f20816001600160a01b0319825416179055857fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef5f80a41680830361351857505050565b6364283d7b60e01b5f5260045260245260445260645ffd5b61354f825f52600560205260405f206001600160a01b03198154169055565b855f52600460205260405f205f1981540190556134b7565b91929050806135c5575b1561357e578282916134a7565b828461359657637e27328960e01b5f5260045260245ffd5b7f177e802f000000000000000000000000000000000000000000000000000000005f523360045260245260445ffd5b5033841480156135f3575b806135715750825f526005602052336001600160a01b0360405f20541614613571565b50835f52600660205260405f206001600160a01b0333165f5260205260ff60405f2054166135d0565b50634274c8e160e11b5f5260045260245ffd5b50815f52600a60205260ff600160405f20015460b01c1615613457565b506001613451565b908160209103126105d8575180151581036105d85790565b919081101561367c5760051b0190565b634e487b7160e01b5f52603260045260245ffd5b6040519061369d8261331a565b5f6040838281528260208201520152565b906040516136bb8161331a565b60406001600160801b03600183958054838116865260801c6020860152015416910152565b60c43564ffffffffff811681036105d85790565b90613700838284613422565b803b61370d575b50505050565b6020916137536001600160a01b03809316956040519586948594630a85bd0160e11b8652336004870152166024850152604484015260806064840152608483019061322d565b03815f865af15f91816137c3575b5061378f575061376f6142f0565b8051908161378a5782633250574960e11b5f5260045260245ffd5b602001fd5b6001600160e01b0319630a85bd0160e11b9116036137b157505f808080613707565b633250574960e11b5f5260045260245ffd5b6137dd91925060203d6020116104d9576104cb818361338c565b905f613761565b908160209103126105d857516001600160e01b0319811681036105d85790565b805f5260036020526001600160a01b0360405f20541690811561284c575090565b805f52600b60205264ffffffffff60405f205416815f52600a60205264ffffffffff60405f205460a01c1690421080156138fd575b6138f757815f52600a60205264ffffffffff60405f205460c81c1690814210156138da578061388c92039042036144d3565b815f52600a6020526138af6001600160801b03600260405f2001541680926145bf565b9081116138c4576001600160801b0391501690565b505f52600a602052600260405f20015460801c90565b50505f52600a6020526001600160801b03600260405f2001541690565b50505f90565b504281101561385a565b805f52600a60205260ff600160405f20015460a01c165f146139295750600490565b805f52600a60205260405f205460f81c61399557805f52600a60205264ffffffffff60405f205460a01c1642106139905761396381613825565b905f52600a6020526001600160801b0380600260405f200154169116105f1461398b57600190565b600290565b505f90565b50600390565b90805f5260036020526001600160a01b0360405f205416151580613ac3575b80613aa6575b612895577ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051838152a1805f5260036020526001600160a01b038060405f2054169283613a6f575b1680613a57575b815f52600360205260405f20816001600160a01b0319825416179055827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef5f80a490565b805f52600460205260405f2060018154019055613a13565b613a8e835f52600560205260405f206001600160a01b03198154169055565b835f52600460205260405f205f198154019055613a0c565b50805f52600a60205260ff600160405f20015460b01c16156139c0565b506001600160a01b03821615156139ba565b6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163003613b0757565b7fa1c0d6e5000000000000000000000000000000000000000000000000000000005f5260045ffd5b805f5260036020526001600160a01b0360405f20541690813314918215613b75575b508115613b5c575090565b90506001600160a01b03613b7033926133e0565b161490565b9091505f52600660205260405f206001600160a01b0333165f5260205260ff60405f205416905f613b51565b805f52600a602052613bb8600260405f20016136ae565b90805f52600a60205260ff600160405f20015460a01c165f14613be65750602001516001600160801b031690565b90815f52600a60205260405f205460f81c613c085750613c0590613825565b90565b613c0591506001600160801b036040818351169201511690613402565b90613c466001600160801b03604084015116602060e085015101519061439c565b916001600160801b0383511660c082015190156142c85764ffffffffff815116156142a0576020810164ffffffffff81511680614214575b5050604064ffffffffff82511691019064ffffffffff82511690818110156141e657505064ffffffffff80421691511690818110156141b85750506007549280516001600160801b03169160405192613cd68461331a565b8352602083015f9052604083015f905260608101516001600160a01b03169260c082015190604082015164ffffffffff16946080840195888751151560a087015115159287516001600160a01b0316965164ffffffffff169160405197613d3c89613353565b885260208801928352604088019182526060880190815260808801915f835260a0890196875260c08901935f855260e08a0195600187526101008b019788526101208b01998a525f52600a60205260405f2099516001600160a01b03166001600160a01b03168a546001600160a01b031916178a5551908954905160c81b7dffffffffff00000000000000000000000000000000000000000000000000169160a01b78ffffffffff000000000000000000000000000000000000000016907fffff00000000000000000000ffffffffffffffffffffffffffffffffffffffff161717885551151587549060f01b7eff000000000000000000000000000000000000000000000000000000000000169060ff60f01b191617875551151586549060f81b7fff0000000000000000000000000000000000000000000000000000000000000016907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff161786556001860193516001600160a01b03166001600160a01b031684546001600160a01b03191617845551151583549060a01b74ff0000000000000000000000000000000000000000169060ff60a01b19161783555115159082549051151560b01b76ff00000000000000000000000000000000000000000000169160a81b75ff00000000000000000000000000000000000000000016907fffffffffffffffffff0000ffffffffffffffffffffffffffffffffffffffffff16171790556002820190519081516001600160801b03166001600160801b031681546001600160801b03191617815560208201516001600160801b0316613fbc91906001600160801b036001600160801b031983549260801b169116179055565b604001516001600160801b031690600301906001600160801b031681546001600160801b03191617905560c08101516020015164ffffffffff1680614198575b50600185016007556001600160a01b036020820151168015610d225761402a866001600160a01b039261399b565b1661416c576140556001600160a01b036060830151166001600160801b038451169030903390614479565b7f44cb432df42caa86b7ec73644ab8aec922bc44c71c98fc330addc75b88adbc7c6101408660208501946001600160801b038651168061413d575b506141346001600160a01b03865116956001600160a01b03602082015116976001600160a01b03606083015116995115156001600160801b0360a0840151151592816001600160a01b0360e060c0880151970151511697604051998a523360208b01525116604089015251166060870152608086015260a085015260c084019064ffffffffff60408092828151168552826020820151166020860152015116910152565b610120820152a4565b614166906001600160a01b036060880151166001600160a01b0360e08901515116903390614479565b5f614090565b7f73c6ac6e000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b855f52600b60205260405f209064ffffffffff198254161790555f613ffc565b7f879842de000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b7f9e7fd91f000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b64ffffffffff8351168181101561427257505064ffffffffff90511664ffffffffff60408301511690818110613c7e577f87d966a0000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b7f84fe0623000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b7feaa6c316000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f779f8816000000000000000000000000000000000000000000000000000000005f5260045ffd5b3d1561431a573d90614301826133ae565b9161430f604051938461338c565b82523d5f602084013e565b606090565b613c059061432c81613ba1565b905f52600a602052600260405f20015460801c90613402565b61439a926001600160a01b03604051937fa9059cbb00000000000000000000000000000000000000000000000000000000602086015216602484015260448301526044825261439560648361338c565b61466d565b565b9190916040516143ab81613370565b5f81525f6020820152926001600160801b03821690811561445c5767016345785d8a00008111614425576143e76001600160801b0391836145bf565b1660208501918183521115614411576001600160801b03918261440c92511690613402565b168252565b634e487b7160e01b5f52600160045260245ffd5b7ffc8a7df4000000000000000000000000000000000000000000000000000000005f5260045267016345785d8a000060245260445ffd5b505050905060405161446d81613370565b5f81525f602082015290565b9091926001600160a01b0361439a9481604051957f23b872dd00000000000000000000000000000000000000000000000000000000602088015216602486015216604484015260648301526064825261439560848361338c565b5f19670de0b6b3a7640000820991670de0b6b3a764000082029182808510940393808503941461459e578184101561456457670de0b6b3a7640000829109600182190182168092046002816003021880820260020302808202600203028082026002030280820260020302808202600203028091026002030293600183805f03040190848311900302920304170290565b7f63a05778000000000000000000000000000000000000000000000000000000005f52600452670de0b6b3a764000060245260445260645ffd5b50915081156145ab570490565b634e487b7160e01b5f52601260045260245ffd5b9091905f198382098382029182808310920391808303921461465c57670de0b6b3a764000082101561462c577faccb18165bd6fe31ae1cf318dc5b51eee0e1ba569b88cd74c1773b91fac106699394670de0b6b3a7640000910990828211900360ee1b910360121c170290565b84907f5173648d000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b5050670de0b6b3a764000090049150565b5f806001600160a01b0361469693169360208151910182865af161468f6142f0565b90836146f2565b80519081151591826146d7575b50506146ac5750565b7f5274afe7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b6146ea9250602080918301019101613654565b155f806146a3565b9061472f575080511561470757805190602001fd5b7f1425ea42000000000000000000000000000000000000000000000000000000005f5260045ffd5b81511580614775575b614740575090565b6001600160a01b03907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b1561473856fea164736f6c634300081a000a"; - bytes public constant BYTECODE_LOCKUP_TRANCHED = - hex"60c0604052346103e157614e306060813803918261001c816103e5565b9384928339810103126103e15780516001600160a01b038116908190036103e15760208201516001600160a01b03811692908390036103e1576040015161006360406103e5565b92601b84527f5361626c696572204c6f636b7570205472616e63686564204e46540000000000602085015261009860406103e5565b600e81526d5341422d4c4f434b55502d54524160901b602082015230608052845190946001600160401b0382116102e45760015490600182811c921680156103d7575b60208310146102c65781601f849311610369575b50602090601f8311600114610303575f926102f8575b50508160011b915f199060031b1c1916176001555b83516001600160401b0381116102e457600254600181811c911680156102da575b60208210146102c657601f8111610263575b50602094601f8211600114610200579481929394955f926101f5575b50508160011b915f199060031b1c1916176002555b5f80546001600160a01b031990811685178255600880549091169290921790915560405192907fbdd36143ee09de60bdefca70680e0f71189b2ed7acee364b53917ad433fdaf808180a360a0526001600755614a25908161040b823960805181613e37015260a051818181612fa60152613ee00152f35b015190505f80610169565b601f1982169560025f52805f20915f5b88811061024b57508360019596979810610233575b505050811b0160025561017e565b01515f1960f88460031b161c191690555f8080610225565b91926020600181928685015181550194019201610210565b60025f527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace601f830160051c810191602084106102bc575b601f0160051c01905b8181106102b1575061014d565b5f81556001016102a4565b909150819061029b565b634e487b7160e01b5f52602260045260245ffd5b90607f169061013b565b634e487b7160e01b5f52604160045260245ffd5b015190505f80610105565b60015f9081528281209350601f198516905b8181106103515750908460019594939210610339575b505050811b0160015561011a565b01515f1960f88460031b161c191690555f808061032b565b92936020600181928786015181550195019301610315565b60015f529091507fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6601f840160051c810191602085106103cd575b90601f859493920160051c01905b8181106103bf57506100ef565b5f81558493506001016103b2565b90915081906103a4565b91607f16916100db565b5f80fd5b6040519190601f01601f191682016001600160401b038111838210176102e45760405256fe6080806040526004361015610012575f80fd5b5f3560e01c90816301ffc9a71461331257508063027b6744146132f057806306fdde0314613235578063081812fc14613217578063095ea7b3146131125780631400ecec146130615780631c1cdd4c14612ffd5780631e99d56914612fe057806323b872dd14612fc95780632fe4304114612f8f578063303acc8514612f5257806332fbe22b14612df5578063406887cb14612c8657806340e58ee5146129af578063425d30dd1461295f57806342842e0e1461293657806342966c6814612772578063442675701461274c5780634857501f146126db5780634869e12d146126a15780634cc55e11146122fb57806357404b121461226d5780636352211e1461223e5780636d0cee751461223e57806370a08231146121d457806375829def146121665780637cad6cd1146120755780637de6b1db14611f285780637f5799f914611ecf5780638659c27014611b17578063894e9a0d146117d8578063897f362b1461150d5780638f69b9931461148d5780639067b6771461143e57806395d89b4114611336578063a22cb46514611282578063a80fc07114611231578063ad35efd4146111d2578063b256456914611182578063b88d4fde146110f8578063b8a3be66146110c3578063b971302a14611075578063bc2be1be14611026578063c156a11d14610c0a578063c87b56dd14610aff578063d4dbd20b14610aae578063d511609f14610a63578063d975dfed14610a18578063e985e9c5146109bf578063ea5ead1914610692578063eac8f5b814610641578063f590c176146105e5578063f851a440146105c05763fdd46d601461026e575f80fd5b346105bc5760603660031901126105bc5760043561028a61343f565b90604435916001600160801b038316908184036105bc576102a9613e2d565b825f52600a60205260ff600160405f20015460a81c16156105a957825f52600a60205260ff600160405f20015460a01c16610596576001600160a01b03811690811561058357821561057057835f5260036020526001600160a01b0360405f205416948583141580610560575b610545576001600160801b0361032b86614685565b1680851161052b575061035090855f52600a602052600260405f20015460801c6146ab565b5f858152600a6020526040902060020180546001600160801b031660809290921b6001600160801b03191691909117815561038a906139d3565b6001600160801b036103ae8160208401511692826040818351169201511690613616565b1611156104f9575b835f52600a6020526103da836001600160a01b03600160405f200154169283614809565b81847f40b88e5c41c5a97ffb7b6ef88a0a2d505aa0c634cf8a0275cb236ea7dd87ed4d6020604051878152a47ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051858152a183331415806104e3575b61044057005b604051926392b9102b60e01b84526004840152336024840152604483015260648201526020816084815f865af19081156104d8576392b9102b60e01b916001600160e01b0319915f916104a9575b50160361049757005b636ade251160e01b5f5260045260245ffd5b6104cb915060203d6020116104d1575b6104c381836135a2565b810190613b16565b5f61048e565b503d6104b9565b6040513d5f823e3d90fd5b50835f52600960205260ff60405f20541661043a565b5f848152600a6020526040902060018101805460ff60a01b1916600160a01b179055805460ff60f01b191690556103b6565b848663066920d760e01b5f5260045260245260445260645ffd5b828563350b320360e11b5f526004523360245260445260645ffd5b5061056a85614560565b15610316565b8363b2ae763360e01b5f5260045260245ffd5b83632da33e5b60e01b5f5260045260245ffd5b826315efa0f360e11b5f5260045260245ffd5b8263699d2de960e01b5f5260045260245ffd5b5f80fd5b346105bc575f3660031901126105bc5760206001600160a01b035f5416604051908152f35b346105bc5760203660031901126105bc57600435805f52600a60205260ff600160405f20015460a81c161561062f575f52600a602052602060405f205460f81c6040519015158152f35b63699d2de960e01b5f5260045260245ffd5b346105bc5760203660031901126105bc57600435805f52600a60205260ff600160405f20015460a81c161561062f575f52600a60205260206001600160a01b03600160405f20015416604051908152f35b346105bc5760403660031901126105bc576004356106ae61343f565b6106b782614685565b916106c0613e2d565b805f52600a60205260ff600160405f20015460a81c161561062f57805f52600a60205260ff600160405f20015460a01c166109ad576001600160a01b038216801561099a576001600160801b03841692831561098757825f5260036020526001600160a01b0360405f205416948583141580610977575b61095c576001600160801b0361074c85614685565b16808611610942575061077190845f52600a602052600260405f20015460801c6146ab565b5f848152600a6020526040902060020180546001600160801b031660809290921b6001600160801b0319169190911781556107ab906139d3565b6001600160801b036107cf8160208401511692826040818351169201511690613616565b161115610910575b825f52600a6020526107fb846001600160a01b03600160405f200154169283614809565b81837f40b88e5c41c5a97ffb7b6ef88a0a2d505aa0c634cf8a0275cb236ea7dd87ed4d6020604051888152a47ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051848152a183331415806108fa575b61086b575b602083604051908152f35b604051916392b9102b60e01b8352600483015233602483015260448201528160648201526020816084815f875af19081156104d8576392b9102b60e01b916001600160e01b0319915f916108db575b5016036108c8578180610860565b50636ade251160e01b5f5260045260245ffd5b6108f4915060203d6020116104d1576104c381836135a2565b856108ba565b50835f52600960205260ff60405f20541661085b565b5f838152600a6020526040902060018101805460ff60a01b1916600160a01b179055805460ff60f01b191690556107d7565b858563066920d760e01b5f5260045260245260445260645ffd5b828463350b320360e11b5f526004523360245260445260645ffd5b5061098184614560565b15610737565b8263b2ae763360e01b5f5260045260245ffd5b50632da33e5b60e01b5f5260045260245ffd5b6315efa0f360e11b5f5260045260245ffd5b346105bc5760403660031901126105bc576109d8613429565b6001600160a01b036109e861343f565b91165f5260066020526001600160a01b0360405f2091165f52602052602060ff60405f2054166040519015158152f35b346105bc5760203660031901126105bc57600435805f52600a60205260ff600160405f20015460a81c161561062f57610a52602091614685565b6001600160801b0360405191168152f35b346105bc5760203660031901126105bc57600435805f52600a60205260ff600160405f20015460a81c161561062f575f52600a6020526020600260405f20015460801c604051908152f35b346105bc5760203660031901126105bc57600435805f52600a60205260ff600160405f20015460a81c161561062f575f52600a60205260206001600160801b03600360405f20015416604051908152f35b346105bc5760203660031901126105bc57600435610b1c81613b36565b505f6001600160a01b0360085416916044604051809481937fe9dc637500000000000000000000000000000000000000000000000000000000835230600484015260248301525afa80156104d8575f90610b8d575b610b8990604051918291602083526020830190613404565b0390f35b503d805f833e610b9d81836135a2565b8101906020818303126105bc5780519067ffffffffffffffff82116105bc57019080601f830112156105bc57815191610bd5836135c4565b91610be360405193846135a2565b838352602084830101116105bc57610b8992610c0591602080850191016133e3565b610b71565b346105bc5760403660031901126105bc57600435610c2661343f565b610c2e613e2d565b815f52600a60205260ff600160405f20015460a81c161561101357815f5260036020526001600160a01b0360405f20541690813303610ffc57610c7083614685565b906001600160801b0382169182158015610d04575b50506001600160a01b03811615610cf157610ca8846001600160a01b0392613cf3565b169182610cc25783637e27328960e01b5f5260045260245ffd5b8084918403610cd657602083604051908152f35b9091506364283d7b60e01b5f5260045260245260445260645ffd5b633250574960e11b5f525f60045260245ffd5b610d0c613e2d565b855f52600a60205260ff600160405f20015460a81c1615610fe957855f52600a60205260ff600160405f20015460a01c16610fd6578415610fc357610fb057845f5260036020526001600160a01b0360405f205416908185141580610fa0575b610f85576001600160801b03610d8187614685565b16808511610f6b5750610da690865f52600a602052600260405f20015460801c6146ab565b5f868152600a6020526040902060020180546001600160801b031660809290921b6001600160801b031916919091178155610de0906139d3565b6001600160801b03610e048160208401511692826040818351169201511690613616565b161115610f39575b845f52600a6020526001600160a01b03600160405f20015416610e30848683614809565b84867f40b88e5c41c5a97ffb7b6ef88a0a2d505aa0c634cf8a0275cb236ea7dd87ed4d6020604051888152a47ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051878152a18033141580610f23575b610e9b575b80610c85565b6040516392b9102b60e01b81528560048201523360248201528460448201528360648201526020816084815f865af19081156104d8576392b9102b60e01b916001600160e01b0319915f91610f04575b501614610e9557636ade251160e01b5f5260045260245ffd5b610f1d915060203d6020116104d1576104c381836135a2565b88610eeb565b50805f52600960205260ff60405f205416610e90565b5f858152600a6020526040902060018101805460ff60a01b1916600160a01b179055805460ff60f01b19169055610e0c565b848763066920d760e01b5f5260045260245260445260645ffd5b848663350b320360e11b5f526004523360245260445260645ffd5b50610faa86614560565b15610d6c565b8463b2ae763360e01b5f5260045260245ffd5b85632da33e5b60e01b5f5260045260245ffd5b856315efa0f360e11b5f5260045260245ffd5b8563699d2de960e01b5f5260045260245ffd5b82632082501160e01b5f526004523360245260445ffd5b5063699d2de960e01b5f5260045260245ffd5b346105bc5760203660031901126105bc57600435805f52600a60205260ff600160405f20015460a81c161561062f575f52600a602052602064ffffffffff60405f205460a01c16604051908152f35b346105bc5760203660031901126105bc57600435805f52600a60205260ff600160405f20015460a81c161561062f575f52600a60205260206001600160a01b0360405f205416604051908152f35b346105bc5760203660031901126105bc576004355f52600a602052602060ff600160405f20015460a81c166040519015158152f35b346105bc5760803660031901126105bc57611111613429565b61111961343f565b6064359167ffffffffffffffff83116105bc57366023840112156105bc57826004013591611146836135c4565b9261115460405194856135a2565b80845236602482870101116105bc576020815f9260246111809801838801378501015260443591613a26565b005b346105bc5760203660031901126105bc57600435805f52600a60205260ff600160405f20015460a81c161561062f575f52600a602052602060ff600160405f20015460b01c166040519015158152f35b346105bc5760203660031901126105bc57600435805f52600a60205260ff600160405f20015460a81c161561062f5761120a90613c5f565b604051600582101561121d576020918152f35b634e487b7160e01b5f52602160045260245ffd5b346105bc5760203660031901126105bc57600435805f52600a60205260ff600160405f20015460a81c161561062f575f52600a60205260206001600160801b03600260405f20015416604051908152f35b346105bc5760403660031901126105bc5761129b613429565b602435908115158092036105bc576001600160a01b031690811561130a57335f52600660205260405f20825f5260205260405f2060ff1981541660ff83161790556040519081527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c3160203392a3005b507f5b08ba18000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b346105bc575f3660031901126105bc576040515f6002548060011c90600181168015611434575b602083108114611420578285529081156113fc575060011461139e575b610b898361138a818503826135a2565b604051918291602083526020830190613404565b91905060025f527f405787fa12a823e0f2b7631cc41b3ba8828b3321ca811111fa75cd3aa3bb5ace915f905b8082106113e25750909150810160200161138a61137a565b9192600181602092548385880101520191019092916113ca565b60ff191660208086019190915291151560051b8401909101915061138a905061137a565b634e487b7160e01b5f52602260045260245ffd5b91607f169161135d565b346105bc5760203660031901126105bc57600435805f52600a60205260ff600160405f20015460a81c161561062f575f52600a602052602064ffffffffff60405f205460c81c16604051908152f35b346105bc5760203660031901126105bc57600435805f52600a60205260ff600160405f20015460a81c161561062f576114c590613c5f565b60058110158061121d5760028214908115611501575b81156114ef575b6020826040519015158152f35b905061121d57600460209114826114e2565b5050600381145f6114db565b346105bc5760203660031901126105bc5760043567ffffffffffffffff81116105bc578036036101206003198201126105bc57611548613e2d565b60c482013590602219018112156105bc5781019060048201359167ffffffffffffffff83116105bc5760248101908360061b80360383136105bc5760046020916115918761387a565b9661159f60405198896135a2565b875282870193010101913683116105bc57905b8282106117be575050508151916115c88361387a565b926115d660405194856135a2565b808452601f196115e58261387a565b015f5b81811061179b57505064ffffffffff4216916001600160801b0361160b82613b57565b51511664ffffffffff80602061162085613b57565b51015116850116604051916116348361354d565b8252602082015261164486613b57565b5261164e85613b57565b5060015b8281106117265750505061166882600401613a05565b9261167560248401613a05565b9261168260448201613933565b916064820135936001600160a01b0385168095036105bc5760209661171e966116de966001600160801b03611713976001600160a01b036116c560848a01613a19565b94816116d360a48c01613a19565b976040519d8e613530565b168c52168c8b0152166040890152606088015215156080870152151560a086015260c085015260e084015260e43691016138c8565b610100820152613e87565b604051908152f35b806001600160801b0361173b60019385613b64565b51511664ffffffffff8060206117545f1986018c613b64565b510151168160206117658689613b64565b510151160116604051916117788361354d565b825260208201526117898289613b64565b526117948188613b64565b5001611652565b6020906040516117aa8161354d565b5f81525f83820152828289010152016115e8565b60206040916117cd3685613892565b8152019101906115b2565b346105bc5760203660031901126105bc5760043560606101606040516117fd81613569565b5f81525f60208201525f60408201525f838201525f60808201525f60a08201525f60c08201525f60e08201525f6101008201525f61012082015260405161184381613586565b5f81525f60208201525f60408201526101408201520152805f52600a60205260ff600160405f20015460a81c161561062f57805f52600a60205260405f2060405191610140830183811067ffffffffffffffff821117611b03576040528154916001600160a01b0383168452602084019264ffffffffff8160a01c168452604085019064ffffffffff8160c81c16825285606081019260ff8360f01c1615158452608082019260f81c1515835260018501549260a08301956001600160a01b0385168752611940600260c086019260ff8860a01c161515845260ff61010060e0890198828b60a81c1615158a52019860b01c1615158852016139d3565b6101208b0190815261195189613c5f565b600581101561121d57600214611afb575b5196516001600160a01b0316925164ffffffffff169551151590511515935115159451151595885f52600360205260405f20546001600160a01b03169a516001600160a01b0316995164ffffffffff16985f52600b60205260405f2092511515926040519a6119d08c613569565b8b5260208b019b8c5260408b01998a5260608b0191825260808b0192835260a08b0193845260c08b0194855260e08b019586526101008b019687526101208b019788526101408b01988952611a249061395f565b986101608b01998a526040519b8c9b60208d52516001600160a01b031660208d0152516001600160a01b031660408c01525164ffffffffff1660608b01525164ffffffffff1660808a015251151560a089015251151560c0880152516001600160a01b031660e08701525115156101008601525115156101208501525115156101408401525180516001600160801b031661016084015260208101516001600160801b0316610180840152604001516001600160801b03166101a0830152516101c082016101c090526101e08201610b89916134d4565b5f8752611962565b634e487b7160e01b5f52604160045260245ffd5b346105bc5760203660031901126105bc5760043567ffffffffffffffff81116105bc57611b489036906004016134a3565b90611b51613e2d565b5f915b808310611b5d57005b611b6883828461390f565b3592611b72613e2d565b835f52600a60205260ff600160405f20015460a81c1615611ebc57835f52600a60205260ff600160405f20015460a01c165f14611bbc57836315efa0f360e11b5f5260045260245ffd5b909192805f52600a60205260405f205460f81c611eaa57611bf1815f52600a6020526001600160a01b0360405f205416331490565b15611e9457611bff81613b78565b90805f52600a602052611c17600260405f20016139d3565b916001600160801b038351166001600160801b0382161015611e8157815f52600a60205260ff60405f205460f01c1615611e6e57806001600160801b03602081611c6b948188511603169501511690613616565b5f828152600a6020526040902080547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b179055916001600160801b038316908115611e49575b825f52600a602052600360405f20016001600160801b0382166001600160801b0319825416179055825f52600a6020526001600160a01b0360405f205416835f5260036020526001600160a01b0360405f20541694845f52600a60205285827f5edb27d6c1a327513b90a792050debf074b7194444885e3144d4decc5caaaa50611d7d6001600160a01b03600160405f2001541694611d55888588614809565b604080518b81526001600160801b03808b166020830152909216908201529081906060820190565b0390a47ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051868152a1845f52600960205260ff60405f205416611dce575b50505050506001019190611b54565b60405193630d4af11f60e31b855260048501526024840152604483015260648201526020816084815f865af19081156104d857630d4af11f60e31b916001600160e01b0319915f91611e2b575b5016036104975780808080611dbf565b611e43915060203d81116104d1576104c381836135a2565b87611e1b565b825f52600a602052600160405f2001600160a01b60ff60a01b19825416179055611cb5565b50635dd950cb60e11b5f5260045260245ffd5b506308aca53f60e21b5f5260045260245ffd5b632082501160e01b5f526004523360245260445ffd5b63d0a172b360e01b5f5260045260245ffd5b8363699d2de960e01b5f5260045260245ffd5b346105bc5760203660031901126105bc57600435805f52600a60205260ff600160405f20015460a81c161561062f575f52600b602052610b89611f1460405f2061395f565b6040519182916020835260208301906134d4565b346105bc5760203660031901126105bc57600435611f44613e2d565b805f52600a60205260ff600160405f20015460a81c161561062f57611f6881613c5f565b600581101561121d5760048103611f8c57506315efa0f360e11b5f5260045260245ffd5b60038103611fa7575063d0a172b360e01b5f5260045260245ffd5b60021461206357611fcc815f52600a6020526001600160a01b0360405f205416331490565b15611e9457805f52600a60205260ff60405f205460f01c1615612051576020817ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce7925f52600a825260405f2060ff60f01b19815416905560405190807f0eb069207093cd3e51cd1370d2d369770057fbe29947e577e5fb428c6c6fc78f5f80a28152a1005b635dd950cb60e11b5f5260045260245ffd5b6308aca53f60e21b5f5260045260245ffd5b346105bc5760203660031901126105bc576004356001600160a01b0381168091036105bc576001600160a01b035f5416338103612150575060085490806001600160a01b03198316176008556001600160a01b036040519216825260208201527fa2548bd4b805e907c1558a47b5858324fe8bb4a2e1ddfca647eecbf65610eebc60403392a26007545f19810190811161213c5760407f6bd5c950a8d8df17f772f5af37cb3655737899cbf903264b9795592da439661c91815190600182526020820152a1005b634e487b7160e01b5f52601160045260245ffd5b6331b339a960e21b5f526004523360245260445ffd5b346105bc5760203660031901126105bc5761217f613429565b5f546001600160a01b03811633810361215057506001600160a01b036001600160a01b0319921691829116175f55337fbdd36143ee09de60bdefca70680e0f71189b2ed7acee364b53917ad433fdaf805f80a3005b346105bc5760203660031901126105bc576001600160a01b036121f5613429565b168015612212575f526004602052602060405f2054604051908152f35b7f89c62b64000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b346105bc5760203660031901126105bc57602061225c600435613b36565b6001600160a01b0360405191168152f35b346105bc5760203660031901126105bc57600435612289613947565b50805f52600a60205260ff600160405f20015460a81c161561062f575f908152600a6020526040908190205481519064ffffffffff60c882901c81169160a01c166122d38361354d565b825260208201526122f98251809264ffffffffff60208092828151168552015116910152565bf35b346105bc5760403660031901126105bc5760043567ffffffffffffffff81116105bc5761232c9036906004016134a3565b9060243567ffffffffffffffff81116105bc5761234d9036906004016134a3565b919092612358613e2d565b828103612671575f5b81811061236a57005b61237581838561390f565b3561238182848661390f565b355f5260036020526001600160a01b0360405f205416906123ab6123a684888a61390f565b613933565b916123b4613e2d565b815f52600a60205260ff600160405f20015460a81c161561101357815f52600a60205260ff600160405f20015460a01c1661265e57801561099a576001600160801b03831690811561098757825f5260036020526001600160a01b0360405f20541693848214158061264e575b612633576001600160801b0361243685614685565b16808411612619575061245b90845f52600a602052600260405f20015460801c6146ab565b5f848152600a6020526040902060020180546001600160801b031660809290921b6001600160801b031916919091178155612495906139d3565b6001600160801b036124b98160208401511692826040818351169201511690613616565b1611156125e7575b825f52600a6020526001600160a01b03600160405f200154166124e5838383614809565b81847f40b88e5c41c5a97ffb7b6ef88a0a2d505aa0c634cf8a0275cb236ea7dd87ed4d6020604051878152a47ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051858152a183331415806125d1575b612556575b50505050600101612361565b604051926392b9102b60e01b84526004840152336024840152604483015260648201526020816084815f865af19081156104d8576392b9102b60e01b916001600160e01b0319915f916125b3575b5016036104975780808061254a565b6125cb915060203d81116104d1576104c381836135a2565b896125a4565b50835f52600960205260ff60405f205416612545565b5f838152600a6020526040902060018101805460ff60a01b1916600160a01b179055805460ff60f01b191690556124c1565b838563066920d760e01b5f5260045260245260445260645ffd5b508263350b320360e11b5f526004523360245260445260645ffd5b5061265884614560565b15612421565b506315efa0f360e11b5f5260045260245ffd5b90507fa5ed43e6000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b346105bc5760203660031901126105bc57600435805f52600a60205260ff600160405f20015460a81c161561062f57610a526020916145d2565b346105bc5760203660031901126105bc57600435805f52600a60205260ff600160405f20015460a81c161561062f575f61271482613c5f565b600581101561121d57600203612732575b6020906040519015158152f35b505f52600a602052602060ff60405f205460f01c16612725565b346105bc575f3660031901126105bc5760206001600160a01b0360085416604051908152f35b346105bc5760203660031901126105bc5760043561278e613e2d565b805f52600a60205260ff600160405f20015460a81c161561062f57805f52600a60205260ff600160405f20015460a01c161561290b576127cd81614560565b15611e9457805f5260036020526001600160a01b0360405f205416151580612904575b806128e7575b6128d5577ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051838152a1805f5260036020526001600160a01b0360405f205416801590811561289e575b825f52600360205260405f206001600160a01b03198154169055825f827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8280a45061288c57005b637e27328960e01b5f5260045260245ffd5b6128bd835f52600560205260405f206001600160a01b03198154169055565b805f52600460205260405f205f198154019055612844565b634274c8e160e11b5f5260045260245ffd5b50805f52600a60205260ff600160405f20015460b01c16156127f6565b505f6127f0565b7f6121eb36000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b346105bc5761118061294736613469565b90604051926129576020856135a2565b5f8452613a26565b346105bc5760203660031901126105bc57600435805f52600a60205260ff600160405f20015460a81c161561062f575f52600a602052602060ff600160405f20015460a01c166040519015158152f35b346105bc5760203660031901126105bc576004356129cb613e2d565b805f52600a60205260ff600160405f20015460a81c161561062f57805f52600a60205260ff600160405f20015460a01c165f14612a14576315efa0f360e11b5f5260045260245ffd5b805f52600a60205260405f205460f81c611eaa57612a46815f52600a6020526001600160a01b0360405f205416331490565b15611e9457612a5481613b78565b90805f52600a602052612a6c600260405f20016139d3565b916001600160801b038351166001600160801b0382161015611e8157815f52600a60205260ff60405f205460f01c1615611e6e57806001600160801b03602081612ac0948188511603169501511690613616565b5f828152600a6020526040902080547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16600160f81b179055916001600160801b038316908115612c61575b825f52600a602052600360405f20016001600160801b0382166001600160801b0319825416179055825f52600a6020526001600160a01b0360405f205416835f5260036020526001600160a01b0360405f20541694845f52600a60205285827f5edb27d6c1a327513b90a792050debf074b7194444885e3144d4decc5caaaa50612baa6001600160a01b03600160405f2001541694611d55888588614809565b0390a47ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051868152a1845f52600960205260ff60405f205416612bed57005b60405193630d4af11f60e31b855260048501526024840152604483015260648201526020816084815f865af19081156104d857630d4af11f60e31b916001600160e01b0319915f91612c425750160361049757005b612c5b915060203d6020116104d1576104c381836135a2565b8461048e565b825f52600a602052600160405f2001600160a01b60ff60a01b19825416179055612b0a565b346105bc5760203660031901126105bc57612c9f613429565b6001600160a01b035f541690338203612dde57806001600160a01b03913b15612db257166040516301ffc9a760e01b81527ff8ee98d3000000000000000000000000000000000000000000000000000000006004820152602081602481855afa9081156104d8575f91612d83575b5015612d5857805f52600960205260405f20600160ff198254161790556040519081527fb4378d4e289cb3f40f4f75a99c9cafa76e3df1c4dc31309babc23dc91bd7280160203392a2005b7ff1dc125d000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b612da5915060203d602011612dab575b612d9d81836135a2565b8101906138f7565b82612d0d565b503d612d93565b7f295097c8000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b506331b339a960e21b5f526004523360245260445ffd5b346105bc5760203660031901126105bc5760043567ffffffffffffffff81116105bc5761014060031982360301126105bc57612e2f613e2d565b604051612e3b81613530565b612e4782600401613455565b8152612e5560248301613455565b6020820152612e66604483016135e0565b604082015260648201356001600160a01b03811681036105bc576060820152612e9160848301613523565b6080820152612ea260a48301613523565b60a0820152612eb360c48301613868565b60c082015260e482013567ffffffffffffffff81116105bc57820191366023840112156105bc57600483013592612ee98461387a565b90612ef760405192836135a2565b848252602060048184019660061b83010101903682116105bc57602401945b818610612f3857602061171e86611713878760e08401526101043691016138c8565b6020604091612f473689613892565b815201950194612f16565b346105bc5760203660031901126105bc576001600160a01b03612f73613429565b165f526009602052602060ff60405f2054166040519015158152f35b346105bc575f3660031901126105bc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346105bc57611180612fda36613469565b91613636565b346105bc575f3660031901126105bc576020600754604051908152f35b346105bc5760203660031901126105bc57600435805f52600a60205260ff600160405f20015460a81c161561062f5761303590613c5f565b600581101561121d578060209115908115613056575b506040519015158152f35b60019150148261304b565b346105bc5760203660031901126105bc57600435805f52600a60205260ff600160405f20015460a81c161561062f576020905f90805f52600a835260ff60405f205460f01c16806130f6575b6130c4575b506001600160801b0360405191168152f35b6130f09150805f52600a83526130ea6001600160801b03600260405f2001541691613b78565b90613616565b826130b2565b50805f52600a835260ff600160405f20015460a01c16156130ad565b346105bc5760403660031901126105bc5761312b613429565b60243561313781613b36565b33151580613204575b806131d1575b6131a55781906001600160a01b0380851691167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9255f80a45f5260056020526001600160a01b0360405f2091166001600160a01b03198254161790555f80f35b7fa9fbf51f000000000000000000000000000000000000000000000000000000005f523360045260245ffd5b506001600160a01b0381165f52600660205260405f206001600160a01b0333165f5260205260ff60405f20541615613146565b50336001600160a01b0382161415613140565b346105bc5760203660031901126105bc57602061225c6004356135f4565b346105bc575f3660031901126105bc576040515f6001548060011c906001811680156132e6575b602083108114611420578285529081156113fc575060011461328857610b898361138a818503826135a2565b91905060015f527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6915f905b8082106132cc5750909150810160200161138a61137a565b9192600181602092548385880101520191019092916132b4565b91607f169161325c565b346105bc575f3660031901126105bc57602060405167016345785d8a00008152f35b346105bc5760203660031901126105bc57600435906001600160e01b031982168092036105bc57817f49064906000000000000000000000000000000000000000000000000000000006020931490811561336e575b5015158152f35b7f80ac58cd000000000000000000000000000000000000000000000000000000008114915081156133b9575b81156133a8575b5083613367565b6301ffc9a760e01b915014836133a1565b7f5b5e139f000000000000000000000000000000000000000000000000000000008114915061339a565b5f5b8381106133f45750505f910152565b81810151838201526020016133e5565b9060209161341d815180928185528580860191016133e3565b601f01601f1916010190565b600435906001600160a01b03821682036105bc57565b602435906001600160a01b03821682036105bc57565b35906001600160a01b03821682036105bc57565b60609060031901126105bc576004356001600160a01b03811681036105bc57906024356001600160a01b03811681036105bc579060443590565b9181601f840112156105bc5782359167ffffffffffffffff83116105bc576020808501948460051b0101116105bc57565b90602080835192838152019201905f5b8181106134f15750505090565b825180516001600160801b0316855260209081015164ffffffffff1681860152604090940193909201916001016134e4565b359081151582036105bc57565b610120810190811067ffffffffffffffff821117611b0357604052565b6040810190811067ffffffffffffffff821117611b0357604052565b610180810190811067ffffffffffffffff821117611b0357604052565b6060810190811067ffffffffffffffff821117611b0357604052565b90601f8019910116810190811067ffffffffffffffff821117611b0357604052565b67ffffffffffffffff8111611b0357601f01601f191660200190565b35906001600160801b03821682036105bc57565b6135fd81613b36565b505f5260056020526001600160a01b0360405f20541690565b906001600160801b03809116911603906001600160801b03821161213c57565b91906001600160a01b03168015610cf157815f5260036020526001600160a01b0360405f205416151580613860575b80613843575b613830577ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051848152a1815f5260036020526001600160a01b0360405f2054169282331515928361377b575b6001600160a01b03935085613744575b805f52600460205260405f2060018154019055815f52600360205260405f20816001600160a01b0319825416179055857fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef5f80a41680830361372c57505050565b6364283d7b60e01b5f5260045260245260445260645ffd5b613763825f52600560205260405f206001600160a01b03198154169055565b855f52600460205260405f205f1981540190556136cb565b91929050806137d9575b15613792578282916136bb565b82846137aa57637e27328960e01b5f5260045260245ffd5b7f177e802f000000000000000000000000000000000000000000000000000000005f523360045260245260445ffd5b503384148015613807575b806137855750825f526005602052336001600160a01b0360405f20541614613785565b50835f52600660205260405f206001600160a01b0333165f5260205260ff60405f2054166137e4565b50634274c8e160e11b5f5260045260245ffd5b50815f52600a60205260ff600160405f20015460b01c161561366b565b506001613665565b359064ffffffffff821682036105bc57565b67ffffffffffffffff8111611b035760051b60200190565b91908260409103126105bc576040516138aa8161354d565b60206138c38183956138bb816135e0565b855201613868565b910152565b91908260409103126105bc576040516138e08161354d565b60208082946138ee81613455565b84520135910152565b908160209103126105bc575180151581036105bc5790565b919081101561391f5760051b0190565b634e487b7160e01b5f52603260045260245ffd5b356001600160801b03811681036105bc5790565b604051906139548261354d565b5f6020838281520152565b90815461396b8161387a565b9261397960405194856135a2565b81845260208401905f5260205f205f915b8383106139975750505050565b6001602081926040516139a98161354d565b64ffffffffff86546001600160801b038116835260801c168382015281520192019201919061398a565b906040516139e081613586565b60406001600160801b03600183958054838116865260801c6020860152015416910152565b356001600160a01b03811681036105bc5790565b3580151581036105bc5790565b90613a32838284613636565b803b613a3f575b50505050565b602091613a856001600160a01b03809316956040519586948594630a85bd0160e11b86523360048701521660248501526044840152608060648401526084830190613404565b03815f865af15f9181613af5575b50613ac15750613aa1614656565b80519081613abc5782633250574960e11b5f5260045260245ffd5b602001fd5b6001600160e01b0319630a85bd0160e11b911603613ae357505f808080613a39565b633250574960e11b5f5260045260245ffd5b613b0f91925060203d6020116104d1576104c381836135a2565b905f613a93565b908160209103126105bc57516001600160e01b0319811681036105bc5790565b805f5260036020526001600160a01b0360405f20541690811561288c575090565b80511561391f5760200190565b805182101561391f5760209160051b010190565b9064ffffffffff421691805f52600b602052613b9660405f2061395f565b908364ffffffffff6020613ba985613b57565b5101511611613c5857805f52600a6020528364ffffffffff60405f205460c81c161115613c3957506001600160801b03613be282613b57565b515116916001925b8251841015613c32578464ffffffffff6020613c068787613b64565b5101511611613c32576001600160801b0360019181613c258787613b64565b5151160116930192613bea565b9350915050565b919250505f52600a6020526001600160801b03600260405f2001541690565b505f925050565b805f52600a60205260ff600160405f20015460a01c165f14613c815750600490565b805f52600a60205260405f205460f81c613ced57805f52600a60205264ffffffffff60405f205460a01c164210613ce857613cbb81613b78565b905f52600a6020526001600160801b0380600260405f200154169116105f14613ce357600190565b600290565b505f90565b50600390565b90805f5260036020526001600160a01b0360405f205416151580613e1b575b80613dfe575b6128d5577ff8e1a15aba9398e019f0b49df1a4fde98ee17ae345cb5f6b5e2c27f5033e8ce76020604051838152a1805f5260036020526001600160a01b038060405f2054169283613dc7575b1680613daf575b815f52600360205260405f20816001600160a01b0319825416179055827fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef5f80a490565b805f52600460205260405f2060018154019055613d6b565b613de6835f52600560205260405f206001600160a01b03198154169055565b835f52600460205260405f205f198154019055613d64565b50805f52600a60205260ff600160405f20015460b01c1615613d18565b506001600160a01b0382161515613d12565b6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163003613e5f57565b7fa1c0d6e5000000000000000000000000000000000000000000000000000000005f5260045ffd5b90613ea96001600160801b0360408401511660206101008501510151906146cb565b916001600160801b038351169060e08101519160c082019264ffffffffff845116821561453857801561451057815180156144e8577f000000000000000000000000000000000000000000000000000000000000000081116144bd575064ffffffffff6020613f1784613b57565b5101511681101561447957505f905f905f81515f905b8082106143f1575050505064ffffffffff804216911690818110156143c35750506001600160801b03169081810361439557505060075493845f52600a60205260405f20916001600160801b038251166001600160801b036002850191166001600160801b03198254161790556001600160a01b03606082015116916001600160a01b036001850193166001600160a01b031984541617835560808201948551151560ff60f01b197eff00000000000000000000000000000000000000000000000000000000000087549260f01b169116178555835493750100000000000000000000000000000000000000000060a08501957fffffffffffffffffff0000ffffffffffffffffffffffffffffffffffffffffff76ff000000000000000000000000000000000000000000008851151560b01b169116171790556001600160a01b0380845116166001600160a01b03198654161785555184549060e0840151917fffff00000000000000000000ffffffffffffffffffffffffffffffffffffffff78ffffffffff00000000000000000000000000000000000000007dffffffffff0000000000000000000000000000000000000000000000000060206140f98751975f19890190613b64565b51015160c81b169360a01b169116171785555f5b8181106142e3575050600187016007556001600160a01b036020830151168015610cf157614143886001600160a01b0392613cf3565b166142b75786826141916001600160a01b0360607ffeb1cb9ce021c8bd5fb1eb836e6284c68866fa32d1d844238de19955238f8076960151166001600160801b0385511690309033906147a8565b6001600160801b0360208401511680614287575b506001600160a01b038151169461427c61425e6001600160a01b03602085015116986001600160a01b036060860151169a511515935115156001600160a01b0361010060e088015193549764ffffffffff604051996142038b61354d565b818160a01c168b5260c81c1660208a015201515116946001600160801b0360206040519a8b9a8b5233828c01528281511660408c01520151166060890152608088015260a087015261014060c08701526101408601906134d4565b9260e085019064ffffffffff60208092828151168552015116910152565b6101208301520390a4565b6142b1906001600160a01b036060840151166001600160a01b0361010085015151169033906147a8565b5f6141a5565b7f73c6ac6e000000000000000000000000000000000000000000000000000000005f525f60045260245ffd5b885f52600b60205260405f20906142fe8160e0870151613b64565b5182549268010000000000000000841015611b03576001840180825584101561391f576001936020915f52815f2001916001600160801b0380825116166001600160801b031984541617835501517fffffffffffffffffffffff0000000000ffffffffffffffffffffffffffffffff74ffffffffff0000000000000000000000000000000083549260801b1691161790550161410d565b7fa4cdf853000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b7f879842de000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b9193509193614415906001600160801b0361440c8588613b64565b515116906146ab565b9364ffffffffff8060206144298685613b64565b5101511694168085111561444557506001849301909291613f2d565b8490847fbfc5f2fa000000000000000000000000000000000000000000000000000000005f5260045260245260445260645ffd5b64ffffffffff602061448a84613b57565b51015116907fab900963000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b7f76d9c284000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f814426ed000000000000000000000000000000000000000000000000000000005f5260045ffd5b7feaa6c316000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f779f8816000000000000000000000000000000000000000000000000000000005f5260045ffd5b805f5260036020526001600160a01b0360405f205416908133149182156145a6575b50811561458d575090565b90506001600160a01b036145a133926135f4565b161490565b9091505f52600660205260405f206001600160a01b0333165f5260205260ff60405f205416905f614582565b805f52600a6020526145e9600260405f20016139d3565b90805f52600a60205260ff600160405f20015460a01c165f146146175750602001516001600160801b031690565b90815f52600a60205260405f205460f81c614639575061463690613b78565b90565b61463691506001600160801b036040818351169201511690613616565b3d15614680573d90614667826135c4565b9161467560405193846135a2565b82523d5f602084013e565b606090565b61463690614692816145d2565b905f52600a602052600260405f20015460801c90613616565b906001600160801b03809116911601906001600160801b03821161213c57565b9190916040516146da8161354d565b5f81525f6020820152926001600160801b03821690811561478b5767016345785d8a00008111614754576147166001600160801b0391836148de565b1660208501918183521115614740576001600160801b03918261473b92511690613616565b168252565b634e487b7160e01b5f52600160045260245ffd5b7ffc8a7df4000000000000000000000000000000000000000000000000000000005f5260045267016345785d8a000060245260445ffd5b505050905060405161479c8161354d565b5f81525f602082015290565b9091926001600160a01b036148079481604051957f23b872dd0000000000000000000000000000000000000000000000000000000060208801521660248601521660448401526064830152606482526148026084836135a2565b614859565b565b614807926001600160a01b03604051937fa9059cbb0000000000000000000000000000000000000000000000000000000060208601521660248401526044830152604482526148026064836135a2565b5f806001600160a01b0361488293169360208151910182865af161487b614656565b908361498c565b80519081151591826148c3575b50506148985750565b7f5274afe7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b6148d692506020809183010191016138f7565b155f8061488f565b9091905f198382098382029182808310920391808303921461497b57670de0b6b3a764000082101561494b577faccb18165bd6fe31ae1cf318dc5b51eee0e1ba569b88cd74c1773b91fac106699394670de0b6b3a7640000910990828211900360ee1b910360121c170290565b84907f5173648d000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b5050670de0b6b3a764000090049150565b906149c957508051156149a157805190602001fd5b7f1425ea42000000000000000000000000000000000000000000000000000000005f5260045ffd5b81511580614a0f575b6149da575090565b6001600160a01b03907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b156149d256fea164736f6c634300081a000a"; bytes public constant BYTECODE_MERKLE_FACTORY = - hex"60808060405234601557613bb9908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c80631e3238761461044c5780634d7c0f11146103795763769bed201461003a575f80fd5b346103755760c03660031901126103755760043567ffffffffffffffff81116103755761006b9036906004016108c2565b602435906001600160a01b03821691828103610375576040366043190112610375576040519061009a8261080f565b60443564ffffffffff8116810361037557825260643564ffffffffff811681036103755760208301528251906020840151151583604086015192606087015160808801519160405180602081019460208652604082016100f9916109ca565b03601f198101825261010b908261082b565b60a08a01519360c08b0151604051818192519081602084019160200191610131926109a9565b810103808252610144906020018261082b565b61014d906109ef565b9060e08c01511515926040519560208701988961017c9164ffffffffff60208092828151168552015116910152565b6040875261018b60608861082b565b6040519a8b9a60208c019d8e3360601b90526bffffffffffffffffffffffff199060601b1660348d015260f81b60488c015260d81b7fffffffffff0000000000000000000000000000000000000000000000000000001660498b01526bffffffffffffffffffffffff199060601b16604e8a015251908160628a0161020f926109a9565b8701946062860152608285015260f81b60a28401526bffffffffffffffffffffffff199060601b1660a383015251918260b7830161024c926109a9565b0160620103605501601f1981018252610265908261082b565b51902060405161157280820182811067ffffffffffffffff821117610361578291610ae88339608081526102c260406102a16080840189610a61565b92896020820152018664ffffffffff60208092828151168552015116910152565b03905ff59283156103565761031961033b937f2ba0fe49588281dbb122dd3b7f3e2b3396338f70dbe3c62bf3e3888b4ba7ffb8926001600160a01b036020971695869560405194859460c0865260c0860190610a61565b9289850152604084019064ffffffffff60208092828151168552015116910152565b608435608083015260a43560a08301520390a2604051908152f35b6040513d5f823e3d90fd5b634e487b7160e01b5f52604160045260245ffd5b5f80fd5b346103755760203660031901126103755760043567ffffffffffffffff811161037557366023820112156103755780600401359067ffffffffffffffff8211610375573660248360061b83010111610375575f90815b8383101561042d5760248360061b830101359067ffffffffffffffff82168092036103755767ffffffffffffffff160167ffffffffffffffff8111610419576001909201916103cf565b634e487b7160e01b5f52601160045260245ffd5b602090670de0b6b3a764000067ffffffffffffffff6040519216148152f35b346103755760a03660031901126103755760043567ffffffffffffffff81116103755761047d9036906004016108c2565b6024356001600160a01b03811690818103610375576044359067ffffffffffffffff8211610375573660238301121561037557816004013567ffffffffffffffff811161036157604051926104d860208360051b018561082b565b8184526024602085019260061b8201019036821161037557602401915b8183106107c2575050505f9082515f905b8082106107835750508451906020860151151584604088015192606089015160808a0151916040518060208101946020865260408201610545916109ca565b03601f1981018252610557908261082b565b8b60a08101519460c082015160405181819251908160208401916020019161057e926109a9565b810103808252610591906020018261082b565b61059a906109ef565b9160e001511515926040519586602081019960208b52604082016105bd91610a11565b03601f19810188526105cf908861082b565b6040519a8b9a60208c019d8e3360601b90526bffffffffffffffffffffffff199060601b1660348d015260f81b60488c015260d81b7fffffffffff0000000000000000000000000000000000000000000000000000001660498b01526bffffffffffffffffffffffff199060601b16604e8a015251908160628a01610653926109a9565b8701946062860152608285015260f81b60a28401526bffffffffffffffffffffffff199060601b1660a383015251918260b78301610690926109a9565b0160620103605501601f19810182526106a9908261082b565b519020604051611b538082019082821067ffffffffffffffff8311176103615782916106fa9161205a8439606081526106e5606082018a610a61565b90886020820152604081830391015286610a11565b03905ff58015610356576020947ffe44018cf74992b2720702385a1728bd329dd136e4f651203176c81c12710a8b926107626001600160a01b03610750941696879660405195869560c0875260c0870190610a61565b918a8601528482036040860152610a11565b906060830152606435608083015260843560a08301520390a2604051908152f35b909284518410156107ae5760019064ffffffffff6020808760051b8901015101511601930190610506565b634e487b7160e01b5f52603260045260245ffd5b60408336031261037557604051906107d98261080f565b83359067ffffffffffffffff8216820361037557826020926040945261080083870161085a565b838201528152019201916104f5565b6040810190811067ffffffffffffffff82111761036157604052565b90601f8019910116810190811067ffffffffffffffff82111761036157604052565b3590811515820361037557565b359064ffffffffff8216820361037557565b81601f820112156103755780359067ffffffffffffffff821161036157604051926108a1601f8401601f19166020018561082b565b8284526020838301011161037557815f926020809301838601378301015290565b919091610100818403126103755760405190610100820182811067ffffffffffffffff82111761036157604052819381356001600160a01b03811681036103755783526109116020830161084d565b60208401526109226040830161085a565b604084015260608201356001600160a01b0381168103610375576060840152608082013567ffffffffffffffff8111610375578161096191840161086c565b608084015260a082013560a084015260c08201359067ffffffffffffffff8211610375578261099960e094926109a49486940161086c565b60c08601520161084d565b910152565b5f5b8381106109ba5750505f910152565b81810151838201526020016109ab565b906020916109e3815180928185528580860191016109a9565b601f01601f1916010190565b602081519101519060208110610a03575090565b5f199060200360031b1b1690565b90602080835192838152019201905f5b818110610a2e5750505090565b8251805167ffffffffffffffff16855260209081015164ffffffffff168186015260409094019390920191600101610a21565b906001600160a01b03825116815260208201511515602082015264ffffffffff60408301511660408201526001600160a01b03606083015116606082015260e080610adc610ac0608086015161010060808701526101008601906109ca565b60a086015160a086015260c086015185820360c08701526109ca565b93015115159101529056fe610160806040523461042857611572803803809161001d8285610560565b833981019080820390608082126104285780516001600160401b03811161042857810190610100828503126104285760405161010081016001600160401b038111828210176105355760405282516001600160a01b038116810361042857815261008960208401610583565b6020820190815261009c60408501610590565b60408301908152606085015194906001600160a01b0386168603610428576060840195865260808201516001600160401b03811161042857886100e09184016105de565b6080850190815260a08381015190860190815260c084015190999192916001600160401b0382116104285761011c60e09161012a9387016105de565b9460c0880195865201610583565b9360e0860194855260208701519560018060a01b038716998a880361042857604090603f1901126104285760408051989089016001600160401b0381118a8210176105355761018d9160609160405261018560408201610590565b8b5201610590565b9860208901998a52855151602081116105495750515f80546001600160a01b0319166001600160a01b0392831617905590511660805251151560a0525164ffffffffff1660c0525180519097906001600160401b03811161053557600154600181811c9116801561052b575b602082101461051757601f81116104b4575b506020601f821160011461044857819064ffffffffff98999a5f9261043d575b50508160011b915f199060031b1c1916176001555b5160e052516040516102736020828161026281830196878151938492016105bd565b81010301601f198101835282610560565b519051906020811061042c575b50610100525115156101205261014052511669ffffffffff0000000000600454925160281b169160018060501b031916171760045560018060a01b0360805116604051905f806020840163095ea7b360e01b815285602486015281196044860152604485526102f0606486610560565b84519082855af16102ff610623565b816103f1575b50806103e7575b156103a2575b604051610e4e90816107248239608051818181610409015281816106440152610ae6015260a05181818161067501526109fe015260c0518181816101520152818161095501528181610c250152610d80015260e05181818161029f015261059d01526101005181610caa01526101205181818161069f01526109c201526101405181818161019501526107b40152f35b6103da6103df936040519063095ea7b360e01b602083015260248201525f6044820152604481526103d4606482610560565b82610652565b610652565b5f8080610312565b50803b151561030c565b8051801592508215610406575b50505f610305565b81925090602091810103126104285760206104219101610583565b5f806103fe565b5f80fd5b5f199060200360031b1b165f610280565b015190505f8061022b565b601f1982169960015f52815f209a5f5b81811061049c57509164ffffffffff999a9b91846001959410610484575b505050811b01600155610240565b01515f1960f88460031b161c191690555f8080610476565b838301518d556001909c019b60209384019301610458565b60015f527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6601f830160051c8101916020841061050d575b601f0160051c01905b818110610502575061020b565b5f81556001016104f5565b90915081906104ec565b634e487b7160e01b5f52602260045260245ffd5b90607f16906101f9565b634e487b7160e01b5f52604160045260245ffd5b630f39860960e01b5f52600452602060245260445ffd5b601f909101601f19168101906001600160401b0382119082101761053557604052565b5190811515820361042857565b519064ffffffffff8216820361042857565b6001600160401b03811161053557601f01601f191660200190565b5f5b8381106105ce5750505f910152565b81810151838201526020016105bf565b81601f820112156104285780516105f4816105a2565b926106026040519485610560565b818452602082840101116104285761062091602080850191016105bd565b90565b3d1561064d573d90610634826105a2565b916106426040519384610560565b82523d5f602084013e565b606090565b5f8061067a9260018060a01b03169360208151910182865af1610673610623565b90836106c5565b80519081151591826106a2575b50506106905750565b635274afe760e01b5f5260045260245ffd5b81925090602091810103126104285760206106bd9101610583565b155f80610687565b906106e957508051156106da57805190602001fd5b630a12f52160e11b5f5260045ffd5b8151158061071a575b6106fa575090565b639996b31560e01b5f9081526001600160a01b0391909116600452602490fd5b50803b156106f256fe6080806040526004361015610012575f80fd5b5f3560e01c90816306fdde0314610c94575080631686c90914610a2357806316c3549d146109e75780631bfd6814146109ab5780633bfe03a81461097d5780633f31ae3f1461042d5780634800d97f146103ea57806349fc73dd146102e65780634e390d3e146102c257806351e75e8b1461028857806375829def146101d357806390e64d13146101b95780639e93e57714610176578063bb4b573414610135578063ce516507146100f55763f851a440146100cc575f80fd5b346100f1575f3660031901126100f15760206001600160a01b035f5416604051908152f35b5f80fd5b346100f15760203660031901126100f157602061012b60043560ff6001918060081c5f526002602052161b60405f205416151590565b6040519015158152f35b346100f1575f3660031901126100f157602060405164ffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b346100f1575f3660031901126100f15760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b346100f1575f3660031901126100f157602061012b610d78565b346100f15760203660031901126100f1576101ec610d24565b5f546001600160a01b03811633810361025957506001600160a01b037fffffffffffffffffffffffff0000000000000000000000000000000000000000921691829116175f55337fbdd36143ee09de60bdefca70680e0f71189b2ed7acee364b53917ad433fdaf805f80a3005b7fc6cce6a4000000000000000000000000000000000000000000000000000000005f526004523360245260445ffd5b346100f1575f3660031901126100f15760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346100f1575f3660031901126100f157602064ffffffffff60035416604051908152f35b346100f1575f3660031901126100f1576040515f6001548060011c906001811680156103e0575b6020831081146103cc578285529081156103a8575060011461034a575b6103468361033a81850382610d56565b60405191829182610cdd565b0390f35b91905060015f527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6915f905b80821061038e5750909150810160200161033a61032a565b919260018160209254838588010152019101909291610376565b60ff191660208086019190915291151560051b8401909101915061033a905061032a565b634e487b7160e01b5f52602260045260245ffd5b91607f169161030d565b346100f1575f3660031901126100f15760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b346100f15760803660031901126100f1576004356024356001600160a01b0381168091036100f157604435916fffffffffffffffffffffffffffffffff83168093036100f1576064359067ffffffffffffffff82116100f157366023830112156100f157816004013567ffffffffffffffff81116100f1578060051b92602484820101903682116100f1576040516020810190858252876040820152886060820152606081526104de608082610d56565b51902060405160208101918252602081526104fa604082610d56565b51902092610506610d78565b6109265761052b8560ff6001918060081c5f526002602052161b60405f205416151590565b6108fa5761053f6020604051970187610d56565b8552602401602085015b8282106108ea57505050935f945b83518610156105995760208660051b85010151908181105f14610588575f52602052600160405f205b950194610557565b905f52602052600160405f20610580565b84907f0000000000000000000000000000000000000000000000000000000000000000036108c25760035464ffffffffff8116156108a8575b508260081c5f52600260205260405f20600160ff85161b81541790556001600160a01b035f54169160405161060681610d3a565b5f81525f602082015260405193610100850185811067ffffffffffffffff8211176108945760405284526020840183815260408501838152606086017f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316815260808701907f00000000000000000000000000000000000000000000000000000000000000001515825260a08801927f000000000000000000000000000000000000000000000000000000000000000015158452604051946106cf86610d3a565b60045464ffffffffff8116875260281c64ffffffffff16602087015260c08a0195865260e08a01968752604051997fab167ccc000000000000000000000000000000000000000000000000000000008b52516001600160a01b031660048b0152516001600160a01b031660248a0152516fffffffffffffffffffffffffffffffff166044890152516001600160a01b03166064880152511515608487015251151560a486015251805164ffffffffff1660c48601526020015164ffffffffff1660e48501525180516001600160a01b03166101048501526020015161012484015282807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165a925f61014492602095f1928315610889575f93610831575b50907f28b58397e03322f670d6b223cc863f8c148e368b8b615412e6798a641a22842d60406020958594825191825287820152a3604051908152f35b939250906020843d602011610881575b8161084e60209383610d56565b810103126100f15792519192907f28b58397e03322f670d6b223cc863f8c148e368b8b615412e6798a641a22842d6107f5565b3d9150610841565b6040513d5f823e3d90fd5b634e487b7160e01b5f52604160045260245ffd5b64ffffffffff19164264ffffffffff1617600355836105d2565b7ffe365d76000000000000000000000000000000000000000000000000000000005f5260045ffd5b8135815260209182019101610549565b847fcd4c86cc000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f99657b41000000000000000000000000000000000000000000000000000000005f524260045264ffffffffff7f00000000000000000000000000000000000000000000000000000000000000001660245260445ffd5b346100f1575f3660031901126100f157604060045464ffffffffff825191818116835260281c166020820152f35b346100f1575f3660031901126100f15760206040517f000000000000000000000000000000000000000000000000000000000000000015158152f35b346100f1575f3660031901126100f15760206040517f000000000000000000000000000000000000000000000000000000000000000015158152f35b346100f15760403660031901126100f157610a3c610d24565b6024356fffffffffffffffffffffffffffffffff81168091036100f1576001600160a01b035f5416338103610259575064ffffffffff6003541680151580610c5f575b80610c50575b610bf657506040515f806001600160a01b0360208401957fa9059cbb000000000000000000000000000000000000000000000000000000008752169485602485015284604485015260448452610adc606485610d56565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001693519082855af13d15610bea573d67ffffffffffffffff811161089457610b4f9160405191610b3f6020601f19601f8401160184610d56565b82523d5f602084013e5b83610db5565b8051908115159182610bc6575b5050610b9b57507f2e9d425ba8b27655048400b366d7b6a1f7180ebdb088e06bb7389704860ffe1f60206001600160a01b035f541692604051908152a3005b7f5274afe7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b81925090602091810103126100f157602001518015908115036100f1578480610b5c565b610b4f90606090610b49565b7f6df0cfdb000000000000000000000000000000000000000000000000000000005f524260045264ffffffffff7f00000000000000000000000000000000000000000000000000000000000000001660245260445260645ffd5b50610c59610d78565b15610a85565b5062093a80810164ffffffffff8111610c805764ffffffffff164211610a7f565b634e487b7160e01b5f52601160045260245ffd5b346100f1575f3660031901126100f157610346907f000000000000000000000000000000000000000000000000000000000000000060208201526020815261033a604082610d56565b9190916020815282518060208301525f5b818110610d0e575060409293505f838284010152601f8019910116010190565b8060208092870101516040828601015201610cee565b600435906001600160a01b03821682036100f157565b6040810190811067ffffffffffffffff82111761089457604052565b90601f8019910116810190811067ffffffffffffffff82111761089457604052565b64ffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168015159081610dad575090565b905042101590565b90610df25750805115610dca57805190602001fd5b7f1425ea42000000000000000000000000000000000000000000000000000000005f5260045ffd5b81511580610e38575b610e03575090565b6001600160a01b03907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b15610dfb56fea164736f6c634300081a000a610180806040523461044857611b53803803809161001d82856106ab565b83398101906060818303126104485780516001600160401b03811161044857810161010081840312610448576040519061010082016001600160401b038111838210176105025760405280516001600160a01b0381168103610448578252610087602082016106ce565b916020810192835261009b604083016106db565b60408201908152606083015193906001600160a01b0385168503610448576060830194855260808401516001600160401b03811161044857876100df918601610729565b916080840192835260a08501519360a0810194855260c086015160018060401b0381116104485760e06101178b610125938a01610729565b9760c08401988952016106ce565b60e082019081526020890151989097906001600160a01b038a168a03610448576040810151906001600160401b03821161044857018a601f82011215610448578051906001600160401b0382116105025760209b8c6040519d8e61018e828760051b01826106ab565b858152019360061b8301019181831161044857602001925b82841061064f5750505050865151602081116106385750515f80546001600160a01b0319166001600160a01b0392831617905590511660805251151560a0525164ffffffffff1660c052518051906001600160401b0382116105025760015490600182811c9216801561062e575b602083101461061a5781601f8493116105ac575b50602090601f8311600114610546575f9261053b575b50508160011b915f199060031b1c1916176001555b5160e05251604051610286602082816102758183019687815193849201610708565b81010301601f1981018352826106ab565b519051906020811061052a575b506101005251151561012052610140528051905f915f915b81831061044c57836101605260018060a01b036080511660018060a01b03610140511690604051905f806020840163095ea7b360e01b815285602486015281196044860152604485526102ff6064866106ab565b84519082855af161030e610782565b81610411575b5080610407575b156103c2575b6040516112d0908161088382396080518181816105590152818161088e0152610e8e015260a0518181816108b80152610daf015260c0518181816102a101528181610d0901528181610fcd0152611114015260e0518181816103ef01526107260152610100518161103e0152610120518181816108ea0152610d7301526101405181818161011f01526109d00152610160518181816102e5015261060a0152f35b6103fa6103ff936040519063095ea7b360e01b602083015260248201525f6044820152604481526103f46064826106ab565b826107b1565b6107b1565b808080610321565b50803b151561031b565b8051801592508215610426575b505084610314565b819250906020918101031261044857602061044191016106ce565b848061041e565b5f80fd5b91929091906001600160401b03610463858461076e565b5151166001600160401b03918216019081116105165792610484818361076e565b519060045491680100000000000000008310156105025760018301806004558310156104ee5760019260045f5260205f200190838060401b038151166cffffffffff00000000000000006020845493015160401b1691858060681b031916171790550191906102ab565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52604160045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b5f199060200360031b1b165f610293565b015190505f8061023e565b60015f9081528281209350601f198516905b818110610594575090846001959493921061057c575b505050811b01600155610253565b01515f1960f88460031b161c191690555f808061056e565b92936020600181928786015181550195019301610558565b60015f529091507fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6601f840160051c81019160208510610610575b90601f859493920160051c01905b8181106106025750610228565b5f81558493506001016105f5565b90915081906105e7565b634e487b7160e01b5f52602260045260245ffd5b91607f1691610214565b630f39860960e01b5f52600452602060245260445ffd5b6040848303126104485760408051919082016001600160401b03811183821017610502576040528451906001600160401b038216820361044857826020926040945261069c8388016106db565b838201528152019301926101a6565b601f909101601f19168101906001600160401b0382119082101761050257604052565b5190811515820361044857565b519064ffffffffff8216820361044857565b6001600160401b03811161050257601f01601f191660200190565b5f5b8381106107195750505f910152565b818101518382015260200161070a565b81601f8201121561044857805161073f816106ed565b9261074d60405194856106ab565b818452602082840101116104485761076b9160208085019101610708565b90565b80518210156104ee5760209160051b010190565b3d156107ac573d90610793826106ed565b916107a160405193846106ab565b82523d5f602084013e565b606090565b5f806107d99260018060a01b03169360208151910182865af16107d2610782565b9083610824565b8051908115159182610801575b50506107ef5750565b635274afe760e01b5f5260045260245ffd5b819250906020918101031261044857602061081c91016106ce565b155f806107e6565b90610848575080511561083957805190602001fd5b630a12f52160e11b5f5260045ffd5b81511580610879575b610859575090565b639996b31560e01b5f9081526001600160a01b0391909116600452602490fd5b50803b1561085156fe6080806040526004361015610012575f80fd5b5f3560e01c90816306fdde0314611028575080631686c90914610dd457806316c3549d14610d985780631bfd681414610d5c5780633f31ae3f1461057d5780634800d97f1461053a57806349fc73dd146104365780634e390d3e1461041257806351e75e8b146103d857806375829def1461032357806390e64d1314610309578063936c63d9146102c5578063bb4b573414610284578063bf4ed03f14610183578063ce51650714610143578063da792468146101005763f851a440146100d7575f80fd5b346100fc575f3660031901126100fc5760206001600160a01b035f5416604051908152f35b5f80fd5b346100fc575f3660031901126100fc5760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b346100fc5760203660031901126100fc57602061017960043560ff6001918060081c5f526002602052161b60405f205416151590565b6040519015158152f35b346100fc575f3660031901126100fc5760045461019f81611149565b906101ad60405192836110ea565b80825260045f9081526020830191907f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b835b838310610247578486604051918291602083019060208452518091526040830191905f5b818110610211575050500390f35b8251805167ffffffffffffffff16855260209081015164ffffffffff168186015286955060409094019390920191600101610203565b600160208192604051610259816110ce565b64ffffffffff865467ffffffffffffffff8116835260401c16838201528152019201920191906101df565b346100fc575f3660031901126100fc57602060405164ffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b346100fc575f3660031901126100fc57602060405167ffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b346100fc575f3660031901126100fc57602061017961110c565b346100fc5760203660031901126100fc5761033c6110b8565b5f546001600160a01b0381163381036103a957506001600160a01b037fffffffffffffffffffffffff0000000000000000000000000000000000000000921691829116175f55337fbdd36143ee09de60bdefca70680e0f71189b2ed7acee364b53917ad433fdaf805f80a3005b7fc6cce6a4000000000000000000000000000000000000000000000000000000005f526004523360245260445ffd5b346100fc575f3660031901126100fc5760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346100fc575f3660031901126100fc57602064ffffffffff60035416604051908152f35b346100fc575f3660031901126100fc576040515f6001548060011c90600181168015610530575b60208310811461051c578285529081156104f8575060011461049a575b6104968361048a818503826110ea565b60405191829182611071565b0390f35b91905060015f527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6915f905b8082106104de5750909150810160200161048a61047a565b9192600181602092548385880101520191019092916104c6565b60ff191660208086019190915291151560051b8401909101915061048a905061047a565b634e487b7160e01b5f52602260045260245ffd5b91607f169161045d565b346100fc575f3660031901126100fc5760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b346100fc5760803660031901126100fc57600435602435906001600160a01b0382168092036100fc57604435916001600160801b038316918284036100fc576064359367ffffffffffffffff85116100fc57366023860112156100fc5784600401359467ffffffffffffffff86116100fc5760248660051b8201013681116100fc5767ffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016670de0b6b3a76400008103610d3157506040516020810190858252866040820152876060820152606081526106606080826110ea565b519020604051602081019182526020815261067c6040826110ea565b5190209161068861110c565b610cda576106ad8560ff6001918060081c5f526002602052161b60405f205416151590565b610cae576106ba88611149565b976106c8604051998a6110ea565b8852602401602088015b828210610c9e57505050925f935b8651851015610722576106f38588611161565b519081811015610711575f52602052600160405f205b9401936106e0565b905f52602052600160405f20610709565b85907f000000000000000000000000000000000000000000000000000000000000000003610c765760035464ffffffffff811615610c5c575b506004549261076984611149565b9361077760405195866110ea565b80855260045f9081527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b602087015b838310610c1f57505050505f84516107bd81611149565b956107cb60405197886110ea565b818752601f196107da83611149565b015f5b818110610bfc5750505f905b828210610b275750506001600160801b038216848111610b13578411610ae8575b5050508360081c5f52600260205260405f20600160ff86161b81541790556001600160a01b035f541692604051610840816110ce565b5f81525f60208201526040519161010083019583871067ffffffffffffffff881117610ad4576001600160a01b039660409492939452815260208101918583526040820185815260608301887f00000000000000000000000000000000000000000000000000000000000000001681528860808501917f0000000000000000000000000000000000000000000000000000000000000000151583526001600160801b0360a08701947f00000000000000000000000000000000000000000000000000000000000000001515865260c0880196875260e08801998a526040519c8d997f897f362b000000000000000000000000000000000000000000000000000000008b52602060048c0152816101448c019a511660248c0152511660448a0152511660648801525116608486015251151560a485015251151560c4840152519061012060e48401528151809152602061016484019201905f5b818110610a9f57505050819060208094516001600160a01b03815116610104850152015161012483015203815f6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165af1928315610a94575f93610a3c575b50907f28b58397e03322f670d6b223cc863f8c148e368b8b615412e6798a641a22842d60406020958594825191825287820152a3604051908152f35b939250906020843d602011610a8c575b81610a59602093836110ea565b810103126100fc5792519192907f28b58397e03322f670d6b223cc863f8c148e368b8b615412e6798a641a22842d610a00565b3d9150610a4c565b6040513d5f823e3d90fd5b825180516001600160801b0316855260209081015164ffffffffff168186015289955060409094019390920191600101610999565b634e487b7160e01b5f52604160045260245ffd5b6001600160801b0391610aff83925f190188611161565b51930316818351160116905284808061080a565b634e487b7160e01b5f52600160045260245ffd5b9092610b4867ffffffffffffffff610b3f8685611161565b51511687611189565b6001600160801b038111610bd1576001600160801b038091169164ffffffffff6020610b748887611161565b5101511660405190610b85826110ce565b8482526020820152610b97878c611161565b52610ba2868b611161565b5016016001600160801b038111610bbd5792600101906107e9565b634e487b7160e01b5f52601160045260245ffd5b7f4916adce000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b602090604051610c0b816110ce565b5f81525f8382015282828c010152016107dd565b600160208192604051610c31816110ce565b64ffffffffff865467ffffffffffffffff8116835260401c16838201528152019201920191906107a6565b64ffffffffff19164264ffffffffff16176003558461075b565b7ffe365d76000000000000000000000000000000000000000000000000000000005f5260045ffd5b81358152602091820191016106d2565b847fcd4c86cc000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7f99657b41000000000000000000000000000000000000000000000000000000005f524260045264ffffffffff7f00000000000000000000000000000000000000000000000000000000000000001660245260445ffd5b7f36d385ef000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b346100fc575f3660031901126100fc5760206040517f000000000000000000000000000000000000000000000000000000000000000015158152f35b346100fc575f3660031901126100fc5760206040517f000000000000000000000000000000000000000000000000000000000000000015158152f35b346100fc5760403660031901126100fc57610ded6110b8565b6024356001600160801b0381168091036100fc576001600160a01b035f54163381036103a9575064ffffffffff6003541680151580611007575b80610ff8575b610f9e57506040515f806001600160a01b0360208401957fa9059cbb000000000000000000000000000000000000000000000000000000008752169485602485015284604485015260448452610e846064856110ea565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001693519082855af13d15610f92573d67ffffffffffffffff8111610ad457610ef79160405191610ee76020601f19601f84011601846110ea565b82523d5f602084013e5b83611237565b8051908115159182610f6e575b5050610f4357507f2e9d425ba8b27655048400b366d7b6a1f7180ebdb088e06bb7389704860ffe1f60206001600160a01b035f541692604051908152a3005b7f5274afe7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b81925090602091810103126100fc57602001518015908115036100fc578480610f04565b610ef790606090610ef1565b7f6df0cfdb000000000000000000000000000000000000000000000000000000005f524260045264ffffffffff7f00000000000000000000000000000000000000000000000000000000000000001660245260445260645ffd5b5061100161110c565b15610e2d565b5062093a80810164ffffffffff8111610bbd5764ffffffffff164211610e27565b346100fc575f3660031901126100fc57610496907f000000000000000000000000000000000000000000000000000000000000000060208201526020815261048a6040826110ea565b9190916020815282518060208301525f5b8181106110a2575060409293505f838284010152601f8019910116010190565b8060208092870101516040828601015201611082565b600435906001600160a01b03821682036100fc57565b6040810190811067ffffffffffffffff821117610ad457604052565b90601f8019910116810190811067ffffffffffffffff821117610ad457604052565b64ffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168015159081611141575090565b905042101590565b67ffffffffffffffff8111610ad45760051b60200190565b80518210156111755760209160051b010190565b634e487b7160e01b5f52603260045260245ffd5b9091905f198382098382029182808310920391808303921461122657670de0b6b3a76400008210156111f6577faccb18165bd6fe31ae1cf318dc5b51eee0e1ba569b88cd74c1773b91fac106699394670de0b6b3a7640000910990828211900360ee1b910360121c170290565b84907f5173648d000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b5050670de0b6b3a764000090049150565b90611274575080511561124c57805190602001fd5b7f1425ea42000000000000000000000000000000000000000000000000000000005f5260045ffd5b815115806112ba575b611285575090565b6001600160a01b03907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b1561127d56fea164736f6c634300081a000aa164736f6c634300081a000a"; + hex"608034609357601f615a8a38819003918201601f19168301916001600160401b03831184841017609757808492602094604052833981010312609357516001600160a01b038116908190036093575f80546001600160a01b0319168217815560405191907fbdd36143ee09de60bdefca70680e0f71189b2ed7acee364b53917ad433fdaf808180a36159de90816100ac8239f35b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe60806040526004361015610011575f80fd5b5f3560e01c8063070a6b8414610d0e5780631134f19414610cf157806345aa9357146109735780634d7c0f11146108a05780636f71d8f41461083057806375829def146107aa5780638af96dfe14610723578063b3ee17cc146102f9578063d498fcf514610265578063f255527814610135578063f851a440146101105763f946eef21461009d575f80fd5b3461010c57602036600319011261010c576004356001600160a01b035f54163381036100f65750806001556040519081527f7d41b7bcff330c958826f81c5de08357f286914f0758727ca23717d24f158fcc60203392a2005b6331b339a960e21b5f526004523360245260445ffd5b5f80fd5b3461010c575f36600319011261010c5760206001600160a01b035f5416604051908152f35b3461010c57604036600319011261010c576004356001600160a01b03811680910361010c57602435906001600160a01b03821680920361010c576001600160a01b035f54163381036100f65750801561023d576040517f164e68de0000000000000000000000000000000000000000000000000000000081528160048201526020816024815f875af1908115610232575f91610200575b5060405191825260208201527ff65e64e219c3199240f0fe17a71d01821ed2dd08283833094e12a9ad6883786560403392a3005b90506020813d60201161022a575b8161021b60209383610f14565b8101031261010c5751836101cc565b3d915061020e565b6040513d5f823e3d90fd5b7f38da7036000000000000000000000000000000000000000000000000000000005f5260045ffd5b3461010c57604036600319011261010c5761027e610f5c565b602435906001600160a01b035f54163381036100f657506001600160a01b031690815f52600260205280600160405f20805460ff8116156102eb575b5001556040519081527f314737fce28d10af73c184056111a030c8124d005478d4a11f5978545779ed9a60203392a3005b60ff191682178155856102ba565b3461010c5761010036600319011261010c5760043567ffffffffffffffff811161010c5761032b903690600401610fc8565b610333611089565b9061033c61109f565b906103456110ae565b9061034e610f36565b60a4359167ffffffffffffffff831161010c573660238401121561010c57826004013567ffffffffffffffff8111610683576040519361039460208360051b0186610f14565b8185526024602086019260061b8201019036821161010c57602401915b8183106106d65750505082515f905f905b808210610697575050336103d5906111e3565b825160208401516040850151908760608701519a604051809c602082016020905260408201610403916110de565b03601f1981018d52610415908d610f14565b6080880151908d60a08a015160405181819251908160208401916020019161043c926110bd565b81010380825261044f9060200182610f14565b61045890611103565b6040519e8f806020810197602089526040820161047491611193565b03601f198101825261048591610f14565b604051978897602089019a3360601b8c526bffffffffffffffffffffffff199060601b1660348a015260d81b6001600160d81b03191660488901526bffffffffffffffffffffffff199060601b16604d880152805190816061890191602001916104ee926110bd565b860193606185015260818401526001600160a01b03169d6bffffffffffffffffffffffff199060601b1660a183015215159a8b60f81b60b583015215159b8c60f81b60b683015264ffffffffff8a169960d81b6001600160d81b03191660b783015251918260bc8301610560926110bd565b0160610103605b01601f19810182526105799082610f14565b519020604051611e7b80820182811067ffffffffffffffff821117610683578460c06105de8e8e8e8897613b57893960e087526105ba8d60e0890190611125565b926020880152604087015260608601528b608086015284810360a08601528c611193565b92015203905ff58015610232576020987f4507eb2a4efc5eacd7bf717901bdc881a42a88f0e7c45a050d6d1375521883649661063a966001600160a01b0361065b94169a8b9a604051998a996101408b526101408b0190611125565b948e8a015260408901526060880152608087015285820360a0870152611193565b9160c084015260c43560e084015260e4356101008401526101208301520390a2604051908152f35b634e487b7160e01b5f52604160045260245ffd5b909185518310156106c25760019064ffffffffff6020808660051b8a010151015116019201906103c2565b634e487b7160e01b5f52603260045260245ffd5b60408336031261010c57604051906106ed82610ef8565b83359067ffffffffffffffff8216820361010c578260209260409452610714838701610f4a565b838201528152019201916103b1565b3461010c57602036600319011261010c5761073c610f5c565b6001600160a01b035f541690338203610793576001600160a01b0316805f5260026020525f6001604082208281550155337f8cebf46c84bf401fa7816b701dfac1f1fdf41e6e601b9a501e7639d2aff5582f5f80a3005b506331b339a960e21b5f526004523360245260445ffd5b3461010c57602036600319011261010c576107c3610f5c565b5f546001600160a01b0381163381036100f657506001600160a01b037fffffffffffffffffffffffff0000000000000000000000000000000000000000921691829116175f55337fbdd36143ee09de60bdefca70680e0f71189b2ed7acee364b53917ad433fdaf805f80a3005b3461010c57602036600319011261010c576001600160a01b03610851610f5c565b5f602060405161086081610ef8565b8281520152165f5260026020526040805f20815161087d81610ef8565b6020600160ff845416151593848452015491019081528251918252516020820152f35b3461010c57602036600319011261010c5760043567ffffffffffffffff811161010c573660238201121561010c5780600401359067ffffffffffffffff821161010c573660248360061b8301011161010c575f90815b838310156109545760248360061b830101359067ffffffffffffffff821680920361010c5767ffffffffffffffff160167ffffffffffffffff8111610940576001909201916108f6565b634e487b7160e01b5f52601160045260245ffd5b602090670de0b6b3a764000067ffffffffffffffff6040519216148152f35b3461010c5761012036600319011261010c5760043567ffffffffffffffff811161010c576109a5903690600401610fc8565b6109ad611089565b6109b561109f565b906109be6110ae565b90606036608319011261010c576040516060810181811067ffffffffffffffff821117610683576040526109f0610f36565b815260a43564ffffffffff8116810361010c57602082015260c43564ffffffffff8116810361010c576040820152845160208601516040870151906060880151966040519081602081019960208b5260408201610a4c916110de565b03601f1981018352610a5e9083610f14565b60808a01519060a08b0151604051818192519081602084019160200191610a84926110bd565b810103808252610a979060200182610f14565b610aa090611103565b6040519a60208c0194610ad2868b64ffffffffff60408092828151168552826020820151166020860152015116910152565b60608d52610ae160808e610f14565b604051978897602089019a3360601b8c526bffffffffffffffffffffffff199060601b1660348a015260d81b6001600160d81b03191660488901526bffffffffffffffffffffffff199060601b16604d88015251908160618801610b44926110bd565b850192606184015260818301526001600160a01b038816976bffffffffffffffffffffffff199060601b1660a18301521515978860f81b60b58301521515988960f81b60b683015251918260b78301610b9c926110bd565b0160610103605601601f1981018252610bb59082610f14565b51902094610bc2336111e3565b9560405161182380820182811067ffffffffffffffff821117610683578291612334833961010081528960e0610bfc610100840188611125565b928960208201528a60408201528b6060820152610c3b608082018a64ffffffffff60408092828151168552826020820151166020860152015116910152565b015203905ff592831561023257602096610cce610c96946001600160a01b037fe7c4d633a3f64a821f1283e2a399b368e633c17776b3cb932e4105d76ca299e397169889986040519788976101408952610140890190611125565b958c88015260408701526060860152608085019064ffffffffff60408092828151168552826020820151166020860152015116910152565b60e43560e0840152610104356101008401526101208301520390a2604051908152f35b3461010c575f36600319011261010c576020600154604051908152f35b3461010c57606036600319011261010c5760043567ffffffffffffffff811161010c57610d3f903690600401610fc8565b80516001600160d81b0319610e3e60406061602086015182870151606088015190845191610d8a83610d7c602082019360208552898301906110de565b03601f198101855284610f14565b610e2360808b015193610dcc60a08d0151610dc760208b5183610db682955180928580860191016110bd565b81010301601f198101835282610f14565b611103565b9388519a8b97602089019d8e3360601b90526bffffffffffffffffffffffff199060601b1660348a015260d81b1660488801526bffffffffffffffffffffffff199060601b16604d870152518092878701906110bd565b83019184830152608182015203016020810184520182610f14565b51902090610e4b336111e3565b9160405161111880820182811067ffffffffffffffff82111761068357829161121c833960408152856020610e836040840188611125565b92015203905ff5918215610232577f12188a644c16704b9e77e2ecd3bb77e911693c22ffb0fcf44d9e4b36638b5d01610ed86001600160a01b0360209516938493604051928392608084526080840190611125565b9060243588840152604435604084015260608301520390a2604051908152f35b6040810190811067ffffffffffffffff82111761068357604052565b90601f8019910116810190811067ffffffffffffffff82111761068357604052565b6084359064ffffffffff8216820361010c57565b359064ffffffffff8216820361010c57565b600435906001600160a01b038216820361010c57565b81601f8201121561010c5780359067ffffffffffffffff82116106835760405192610fa7601f8401601f191660200185610f14565b8284526020838301011161010c57815f926020809301838601378301015290565b919060c08382031261010c576040519060c0820182811067ffffffffffffffff82111761068357604052819380356001600160a01b038116810361010c57835261101460208201610f4a565b602084015260408101356001600160a01b038116810361010c576040840152606081013567ffffffffffffffff811161010c5782611053918301610f72565b60608401526080810135608084015260a08101359167ffffffffffffffff831161010c5760a0926110849201610f72565b910152565b602435906001600160a01b038216820361010c57565b60443590811515820361010c57565b60643590811515820361010c57565b5f5b8381106110ce5750505f910152565b81810151838201526020016110bf565b906020916110f7815180928185528580860191016110bd565b601f01601f1916010190565b602081519101519060208110611117575090565b5f199060200360031b1b1690565b611190916001600160a01b03825116815264ffffffffff60208301511660208201526001600160a01b03604083015116604082015260a0611175606084015160c0606085015260c08401906110de565b926080810151608084015201519060a08184039101526110de565b90565b90602080835192838152019201905f5b8181106111b05750505090565b8251805167ffffffffffffffff16855260209081015164ffffffffff1681860152604090940193909201916001016111a3565b6001600160a01b0316805f52600260205260ff60405f2054165f14611214575f526002602052600160405f20015490565b506001549056fe61014080604052346103d457611118803803809161001d82856103d8565b83398101906040818303126103d45780516001600160401b0381116103d45781019060c0828403126103d4576040519160c083016001600160401b038111848210176103a95760405280516001600160a01b03811681036103d4578352602081015164ffffffffff811681036103d457602084019081526040820151936001600160a01b03851685036103d4576040810194855260608301516001600160401b0381116103d457866100d091850161041c565b92606082019384526080810151966080830197885260a082015160018060401b0381116103d457610101920161041c565b60a082018181526020959095015195515f80546001600160a01b0319166001600160a01b039290921691821781557fbdd36143ee09de60bdefca70680e0f71189b2ed7acee364b53917ad433fdaf808180a351602081116103bd5750516001600160a01b03166080525164ffffffffff1660a0523360c0525180519093906001600160401b0381116103a957600154600181811c9116801561039f575b602082101461038b57601f8111610328575b50602094601f82116001146102c5579481929394955f926102ba575b50508160011b915f199060031b1c1916176001555b5160e052516040516102146020828161020381830196878151938492016103fb565b81010301601f1981018352826103d8565b51905190602081106102a9575b506101005261012052604051610ca7908161047182396080518181816103f50152818161062a0152610811015260a051818181610181015281816107370152818161089f0152610b07015260c05181818161077e015261093d015260e05181818161028b01526105af015261010051816109fa01526101205181818161014101526104af0152f35b5f199060200360031b1b165f610221565b015190505f806101cc565b601f1982169560015f52805f20915f5b888110610310575083600195969798106102f8575b505050811b016001556101e1565b01515f1960f88460031b161c191690555f80806102ea565b919260206001819286850151815501940192016102d5565b60015f527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6601f830160051c81019160208410610381575b601f0160051c01905b81811061037657506101b0565b5f8155600101610369565b9091508190610360565b634e487b7160e01b5f52602260045260245ffd5b90607f169061019e565b634e487b7160e01b5f52604160045260245ffd5b6371935f2960e11b5f52600452602060245260445ffd5b5f80fd5b601f909101601f19168101906001600160401b038211908210176103a957604052565b5f5b83811061040c5750505f910152565b81810151838201526020016103fd565b81601f820112156103d45780516001600160401b0381116103a9576040519261044f601f8301601f1916602001856103d8565b818452602082840101116103d45761046d91602080850191016103fb565b9056fe6080806040526004361015610012575f80fd5b5f3560e01c90816306fdde03146109e457508063164e68de1461090e5780631686c909146107a25780632dd310001461075f5780633f31ae3f146104195780634800d97f146103d657806349fc73dd146102d25780634e390d3e146102ae57806351e75e8b1461027457806375829def146101bf57806390e64d13146101a5578063bb4b573414610164578063bba72cd31461012a578063ce516507146100ea5763f851a440146100c1575f80fd5b346100e6575f3660031901126100e65760206001600160a01b035f5416604051908152f35b5f80fd5b346100e65760203660031901126100e657602061012060043560ff6001918060081c5f526002602052161b60405f205416151590565b6040519015158152f35b346100e6575f3660031901126100e65760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346100e6575f3660031901126100e657602060405164ffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b346100e6575f3660031901126100e6576020610120610aff565b346100e65760203660031901126100e6576101d8610a74565b5f546001600160a01b03811633810361024557506001600160a01b037fffffffffffffffffffffffff0000000000000000000000000000000000000000921691829116175f55337fbdd36143ee09de60bdefca70680e0f71189b2ed7acee364b53917ad433fdaf805f80a3005b7fc6cce6a4000000000000000000000000000000000000000000000000000000005f526004523360245260445ffd5b346100e6575f3660031901126100e65760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b346100e6575f3660031901126100e657602064ffffffffff60035416604051908152f35b346100e6575f3660031901126100e6576040515f6001548060011c906001811680156103cc575b6020831081146103b8578285529081156103945750600114610336575b6103328361032681850382610a8a565b60405191829182610a2d565b0390f35b91905060015f527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6915f905b80821061037a57509091508101602001610326610316565b919260018160209254838588010152019101909291610362565b60ff191660208086019190915291151560051b840190910191506103269050610316565b634e487b7160e01b5f52602260045260245ffd5b91607f16916102f9565b346100e6575f3660031901126100e65760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b60803660031901126100e657600435602435906001600160a01b038216918281036100e657604435926fffffffffffffffffffffffffffffffff84168094036100e6576064359367ffffffffffffffff85116100e657366023860112156100e657846004013567ffffffffffffffff81116100e6578060051b95602487820101903682116100e6576104a9610aff565b610708577f00000000000000000000000000000000000000000000000000000000000000008034106106d957506104f78760ff6001918060081c5f526002602052161b60405f205416151590565b6106ad57604051602081019088825286604082015285606082015260608152610521608082610a8a565b519020604051602081019182526020815261053d604082610a8a565b5190209261055160206040519a018a610a8a565b8852602401602088015b82821061069d57505050925f935b86518510156105ab5760208560051b88010151908181105f1461059a575f52602052600160405f205b940193610569565b905f52602052600160405f20610592565b85907f000000000000000000000000000000000000000000000000000000000000000003610675578261064e7f1dcd2362ae467d43bf31cbcac0526c0958b23eb063e011ab49a5179c839ed9a99460409460035464ffffffffff81161561065b575b508460081c5f526002602052855f20600160ff87161b81541790557f0000000000000000000000000000000000000000000000000000000000000000610b3c565b82519182526020820152a2005b64ffffffffff19164264ffffffffff16176003558861060d565b7fb4f06787000000000000000000000000000000000000000000000000000000005f5260045ffd5b813581526020918201910161055b565b867febe6f30d000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7fa164c6b4000000000000000000000000000000000000000000000000000000005f523460045260245260445ffd5b7fdf4bae05000000000000000000000000000000000000000000000000000000005f524260045264ffffffffff7f00000000000000000000000000000000000000000000000000000000000000001660245260445ffd5b346100e6575f3660031901126100e65760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b346100e65760403660031901126100e6576107bb610a74565b602435906fffffffffffffffffffffffffffffffff82168092036100e6576001600160a01b035f5416338103610245575064ffffffffff60035416801515806108d9575b806108ca575b610870575061083582827f0000000000000000000000000000000000000000000000000000000000000000610b3c565b7f2e9d425ba8b27655048400b366d7b6a1f7180ebdb088e06bb7389704860ffe1f60206001600160a01b03805f5416936040519586521693a3005b7fe2e40a0c000000000000000000000000000000000000000000000000000000005f524260045264ffffffffff7f00000000000000000000000000000000000000000000000000000000000000001660245260445260645ffd5b506108d3610aff565b15610805565b5062093a80810164ffffffffff81116108fa5764ffffffffff1642116107ff565b634e487b7160e01b5f52601160045260245ffd5b346100e65760203660031901126100e6576004356001600160a01b0381168091036100e6576001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168033036109b5575047905f80808085855af1610977610ac0565b501561098857602082604051908152f35b7e534073000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b7f4e3ddeed000000000000000000000000000000000000000000000000000000005f526004523360245260445ffd5b346100e6575f3660031901126100e657610332907f0000000000000000000000000000000000000000000000000000000000000000602082015260208152610326604082610a8a565b9190916020815282518060208301525f5b818110610a5e575060409293505f838284010152601f8019910116010190565b8060208092870101516040828601015201610a3e565b600435906001600160a01b03821682036100e657565b90601f8019910116810190811067ffffffffffffffff821117610aac57604052565b634e487b7160e01b5f52604160045260245ffd5b3d15610afa573d9067ffffffffffffffff8211610aac5760405191610aef601f8201601f191660200184610a8a565b82523d5f602084013e565b606090565b64ffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168015159081610b34575090565b905042101590565b5f610ba9926001600160a01b038293604051968260208901947fa9059cbb000000000000000000000000000000000000000000000000000000008652166024890152604488015260448752610b92606488610a8a565b1694519082865af1610ba2610ac0565b9083610c0e565b8051908115159182610bea575b5050610bbf5750565b7f5274afe7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b81925090602091810103126100e657602001518015908115036100e6575f80610bb6565b90610c4b5750805115610c2357805190602001fd5b7f1425ea42000000000000000000000000000000000000000000000000000000005f5260045ffd5b81511580610c91575b610c5c575090565b6001600160a01b03907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b15610c5456fea164736f6c634300081a000a6101a0806040523461049a57611823803803809161001d82856105d8565b8339810190808203610100811261049a5781516001600160401b03811161049a5782019160c08385031261049a576040519160c083016001600160401b038111848210176105ad5760405283516001600160a01b038116810361049a578352610088602085016105fb565b6020840190815260408501519091906001600160a01b038116810361049a576040850190815260608601516001600160401b03811161049a57876100cd918801610649565b95606086019687526080810151976080870198895260a082015160018060401b03811161049a576100fe9201610649565b60a0860190815260208501516001600160a01b038116979092909188840361049a5761012c6040880161068e565b92606061013a818a0161068e565b96607f19011261049a5760405196606088016001600160401b038111898210176105ad5760405261016d60808a016105fb565b885261017b60a08a016105fb565b99602089019a8b5260e061019160c08c016105fb565b60408b019081529a015194515f80546001600160a01b0319166001600160a01b039290921691821781557fbdd36143ee09de60bdefca70680e0f71189b2ed7acee364b53917ad433fdaf808180a3835151602081116105c15750516001600160a01b03166080525164ffffffffff1660a0523360c052518051909a906001600160401b0381116105ad57600154600181811c911680156105a3575b602082101461058f57601f811161052c575b506020601f82116001146104ba57819064ffffffffff9a9b9c9d5f926104af575b50508160011b915f199060031b1c1916176001555b5160e052516040516102a7602082816102968183019687815193849201610628565b81010301601f1981018352826105d8565b519051906020811061049e575b50610100526101205261014052610160526101805251166effffffffff0000000000000000000069ffffffffff0000000000600454945160281b16925160501b169260018060781b03191617171760045560018060a01b0360805116604051905f806020840163095ea7b360e01b815285602486015281196044860152604485526103406064866105d8565b84519082855af161034f61069b565b81610463575b5080610459575b15610414575b604051611087908161079c823960805181818161049e015281816107830152610c65015260a0518181816101ad01528181610abc01528181610d650152610fb9015260c051818181610b030152610e03015260e051818181610334015261065601526101005181610ec001526101205181818161016d01526105560152610140518181816107b40152610b7a01526101605181818161024401526108dd0152610180518181816107de0152610b3e0152f35b61044c610451936040519063095ea7b360e01b602083015260248201525f6044820152604481526104466064826105d8565b826106ca565b6106ca565b5f8080610362565b50803b151561035c565b8051801592508215610478575b50505f610355565b819250906020918101031261049a576020610493910161068e565b5f80610470565b5f80fd5b5f199060200360031b1b165f6102b4565b015190505f8061025f565b601f1982169c60015f52815f209d5f5b81811061050f57509164ffffffffff9b9c9d9e918460019594106104f7575b505050811b01600155610274565b01515f1960f88460031b161c191690555f80806104e9565b919e8f60016020928684930151815501940192019e91929e6104ca565b60015f527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6601f830160051c81019160208410610585575b601f0160051c01905b81811061057a575061023e565b5f815560010161056d565b9091508190610564565b634e487b7160e01b5f52602260045260245ffd5b90607f169061022c565b634e487b7160e01b5f52604160045260245ffd5b6371935f2960e11b5f52600452602060245260445ffd5b601f909101601f19168101906001600160401b038211908210176105ad57604052565b519064ffffffffff8216820361049a57565b6001600160401b0381116105ad57601f01601f191660200190565b5f5b8381106106395750505f910152565b818101518382015260200161062a565b81601f8201121561049a57805161065f8161060d565b9261066d60405194856105d8565b8184526020828401011161049a5761068b9160208085019101610628565b90565b5190811515820361049a57565b3d156106c5573d906106ac8261060d565b916106ba60405193846105d8565b82523d5f602084013e565b606090565b5f806106f29260018060a01b03169360208151910182865af16106eb61069b565b908361073d565b805190811515918261071a575b50506107085750565b635274afe760e01b5f5260045260245ffd5b819250906020918101031261049a576020610735910161068e565b155f806106ff565b90610761575080511561075257805190602001fd5b630a12f52160e11b5f5260045ffd5b81511580610792575b610772575090565b639996b31560e01b5f9081526001600160a01b0391909116600452602490fd5b50803b1561076a56fe6080806040526004361015610012575f80fd5b5f3560e01c90816306fdde0314610eaa57508063164e68de14610dd45780631686c90914610b9f57806316c3549d14610b635780631bfd681414610b275780632dd3100014610ae45780633f31ae3f146104c25780634800d97f1461047f57806349fc73dd1461037b5780634e390d3e1461035757806351e75e8b1461031d57806375829def14610268578063845aef4b1461022557806390e64d131461020b578063b0604a26146101d1578063bb4b573414610190578063bba72cd314610156578063ce516507146101165763f851a440146100ed575f80fd5b34610112575f3660031901126101125760206001600160a01b035f5416604051908152f35b5f80fd5b3461011257602036600319011261011257602061014c60043560ff6001918060081c5f526002602052161b60405f205416151590565b6040519015158152f35b34610112575f3660031901126101125760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b34610112575f36600319011261011257602060405164ffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b34610112575f36600319011261011257606060045464ffffffffff604051918181168352818160281c16602084015260501c166040820152f35b34610112575f36600319011261011257602061014c610fb1565b34610112575f3660031901126101125760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b3461011257602036600319011261011257610281610f3a565b5f546001600160a01b0381163381036102ee57506001600160a01b037fffffffffffffffffffffffff0000000000000000000000000000000000000000921691829116175f55337fbdd36143ee09de60bdefca70680e0f71189b2ed7acee364b53917ad433fdaf805f80a3005b7fc6cce6a4000000000000000000000000000000000000000000000000000000005f526004523360245260445ffd5b34610112575f3660031901126101125760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b34610112575f36600319011261011257602064ffffffffff60035416604051908152f35b34610112575f366003190112610112576040515f6001548060011c90600181168015610475575b6020831081146104615782855290811561043d57506001146103df575b6103db836103cf81850382610f50565b60405191829182610ef3565b0390f35b91905060015f527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6915f905b808210610423575090915081016020016103cf6103bf565b91926001816020925483858801015201910190929161040b565b60ff191660208086019190915291151560051b840190910191506103cf90506103bf565b634e487b7160e01b5f52602260045260245ffd5b91607f16916103a2565b34610112575f3660031901126101125760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b608036600319011261011257600435602435906001600160a01b038216809203610112576044356fffffffffffffffffffffffffffffffff8116809103610112576064359067ffffffffffffffff8211610112573660238301121561011257816004013567ffffffffffffffff8111610112578060051b926024848201019036821161011257610550610fb1565b610a8d577f0000000000000000000000000000000000000000000000000000000000000000803410610a5e575061059e8660ff6001918060081c5f526002602052161b60405f205416151590565b610a32576040516020810190878252886040820152856060820152606081526105c8608082610f50565b51902060405160208101918252602081526105e4604082610f50565b519020926105f86020604051970187610f50565b8552602401602085015b828210610a2257505050935f945b83518610156106525760208660051b85010151908181105f14610641575f52602052600160405f205b950194610610565b905f52602052600160405f20610639565b84907f0000000000000000000000000000000000000000000000000000000000000000036109fa5760035464ffffffffff8116156109e0575b508060081c5f52600260205260405f20600160ff83161b815417905560405192606084019380851067ffffffffffffffff8611176109b057604094855260208101905f82526004549064ffffffffff821680155f146109d9575064ffffffffff421681525b64ffffffffff8260281c16806109c4575b5064ffffffffff80808351169360501c1683011696879101526001600160a01b035f541695604051916040830183811067ffffffffffffffff8211176109b0576040525f83525f602084015260405197610120890189811067ffffffffffffffff8211176109b057604052885260208801938785526040890186815260608a017f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316815260808b01917f00000000000000000000000000000000000000000000000000000000000000001515835260a08c01937f00000000000000000000000000000000000000000000000000000000000000001515855260c08d0195865260e08d019687526101008d019788525164ffffffffff1697604051809d7f9e7f5dbb000000000000000000000000000000000000000000000000000000008252516001600160a01b03169060040152516001600160a01b031660248d0152516fffffffffffffffffffffffffffffffff1660448c0152516001600160a01b031660648b015251151560848a015251151560a48901525164ffffffffff1660c48801525164ffffffffff1660e48701525180516001600160a01b03166101048701526020015161012486015261014485015283807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165a925f61016492602095f19384156109a5575f94610951575b507f28b58397e03322f670d6b223cc863f8c148e368b8b615412e6798a641a22842d9160409182519182526020820152a3005b9093506020813d60201161099d575b8161096d60209383610f50565b810103126101125751927f28b58397e03322f670d6b223cc863f8c148e368b8b615412e6798a641a22842d61091e565b3d9150610960565b6040513d5f823e3d90fd5b634e487b7160e01b5f52604160045260245ffd5b64ffffffffff90818351160116835287610701565b81526106f0565b64ffffffffff19164264ffffffffff16176003558361068b565b7fb4f06787000000000000000000000000000000000000000000000000000000005f5260045ffd5b8135815260209182019101610602565b857febe6f30d000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7fa164c6b4000000000000000000000000000000000000000000000000000000005f523460045260245260445ffd5b7fdf4bae05000000000000000000000000000000000000000000000000000000005f524260045264ffffffffff7f00000000000000000000000000000000000000000000000000000000000000001660245260445ffd5b34610112575f3660031901126101125760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b34610112575f3660031901126101125760206040517f000000000000000000000000000000000000000000000000000000000000000015158152f35b34610112575f3660031901126101125760206040517f000000000000000000000000000000000000000000000000000000000000000015158152f35b3461011257604036600319011261011257610bb8610f3a565b6024356fffffffffffffffffffffffffffffffff8116809103610112576001600160a01b035f54163381036102ee575064ffffffffff6003541680151580610d9f575b80610d90575b610d365750604051610c9b5f806001600160a01b0360208501967fa9059cbb000000000000000000000000000000000000000000000000000000008852169586602486015285604486015260448552610c5b606486610f50565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001694519082865af1610c94610f72565b9083610fee565b8051908115159182610d12575b5050610ce757507f2e9d425ba8b27655048400b366d7b6a1f7180ebdb088e06bb7389704860ffe1f60206001600160a01b035f541692604051908152a3005b7f5274afe7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b81925090602091810103126101125760200151801590811503610112578480610ca8565b7fe2e40a0c000000000000000000000000000000000000000000000000000000005f524260045264ffffffffff7f00000000000000000000000000000000000000000000000000000000000000001660245260445260645ffd5b50610d99610fb1565b15610c01565b5062093a80810164ffffffffff8111610dc05764ffffffffff164211610bfb565b634e487b7160e01b5f52601160045260245ffd5b34610112576020366003190112610112576004356001600160a01b038116809103610112576001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016803303610e7b575047905f80808085855af1610e3d610f72565b5015610e4e57602082604051908152f35b7e534073000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b7f4e3ddeed000000000000000000000000000000000000000000000000000000005f526004523360245260445ffd5b34610112575f366003190112610112576103db907f00000000000000000000000000000000000000000000000000000000000000006020820152602081526103cf604082610f50565b9190916020815282518060208301525f5b818110610f24575060409293505f838284010152601f8019910116010190565b8060208092870101516040828601015201610f04565b600435906001600160a01b038216820361011257565b90601f8019910116810190811067ffffffffffffffff8211176109b057604052565b3d15610fac573d9067ffffffffffffffff82116109b05760405191610fa1601f8201601f191660200184610f50565b82523d5f602084013e565b606090565b64ffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168015159081610fe6575090565b905042101590565b9061102b575080511561100357805190602001fd5b7f1425ea42000000000000000000000000000000000000000000000000000000005f5260045ffd5b81511580611071575b61103c575090565b6001600160a01b03907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b1561103456fea164736f6c634300081a000a6101e080604052346104ba57611e7b803803809161001d828561071d565b833981019060e0818303126104ba5780516001600160401b0381116104ba5781019060c0828403126104ba576040519160c083016001600160401b038111848210176105745760405280516001600160a01b03811681036104ba57835261008660208201610740565b6020840190815260408201516001600160a01b03811681036104ba576040850190815260608301516001600160401b0381116104ba57866100c891850161078e565b6060860190815260808085015190870190815260a085015191949091906001600160401b0382116104ba576100ff9189910161078e565b60a087019081526020860151949091906001600160a01b03861686036104ba5761012b604088016107d3565b94610138606089016107d3565b9861014560808a01610740565b60a08a01519099906001600160401b0381116104ba5781018c601f820112156104ba578051906001600160401b038211610574576040519d8e8360051b60200161018f908261071d565b8381526020019260061b8201602001918183116104ba57602001925b8284106106c1575050505060c0015195515f80546001600160a01b0319166001600160a01b039290921691821781557fbdd36143ee09de60bdefca70680e0f71189b2ed7acee364b53917ad433fdaf808180a3845151602081116106aa5750516001600160a01b03166080525164ffffffffff1660a0523360c052518051906001600160401b0382116105745760015490600182811c921680156106a0575b602083101461068c5781601f84931161061e575b50602090601f83116001146105b8575f926105ad575b50508160011b915f199060031b1c1916176001555b5160e052516040516102bc602082816102ab818301968781519384920161076d565b81010301601f19810183528261071d565b519051906020811061059c575b5061010052610120526101405261016052610180526101c0528051905f915f915b8183106104be57836101a05260018060a01b036080511660018060a01b03610160511690604051905f806020840163095ea7b360e01b8152856024860152811960448601526044855261033e60648661071d565b84519082855af161034d6107f4565b81610483575b5080610479575b15610434575b60405161158690816108f58239608051818181610578015281816109440152610f74015260a05181818161027d01528181610dd40152818161107401526112fc015260c051818181610e1b0152611112015260e05181818161040e015261072e015261010051816111cf01526101205181818161023d01526106270152610140518181816109740152610e9201526101605181818161031e0152610ac601526101805181818161018901526107bc01526101a0518181816102c1015261078a01526101c05181818161099e0152610e560152f35b61046c610471936040519063095ea7b360e01b602083015260248201525f60448201526044815261046660648261071d565b82610823565b610823565b808080610360565b50803b151561035a565b8051801592508215610498575b505084610353565b81925090602091810103126104ba5760206104b391016107d3565b8480610490565b5f80fd5b91929091906001600160401b036104d585846107e0565b5151166001600160401b039182160190811161058857926104f681836107e0565b519060045491680100000000000000008310156105745760018301806004558310156105605760019260045f5260205f200190838060401b038151166cffffffffff00000000000000006020845493015160401b1691858060681b031916171790550191906102ea565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52604160045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b5f199060200360031b1b165f6102c9565b015190505f80610274565b60015f9081528281209350601f198516905b81811061060657509084600195949392106105ee575b505050811b01600155610289565b01515f1960f88460031b161c191690555f80806105e0565b929360206001819287860151815501950193016105ca565b60015f529091507fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6601f840160051c81019160208510610682575b90601f859493920160051c01905b818110610674575061025e565b5f8155849350600101610667565b9091508190610659565b634e487b7160e01b5f52602260045260245ffd5b91607f169161024a565b6371935f2960e11b5f52600452602060245260445ffd5b6040848303126104ba5760408051919082016001600160401b03811183821017610574576040528451906001600160401b03821682036104ba57826020926040945261070e838801610740565b838201528152019301926101ab565b601f909101601f19168101906001600160401b0382119082101761057457604052565b519064ffffffffff821682036104ba57565b6001600160401b03811161057457601f01601f191660200190565b5f5b83811061077e5750505f910152565b818101518382015260200161076f565b81601f820112156104ba5780516107a481610752565b926107b2604051948561071d565b818452602082840101116104ba576107d0916020808501910161076d565b90565b519081151582036104ba57565b80518210156105605760209160051b010190565b3d1561081e573d9061080582610752565b91610813604051938461071d565b82523d5f602084013e565b606090565b5f8061084b9260018060a01b03169360208151910182865af16108446107f4565b9083610896565b8051908115159182610873575b50506108615750565b635274afe760e01b5f5260045260245ffd5b81925090602091810103126104ba57602061088e91016107d3565b155f80610858565b906108ba57508051156108ab57805190602001fd5b630a12f52160e11b5f5260045ffd5b815115806108eb575b6108cb575090565b639996b31560e01b5f9081526001600160a01b0391909116600452602490fd5b50803b156108c356fe6080806040526004361015610012575f80fd5b5f3560e01c90816306fdde03146111b957508063164e68de146110e35780631686c90914610eb757806316c3549d14610e7b5780631bfd681414610e3f5780632dd3100014610dfc5780633f31ae3f1461059c5780634800d97f1461055957806349fc73dd146104555780634e390d3e1461043157806351e75e8b146103f757806375829def14610342578063845aef4b146102ff57806390e64d13146102e5578063936c63d9146102a1578063bb4b573414610260578063bba72cd314610226578063bf4ed03f146101ad578063ce36b3351461016c578063ce5165071461012c5763f851a44014610103575f80fd5b34610128575f3660031901126101285760206001600160a01b035f5416604051908152f35b5f80fd5b3461012857602036600319011261012857602061016260043560ff6001918060081c5f526002602052161b60405f205416151590565b6040519015158152f35b34610128575f36600319011261012857602060405164ffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b34610128575f366003190112610128576101c5611331565b6040518091602082016020835281518091526020604084019201905f5b8181106101f0575050500390f35b8251805167ffffffffffffffff16855260209081015164ffffffffff1681860152869550604090940193909201916001016101e2565b34610128575f3660031901126101285760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b34610128575f36600319011261012857602060405164ffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b34610128575f36600319011261012857602060405167ffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b34610128575f3660031901126101285760206101626112f4565b34610128575f3660031901126101285760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b346101285760203660031901126101285761035b611249565b5f546001600160a01b0381163381036103c857506001600160a01b037fffffffffffffffffffffffff0000000000000000000000000000000000000000921691829116175f55337fbdd36143ee09de60bdefca70680e0f71189b2ed7acee364b53917ad433fdaf805f80a3005b7fc6cce6a4000000000000000000000000000000000000000000000000000000005f526004523360245260445ffd5b34610128575f3660031901126101285760206040517f00000000000000000000000000000000000000000000000000000000000000008152f35b34610128575f36600319011261012857602064ffffffffff60035416604051908152f35b34610128575f366003190112610128576040515f6001548060011c9060018116801561054f575b60208310811461053b5782855290811561051757506001146104b9575b6104b5836104a98185038261127b565b60405191829182611202565b0390f35b91905060015f527fb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6915f905b8082106104fd575090915081016020016104a9610499565b9192600181602092548385880101520191019092916104e5565b60ff191660208086019190915291151560051b840190910191506104a99050610499565b634e487b7160e01b5f52602260045260245ffd5b91607f169161047c565b34610128575f3660031901126101285760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b608036600319011261012857600435602435906001600160a01b03821680920361012857604435906001600160801b03821691828103610128576064359367ffffffffffffffff851161012857366023860112156101285784600401359467ffffffffffffffff86116101285760248660051b820101368111610128576106216112f4565b610da5577f0000000000000000000000000000000000000000000000000000000000000000803410610d76575061066f8560ff6001918060081c5f526002602052161b60405f205416151590565b610d4a5760405160208101908682528460408201528760608201526060815261069960808261127b565b51902060405160208101918252602081526106b560408261127b565b519020916106c2886112dc565b976106d0604051998a61127b565b8852602401602088015b828210610d3a57505050925f935b865185101561072a576106fb85886113e6565b519081811015610719575f52602052600160405f205b9401936106e8565b905f52602052600160405f20610711565b85907f000000000000000000000000000000000000000000000000000000000000000003610d125760035464ffffffffff811615610cf8575b508160081c5f52600260205260405f20600160ff84161b815417905567ffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016670de0b6b3a76400008103610ccd57507f000000000000000000000000000000000000000000000000000000000000000064ffffffffff8116610cc7575064ffffffffff4216935b6107fa611331565b90815191610807836112dc565b92610815604051948561127b565b808452601f19610824826112dc565b015f5b818110610ca457505061085661085167ffffffffffffffff610848856113c5565b5151168761143f565b6113fa565b64ffffffffff806020610868866113c5565b510151168a0116906001600160801b03604051916108858361125f565b16918282526020820152610898866113c5565b526108a2856113c5565b50916001905b828210610c105750506001600160801b038216858111610bfc578511610bd1575b50505064ffffffffff60206108e25f19845101846113e6565b510151166001600160a01b035f541690604051906108ff8261125f565b5f82525f602083015260405192610120840184811067ffffffffffffffff821117610bbd576040959493929552825260208201938785526040830198868a52606084017f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152608085017f00000000000000000000000000000000000000000000000000000000000000001515815260a08601917f00000000000000000000000000000000000000000000000000000000000000001515835260c087019364ffffffffff16845260e0870194855261010087019586526040519c8d997f56b955e8000000000000000000000000000000000000000000000000000000008b526101648b0198516001600160a01b031660048c0152516001600160a01b031660248b0152516001600160801b031660448a0152516001600160a01b03166064890152511515608488015251151560a48701525164ffffffffff1660c48601525164ffffffffff1660e48501525180516001600160a01b031661010485015260200151610124840152610144830161016090528151809152610184830191602001905f5b818110610b8857505050908060209203815f6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000165af1938415610b7d575f94610b29575b507f28b58397e03322f670d6b223cc863f8c148e368b8b615412e6798a641a22842d9160409182519182526020820152a3005b9093506020813d602011610b75575b81610b456020938361127b565b810103126101285751927f28b58397e03322f670d6b223cc863f8c148e368b8b615412e6798a641a22842d610af6565b3d9150610b38565b6040513d5f823e3d90fd5b825180516001600160801b0316855260209081015164ffffffffff168186015289955060409094019390920191600101610aaa565b634e487b7160e01b5f52604160045260245ffd5b6001600160801b0391610be883925f1901866113e6565b5193031681835116011690528580806108c9565b634e487b7160e01b5f52600160045260245ffd5b90926001600160801b03600191610c3f61085167ffffffffffffffff610c3689886113e6565b5151168b61143f565b9064ffffffffff806020610c565f198b018d6113e6565b51015116816020610c678b8a6113e6565b51015116011660405190610c7a8261125f565b84841682526020820152610c8e888b6113e6565b52610c99878a6113e6565b5001169301906108a8565b602090604051610cb38161125f565b5f81525f8382015282828901015201610827565b936107f2565b7f36d385ef000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b64ffffffffff19164264ffffffffff161760035584610763565b7fb4f06787000000000000000000000000000000000000000000000000000000005f5260045ffd5b81358152602091820191016106da565b847febe6f30d000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b7fa164c6b4000000000000000000000000000000000000000000000000000000005f523460045260245260445ffd5b7fdf4bae05000000000000000000000000000000000000000000000000000000005f524260045264ffffffffff7f00000000000000000000000000000000000000000000000000000000000000001660245260445ffd5b34610128575f3660031901126101285760206040516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168152f35b34610128575f3660031901126101285760206040517f000000000000000000000000000000000000000000000000000000000000000015158152f35b34610128575f3660031901126101285760206040517f000000000000000000000000000000000000000000000000000000000000000015158152f35b3461012857604036600319011261012857610ed0611249565b6024356001600160801b038116809103610128576001600160a01b035f54163381036103c8575064ffffffffff60035416801515806110ae575b8061109f575b6110455750604051610faa5f806001600160a01b0360208501967fa9059cbb000000000000000000000000000000000000000000000000000000008852169586602486015285604486015260448552610f6a60648661127b565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001694519082865af1610fa361129d565b90836114ed565b8051908115159182611021575b5050610ff657507f2e9d425ba8b27655048400b366d7b6a1f7180ebdb088e06bb7389704860ffe1f60206001600160a01b035f541692604051908152a3005b7f5274afe7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b81925090602091810103126101285760200151801590811503610128578480610fb7565b7fe2e40a0c000000000000000000000000000000000000000000000000000000005f524260045264ffffffffff7f00000000000000000000000000000000000000000000000000000000000000001660245260445260645ffd5b506110a86112f4565b15610f10565b5062093a80810164ffffffffff81116110cf5764ffffffffff164211610f0a565b634e487b7160e01b5f52601160045260245ffd5b34610128576020366003190112610128576004356001600160a01b038116809103610128576001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001680330361118a575047905f80808085855af161114c61129d565b501561115d57602082604051908152f35b7e534073000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b7f4e3ddeed000000000000000000000000000000000000000000000000000000005f526004523360245260445ffd5b34610128575f366003190112610128576104b5907f00000000000000000000000000000000000000000000000000000000000000006020820152602081526104a960408261127b565b9190916020815282518060208301525f5b818110611233575060409293505f838284010152601f8019910116010190565b8060208092870101516040828601015201611213565b600435906001600160a01b038216820361012857565b6040810190811067ffffffffffffffff821117610bbd57604052565b90601f8019910116810190811067ffffffffffffffff821117610bbd57604052565b3d156112d7573d9067ffffffffffffffff8211610bbd57604051916112cc601f8201601f19166020018461127b565b82523d5f602084013e565b606090565b67ffffffffffffffff8111610bbd5760051b60200190565b64ffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168015159081611329575090565b905042101590565b6004549061133e826112dc565b9161134c604051938461127b565b80835260045f9081527f8a35acfbc15ff81a39ae7d344fd709f28e8600b4aa8c65c6b64bfe7fe36bd19b602085015b8383106113885750505050565b60016020819260405161139a8161125f565b64ffffffffff865467ffffffffffffffff8116835260401c168382015281520192019201919061137b565b8051156113d25760200190565b634e487b7160e01b5f52603260045260245ffd5b80518210156113d25760209160051b010190565b6001600160801b038111611414576001600160801b031690565b7f4916adce000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b9091905f19838209838202918280831092039180830392146114dc57670de0b6b3a76400008210156114ac577faccb18165bd6fe31ae1cf318dc5b51eee0e1ba569b88cd74c1773b91fac106699394670de0b6b3a7640000910990828211900360ee1b910360121c170290565b84907f5173648d000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b5050670de0b6b3a764000090049150565b9061152a575080511561150257805190602001fd5b7f1425ea42000000000000000000000000000000000000000000000000000000005f5260045ffd5b81511580611570575b61153b575090565b6001600160a01b03907f9996b315000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b50803b1561153356fea164736f6c634300081a000aa164736f6c634300081a000a"; bytes public constant BYTECODE_NFT_DESCRIPTOR = - hex"60808060405234601557615f8d908161001a8239f35b5f80fdfe6102406040526004361015610012575f80fd5b5f3560e01c63e9dc637514610025575f80fd5b346141bc5760403660031901126141bc576001600160a01b036004351680600435036141bc576103e06040525f61024081905260606102608190526102808290526102a08290526102c08190526102e0819052610320819052610340819052610360819052610380526103a08190526103c0526103008190526100b6906100ad600435614824565b61032052614b1e565b61034052610300516040517feac8f5b80000000000000000000000000000000000000000000000000000000081526024803560048301529091602091839182906001600160a01b03165afa9081156145b8575f91614681575b506001600160a01b0361012791168061024052614c1a565b61026052610300516040517fa80fc0710000000000000000000000000000000000000000000000000000000081526024803560048301529091602091839182906001600160a01b03165afa80156145b8576fffffffffffffffffffffffffffffffff915f91614662575b501661028052610300516040517fad35efd40000000000000000000000000000000000000000000000000000000081526024803560048301529091602091839182906001600160a01b03165afa80156145b8575f90614625575b6101f59150614dbc565b61036052610300516040517f4869e12d0000000000000000000000000000000000000000000000000000000081526024803560048301529091602091839182906001600160a01b03165afa9081156145b8575f916145f6575b50610280516fffffffffffffffffffffffffffffffff1680156145e2576fffffffffffffffffffffffffffffffff612710819302160416610160610240015260405160208101904682526bffffffffffffffffffffffff1960043560601b1660408201526024356054820152605481526102c9607482614710565b51902061040a60028061016861ffff8560101c160693600161031c63ffffffff601e61031482601461030c82604660ff6050818d60081c16069b16069d166154e9565b9701166154e9565b9801166154e9565b60246040519788947f68736c2800000000000000000000000000000000000000000000000000000000602087015261035d815180926020868a0191016146ca565b85017f2c00000000000000000000000000000000000000000000000000000000000000838201526103988251809360206025850191016146ca565b01017f252c000000000000000000000000000000000000000000000000000000000000838201526103d38251809360206003850191016146ca565b01017f2529000000000000000000000000000000000000000000000000000000000000838201520301601d19810184520182614710565b6104446fffffffffffffffffffffffffffffffff604061024001511660ff61043d6001600160a01b036102405116614ebc565b1690615022565b9061045a6001600160a01b036102405116614b1e565b6020610240015190602460206001600160a01b0360c0610240015116604051928380927fbc2be1be000000000000000000000000000000000000000000000000000000008252823560048301525afa80156145b8576024915f916145c3575b5060206001600160a01b0360c0610240015116604051938480927f9067b677000000000000000000000000000000000000000000000000000000008252823560048301525afa80156145b85764ffffffffff8091610521945f91614589575b50169116615318565b610340516103a05190939091906105ac600161054a6064610543818806615994565b96046154e9565b6020604051968261056489945180928580880191016146ca565b8301610578825180938580850191016146ca565b01017f2500000000000000000000000000000000000000000000000000000000000000815203601e19810186520184614710565b61016061024001519361012061024001519760e061024001519760405161016052610140610160510161016051811067ffffffffffffffff821117614575576040526101605152602061016051015260406101605101526060610160510152608061016051015260a061016051015260c061016051015260e06101605101526101006101605101526101206101605101526040516101c0810181811067ffffffffffffffff82111761457557604052606081525f60208201525f60408201526060808201525f6080820152606060a08201525f60c08201525f60e082015260606101008201525f6101208201525f61014082015260606101608201525f6101808201525f6101a082015260a06101605101516108eb6109ca60046007602760586106e260c0610160510151610160515190615a95565b60b76106ed5f615d85565b985f6102205260206102205261071560405161070c6102205182614710565b5f8152846157b3565b1561456b57601b60909a5b6107298c6154e9565b906040519b8c9889937f3c672069643d220000000000000000000000000000000000000000000000000061022051860152835161076f81846102205188019801886146ca565b8b017f222066696c6c3d2223666666223e000000000000000000000000000000000000838201527f3c726563742077696474683d220000000000000000000000000000000000000060358201526107d282518093604284019061022051016146ca565b0101917f22206865696768743d22313030222066696c6c2d6f7061636974793d222e3033858401527f222072783d223135222072793d22313522207374726f6b653d22236666662220603b8401527f7374726f6b652d6f7061636974793d222e3122207374726f6b652d7769647468605b8401527f3d2234222f3e0000000000000000000000000000000000000000000000000000607b8401527f3c7465787420783d2232302220793d2233342220666f6e742d66616d696c793d60818401527f2227436f7572696572204e6577272c417269616c2c6d6f6e6f7370616365222060a18401527f666f6e742d73697a653d2232327078223e00000000000000000000000000000060c184015251809360d28401906146ca565b0101661e17ba32bc3a1f60c91b838201527f3c7465787420783d2232302220793d2237322220666f6e742d66616d696c793d60be8201527f2227436f7572696572204e6577272c417269616c2c6d6f6e6f7370616365222060de8201527f666f6e742d73697a653d2232367078223e00000000000000000000000000000060fe8201526109858251809361010f84019061022051016146ca565b0101661e17ba32bc3a1f60c91b838201526109ac82518093605f84019061022051016146ca565b0101631e17b39f60e11b838201520301601b19810184520182614710565b6101008301526101208201526101206101605101516108eb610a3860046007602760586040516109fd6102205182614710565b5f815260b7610a0c6001615d85565b98601b6028610a1a8c615e90565b610a2384615f08565b8082111561456457505b019a6107298c6154e9565b61016083015261018082015260206101605101516108eb610a796004600760276058604051610a6a6102205182614710565b5f815260b7610a0c6002615d85565b8252602082015260286080610160510151604051610a9a6102205182614710565b5f81526108eb610ae46004600760276058610ab56003615d85565b9660b7610ac189615e90565b610aca8b615f08565b8082111561455c5750995b601b8c8c019a6107298c6154e9565b60a085015260c0840152602083015101016101208201510161018082015101603081016080830152602f19906103e8030160011c8061014083015261012082015101601081016101a083015261018082015101610220518101604083015260106102205191602084015101010160e0820152610b7361010082015161016083015183519060a085015192614fb5565b60608201526101006101208190526040516101a0819052610b949190614710565b60c76101a051527f3c726563742077696474683d223130302522206865696768743d223130302522610220516101a05101527f2066696c7465723d2275726c28234e6f69736529222f3e3c7265637420783d2260406101a05101527f37302220793d223730222077696474683d2238363022206865696768743d223860606101a05101527f3630222066696c6c3d2223666666222066696c6c2d6f7061636974793d222e3060806101a05101527f33222072783d223435222072793d22343522207374726f6b653d22236666662260a06101a05101527f207374726f6b652d6f7061636974793d222e3122207374726f6b652d7769647460c06101a05101527f683d2234222f3e0000000000000000000000000000000000000000000000000060e06101a05101526101605151610120610160510151906060830151610140525f610200526060610200526040516101e052610cf6610200516101e051614710565b60336101e051527f3c636972636c652069643d22476c6f772220723d22353030222066696c6c3d22610220516101e05101527f75726c282352616469616c476c6f7729222f3e0000000000000000000000000060406101e051015261014060405190610d628183614710565b61011c82527f3c66696c7465722069643d224e6f697365223e3c6665466c6f6f6420783d2230610220518301527f2220793d2230222077696474683d223130302522206865696768743d2231303060408301527f252220666c6f6f642d636f6c6f723d2268736c283233302c3231252c31312529610200518301527f2220666c6f6f642d6f7061636974793d22312220726573756c743d22666c6f6f60808301527f6446696c6c222f3e3c666554757262756c656e6365206261736546726571756560a08301527f6e63793d222e3422206e756d4f6374617665733d22332220726573756c743d2260c08301527f4e6f6973652220747970653d226672616374616c4e6f697365222f3e3c66654260e08301527f6c656e6420696e3d224e6f6973652220696e323d22666c6f6f6446696c6c2220610120518301527f6d6f64653d22736f66742d6c69676874222f3e3c2f66696c7465723e000000006101208301525f6101c0526103a06101c0526119416118bc6073606b60405196610eeb6101c05189614710565b61037b88527f3c706174682069643d224c6f676f222066696c6c3d2223666666222066696c6c610220518901527f2d6f7061636974793d222e312220643d226d3133332e3535392c3132342e303360408901527f34632d2e3031332c322e3431322d312e3035392c342e3834382d322e3932332c610200518901527f362e3430322d322e3535382c312e3831392d352e3136382c332e3433392d372e60808901527f3838382c342e3939362d31342e34342c382e3236322d33312e3034372c31322e60a08901527f3536352d34372e3637342c31322e3536392d382e3835382e3033362d31372e3860c08901527f33382d312e3237322d32362e3332382d332e3636332d392e3830362d322e373660e08901527f362d31392e3038372d372e3131332d32372e3536322d31322e3737382d31332e610120518901527f3834322d382e3032352c392e3436382d32382e3630362c31362e3135332d33356101208901527f2e323635683063322e3033352d312e3833382c342e3235322d332e3534362c36868901527f2e3436332d352e323234683063362e3432392d352e3635352c31362e3231382d6101608901527f322e3833352c32302e3335382c342e31372c342e3134332c352e3035372c382e6101808901527f3831362c392e3634392c31332e39322c31332e373334682e30333763352e37336101a08901527f362c362e3436312c31352e3335372d322e3235332c392e33382d382e34382c306101c08901527f2c302d332e3531352d332e3531352d332e3531352d332e3531352d31312e34396101e08901527f2d31312e3437382d35322e3635362d35322e3636342d36342e3833372d36342e6102008901527f3833376c2e3034392d2e303337632d312e3732352d312e3630362d322e3731396102208901527f2d332e3834372d322e3735312d362e3230346830632d2e3034362d322e3337356102408901527f2c312e3036322d342e3538322c322e3732362d362e32323968306c2e3138352d6102608901527f2e3134386830632e3039392d2e3036322c2e3232322d2e3134382c2e33372d2e6102808901527f323539683063322e30362d312e3336322c332e3935312d322e3632312c362e306102a08901527f34342d332e3834324335372e3736332d332e3437332c39372e37362d322e33346102c08901527f312c3132382e3633372c31382e3333326331362e3637312c392e3934362d32366102e08901527f2e3334342c35342e3831332d33382e3635312c34302e3139392d362e3239392d6103008901527f362e3039362d31382e3036332d31372e3734332d31392e3636382d31382e38316103208901527f312d362e3031362d342e3034372d31332e3036312c342e3737362d372e3735326103408901527f2c392e3735316c36382e3235342c36382e33373163312e3732342c312e3630316103608901527f2c322e3731342c332e38342c322e3733382c362e3139325a222f3e00000000006103808901525f6101805260a06101805260405160a0526113506101805160a051614710565b607560a051527f3c706174682069643d22466c6f6174696e6754657874222066696c6c3d226e6f6102205160a05101527f6e652220643d224d313235203435683735307338302030203830203830763735604060a05101527f307330203830202d3830203830682d373530732d38302030202d3830202d38306102005160a05101527f762d3735307330202d3830203830202d3830222f3e0000000000000000000000608060a051015261193c60146022611409615a5a565b9360a2604051957f3c72616469616c4772616469656e742069643d2252616469616c476c6f77223e610220518801527f3c73746f70206f66667365743d223025222073746f702d636f6c6f723d220000604088015261153e6025603589605e8751956102205189019661147f818486018a6146ca565b83017f222073746f702d6f7061636974793d222e36222f3e0000000000000000000000838201528f7f3c73746f70206f66667365743d2231303025222073746f702d636f6c6f723d22908201526114e282518093609384019061022051016146ca565b01017f222073746f702d6f7061636974793d2230222f3e000000000000000000000000838201527f3c2f72616469616c4772616469656e743e00000000000000000000000000000060498201520301600581018a520188614710565b61165585602361154c615a5a565b6040519b8c917f3c6c696e6561724772616469656e742069643d2253616e64546f70222078313d610220518401527f223025222079313d223025223e0000000000000000000000000000000000000060408401527f3c73746f70206f66667365743d223025222073746f702d636f6c6f723d220000604d84015288516115d5818486018a6146ca565b83016211179f60e91b838201527f3c73746f70206f66667365743d2231303025222073746f702d636f6c6f723d22606e82015261161e82518093608e84019061022051016146ca565b01016211179f60e91b83820152701e17b634b732b0b923b930b234b2b73a1f60791b60268201520301600b1981018b520189614710565b6117df60726023611664615a5a565b6040519c8d917f3c6c696e6561724772616469656e742069643d2253616e64426f74746f6d2220610220518401527f78313d2231303025222079313d2231303025223e00000000000000000000000060408401527f3c73746f70206f66667365743d22313025222073746f702d636f6c6f723d220060548401526116f3815180928486019061022051016146ca565b82016211179f60e91b828201527f3c73746f70206f66667365743d2231303025222073746f702d636f6c6f723d22607682015288519061173782609683018a6146ca565b01016211179f60e91b838201527f3c616e696d617465206174747269627574654e616d653d22783122206475723d60268201527f2236732220726570656174436f756e743d22696e646566696e6974652220766160468201527f6c7565733d223330253b3630253b313230253b3630253b3330253b222f3e00006066820152701e17b634b732b0b923b930b234b2b73a1f60791b60848201520301605281018c52018a614710565b6117e7615a5a565b906040519a8b947f3c6c696e6561724772616469656e742069643d22486f7572676c617373537472610220518701527f6f6b6522206772616469656e745472616e73666f726d3d22726f74617465283960408701527f302922206772616469656e74556e6974733d227573657253706163654f6e5573610200518701527f65223e000000000000000000000000000000000000000000000000000000000060808701527f3c73746f70206f66667365743d22353025222073746f702d636f6c6f723d22006083870152518092858701906146ca565b83016211179f60e91b838201527f3c73746f70206f66667365743d22383025222073746f702d636f6c6f723d220060a58201526119058251809360c484019061022051016146ca565b01016211179f60e91b83820152701e17b634b732b0b923b930b234b2b73a1f60791b60258201520301600b19810187520185614710565b614fb5565b60e05261195561194f614d46565b856157b3565b938415614541575b5060c061010081905260405191906119759083614710565b609082527f3c7061746820643d224d2035302c3336302061203330302c333030203020312c610220518301527f31203630302c302061203330302c333030203020312c31202d3630302c30222060408301527f66696c6c3d2223666666222066696c6c2d6f7061636974793d222e3032222073610200518301527f74726f6b653d2275726c2823486f7572676c6173735374726f6b65292220737460808301527f726f6b652d77696474683d2234222f3e00000000000000000000000000000000610180518301526102c060405160c052611a528160c051614710565b61029860c051527f3c7061746820643d226d3536362c3136312e323031762d35332e39323463302d6102205160c05101527f31392e3338322d32322e3531332d33372e3536332d36332e3339382d35312e31604060c05101527f39382d34302e3735362d31332e3539322d39342e3934362d32312e3037392d316102005160c05101527f35322e3538372d32312e303739732d3131312e3833382c372e3438372d313532608060c05101527f2e3630322c32312e303739632d34302e3839332c31332e3633362d36332e34316101805160c05101527f332c33312e3831362d36332e3431332c35312e3139387635332e39323463302c6101005160c05101527f31372e3138312c31372e3730342c33332e3432372c35302e3232332c34362e3360e060c05101527f3934763238342e383039632d33322e3531392c31322e39362d35302e3232332c6101205160c05101527f32392e3230362d35302e3232332c34362e3339347635332e39323463302c313961012060c05101527f2e3338322c32322e35322c33372e3536332c36332e3431332c35312e3139382c8260c05101527f34302e3736332c31332e3539322c39342e3935342c32312e3037392c3135322e61016060c05101527f3630322c32312e303739733131312e3833312d372e3438372c3135322e35383761018060c05101527f2d32312e3037396334302e3838362d31332e3633362c36332e3339382d33312e6101a060c05101527f3831362c36332e3339382d35312e313938762d35332e39323463302d31372e316101c060c05101527f39362d31372e3730342d33332e3433352d35302e3232332d34362e34303156326101e060c05101527f30372e3630336333322e3531392d31322e3936372c35302e3232332d32392e3261020060c05101527f30362c35302e3232332d34362e3430315a6d2d3334372e3436322c35372e373961022060c05101527f336c3133302e3935392c3133312e3032372d3133302e3935392c3133312e303161024060c05101527f33563231382e3939345a6d3236322e3932342e303232763236322e3031386c2d61026060c05101527f3133302e3933372d3133312e3030362c3133302e3933372d3133312e3031335a61028060c05101527f222066696c6c3d2223313631383232223e3c2f706174683e00000000000000006102a060c0510152855f1461432c57604051611dcd6102205182614710565b5f8152955b156141d957604051611de66101e082614710565b6101b181527f3c7061746820643d226d3438312e34362c3438312e35347638312e3031632d32610220518201527f2e33352e37372d342e38322c312e35312d372e33392c322e32332d33302e332c60408201527f382e35342d37342e36352c31332e39322d3132342e30362c31332e39322d3533610200518201527f2e362c302d3130312e32342d362e33332d3133312e34372d31362e3136762d3860808201527f316c34362e332d34362e3331683137302e33336c34362e32392c34362e33315a610180518201527f222066696c6c3d2275726c282353616e64426f74746f6d29222f3e3c70617468610100518201527f20643d226d3433352e31372c3433352e323363302c312e31372d2e34362c322e60e08201527f33322d312e33332c332e34342d372e31312c392e30382d34312e39332c31352e610120518201527f39382d38332e38312c31352e3938732d37362e372d362e392d38332e38322d316101208201527f352e3938632d2e38372d312e31322d312e33332d322e32372d312e33332d332e838201527f3434762d2e30346c382e33342d382e33352e30312d2e30316331332e37322d366101608201527f2e35312c34322e39352d31312e30322c37362e382d31312e30327336322e39376101808201527f2c342e34392c37362e37322c31316c382e34322c382e34325a222066696c6c3d6101a08201527f2275726c282353616e64546f7029222f3e0000000000000000000000000000006101c0820152905b6040519261201f6107e085614710565b6107a7845261022080517f3c672066696c6c3d226e6f6e6522207374726f6b653d2275726c2823486f7572908601527f676c6173735374726f6b652922207374726f6b652d6c696e656361703d22726f60408087019190915261020080517f756e6422207374726f6b652d6d697465726c696d69743d22313022207374726f908801527f6b652d77696474683d2234223e3c7061746820643d226d3536352e3634312c31608088015261018080517f30372e323863302c392e3533372d352e35362c31382e3632392d31352e36373690890152610100517f2c32362e393733682d2e303233632d392e3230342c372e3539362d32322e3139908901527f342c31342e3536322d33382e3139372c32302e3539322d33392e3530342c313460e089015261012080517f2e3933362d39372e3332352c32342e3335352d3136312e3733332c32342e3335908a01527f352d39302e34382c302d3136372e3934382d31382e3538322d3139392e393533908901527f2d34342e393438682d2e303233632d31302e3131352d382e3334342d31352e36948801949094527f37362d31372e3433372d31352e3637362d32362e3937332c302d33392e3733356101608801527f2c39362e3535342d37312e3932312c3231352e3635322d37312e393231733231938701939093527f352e3632392c33322e3138352c3231352e3632392c37312e3932315a222f3e3c6101a08701527f7061746820643d226d3133342e33362c3136312e32303363302c33392e3733356101c0808801919091527f2c39362e3535342c37312e3932312c3231352e3635322c37312e3932317332316101e08801527f352e3632392d33322e3138362c3231352e3632392d37312e393231222f3e3c6c938701939093527f696e652078313d223133342e3336222079313d223136312e323033222078323d828701527f223133342e3336222079323d223130372e3238222f3e3c6c696e652078313d226102408701527f3536352e3634222079313d223136312e323033222078323d223536352e3634226102608701527f2079323d223130372e3238222f3e3c6c696e652078313d223138342e353834226102808701527f2079313d223230362e383233222078323d223138342e353835222079323d22356102a08701527f33372e353739222f3e3c6c696e652078313d223231382e313831222079313d22938601939093527f3231382e313138222078323d223231382e313831222079323d223536322e35336102e08601527f37222f3e3c6c696e652078313d223438312e383138222079313d223231382e316103008601527f3432222078323d223438312e383139222079323d223536322e343238222f3e3c6103208601527f6c696e652078313d223531352e343135222079313d223230372e3335322220786103408601527f323d223531352e343136222079323d223533372e353739222f3e3c70617468206103608601527f643d226d3138342e35382c3533372e353863302c352e34352c342e32372c313061038086015290517f2e36352c31322e30332c31352e3432682e303263352e35312c332e33392c3132908501527f2e37392c362e35352c32312e35352c392e34322c33302e32312c392e392c37386103c08501527f2e30322c31362e32382c3133312e38332c31362e32382c34392e34312c302c396103e08501527f332e37362d352e33382c3132342e30362d31332e39322c322e372d2e37362c356104008501527f2e32392d312e35342c372e37352d322e33352c382e37372d322e38372c31362e6104208501527f30352d362e30342c32312e35362d392e3433683063372e37362d342e37372c316104408501527f322e30342d392e39372c31322e30342d31352e3432222f3e3c7061746820643d6104608501527f226d3138342e3538322c3439322e363536632d33312e3335342c31322e3438356104808501527f2d35302e3232332c32382e35382d35302e3232332c34362e3134322c302c392e6104a08501527f3533362c352e3536342c31382e3632372c31352e3637372c32362e393639682e6104c08501527f30323263382e3530332c372e3030352c32302e3231332c31332e3436332c33346104e08501527f2e3532342c31392e3135392c392e3939392c332e3939312c32312e3236392c376105008501527f2e3630392c33332e3539372c31302e3738382c33362e34352c392e3430372c386105208501527f322e3138312c31352e3030322c3133312e3833352c31352e3030327339352e336105408501527f36332d352e3539352c3133312e3830372d31352e3030326331302e3834372d326105608501527f2e37392c32302e3836372d352e3932362c32392e3932342d392e3334392c312e6105808501527f3234342d2e3436372c322e3437332d2e3934322c332e3637332d312e3432342c6105a08501527f31342e3332362d352e3639362c32362e3033352d31322e3136312c33342e35326105c08501527f342d31392e313733682e3032326331302e3131342d382e3334322c31352e36376105e08501527f372d31372e3433332c31352e3637372d32362e3936392c302d31372e3536322d6106008501527f31382e3836392d33332e3636352d35302e3232332d34362e3135222f3e3c70616106208501527f746820643d226d3133342e33362c3539322e373263302c33392e3733352c39366106408501527f2e3535342c37312e3932312c3231352e3635322c37312e393231733231352e366106608501527f32392d33322e3138362c3231352e3632392d37312e393231222f3e3c6c696e656106808501527f2078313d223133342e3336222079313d223539322e3732222078323d223133346106a08501527f2e3336222079323d223533382e373937222f3e3c6c696e652078313d223536356106c08501527f2e3634222079313d223539322e3732222078323d223536352e3634222079323d6106e08501527f223533382e373937222f3e3c706f6c796c696e6520706f696e74733d223438316107008501527f2e383232203438312e393031203438312e373938203438312e383737203438316107208501527f2e373735203438312e383534203335302e303135203335302e303236203231386107408501527f2e313835203231382e313239222f3e3c706f6c796c696e6520706f696e74733d6107608501527f223231382e313835203438312e393031203231382e323331203438312e3835346107808501527f203335302e303135203335302e303236203438312e383232203231382e3135326107a08501527f222f3e3c2f673e000000000000000000000000000000000000000000000000006107c0850152905181517f3c672069643d22486f7572676c617373223e00000000000000000000000000009082015284519151909788959092916129ee9183916032890191016146ca565b840160c051519060328101826102205160c0510191612a0c926146ca565b016032018082518093610220510191612a24926146ca565b018082518093610220510191612a39926146ca565b018082518093610220510191612a4e926146ca565b01631e17b39f60e11b815203601b1981018452600401612a6e9084614710565b60405160805261022051608051017f3c646566733e000000000000000000000000000000000000000000000000000090526101e0515160805160260181610220516101e0510191612abe926146ca565b60805101815191826026830191610220510191612ada926146ca565b016026018082518093610220510191612af2926146ca565b0160a051519080826102205160a0510191612b0c926146ca565b0160e051519080826102205160e0510191612b26926146ca565b018082518093610220510191612b3b926146ca565b01610140515190808261022051610140510191612b57926146ca565b017f3c2f646566733e000000000000000000000000000000000000000000000000008152608051900360181981016080515260070160805190612b9991614710565b6101605160e0015190610160516101000151916101605160400151906101605160600151612bc78583615cd9565b916040958651612bd78882614710565b600581526102205181017f2d31303025000000000000000000000000000000000000000000000000000000905287519485916102205183017f3c74657874506174682073746172744f66667365743d220000000000000000009052805190816037850191610220510191612c4a926146ca565b7f2220687265663d2223466c6f6174696e6754657874222066696c6c3d222366666037918401918201527f662220666f6e742d66616d696c793d2227436f7572696572204e6577272c417260578201527f69616c2c6d6f6e6f7370616365222066696c6c2d6f7061636974793d222e3822607782015271103337b73a16b9b4bd329e91191b383c111f60711b60978201527f3c616e696d6174652061646469746976653d2273756d2220617474726962757460a98201527f654e616d653d2273746172744f66667365742220626567696e3d22307322206460c98201527f75723d22353073222066726f6d3d2230252220726570656174436f756e743d2260e98201527f696e646566696e6974652220746f3d2231303025222f3e0000000000000000006101098201528151610220519092612d8e918491610120850191016146ca565b0160370160e981016a1e17ba32bc3a2830ba341f60a91b90520360e90160141981018552600b01612dbf9085614710565b612dc891615cd9565b928551612dd58782614710565b60028152610220518101947f3025000000000000000000000000000000000000000000000000000000000000865287519586926102205184017f3c74657874506174682073746172744f66667365743d22000000000000000000905251908160378501612e41926146ca565b7f2220687265663d2223466c6f6174696e6754657874222066696c6c3d222366666037918401918201527f662220666f6e742d66616d696c793d2227436f7572696572204e6577272c417260578201527f69616c2c6d6f6e6f7370616365222066696c6c2d6f7061636974793d222e3822607782015271103337b73a16b9b4bd329e91191b383c111f60711b60978201527f3c616e696d6174652061646469746976653d2273756d2220617474726962757460a98201527f654e616d653d2273746172744f66667365742220626567696e3d22307322206460c98201527f75723d22353073222066726f6d3d2230252220726570656174436f756e743d2260e98201527f696e646566696e6974652220746f3d2231303025222f3e0000000000000000006101098201528151610220519092612f85918491610120850191016146ca565b0160370160e981016a1e17ba32bc3a2830ba341f60a91b90520360e90160141981018552600b01612fb69085614710565b612fc08282615d40565b918651612fcd8882614710565b60048152610220518101937f2d35302500000000000000000000000000000000000000000000000000000000855288519485926102205184017f3c74657874506174682073746172744f66667365743d22000000000000000000905251908160378501613039926146ca565b7f2220687265663d2223466c6f6174696e6754657874222066696c6c3d222366666037918401918201527f662220666f6e742d66616d696c793d2227436f7572696572204e6577272c417260578201527f69616c2c6d6f6e6f7370616365222066696c6c2d6f7061636974793d222e3822607782015271103337b73a16b9b4bd329e91191b383c111f60711b60978201527f3c616e696d6174652061646469746976653d2273756d2220617474726962757460a98201527f654e616d653d2273746172744f66667365742220626567696e3d22307322206460c98201527f75723d22353073222066726f6d3d2230252220726570656174436f756e743d2260e98201527f696e646566696e6974652220746f3d2231303025222f3e000000000000000000610109820152815161022051909261317d918491610120850191016146ca565b0160370160e981016a1e17ba32bc3a2830ba341f60a91b90520360e90160141981018452600b016131ae9084614710565b6131b791615d40565b9085516131c48782614710565b60038152610220518101927f3530250000000000000000000000000000000000000000000000000000000000845287519384926102205184017f3c74657874506174682073746172744f66667365743d22000000000000000000905251908160378501613230926146ca565b7f2220687265663d2223466c6f6174696e6754657874222066696c6c3d222366666037918401918201527f662220666f6e742d66616d696c793d2227436f7572696572204e6577272c417260578201527f69616c2c6d6f6e6f7370616365222066696c6c2d6f7061636974793d222e3822607782015271103337b73a16b9b4bd329e91191b383c111f60711b60978201527f3c616e696d6174652061646469746976653d2273756d2220617474726962757460a98201527f654e616d653d2273746172744f66667365742220626567696e3d22307322206460c98201527f75723d22353073222066726f6d3d2230252220726570656174436f756e743d2260e98201527f696e646566696e6974652220746f3d2231303025222f3e0000000000000000006101098201528151610220519092613374918491610120850191016146ca565b0160370160e981016a1e17ba32bc3a2830ba341f60a91b90520360e90160141981018352600b016133a59083614710565b85519384936102205185017f3c7465787420746578742d72656e646572696e673d226f7074696d697a65537090528785017f656564223e0000000000000000000000000000000000000000000000000000009052805190816045870191610220510191613411926146ca565b840181519182604583019161022051019161342b926146ca565b016045018082518093610220510191613443926146ca565b018082518093610220510191613458926146ca565b01661e17ba32bc3a1f60c91b8152036018198101825260070161347b9082614710565b610140820151916101a08101519060408101519060e001519361349d906154e9565b916134a7906154e9565b906134b1906154e9565b936134bb906154e9565b8551948592610220518401947f3c75736520687265663d2223476c6f77222066696c6c2d6f7061636974793d2286528885017f2e39222f3e0000000000000000000000000000000000000000000000000000009052604585017f3c75736520687265663d2223476c6f772220783d22313030302220793d2231309052606585017f3030222066696c6c2d6f7061636974793d222e39222f3e0000000000000000009052607c85017f3c75736520687265663d22234c6f676f2220783d223137302220793d223137309052609c85017f22207472616e73666f726d3d227363616c65282e3629222f3e3c757365206872905260bc85017f65663d2223486f7572676c6173732220783d223135302220793d223930222074905260dc85017f72616e73666f726d3d22726f746174652831302922207472616e73666f726d2d905260fc85017f6f726967696e3d2235303020353030222f3e0000000000000000000000000000905261010e85017f3c75736520687265663d222350726f67726573732220783d2200000000000000905280519081610127870191610220510191613662926146ca565b840161012781016a11103c9e911b9c9811179f60a91b905261013281017f3c75736520687265663d22235374617475732220783d220000000000000000009052815191826101498301916102205101916136bb926146ca565b0161012701602281016a11103c9e911b9c9811179f60a91b9052602d81017f3c75736520687265663d2223416d6f756e742220783d220000000000000000009052815191826044830191610220510191613714926146ca565b01602201602281016a11103c9e911b9c9811179f60a91b9052602d81017f3c75736520687265663d22234475726174696f6e2220783d2200000000000000905281519182604683019161022051019161376c926146ca565b01602201602481016a11103c9e911b9c9811179f60a91b90520360240160141981018452600b0161379d9084614710565b83519283926102205184017f3c73766720786d6c6e733d22687474703a2f2f7777772e77332e6f72672f323090528584017f30302f737667222077696474683d223130303022206865696768743d2231303090526102005184017f30222076696577426f783d2230203020313030302031303030223e000000000090526101a05151607b850181610220516101a0510191613837926146ca565b84016080515190607b810182610220516080510191613855926146ca565b01607b01808251809361022051019161386d926146ca565b0191829151809361387d926146ca565b017f3c2f7376673e0000000000000000000000000000000000000000000000000000815203601919810182526006016138b69082614710565b61038052610300518151610220517fb25645690000000000000000000000000000000000000000000000000000000090820190815260248035818401528252916001600160a01b03169061390b604482614710565b515a925f93928493fa61391c614793565b6102e0819052901580156103c0526141d15761022051818051810103126141bc5761022051015180151581036141bc575b15156102a052610260516103005182517fb971302a00000000000000000000000000000000000000000000000000000000815260248035600483015261022051919283919082906001600160a01b03165afa9081156141c7575f9161417b575b50600360236139be613ad693614b1e565b938161012061024001518780519788947f5b7b2274726169745f74797065223a224173736574222c2276616c7565223a2261022051870152613a0b815180928589019061022051016146ca565b85017f227d2c7b2274726169745f74797065223a2253656e646572222c2276616c75658382015262111d1160e91b61020051820152613a5682518093606384019061022051016146ca565b01017f227d2c7b2274726169745f74797065223a22537461747573222c2276616c75658382015262111d1160e91b6043820152613a9f82518093604684019061022051016146ca565b01017f227d5d0000000000000000000000000000000000000000000000000000000000838201520301601c19810184520182614710565b6103205161026051610340516102405191939291613afc906001600160a01b0316614b1e565b613b076024356154e9565b6102a051909190156140ef5761010051875190613b249082614710565b609b81527fe29aa0efb88f205741524e494e473a205472616e7366657272696e6720746865610220518201527f204e4654206d616b657320746865206e6577206f776e65722074686520726563888201527f697069656e74206f66207468652073747265616d2e205468652066756e647320610200518201527f617265206e6f74206175746f6d61746963616c6c792077697468647261776e2060808201527f666f72207468652070726576696f757320726563697069656e742e000000000061018051820152915b8751968794610220518601967f54686973204e465420726570726573656e74732061207061796d656e7420737488528a87017f7265616d20696e2061205361626c696572204c6f636b757020000000000000009052805190610220518101918060598a0190613c5c91856146ca565b7f20636f6e74726163742e20546865206f776e6572206f662074686973204e46546059918a01918201527f2063616e207769746864726177207468652073747265616d656420617373657460798201527f732c207768696368206172652064656e6f6d696e6174656420696e2000000000609982015284516102205186019691613cea8260b583018a6146ca565b01605901605c81017f2e5c6e5c6e2d2053747265616d2049443a200000000000000000000000000000905281519182606e830191610220510191613d2d926146ca565b01605c0190601282016302e3716960e51b905251918260168301613d50926146ca565b01601201600481016901020b2323932b9b99d160b51b905281519182600e830191610220510191613d80926146ca565b0160040190600a82016302e3716960e51b9052519182600e8301613da3926146ca565b01600a01600481016901020b2323932b9b99d160b51b905281519182600e830191610220510191613dd3926146ca565b01600401600a81017f5c6e5c6e00000000000000000000000000000000000000000000000000000000905281519182600e830191610220510191613e16926146ca565b01600a0103600401601f1981018452613e2f9084614710565b61032051613e3e6024356154e9565b855180916102205182019367029b0b13634b2b9160c51b8552805190816028850191610220510191613e6f926146ca565b8201602881017f2023000000000000000000000000000000000000000000000000000000000000905281519182602a830191610220510191613eb0926146ca565b0160280103600201601f1981018252613ec99082614710565b61038051613ed690615648565b9286519586956102205187017f7b2261747472696275746573223a000000000000000000000000000000000000905280519081602e890191610220510191613f1d926146ca565b860190602e82017f2c226465736372697074696f6e223a22000000000000000000000000000000009052519182603e8301613f57926146ca565b01602e0190601082017f222c2265787465726e616c5f75726c223a2268747470733a2f2f7361626c69659052603082017f722e636f6d222c226e616d65223a2200000000000000000000000000000000009052519182603f8301613fba926146ca565b01601001602f81017f222c22696d616765223a22646174613a696d6167652f7376672b786d6c3b62619052604f81017f736536342c0000000000000000000000000000000000000000000000000000009052815191826054830191610220510191614024926146ca565b01602f01602581017f227d000000000000000000000000000000000000000000000000000000000000905203602501601d19810182526002016140679082614710565b6102c081905261407690615648565b90805180926102205182017f646174613a6170706c69636174696f6e2f6a736f6e3b6261736536342c000000905280519081603d8401916102205101916140bc926146ca565b810103603d01601f19810183526140d39083614710565b5180916102205182526102205182016140eb916146eb565b0390f35b86516140fc608082614710565b605b81527fe29d95494e464f3a2054686973204e4654206973206e6f6e2d7472616e736665610220518201527f7261626c652e2049742063616e6e6f7420626520736f6c64206f72207472616e888201527f7366657272656420746f20616e6f74686572206163636f756e742e00000000006102005182015291613bed565b9050610220513d61022051116141c0575b6141968183614710565b816102205191810103126141bc57516001600160a01b03811681036141bc5760036139ad565b5f80fd5b503d61418c565b83513d5f823e3d90fd5b50600161394d565b6040516141e861012082614710565b60f881527f3c7061746820643d226d3438312e34362c3530342e3130317635382e34343963610220518201527f2d322e33352e37372d342e38322c312e35312d372e33392c322e32332d33302e60408201527f332c382e35342d37342e36352c31332e39322d3132342e30362c31332e39322d610200518201527f35332e362c302d3130312e32342d362e33332d3133312e34372d31362e31367660808201527f2d35382e343339683236322e39325a222066696c6c3d2275726c282353616e64610180518201527f426f74746f6d29222f3e3c656c6c697073652063783d22333530222063793d22610100518201527f3530342e313031222072783d223133312e343632222072793d2232382e31303860e08201527f222066696c6c3d2275726c282353616e64546f7029222f3e0000000000000000610120518201529061200f565b60405161433b6101c082614710565b61019981527f3c706f6c79676f6e20706f696e74733d22333530203335302e30323620343135610220518201527f2e3033203238342e39373820323835203238342e39373820333530203335302e60408201527f303236222066696c6c3d2275726c282353616e64426f74746f6d29222f3e3c70610200518201527f61746820643d226d3431362e3334312c3238312e39373563302c2e3931342d2e60808201527f3335342c312e3830392d312e3033352c322e36382d352e3534322c372e303736610180518201527f2d33322e3636312c31322e34352d36352e32382c31322e34352d33322e363234610100518201527f2c302d35392e3733382d352e3337342d36352e32382d31322e34352d2e36383160e08201527f2d2e3837322d312e3033352d312e3736372d312e3033352d322e36382c302d2e610120518201527f3931342e3335342d312e3830382c312e3033352d322e3637362c352e3534322d6101208201527f372e3037362c33322e3635362d31322e34352c36352e32382d31322e34352c33838201527f322e3631392c302c35392e3733382c352e3337342c36352e32382c31322e34356101608201527f2e3638312e3836372c312e3033352c312e3736322c312e3033352c322e3637366101808201527f5a222066696c6c3d2275726c282353616e64546f7029222f3e000000000000006101a082015295611dd2565b61455591945061454f614d81565b906157b3565b925f61195d565b905099610ad5565b9050610a2d565b601b60d09a610720565b634e487b7160e01b5f52604160045260245ffd5b6145ab915060203d6020116145b1575b6145a38183614710565b81019061475a565b5f610518565b503d614599565b6040513d5f823e3d90fd5b6145dc915060203d6020116145b1576145a38183614710565b5f6104b9565b634e487b7160e01b5f52601260045260245ffd5b614618915060203d60201161461e575b6146108183614710565b810190614732565b5f61024e565b503d614606565b506020813d60201161465a575b8161463f60209383614710565b810103126141bc575160058110156141bc576101f5906101eb565b3d9150614632565b61467b915060203d60201161461e576146108183614710565b5f610191565b90506020813d6020116146c2575b8161469c60209383614710565b810103126141bc57516001600160a01b03811681036141bc576001600160a01b0361010f565b3d915061468f565b5f5b8381106146db5750505f910152565b81810151838201526020016146cc565b90602091614704815180928185528580860191016146ca565b601f01601f1916010190565b90601f8019910116810190811067ffffffffffffffff82111761457557604052565b908160209103126141bc57516fffffffffffffffffffffffffffffffff811681036141bc5790565b908160209103126141bc575164ffffffffff811681036141bc5790565b67ffffffffffffffff811161457557601f01601f191660200190565b3d156147bd573d906147a482614777565b916147b26040519384614710565b82523d5f602084013e565b606090565b6020818303126141bc5780519067ffffffffffffffff82116141bc570181601f820112156141bc5780516147f581614777565b926148036040519485614710565b818452602082840101116141bc5761482191602080850191016146ca565b90565b6001600160a01b0316604051906395d89b4160e01b82525f82600481845afa9182156145b8575f92614afa575b5060409161489483516148648582614710565b600e81527f5341422d4c4f434b55502d4c494e0000000000000000000000000000000000006020820152826157b3565b8015614ab5575b156148db5750506148ae81519182614710565b600d81527f4c6f636b7570204c696e65617200000000000000000000000000000000000000602082015290565b61491a83516148ea8582614710565b600e81527f5341422d4c4f434b55502d44594e0000000000000000000000000000000000006020820152826157b3565b8015614a70575b1561496157505061493481519182614710565b600e81527f4c6f636b75702044796e616d6963000000000000000000000000000000000000602082015290565b6149a083516149708582614710565b600e81527f5341422d4c4f434b55502d5452410000000000000000000000000000000000006020820152826157b3565b8015614a2b575b156149e75750506149ba81519182614710565b600f81527f4c6f636b7570205472616e636865640000000000000000000000000000000000602082015290565b614a279083519384937f7498e3a50000000000000000000000000000000000000000000000000000000085526004850152602484015260448301906146eb565b0390fd5b50614a6b8351614a3b8582614710565b601181527f5341422d56322d4c4f434b55502d5452410000000000000000000000000000006020820152826157b3565b6149a7565b50614ab08351614a808582614710565b601181527f5341422d56322d4c4f434b55502d44594e0000000000000000000000000000006020820152826157b3565b614921565b50614af58351614ac58582614710565b601181527f5341422d56322d4c4f434b55502d4c494e0000000000000000000000000000006020820152826157b3565b61489b565b614b179192503d805f833e614b0f8183614710565b8101906147c2565b905f614851565b6001600160a01b03168060405191614b37606084614710565b602a8352602083016040368237835115614c065760309053825160011015614c06576078602184015360295b60018111614ba45750614b74575090565b7fe22e27eb000000000000000000000000000000000000000000000000000000005f52600452601460245260445ffd5b90600f81166010811015614c06577f3031323334353637383961626364656600000000000000000000000000000000901a614bdf83866157e0565b5360041c908015614bf2575f1901614b63565b634e487b7160e01b5f52601160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b5f809160405160208101906395d89b4160e01b825260048152614c3e602482614710565b51915afa614c4a614793565b90158015614d3a575b614cfe5780602080614c6a935183010191016147c2565b601e8151115f14614cb15750604051614c84604082614710565b600b81527f4c6f6e672053796d626f6c000000000000000000000000000000000000000000602082015290565b614cba816157f1565b15614cc25790565b50604051614cd1604082614710565b601281527f556e737570706f727465642053796d626f6c0000000000000000000000000000602082015290565b50604051614d0d604082614710565b600581527f4552433230000000000000000000000000000000000000000000000000000000602082015290565b50604081511115614c53565b60405190614d55604083614710565b600782527f536574746c6564000000000000000000000000000000000000000000000000006020830152565b60405190614d90604083614710565b600882527f4465706c657465640000000000000000000000000000000000000000000000006020830152565b6005811015614ea85760048103614dd65750614821614d81565b60038103614e1a5750604051614ded604082614710565b600881527f43616e63656c6564000000000000000000000000000000000000000000000000602082015290565b60018103614e5e5750604051614e31604082614710565b600981527f53747265616d696e670000000000000000000000000000000000000000000000602082015290565b600203614e6d57614821614d46565b604051614e7b604082614710565b600781527f50656e64696e6700000000000000000000000000000000000000000000000000602082015290565b634e487b7160e01b5f52602160045260245ffd5b5f809160405160208101907f313ce56700000000000000000000000000000000000000000000000000000000825260048152614ef9602482614710565b51915afa614f05614793565b9080614f34575b15614f2f576020818051810103126141bc576020015160ff811681036141bc5790565b505f90565b506020815114614f0c565b60405190614f4e604083614710565b600482527f2667743b000000000000000000000000000000000000000000000000000000006020830152565b60405190614f89604083614710565b600482527f266c743b000000000000000000000000000000000000000000000000000000006020830152565b90614fe09493615011602061502095615003828096816040519c8d8b83829d519485930191016146ca565b8901614ff4825180938580850191016146ca565b010191828151948592016146ca565b0191828151948592016146ca565b0103601f198101845283614710565b565b9081156152f757806152e757505b806001811015615099575050615044614f7a565b614821600260206040518461506282965180928580860191016146ca565b81017f2031000000000000000000000000000000000000000000000000000000000000838201520301601d19810184520182614710565b66038d7ea4c680001115615289576040519060a0820182811067ffffffffffffffff821117614575576040526020916040516150d58482614710565b5f8152815260409182516150e98482614710565b600181527f4b00000000000000000000000000000000000000000000000000000000000000858201528483015282516151228482614710565b600181527f4d000000000000000000000000000000000000000000000000000000000000008582015283830152825161515b8482614710565b600181527f420000000000000000000000000000000000000000000000000000000000000085820152606083015282516151958482614710565b600181527f54000000000000000000000000000000000000000000000000000000000000008582015260808301525f905f945b6103e882101561526f578451946151df8187614710565b600786527f2623383830353b000000000000000000000000000000000000000000000000008287015251945f5b6007811061525c575050600160fd1b602786015250600884526152439061523d90615238602887614710565b6154e9565b91615994565b916005851015614c06576148219460051b015192614fb5565b818101830151878201840152820161520c565b9490915060016103e86064600a85040693049101946151c8565b50615292614f3f565b61482160086020604051846152b082965180928580860191016146ca565b81017f203939392e393954000000000000000000000000000000000000000000000000838201520301601719810184520182614710565b600a0a9081156145e25704615030565b5050604051615307604082614710565b60018152600360fc1b602082015290565b6201518091030480615382575061532d614f7a565b614821600660206040518461534b82965180928580860191016146ca565b81017f2031204461790000000000000000000000000000000000000000000000000000838201520301601919810184520182614710565b61270f81116154595760018103615415576148216153d76040516153a7604082614710565b600481527f20446179000000000000000000000000000000000000000000000000000000006020820152926154e9565b602060405193826153f186945180928580880191016146ca565b8301615405825180938580850191016146ca565b010103601f198101835282614710565b6148216153d7604051615429604082614710565b600581527f20446179730000000000000000000000000000000000000000000000000000006020820152926154e9565b50615462614f3f565b614821600a60206040518461548082965180928580860191016146ca565b81017f2039393939204461797300000000000000000000000000000000000000000000838201520301601519810184520182614710565b906154c182614777565b6154ce6040519182614710565b82815280926154df601f1991614777565b0190602036910137565b805f917a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615620575b806d04ee2d6d415b85acef8100000000600a921015615605575b662386f26fc100008110156155f1575b6305f5e1008110156155e0575b6127108110156155d1575b60648110156155c3575b10156155b8575b600a6021615573600185016154b7565b938401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a83530480156155b357600a9091615578565b505090565b600190910190615563565b60646002910493019261555c565b61271060049104930192615552565b6305f5e10060089104930192615547565b662386f26fc100006010910493019261553a565b6d04ee2d6d415b85acef81000000006020910493019261552a565b50604091507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104615510565b9081511561579d576040519161565f606084614710565b604083527f4142434445464748494a4b4c4d4e4f505152535455565758595a61626364656660208401527f6768696a6b6c6d6e6f707172737475767778797a303132333435363738392b2f6040840152805160028101809111614bf257600390047f3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81168103614bf2576156f59060021b6154b7565b90602082019080815182019560208701908151925f83525b88811061574f575050600393949596505251068060011461573d57600214615733575090565b603d905f19015390565b50603d90815f19820153600119015390565b600360049199969901986001603f8b5182828260121c16870101518453828282600c1c16870101518385015382828260061c168701015160028501531684010151600382015301949761570d565b90506040516157ad602082614710565b5f815290565b90815181519081811493846157ca575b5050505090565b602092939450820120920120145f8080806157c3565b908151811015614c06570160200190565b8051905f5b82811061580557505050600190565b7fff0000000000000000000000000000000000000000000000000000000000000061583082846157e0565b5116600160fd1b811490600360fc1b8110158061596a575b7f4100000000000000000000000000000000000000000000000000000000000000821015908161593f575b7f61000000000000000000000000000000000000000000000000000000000000008310159283615914575b84156158ea575b5083156158e2575b5082156158da575b5081156158d2575b50156158cb576001016157f6565b5050505f90565b90505f6158bd565b91505f6158b5565b92505f6158ad565b7f2d000000000000000000000000000000000000000000000000000000000000001493505f6158a5565b7f7a00000000000000000000000000000000000000000000000000000000000000811115935061589e565b7f5a000000000000000000000000000000000000000000000000000000000000008311159150615873565b507f3900000000000000000000000000000000000000000000000000000000000000811115615848565b806159a857506040516157ad602082614710565b600a811015615a0e576159ba906154e9565b614821602260405180937f2e3000000000000000000000000000000000000000000000000000000000000060208301526159fd81518092602086860191016146ca565b81010301601f198101835282614710565b615a17906154e9565b614821602160405180937f2e0000000000000000000000000000000000000000000000000000000000000060208301526159fd81518092602086860191016146ca565b60405190615a69604083614710565b601082527f68736c283233302c3231252c31312529000000000000000000000000000000006020830152565b8015615cc957615aa3615a5a565b9061271003906127108211614bf257602e6061916050615ac5614821956154e9565b60576040519788947f3c672066696c6c3d226e6f6e65223e000000000000000000000000000000000060208701527f3c636972636c652063783d22313636222063793d2235302220723d2232322220602f8701527f7374726f6b653d22000000000000000000000000000000000000000000000000604f870152615b52815180926020868a0191016146ca565b85017f22207374726f6b652d77696474683d223130222f3e0000000000000000000000838201527f3c636972636c652063783d22313636222063793d2235302220706174684c656e606c8201527f6774683d2231303030302220723d22323222207374726f6b653d220000000000608c820152615bd982518093602060a7850191016146ca565b01017f22207374726f6b652d6461736861727261793d22313030303022207374726f6b838201527f652d646173686f66667365743d220000000000000000000000000000000000006070820152615c3a825180936020607e850191016146ca565b01017f22207374726f6b652d6c696e656361703d22726f756e6422207374726f6b652d838201527f77696474683d223522207472616e73666f726d3d22726f74617465282d393029604e8201527f22207472616e73666f726d2d6f726967696e3d22313636203530222f3e000000606e820152631e17b39f60e11b608b82015203016041810184520182614710565b50506040516157ad602082614710565b600d6150209193929360206040519582615cfc88945180928580880191016146ca565b830164010714051160dd1b8382015267029b0b13634b2b9160c51b6025820152615d2f8251809385602d850191016146ca565b01010301601f198101845283614710565b60056150209193929360206040519582615d6388945180928580880191016146ca565b830164010714051160dd1b83820152615d2f82518093856025850191016146ca565b6004811015614ea85780615dcf5750604051615da2604082614710565b600881527f50726f6772657373000000000000000000000000000000000000000000000000602082015290565b60018103615e135750604051615de6604082614710565b600681527f5374617475730000000000000000000000000000000000000000000000000000602082015290565b600203615e5557604051615e28604082614710565b600681527f416d6f756e740000000000000000000000000000000000000000000000000000602082015290565b604051615e63604082614710565b600881527f4475726174696f6e000000000000000000000000000000000000000000000000602082015290565b5f90805180156158cb5790600d915f925f925b828410615eb65750505050600d02900390565b90919294603b60f81b7fff00000000000000000000000000000000000000000000000000000000000000615eea88856157e0565b511614615f00575b820194600101929190615ea3565b859450615ef2565b5f90805180156158cb57906010915f925f925b828410615f2e575050505060041b900390565b90919294603b60f81b7fff00000000000000000000000000000000000000000000000000000000000000615f6288856157e0565b511614615f78575b820194600101929190615f1b565b859450615f6a56fea164736f6c634300081a000a"; + hex"60808060405234601557615f8d908161001a8239f35b5f80fdfe6102406040526004361015610012575f80fd5b5f3560e01c63e9dc637514610025575f80fd5b346141bc5760403660031901126141bc576001600160a01b036004351680600435036141bc576103e06040525f61024081905260606102608190526102808290526102a08290526102c0819052610300819052610320819052610340819052610360819052610380526103a08190526103c0526102e08190526100b6906100ad600435614824565b61030052614b1e565b610320526102e0516040517feac8f5b80000000000000000000000000000000000000000000000000000000081526024803560048301529091602091839182906001600160a01b03165afa9081156145b8575f91614681575b506001600160a01b0361012791168061024052614c1a565b610260526102e0516040517fa80fc0710000000000000000000000000000000000000000000000000000000081526024803560048301529091602091839182906001600160a01b03165afa80156145b8576fffffffffffffffffffffffffffffffff915f91614662575b5016610280526102e0516040517fad35efd40000000000000000000000000000000000000000000000000000000081526024803560048301529091602091839182906001600160a01b03165afa80156145b8575f90614625575b6101f59150614dbc565b610360526102e0516040517f4869e12d0000000000000000000000000000000000000000000000000000000081526024803560048301529091602091839182906001600160a01b03165afa9081156145b8575f916145f6575b50610280516fffffffffffffffffffffffffffffffff1680156145e2576fffffffffffffffffffffffffffffffff612710819302160416610160610240015260405160208101904682526bffffffffffffffffffffffff1960043560601b1660408201526024356054820152605481526102c9607482614710565b51902061040a60028061016861ffff8560101c160693600161031c63ffffffff601e61031482601461030c82604660ff6050818d60081c16069b16069d166154e9565b9701166154e9565b9801166154e9565b60246040519788947f68736c2800000000000000000000000000000000000000000000000000000000602087015261035d815180926020868a0191016146ca565b85017f2c00000000000000000000000000000000000000000000000000000000000000838201526103988251809360206025850191016146ca565b01017f252c000000000000000000000000000000000000000000000000000000000000838201526103d38251809360206003850191016146ca565b01017f2529000000000000000000000000000000000000000000000000000000000000838201520301601d19810184520182614710565b6104446fffffffffffffffffffffffffffffffff604061024001511660ff61043d6001600160a01b036102405116614ebc565b1690615022565b9061045a6001600160a01b036102405116614b1e565b906020610240015190602460206001600160a01b0360a0610240015116604051928380927fbc2be1be000000000000000000000000000000000000000000000000000000008252823560048301525afa80156145b8576024915f916145c3575b5060206001600160a01b0360a0610240015116604051938480927f9067b677000000000000000000000000000000000000000000000000000000008252823560048301525afa80156145b85764ffffffffff8091610522945f91614589575b50169116615318565b610320516103a051939091906105ac600161054a6064610543818a06615994565b98046154e9565b602060405198826105648b945180928580880191016146ca565b8301610578825180938580850191016146ca565b01017f2500000000000000000000000000000000000000000000000000000000000000815203601e19810188520186614710565b61016061024001519561012061024001519760c061024001519560405161016052610140610160510161016051811067ffffffffffffffff821117614575576040526101605152602061016051015260406101605101526060610160510152608061016051015260a061016051015260c061016051015260e06101605101526101006101605101526101206101605101526040516101c0810181811067ffffffffffffffff82111761457557604052606081525f60208201525f60408201526060808201525f6080820152606060a08201525f60c08201525f60e082015260606101008201525f6101208201525f61014082015260606101608201525f6101808201525f6101a082015260e06101605101516108ec6109cb60046007602760586106e3610100610160510151610160515190615a95565b60b76106ee5f615d85565b985f6102205260206102205261071660405161070d6102205182614710565b5f8152846157b3565b1561456b57601b60909a5b61072a8c6154e9565b906040519b8c9889937f3c672069643d220000000000000000000000000000000000000000000000000061022051860152835161077081846102205188019801886146ca565b8b017f222066696c6c3d2223666666223e000000000000000000000000000000000000838201527f3c726563742077696474683d220000000000000000000000000000000000000060358201526107d382518093604284019061022051016146ca565b0101917f22206865696768743d22313030222066696c6c2d6f7061636974793d222e3033858401527f222072783d223135222072793d22313522207374726f6b653d22236666662220603b8401527f7374726f6b652d6f7061636974793d222e3122207374726f6b652d7769647468605b8401527f3d2234222f3e0000000000000000000000000000000000000000000000000000607b8401527f3c7465787420783d2232302220793d2233342220666f6e742d66616d696c793d60818401527f2227436f7572696572204e6577272c417269616c2c6d6f6e6f7370616365222060a18401527f666f6e742d73697a653d2232327078223e00000000000000000000000000000060c184015251809360d28401906146ca565b0101661e17ba32bc3a1f60c91b838201527f3c7465787420783d2232302220793d2237322220666f6e742d66616d696c793d60be8201527f2227436f7572696572204e6577272c417269616c2c6d6f6e6f7370616365222060de8201527f666f6e742d73697a653d2232367078223e00000000000000000000000000000060fe8201526109868251809361010f84019061022051016146ca565b0101661e17ba32bc3a1f60c91b838201526109ad82518093605f84019061022051016146ca565b0101631e17b39f60e11b838201520301601b19810184520182614710565b6101008301526101208201526101206101605101516108ec610a3960046007602760586040516109fe6102205182614710565b5f815260b7610a0d6001615d85565b98601b6028610a1b8c615e90565b610a2484615f08565b8082111561456457505b019a61072a8c6154e9565b61016083015261018082015260206101605101516108ec610a7a6004600760276058604051610a6b6102205182614710565b5f815260b7610a0d6002615d85565b8252602082015260286080610160510151604051610a9b6102205182614710565b5f81526108ec610ae56004600760276058610ab66003615d85565b9660b7610ac289615e90565b610acb8b615f08565b8082111561455c5750995b601b8c8c019a61072a8c6154e9565b60a085015260c0840152602083015101016101208201510161018082015101603081016080830152602f19906103e8030160011c8061014083015261012082015101601081016101a083015261018082015101610220518101604083015260106102205191602084015101010160e0820152610b7461010082015161016083015183519060a085015192614fb5565b60608201526101006101208190526040516101a0819052610b959190614710565b60c76101a051527f3c726563742077696474683d223130302522206865696768743d223130302522610220516101a05101527f2066696c7465723d2275726c28234e6f69736529222f3e3c7265637420783d2260406101a05101527f37302220793d223730222077696474683d2238363022206865696768743d223860606101a05101527f3630222066696c6c3d2223666666222066696c6c2d6f7061636974793d222e3060806101a05101527f33222072783d223435222072793d22343522207374726f6b653d22236666662260a06101a05101527f207374726f6b652d6f7061636974793d222e3122207374726f6b652d7769647460c06101a05101527f683d2234222f3e0000000000000000000000000000000000000000000000000060e06101a05101526101605151610120610160510151906060830151610140525f610200526060610200526040516101e052610cf7610200516101e051614710565b60336101e051527f3c636972636c652069643d22476c6f772220723d22353030222066696c6c3d22610220516101e05101527f75726c282352616469616c476c6f7729222f3e0000000000000000000000000060406101e051015261014060405190610d638183614710565b61011c82527f3c66696c7465722069643d224e6f697365223e3c6665466c6f6f6420783d2230610220518301527f2220793d2230222077696474683d223130302522206865696768743d2231303060408301527f252220666c6f6f642d636f6c6f723d2268736c283233302c3231252c31312529610200518301527f2220666c6f6f642d6f7061636974793d22312220726573756c743d22666c6f6f60808301527f6446696c6c222f3e3c666554757262756c656e6365206261736546726571756560a08301527f6e63793d222e3422206e756d4f6374617665733d22332220726573756c743d2260c08301527f4e6f6973652220747970653d226672616374616c4e6f697365222f3e3c66654260e08301527f6c656e6420696e3d224e6f6973652220696e323d22666c6f6f6446696c6c2220610120518301527f6d6f64653d22736f66742d6c69676874222f3e3c2f66696c7465723e000000006101208301525f6101c0526103a06101c0526119426118bd6073606b60405196610eec6101c05189614710565b61037b88527f3c706174682069643d224c6f676f222066696c6c3d2223666666222066696c6c610220518901527f2d6f7061636974793d222e312220643d226d3133332e3535392c3132342e303360408901527f34632d2e3031332c322e3431322d312e3035392c342e3834382d322e3932332c610200518901527f362e3430322d322e3535382c312e3831392d352e3136382c332e3433392d372e60808901527f3838382c342e3939362d31342e34342c382e3236322d33312e3034372c31322e60a08901527f3536352d34372e3637342c31322e3536392d382e3835382e3033362d31372e3860c08901527f33382d312e3237322d32362e3332382d332e3636332d392e3830362d322e373660e08901527f362d31392e3038372d372e3131332d32372e3536322d31322e3737382d31332e610120518901527f3834322d382e3032352c392e3436382d32382e3630362c31362e3135332d33356101208901527f2e323635683063322e3033352d312e3833382c342e3235322d332e3534362c36868901527f2e3436332d352e323234683063362e3432392d352e3635352c31362e3231382d6101608901527f322e3833352c32302e3335382c342e31372c342e3134332c352e3035372c382e6101808901527f3831362c392e3634392c31332e39322c31332e373334682e30333763352e37336101a08901527f362c362e3436312c31352e3335372d322e3235332c392e33382d382e34382c306101c08901527f2c302d332e3531352d332e3531352d332e3531352d332e3531352d31312e34396101e08901527f2d31312e3437382d35322e3635362d35322e3636342d36342e3833372d36342e6102008901527f3833376c2e3034392d2e303337632d312e3732352d312e3630362d322e3731396102208901527f2d332e3834372d322e3735312d362e3230346830632d2e3034362d322e3337356102408901527f2c312e3036322d342e3538322c322e3732362d362e32323968306c2e3138352d6102608901527f2e3134386830632e3039392d2e3036322c2e3232322d2e3134382c2e33372d2e6102808901527f323539683063322e30362d312e3336322c332e3935312d322e3632312c362e306102a08901527f34342d332e3834324335372e3736332d332e3437332c39372e37362d322e33346102c08901527f312c3132382e3633372c31382e3333326331362e3637312c392e3934362d32366102e08901527f2e3334342c35342e3831332d33382e3635312c34302e3139392d362e3239392d6103008901527f362e3039362d31382e3036332d31372e3734332d31392e3636382d31382e38316103208901527f312d362e3031362d342e3034372d31332e3036312c342e3737362d372e3735326103408901527f2c392e3735316c36382e3235342c36382e33373163312e3732342c312e3630316103608901527f2c322e3731342c332e38342c322e3733382c362e3139325a222f3e00000000006103808901525f6101805260a06101805260405160a0526113516101805160a051614710565b607560a051527f3c706174682069643d22466c6f6174696e6754657874222066696c6c3d226e6f6102205160a05101527f6e652220643d224d313235203435683735307338302030203830203830763735604060a05101527f307330203830202d3830203830682d373530732d38302030202d3830202d38306102005160a05101527f762d3735307330202d3830203830202d3830222f3e0000000000000000000000608060a051015261193d6014602261140a615a5a565b9360a2604051957f3c72616469616c4772616469656e742069643d2252616469616c476c6f77223e610220518801527f3c73746f70206f66667365743d223025222073746f702d636f6c6f723d220000604088015261153f6025603589605e87519561022051890196611480818486018a6146ca565b83017f222073746f702d6f7061636974793d222e36222f3e0000000000000000000000838201528f7f3c73746f70206f66667365743d2231303025222073746f702d636f6c6f723d22908201526114e382518093609384019061022051016146ca565b01017f222073746f702d6f7061636974793d2230222f3e000000000000000000000000838201527f3c2f72616469616c4772616469656e743e00000000000000000000000000000060498201520301600581018a520188614710565b61165685602361154d615a5a565b6040519b8c917f3c6c696e6561724772616469656e742069643d2253616e64546f70222078313d610220518401527f223025222079313d223025223e0000000000000000000000000000000000000060408401527f3c73746f70206f66667365743d223025222073746f702d636f6c6f723d220000604d84015288516115d6818486018a6146ca565b83016211179f60e91b838201527f3c73746f70206f66667365743d2231303025222073746f702d636f6c6f723d22606e82015261161f82518093608e84019061022051016146ca565b01016211179f60e91b83820152701e17b634b732b0b923b930b234b2b73a1f60791b60268201520301600b1981018b520189614710565b6117e060726023611665615a5a565b6040519c8d917f3c6c696e6561724772616469656e742069643d2253616e64426f74746f6d2220610220518401527f78313d2231303025222079313d2231303025223e00000000000000000000000060408401527f3c73746f70206f66667365743d22313025222073746f702d636f6c6f723d220060548401526116f4815180928486019061022051016146ca565b82016211179f60e91b828201527f3c73746f70206f66667365743d2231303025222073746f702d636f6c6f723d22607682015288519061173882609683018a6146ca565b01016211179f60e91b838201527f3c616e696d617465206174747269627574654e616d653d22783122206475723d60268201527f2236732220726570656174436f756e743d22696e646566696e6974652220766160468201527f6c7565733d223330253b3630253b313230253b3630253b3330253b222f3e00006066820152701e17b634b732b0b923b930b234b2b73a1f60791b60848201520301605281018c52018a614710565b6117e8615a5a565b906040519a8b947f3c6c696e6561724772616469656e742069643d22486f7572676c617373537472610220518701527f6f6b6522206772616469656e745472616e73666f726d3d22726f74617465283960408701527f302922206772616469656e74556e6974733d227573657253706163654f6e5573610200518701527f65223e000000000000000000000000000000000000000000000000000000000060808701527f3c73746f70206f66667365743d22353025222073746f702d636f6c6f723d22006083870152518092858701906146ca565b83016211179f60e91b838201527f3c73746f70206f66667365743d22383025222073746f702d636f6c6f723d220060a58201526119068251809360c484019061022051016146ca565b01016211179f60e91b83820152701e17b634b732b0b923b930b234b2b73a1f60791b60258201520301600b19810187520185614710565b614fb5565b60e052611956611950614d46565b856157b3565b938415614541575b5060c061010081905260405191906119769083614710565b609082527f3c7061746820643d224d2035302c3336302061203330302c333030203020312c610220518301527f31203630302c302061203330302c333030203020312c31202d3630302c30222060408301527f66696c6c3d2223666666222066696c6c2d6f7061636974793d222e3032222073610200518301527f74726f6b653d2275726c2823486f7572676c6173735374726f6b65292220737460808301527f726f6b652d77696474683d2234222f3e00000000000000000000000000000000610180518301526102c060405160c052611a538160c051614710565b61029860c051527f3c7061746820643d226d3536362c3136312e323031762d35332e39323463302d6102205160c05101527f31392e3338322d32322e3531332d33372e3536332d36332e3339382d35312e31604060c05101527f39382d34302e3735362d31332e3539322d39342e3934362d32312e3037392d316102005160c05101527f35322e3538372d32312e303739732d3131312e3833382c372e3438372d313532608060c05101527f2e3630322c32312e303739632d34302e3839332c31332e3633362d36332e34316101805160c05101527f332c33312e3831362d36332e3431332c35312e3139387635332e39323463302c6101005160c05101527f31372e3138312c31372e3730342c33332e3432372c35302e3232332c34362e3360e060c05101527f3934763238342e383039632d33322e3531392c31322e39362d35302e3232332c6101205160c05101527f32392e3230362d35302e3232332c34362e3339347635332e39323463302c313961012060c05101527f2e3338322c32322e35322c33372e3536332c36332e3431332c35312e3139382c8260c05101527f34302e3736332c31332e3539322c39342e3935342c32312e3037392c3135322e61016060c05101527f3630322c32312e303739733131312e3833312d372e3438372c3135322e35383761018060c05101527f2d32312e3037396334302e3838362d31332e3633362c36332e3339382d33312e6101a060c05101527f3831362c36332e3339382d35312e313938762d35332e39323463302d31372e316101c060c05101527f39362d31372e3730342d33332e3433352d35302e3232332d34362e34303156326101e060c05101527f30372e3630336333322e3531392d31322e3936372c35302e3232332d32392e3261020060c05101527f30362c35302e3232332d34362e3430315a6d2d3334372e3436322c35372e373961022060c05101527f336c3133302e3935392c3133312e3032372d3133302e3935392c3133312e303161024060c05101527f33563231382e3939345a6d3236322e3932342e303232763236322e3031386c2d61026060c05101527f3133302e3933372d3133312e3030362c3133302e3933372d3133312e3031335a61028060c05101527f222066696c6c3d2223313631383232223e3c2f706174683e00000000000000006102a060c0510152855f1461432c57604051611dce6102205182614710565b5f8152955b156141d957604051611de76101e082614710565b6101b181527f3c7061746820643d226d3438312e34362c3438312e35347638312e3031632d32610220518201527f2e33352e37372d342e38322c312e35312d372e33392c322e32332d33302e332c60408201527f382e35342d37342e36352c31332e39322d3132342e30362c31332e39322d3533610200518201527f2e362c302d3130312e32342d362e33332d3133312e34372d31362e3136762d3860808201527f316c34362e332d34362e3331683137302e33336c34362e32392c34362e33315a610180518201527f222066696c6c3d2275726c282353616e64426f74746f6d29222f3e3c70617468610100518201527f20643d226d3433352e31372c3433352e323363302c312e31372d2e34362c322e60e08201527f33322d312e33332c332e34342d372e31312c392e30382d34312e39332c31352e610120518201527f39382d38332e38312c31352e3938732d37362e372d362e392d38332e38322d316101208201527f352e3938632d2e38372d312e31322d312e33332d322e32372d312e33332d332e838201527f3434762d2e30346c382e33342d382e33352e30312d2e30316331332e37322d366101608201527f2e35312c34322e39352d31312e30322c37362e382d31312e30327336322e39376101808201527f2c342e34392c37362e37322c31316c382e34322c382e34325a222066696c6c3d6101a08201527f2275726c282353616e64546f7029222f3e0000000000000000000000000000006101c0820152905b604051926120206107e085614710565b6107a7845261022080517f3c672066696c6c3d226e6f6e6522207374726f6b653d2275726c2823486f7572908601527f676c6173735374726f6b652922207374726f6b652d6c696e656361703d22726f60408087019190915261020080517f756e6422207374726f6b652d6d697465726c696d69743d22313022207374726f908801527f6b652d77696474683d2234223e3c7061746820643d226d3536352e3634312c31608088015261018080517f30372e323863302c392e3533372d352e35362c31382e3632392d31352e36373690890152610100517f2c32362e393733682d2e303233632d392e3230342c372e3539362d32322e3139908901527f342c31342e3536322d33382e3139372c32302e3539322d33392e3530342c313460e089015261012080517f2e3933362d39372e3332352c32342e3335352d3136312e3733332c32342e3335908a01527f352d39302e34382c302d3136372e3934382d31382e3538322d3139392e393533908901527f2d34342e393438682d2e303233632d31302e3131352d382e3334342d31352e36948801949094527f37362d31372e3433372d31352e3637362d32362e3937332c302d33392e3733356101608801527f2c39362e3535342d37312e3932312c3231352e3635322d37312e393231733231938701939093527f352e3632392c33322e3138352c3231352e3632392c37312e3932315a222f3e3c6101a08701527f7061746820643d226d3133342e33362c3136312e32303363302c33392e3733356101c0808801919091527f2c39362e3535342c37312e3932312c3231352e3635322c37312e3932317332316101e08801527f352e3632392d33322e3138362c3231352e3632392d37312e393231222f3e3c6c938701939093527f696e652078313d223133342e3336222079313d223136312e323033222078323d828701527f223133342e3336222079323d223130372e3238222f3e3c6c696e652078313d226102408701527f3536352e3634222079313d223136312e323033222078323d223536352e3634226102608701527f2079323d223130372e3238222f3e3c6c696e652078313d223138342e353834226102808701527f2079313d223230362e383233222078323d223138342e353835222079323d22356102a08701527f33372e353739222f3e3c6c696e652078313d223231382e313831222079313d22938601939093527f3231382e313138222078323d223231382e313831222079323d223536322e35336102e08601527f37222f3e3c6c696e652078313d223438312e383138222079313d223231382e316103008601527f3432222078323d223438312e383139222079323d223536322e343238222f3e3c6103208601527f6c696e652078313d223531352e343135222079313d223230372e3335322220786103408601527f323d223531352e343136222079323d223533372e353739222f3e3c70617468206103608601527f643d226d3138342e35382c3533372e353863302c352e34352c342e32372c313061038086015290517f2e36352c31322e30332c31352e3432682e303263352e35312c332e33392c3132908501527f2e37392c362e35352c32312e35352c392e34322c33302e32312c392e392c37386103c08501527f2e30322c31362e32382c3133312e38332c31362e32382c34392e34312c302c396103e08501527f332e37362d352e33382c3132342e30362d31332e39322c322e372d2e37362c356104008501527f2e32392d312e35342c372e37352d322e33352c382e37372d322e38372c31362e6104208501527f30352d362e30342c32312e35362d392e3433683063372e37362d342e37372c316104408501527f322e30342d392e39372c31322e30342d31352e3432222f3e3c7061746820643d6104608501527f226d3138342e3538322c3439322e363536632d33312e3335342c31322e3438356104808501527f2d35302e3232332c32382e35382d35302e3232332c34362e3134322c302c392e6104a08501527f3533362c352e3536342c31382e3632372c31352e3637372c32362e393639682e6104c08501527f30323263382e3530332c372e3030352c32302e3231332c31332e3436332c33346104e08501527f2e3532342c31392e3135392c392e3939392c332e3939312c32312e3236392c376105008501527f2e3630392c33332e3539372c31302e3738382c33362e34352c392e3430372c386105208501527f322e3138312c31352e3030322c3133312e3833352c31352e3030327339352e336105408501527f36332d352e3539352c3133312e3830372d31352e3030326331302e3834372d326105608501527f2e37392c32302e3836372d352e3932362c32392e3932342d392e3334392c312e6105808501527f3234342d2e3436372c322e3437332d2e3934322c332e3637332d312e3432342c6105a08501527f31342e3332362d352e3639362c32362e3033352d31322e3136312c33342e35326105c08501527f342d31392e313733682e3032326331302e3131342d382e3334322c31352e36376105e08501527f372d31372e3433332c31352e3637372d32362e3936392c302d31372e3536322d6106008501527f31382e3836392d33332e3636352d35302e3232332d34362e3135222f3e3c70616106208501527f746820643d226d3133342e33362c3539322e373263302c33392e3733352c39366106408501527f2e3535342c37312e3932312c3231352e3635322c37312e393231733231352e366106608501527f32392d33322e3138362c3231352e3632392d37312e393231222f3e3c6c696e656106808501527f2078313d223133342e3336222079313d223539322e3732222078323d223133346106a08501527f2e3336222079323d223533382e373937222f3e3c6c696e652078313d223536356106c08501527f2e3634222079313d223539322e3732222078323d223536352e3634222079323d6106e08501527f223533382e373937222f3e3c706f6c796c696e6520706f696e74733d223438316107008501527f2e383232203438312e393031203438312e373938203438312e383737203438316107208501527f2e373735203438312e383534203335302e303135203335302e303236203231386107408501527f2e313835203231382e313239222f3e3c706f6c796c696e6520706f696e74733d6107608501527f223231382e313835203438312e393031203231382e323331203438312e3835346107808501527f203335302e303135203335302e303236203438312e383232203231382e3135326107a08501527f222f3e3c2f673e000000000000000000000000000000000000000000000000006107c0850152905181517f3c672069643d22486f7572676c617373223e00000000000000000000000000009082015284519151909788959092916129ef9183916032890191016146ca565b840160c051519060328101826102205160c0510191612a0d926146ca565b016032018082518093610220510191612a25926146ca565b018082518093610220510191612a3a926146ca565b018082518093610220510191612a4f926146ca565b01631e17b39f60e11b815203601b1981018452600401612a6f9084614710565b60405160805261022051608051017f3c646566733e000000000000000000000000000000000000000000000000000090526101e0515160805160260181610220516101e0510191612abf926146ca565b60805101815191826026830191610220510191612adb926146ca565b016026018082518093610220510191612af3926146ca565b0160a051519080826102205160a0510191612b0d926146ca565b0160e051519080826102205160e0510191612b27926146ca565b018082518093610220510191612b3c926146ca565b01610140515190808261022051610140510191612b58926146ca565b017f3c2f646566733e000000000000000000000000000000000000000000000000008152608051900360181981016080515260070160805190612b9a91614710565b6101605160a00151906101605160c00151916101605160400151906101605160600151612bc78583615cd9565b916040958651612bd78882614710565b600581526102205181017f2d31303025000000000000000000000000000000000000000000000000000000905287519485916102205183017f3c74657874506174682073746172744f66667365743d220000000000000000009052805190816037850191610220510191612c4a926146ca565b7f2220687265663d2223466c6f6174696e6754657874222066696c6c3d222366666037918401918201527f662220666f6e742d66616d696c793d2227436f7572696572204e6577272c417260578201527f69616c2c6d6f6e6f7370616365222066696c6c2d6f7061636974793d222e3822607782015271103337b73a16b9b4bd329e91191b383c111f60711b60978201527f3c616e696d6174652061646469746976653d2273756d2220617474726962757460a98201527f654e616d653d2273746172744f66667365742220626567696e3d22307322206460c98201527f75723d22353073222066726f6d3d2230252220726570656174436f756e743d2260e98201527f696e646566696e6974652220746f3d2231303025222f3e0000000000000000006101098201528151610220519092612d8e918491610120850191016146ca565b0160370160e981016a1e17ba32bc3a2830ba341f60a91b90520360e90160141981018552600b01612dbf9085614710565b612dc891615cd9565b928551612dd58782614710565b60028152610220518101947f3025000000000000000000000000000000000000000000000000000000000000865287519586926102205184017f3c74657874506174682073746172744f66667365743d22000000000000000000905251908160378501612e41926146ca565b7f2220687265663d2223466c6f6174696e6754657874222066696c6c3d222366666037918401918201527f662220666f6e742d66616d696c793d2227436f7572696572204e6577272c417260578201527f69616c2c6d6f6e6f7370616365222066696c6c2d6f7061636974793d222e3822607782015271103337b73a16b9b4bd329e91191b383c111f60711b60978201527f3c616e696d6174652061646469746976653d2273756d2220617474726962757460a98201527f654e616d653d2273746172744f66667365742220626567696e3d22307322206460c98201527f75723d22353073222066726f6d3d2230252220726570656174436f756e743d2260e98201527f696e646566696e6974652220746f3d2231303025222f3e0000000000000000006101098201528151610220519092612f85918491610120850191016146ca565b0160370160e981016a1e17ba32bc3a2830ba341f60a91b90520360e90160141981018552600b01612fb69085614710565b612fc08282615d40565b918651612fcd8882614710565b60048152610220518101937f2d35302500000000000000000000000000000000000000000000000000000000855288519485926102205184017f3c74657874506174682073746172744f66667365743d22000000000000000000905251908160378501613039926146ca565b7f2220687265663d2223466c6f6174696e6754657874222066696c6c3d222366666037918401918201527f662220666f6e742d66616d696c793d2227436f7572696572204e6577272c417260578201527f69616c2c6d6f6e6f7370616365222066696c6c2d6f7061636974793d222e3822607782015271103337b73a16b9b4bd329e91191b383c111f60711b60978201527f3c616e696d6174652061646469746976653d2273756d2220617474726962757460a98201527f654e616d653d2273746172744f66667365742220626567696e3d22307322206460c98201527f75723d22353073222066726f6d3d2230252220726570656174436f756e743d2260e98201527f696e646566696e6974652220746f3d2231303025222f3e000000000000000000610109820152815161022051909261317d918491610120850191016146ca565b0160370160e981016a1e17ba32bc3a2830ba341f60a91b90520360e90160141981018452600b016131ae9084614710565b6131b791615d40565b9085516131c48782614710565b60038152610220518101927f3530250000000000000000000000000000000000000000000000000000000000845287519384926102205184017f3c74657874506174682073746172744f66667365743d22000000000000000000905251908160378501613230926146ca565b7f2220687265663d2223466c6f6174696e6754657874222066696c6c3d222366666037918401918201527f662220666f6e742d66616d696c793d2227436f7572696572204e6577272c417260578201527f69616c2c6d6f6e6f7370616365222066696c6c2d6f7061636974793d222e3822607782015271103337b73a16b9b4bd329e91191b383c111f60711b60978201527f3c616e696d6174652061646469746976653d2273756d2220617474726962757460a98201527f654e616d653d2273746172744f66667365742220626567696e3d22307322206460c98201527f75723d22353073222066726f6d3d2230252220726570656174436f756e743d2260e98201527f696e646566696e6974652220746f3d2231303025222f3e0000000000000000006101098201528151610220519092613374918491610120850191016146ca565b0160370160e981016a1e17ba32bc3a2830ba341f60a91b90520360e90160141981018352600b016133a59083614710565b85519384936102205185017f3c7465787420746578742d72656e646572696e673d226f7074696d697a65537090528785017f656564223e0000000000000000000000000000000000000000000000000000009052805190816045870191610220510191613411926146ca565b840181519182604583019161022051019161342b926146ca565b016045018082518093610220510191613443926146ca565b018082518093610220510191613458926146ca565b01661e17ba32bc3a1f60c91b8152036018198101825260070161347b9082614710565b610140820151916101a08101519060408101519060e001519361349d906154e9565b916134a7906154e9565b906134b1906154e9565b936134bb906154e9565b8551948592610220518401947f3c75736520687265663d2223476c6f77222066696c6c2d6f7061636974793d2286528885017f2e39222f3e0000000000000000000000000000000000000000000000000000009052604585017f3c75736520687265663d2223476c6f772220783d22313030302220793d2231309052606585017f3030222066696c6c2d6f7061636974793d222e39222f3e0000000000000000009052607c85017f3c75736520687265663d22234c6f676f2220783d223137302220793d223137309052609c85017f22207472616e73666f726d3d227363616c65282e3629222f3e3c757365206872905260bc85017f65663d2223486f7572676c6173732220783d223135302220793d223930222074905260dc85017f72616e73666f726d3d22726f746174652831302922207472616e73666f726d2d905260fc85017f6f726967696e3d2235303020353030222f3e0000000000000000000000000000905261010e85017f3c75736520687265663d222350726f67726573732220783d2200000000000000905280519081610127870191610220510191613662926146ca565b840161012781016a11103c9e911b9c9811179f60a91b905261013281017f3c75736520687265663d22235374617475732220783d220000000000000000009052815191826101498301916102205101916136bb926146ca565b0161012701602281016a11103c9e911b9c9811179f60a91b9052602d81017f3c75736520687265663d2223416d6f756e742220783d220000000000000000009052815191826044830191610220510191613714926146ca565b01602201602281016a11103c9e911b9c9811179f60a91b9052602d81017f3c75736520687265663d22234475726174696f6e2220783d2200000000000000905281519182604683019161022051019161376c926146ca565b01602201602481016a11103c9e911b9c9811179f60a91b90520360240160141981018452600b0161379d9084614710565b83519283926102205184017f3c73766720786d6c6e733d22687474703a2f2f7777772e77332e6f72672f323090528584017f30302f737667222077696474683d223130303022206865696768743d2231303090526102005184017f30222076696577426f783d2230203020313030302031303030223e000000000090526101a05151607b850181610220516101a0510191613837926146ca565b84016080515190607b810182610220516080510191613855926146ca565b01607b01808251809361022051019161386d926146ca565b0191829151809361387d926146ca565b017f3c2f7376673e0000000000000000000000000000000000000000000000000000815203601919810182526006016138b69082614710565b610380526102e0518151610220517fb25645690000000000000000000000000000000000000000000000000000000090820190815260248035818401528252916001600160a01b03169061390b604482614710565b515a925f93928493fa61391c614793565b610340819052901580156103c0526141d15761022051818051810103126141bc5761022051015180151581036141bc575b15156102a052610260516102e05182517fb971302a00000000000000000000000000000000000000000000000000000000815260248035600483015261022051919283919082906001600160a01b03165afa9081156141c7575f9161417b575b50600360236139be613ad693614b1e565b938161012061024001518780519788947f5b7b2274726169745f74797065223a224173736574222c2276616c7565223a2261022051870152613a0b815180928589019061022051016146ca565b85017f227d2c7b2274726169745f74797065223a2253656e646572222c2276616c75658382015262111d1160e91b61020051820152613a5682518093606384019061022051016146ca565b01017f227d2c7b2274726169745f74797065223a22537461747573222c2276616c75658382015262111d1160e91b6043820152613a9f82518093604684019061022051016146ca565b01017f227d5d0000000000000000000000000000000000000000000000000000000000838201520301601c19810184520182614710565b6103005161026051610320516102405191939291613afc906001600160a01b0316614b1e565b613b076024356154e9565b6102a051909190156140ef5761010051875190613b249082614710565b609b81527fe29aa0efb88f205741524e494e473a205472616e7366657272696e6720746865610220518201527f204e4654206d616b657320746865206e6577206f776e65722074686520726563888201527f697069656e74206f66207468652073747265616d2e205468652066756e647320610200518201527f617265206e6f74206175746f6d61746963616c6c792077697468647261776e2060808201527f666f72207468652070726576696f757320726563697069656e742e000000000061018051820152915b8751968794610220518601967f54686973204e465420726570726573656e74732061207061796d656e7420737488528a87017f7265616d20696e2061205361626c696572204c6f636b757020000000000000009052805190610220518101918060598a0190613c5c91856146ca565b7f20636f6e74726163742e20546865206f776e6572206f662074686973204e46546059918a01918201527f2063616e207769746864726177207468652073747265616d656420617373657460798201527f732c207768696368206172652064656e6f6d696e6174656420696e2000000000609982015284516102205186019691613cea8260b583018a6146ca565b01605901605c81017f2e5c6e5c6e2d2053747265616d2049443a200000000000000000000000000000905281519182606e830191610220510191613d2d926146ca565b01605c0190601282016302e3716960e51b905251918260168301613d50926146ca565b01601201600481016901020b2323932b9b99d160b51b905281519182600e830191610220510191613d80926146ca565b0160040190600a82016302e3716960e51b9052519182600e8301613da3926146ca565b01600a01600481016901020b2323932b9b99d160b51b905281519182600e830191610220510191613dd3926146ca565b01600401600a81017f5c6e5c6e00000000000000000000000000000000000000000000000000000000905281519182600e830191610220510191613e16926146ca565b01600a0103600401601f1981018452613e2f9084614710565b61030051613e3e6024356154e9565b855180916102205182019367029b0b13634b2b9160c51b8552805190816028850191610220510191613e6f926146ca565b8201602881017f2023000000000000000000000000000000000000000000000000000000000000905281519182602a830191610220510191613eb0926146ca565b0160280103600201601f1981018252613ec99082614710565b61038051613ed690615648565b9286519586956102205187017f7b2261747472696275746573223a000000000000000000000000000000000000905280519081602e890191610220510191613f1d926146ca565b860190602e82017f2c226465736372697074696f6e223a22000000000000000000000000000000009052519182603e8301613f57926146ca565b01602e0190601082017f222c2265787465726e616c5f75726c223a2268747470733a2f2f7361626c69659052603082017f722e636f6d222c226e616d65223a2200000000000000000000000000000000009052519182603f8301613fba926146ca565b01601001602f81017f222c22696d616765223a22646174613a696d6167652f7376672b786d6c3b62619052604f81017f736536342c0000000000000000000000000000000000000000000000000000009052815191826054830191610220510191614024926146ca565b01602f01602581017f227d000000000000000000000000000000000000000000000000000000000000905203602501601d19810182526002016140679082614710565b6102c081905261407690615648565b90805180926102205182017f646174613a6170706c69636174696f6e2f6a736f6e3b6261736536342c000000905280519081603d8401916102205101916140bc926146ca565b810103603d01601f19810183526140d39083614710565b5180916102205182526102205182016140eb916146eb565b0390f35b86516140fc608082614710565b605b81527fe29d95494e464f3a2054686973204e4654206973206e6f6e2d7472616e736665610220518201527f7261626c652e2049742063616e6e6f7420626520736f6c64206f72207472616e888201527f7366657272656420746f20616e6f74686572206163636f756e742e00000000006102005182015291613bed565b9050610220513d61022051116141c0575b6141968183614710565b816102205191810103126141bc57516001600160a01b03811681036141bc5760036139ad565b5f80fd5b503d61418c565b83513d5f823e3d90fd5b50600161394d565b6040516141e861012082614710565b60f881527f3c7061746820643d226d3438312e34362c3530342e3130317635382e34343963610220518201527f2d322e33352e37372d342e38322c312e35312d372e33392c322e32332d33302e60408201527f332c382e35342d37342e36352c31332e39322d3132342e30362c31332e39322d610200518201527f35332e362c302d3130312e32342d362e33332d3133312e34372d31362e31367660808201527f2d35382e343339683236322e39325a222066696c6c3d2275726c282353616e64610180518201527f426f74746f6d29222f3e3c656c6c697073652063783d22333530222063793d22610100518201527f3530342e313031222072783d223133312e343632222072793d2232382e31303860e08201527f222066696c6c3d2275726c282353616e64546f7029222f3e00000000000000006101205182015290612010565b60405161433b6101c082614710565b61019981527f3c706f6c79676f6e20706f696e74733d22333530203335302e30323620343135610220518201527f2e3033203238342e39373820323835203238342e39373820333530203335302e60408201527f303236222066696c6c3d2275726c282353616e64426f74746f6d29222f3e3c70610200518201527f61746820643d226d3431362e3334312c3238312e39373563302c2e3931342d2e60808201527f3335342c312e3830392d312e3033352c322e36382d352e3534322c372e303736610180518201527f2d33322e3636312c31322e34352d36352e32382c31322e34352d33322e363234610100518201527f2c302d35392e3733382d352e3337342d36352e32382d31322e34352d2e36383160e08201527f2d2e3837322d312e3033352d312e3736372d312e3033352d322e36382c302d2e610120518201527f3931342e3335342d312e3830382c312e3033352d322e3637362c352e3534322d6101208201527f372e3037362c33322e3635362d31322e34352c36352e32382d31322e34352c33838201527f322e3631392c302c35392e3733382c352e3337342c36352e32382c31322e34356101608201527f2e3638312e3836372c312e3033352c312e3736322c312e3033352c322e3637366101808201527f5a222066696c6c3d2275726c282353616e64546f7029222f3e000000000000006101a082015295611dd3565b61455591945061454f614d81565b906157b3565b925f61195e565b905099610ad6565b9050610a2e565b601b60d09a610721565b634e487b7160e01b5f52604160045260245ffd5b6145ab915060203d6020116145b1575b6145a38183614710565b81019061475a565b5f610519565b503d614599565b6040513d5f823e3d90fd5b6145dc915060203d6020116145b1576145a38183614710565b5f6104ba565b634e487b7160e01b5f52601260045260245ffd5b614618915060203d60201161461e575b6146108183614710565b810190614732565b5f61024e565b503d614606565b506020813d60201161465a575b8161463f60209383614710565b810103126141bc575160058110156141bc576101f5906101eb565b3d9150614632565b61467b915060203d60201161461e576146108183614710565b5f610191565b90506020813d6020116146c2575b8161469c60209383614710565b810103126141bc57516001600160a01b03811681036141bc576001600160a01b0361010f565b3d915061468f565b5f5b8381106146db5750505f910152565b81810151838201526020016146cc565b90602091614704815180928185528580860191016146ca565b601f01601f1916010190565b90601f8019910116810190811067ffffffffffffffff82111761457557604052565b908160209103126141bc57516fffffffffffffffffffffffffffffffff811681036141bc5790565b908160209103126141bc575164ffffffffff811681036141bc5790565b67ffffffffffffffff811161457557601f01601f191660200190565b3d156147bd573d906147a482614777565b916147b26040519384614710565b82523d5f602084013e565b606090565b6020818303126141bc5780519067ffffffffffffffff82116141bc570181601f820112156141bc5780516147f581614777565b926148036040519485614710565b818452602082840101116141bc5761482191602080850191016146ca565b90565b6001600160a01b0316604051906395d89b4160e01b82525f82600481845afa9182156145b8575f92614afa575b5060409161489483516148648582614710565b600e81527f5341422d4c4f434b55502d4c494e0000000000000000000000000000000000006020820152826157b3565b8015614ab5575b156148db5750506148ae81519182614710565b601581527f5361626c696572204c6f636b7570204c696e6561720000000000000000000000602082015290565b61491a83516148ea8582614710565b600e81527f5341422d4c4f434b55502d44594e0000000000000000000000000000000000006020820152826157b3565b8015614a70575b1561496157505061493481519182614710565b601681527f5361626c696572204c6f636b75702044796e616d696300000000000000000000602082015290565b6149a083516149708582614710565b600e81527f5341422d4c4f434b55502d5452410000000000000000000000000000000000006020820152826157b3565b8015614a2b575b156149e75750506149ba81519182614710565b601781527f5361626c696572204c6f636b7570205472616e63686564000000000000000000602082015290565b614a279083519384937f16ee429d0000000000000000000000000000000000000000000000000000000085526004850152602484015260448301906146eb565b0390fd5b50614a6b8351614a3b8582614710565b601181527f5341422d56322d4c4f434b55502d5452410000000000000000000000000000006020820152826157b3565b6149a7565b50614ab08351614a808582614710565b601181527f5341422d56322d4c4f434b55502d44594e0000000000000000000000000000006020820152826157b3565b614921565b50614af58351614ac58582614710565b601181527f5341422d56322d4c4f434b55502d4c494e0000000000000000000000000000006020820152826157b3565b61489b565b614b179192503d805f833e614b0f8183614710565b8101906147c2565b905f614851565b6001600160a01b03168060405191614b37606084614710565b602a8352602083016040368237835115614c065760309053825160011015614c06576078602184015360295b60018111614ba45750614b74575090565b7fe22e27eb000000000000000000000000000000000000000000000000000000005f52600452601460245260445ffd5b90600f81166010811015614c06577f3031323334353637383961626364656600000000000000000000000000000000901a614bdf83866157e0565b5360041c908015614bf2575f1901614b63565b634e487b7160e01b5f52601160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b5f809160405160208101906395d89b4160e01b825260048152614c3e602482614710565b51915afa614c4a614793565b90158015614d3a575b614cfe5780602080614c6a935183010191016147c2565b601e8151115f14614cb15750604051614c84604082614710565b600b81527f4c6f6e672053796d626f6c000000000000000000000000000000000000000000602082015290565b614cba816157f1565b15614cc25790565b50604051614cd1604082614710565b601281527f556e737570706f727465642053796d626f6c0000000000000000000000000000602082015290565b50604051614d0d604082614710565b600581527f4552433230000000000000000000000000000000000000000000000000000000602082015290565b50604081511115614c53565b60405190614d55604083614710565b600782527f536574746c6564000000000000000000000000000000000000000000000000006020830152565b60405190614d90604083614710565b600882527f4465706c657465640000000000000000000000000000000000000000000000006020830152565b6005811015614ea85760048103614dd65750614821614d81565b60038103614e1a5750604051614ded604082614710565b600881527f43616e63656c6564000000000000000000000000000000000000000000000000602082015290565b60018103614e5e5750604051614e31604082614710565b600981527f53747265616d696e670000000000000000000000000000000000000000000000602082015290565b600203614e6d57614821614d46565b604051614e7b604082614710565b600781527f50656e64696e6700000000000000000000000000000000000000000000000000602082015290565b634e487b7160e01b5f52602160045260245ffd5b5f809160405160208101907f313ce56700000000000000000000000000000000000000000000000000000000825260048152614ef9602482614710565b51915afa614f05614793565b9080614f34575b15614f2f576020818051810103126141bc576020015160ff811681036141bc5790565b505f90565b506020815114614f0c565b60405190614f4e604083614710565b600482527f2667743b000000000000000000000000000000000000000000000000000000006020830152565b60405190614f89604083614710565b600482527f266c743b000000000000000000000000000000000000000000000000000000006020830152565b90614fe09493615011602061502095615003828096816040519c8d8b83829d519485930191016146ca565b8901614ff4825180938580850191016146ca565b010191828151948592016146ca565b0191828151948592016146ca565b0103601f198101845283614710565b565b9081156152f757806152e757505b806001811015615099575050615044614f7a565b614821600260206040518461506282965180928580860191016146ca565b81017f2031000000000000000000000000000000000000000000000000000000000000838201520301601d19810184520182614710565b66038d7ea4c680001115615289576040519060a0820182811067ffffffffffffffff821117614575576040526020916040516150d58482614710565b5f8152815260409182516150e98482614710565b600181527f4b00000000000000000000000000000000000000000000000000000000000000858201528483015282516151228482614710565b600181527f4d000000000000000000000000000000000000000000000000000000000000008582015283830152825161515b8482614710565b600181527f420000000000000000000000000000000000000000000000000000000000000085820152606083015282516151958482614710565b600181527f54000000000000000000000000000000000000000000000000000000000000008582015260808301525f905f945b6103e882101561526f578451946151df8187614710565b600786527f2623383830353b000000000000000000000000000000000000000000000000008287015251945f5b6007811061525c575050600160fd1b602786015250600884526152439061523d90615238602887614710565b6154e9565b91615994565b916005851015614c06576148219460051b015192614fb5565b818101830151878201840152820161520c565b9490915060016103e86064600a85040693049101946151c8565b50615292614f3f565b61482160086020604051846152b082965180928580860191016146ca565b81017f203939392e393954000000000000000000000000000000000000000000000000838201520301601719810184520182614710565b600a0a9081156145e25704615030565b5050604051615307604082614710565b60018152600360fc1b602082015290565b6201518091030480615382575061532d614f7a565b614821600660206040518461534b82965180928580860191016146ca565b81017f2031204461790000000000000000000000000000000000000000000000000000838201520301601919810184520182614710565b61270f81116154595760018103615415576148216153d76040516153a7604082614710565b600481527f20446179000000000000000000000000000000000000000000000000000000006020820152926154e9565b602060405193826153f186945180928580880191016146ca565b8301615405825180938580850191016146ca565b010103601f198101835282614710565b6148216153d7604051615429604082614710565b600581527f20446179730000000000000000000000000000000000000000000000000000006020820152926154e9565b50615462614f3f565b614821600a60206040518461548082965180928580860191016146ca565b81017f2039393939204461797300000000000000000000000000000000000000000000838201520301601519810184520182614710565b906154c182614777565b6154ce6040519182614710565b82815280926154df601f1991614777565b0190602036910137565b805f917a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000821015615620575b806d04ee2d6d415b85acef8100000000600a921015615605575b662386f26fc100008110156155f1575b6305f5e1008110156155e0575b6127108110156155d1575b60648110156155c3575b10156155b8575b600a6021615573600185016154b7565b938401015b5f1901917f30313233343536373839616263646566000000000000000000000000000000008282061a83530480156155b357600a9091615578565b505090565b600190910190615563565b60646002910493019261555c565b61271060049104930192615552565b6305f5e10060089104930192615547565b662386f26fc100006010910493019261553a565b6d04ee2d6d415b85acef81000000006020910493019261552a565b50604091507a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008104615510565b9081511561579d576040519161565f606084614710565b604083527f4142434445464748494a4b4c4d4e4f505152535455565758595a61626364656660208401527f6768696a6b6c6d6e6f707172737475767778797a303132333435363738392b2f6040840152805160028101809111614bf257600390047f3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81168103614bf2576156f59060021b6154b7565b90602082019080815182019560208701908151925f83525b88811061574f575050600393949596505251068060011461573d57600214615733575090565b603d905f19015390565b50603d90815f19820153600119015390565b600360049199969901986001603f8b5182828260121c16870101518453828282600c1c16870101518385015382828260061c168701015160028501531684010151600382015301949761570d565b90506040516157ad602082614710565b5f815290565b90815181519081811493846157ca575b5050505090565b602092939450820120920120145f8080806157c3565b908151811015614c06570160200190565b8051905f5b82811061580557505050600190565b7fff0000000000000000000000000000000000000000000000000000000000000061583082846157e0565b5116600160fd1b811490600360fc1b8110158061596a575b7f4100000000000000000000000000000000000000000000000000000000000000821015908161593f575b7f61000000000000000000000000000000000000000000000000000000000000008310159283615914575b84156158ea575b5083156158e2575b5082156158da575b5081156158d2575b50156158cb576001016157f6565b5050505f90565b90505f6158bd565b91505f6158b5565b92505f6158ad565b7f2d000000000000000000000000000000000000000000000000000000000000001493505f6158a5565b7f7a00000000000000000000000000000000000000000000000000000000000000811115935061589e565b7f5a000000000000000000000000000000000000000000000000000000000000008311159150615873565b507f3900000000000000000000000000000000000000000000000000000000000000811115615848565b806159a857506040516157ad602082614710565b600a811015615a0e576159ba906154e9565b614821602260405180937f2e3000000000000000000000000000000000000000000000000000000000000060208301526159fd81518092602086860191016146ca565b81010301601f198101835282614710565b615a17906154e9565b614821602160405180937f2e0000000000000000000000000000000000000000000000000000000000000060208301526159fd81518092602086860191016146ca565b60405190615a69604083614710565b601082527f68736c283233302c3231252c31312529000000000000000000000000000000006020830152565b8015615cc957615aa3615a5a565b9061271003906127108211614bf257602e6061916050615ac5614821956154e9565b60576040519788947f3c672066696c6c3d226e6f6e65223e000000000000000000000000000000000060208701527f3c636972636c652063783d22313636222063793d2235302220723d2232322220602f8701527f7374726f6b653d22000000000000000000000000000000000000000000000000604f870152615b52815180926020868a0191016146ca565b85017f22207374726f6b652d77696474683d223130222f3e0000000000000000000000838201527f3c636972636c652063783d22313636222063793d2235302220706174684c656e606c8201527f6774683d2231303030302220723d22323222207374726f6b653d220000000000608c820152615bd982518093602060a7850191016146ca565b01017f22207374726f6b652d6461736861727261793d22313030303022207374726f6b838201527f652d646173686f66667365743d220000000000000000000000000000000000006070820152615c3a825180936020607e850191016146ca565b01017f22207374726f6b652d6c696e656361703d22726f756e6422207374726f6b652d838201527f77696474683d223522207472616e73666f726d3d22726f74617465282d393029604e8201527f22207472616e73666f726d2d6f726967696e3d22313636203530222f3e000000606e820152631e17b39f60e11b608b82015203016041810184520182614710565b50506040516157ad602082614710565b600d6150209193929360206040519582615cfc88945180928580880191016146ca565b830164010714051160dd1b8382015267029b0b13634b2b9160c51b6025820152615d2f8251809385602d850191016146ca565b01010301601f198101845283614710565b60056150209193929360206040519582615d6388945180928580880191016146ca565b830164010714051160dd1b83820152615d2f82518093856025850191016146ca565b6004811015614ea85780615dcf5750604051615da2604082614710565b600881527f50726f6772657373000000000000000000000000000000000000000000000000602082015290565b60018103615e135750604051615de6604082614710565b600681527f5374617475730000000000000000000000000000000000000000000000000000602082015290565b600203615e5557604051615e28604082614710565b600681527f416d6f756e740000000000000000000000000000000000000000000000000000602082015290565b604051615e63604082614710565b600881527f4475726174696f6e000000000000000000000000000000000000000000000000602082015290565b5f90805180156158cb5790600d915f925f925b828410615eb65750505050600d02900390565b90919294603b60f81b7fff00000000000000000000000000000000000000000000000000000000000000615eea88856157e0565b511614615f00575b820194600101929190615ea3565b859450615ef2565b5f90805180156158cb57906010915f925f925b828410615f2e575050505060041b900390565b90919294603b60f81b7fff00000000000000000000000000000000000000000000000000000000000000615f6288856157e0565b511614615f78575b820194600101929190615f1b565b859450615f6a56fea164736f6c634300081a000a"; - uint256 public constant MAX_SEGMENT_COUNT = 500; - uint256 public constant MAX_TRANCHE_COUNT = 500; + uint256 public constant MAX_COUNT = 500; /*////////////////////////////////////////////////////////////////////////// CORE //////////////////////////////////////////////////////////////////////////*/ - /// @notice Deploys {SablierLockupDynamic} from precompiled bytecode, passing a default value for the - /// `maxSegmentCount` parameter. + /// @notice Deploys {SablierLockup} from precompiled bytecode, passing a default value for the `maxCount` parameter. /// @dev Notes: - /// - A default value is passed for `maxSegmentCount`. + /// - A default value is passed for `maxCount`. /// - A dummy {LockupNFTDescriptor} is deployed so that the user does not have to provide one. - function deployLockupDynamic(address initialAdmin) public returns (ISablierLockupDynamic lockupDynamic) { - uint256 maxSegmentCount = MAX_SEGMENT_COUNT; - lockupDynamic = deployLockupDynamic(initialAdmin, maxSegmentCount); + function deployLockup(address initialAdmin) public returns (ISablierLockup lockup) { + lockup = deployLockup(initialAdmin, MAX_COUNT); } - /// @notice Deploys {SablierLockupDynamic} from precompiled bytecode. + /// @notice Deploys {SablierLockup} from precompiled bytecode. /// @dev A dummy {LockupNFTDescriptor} is deployed so that the user does not have to provide one. - function deployLockupDynamic( - address initialAdmin, - uint256 maxSegmentCount - ) - public - returns (ISablierLockupDynamic lockupDynamic) - { - ILockupNFTDescriptor nftDescriptor = new LockupNFTDescriptor(); - lockupDynamic = deployLockupDynamic(initialAdmin, nftDescriptor, maxSegmentCount); - } - - /// @notice Deploys {SablierLockupDynamic} from precompiled bytecode. - /// @dev A default value is passed for `maxSegmentCount`. - function deployLockupDynamic( - address initialAdmin, - ILockupNFTDescriptor nftDescriptor - ) - public - returns (ISablierLockupDynamic lockupDynamic) - { - lockupDynamic = deployLockupDynamic(initialAdmin, nftDescriptor, MAX_SEGMENT_COUNT); - } - - /// @notice Deploys {SablierLockupDynamic} from precompiled bytecode. - function deployLockupDynamic( - address initialAdmin, - ILockupNFTDescriptor nftDescriptor, - uint256 maxSegmentCount - ) - public - returns (ISablierLockupDynamic lockupDynamic) - { - bytes memory creationBytecode = - bytes.concat(BYTECODE_LOCKUP_DYNAMIC, abi.encode(initialAdmin, nftDescriptor, maxSegmentCount)); - assembly { - lockupDynamic := create(0, add(creationBytecode, 0x20), mload(creationBytecode)) - } - require( - address(lockupDynamic) != address(0), "Lockup Precompiles: deployment failed for LockupDynamic contract" - ); - } - - /// @notice Deploys {SablierLockupLinear} from precompiled bytecode. - /// @dev A dummy {LockupNFTDescriptor} is deployed so that the user does not have to provide one. - function deployLockupLinear(address initialAdmin) public returns (ISablierLockupLinear lockupLinear) { + function deployLockup(address initialAdmin, uint256 maxCount) public returns (ISablierLockup lockup) { ILockupNFTDescriptor nftDescriptor = new LockupNFTDescriptor(); - lockupLinear = deployLockupLinear(initialAdmin, nftDescriptor); + lockup = deployLockup(initialAdmin, nftDescriptor, maxCount); } - /// @notice Deploys {SablierLockupLinear} from precompiled bytecode. - function deployLockupLinear( + /// @notice Deploys {SablierLockup} from precompiled bytecode. + /// @dev A default value is passed for `maxCount`. + function deployLockup( address initialAdmin, ILockupNFTDescriptor nftDescriptor ) public - returns (ISablierLockupLinear lockupLinear) + returns (ISablierLockup lockup) { - bytes memory creationBytecode = bytes.concat(BYTECODE_LOCKUP_LINEAR, abi.encode(initialAdmin, nftDescriptor)); - assembly { - lockupLinear := create(0, add(creationBytecode, 0x20), mload(creationBytecode)) - } - require(address(lockupLinear) != address(0), "Lockup Precompiles: deployment failed for LockupLinear contract"); - } - - /// @notice Deploys {SablierLockupTranched} from precompiled bytecode, passing a default value for the - /// `maxTrancheCount` parameter. - /// @dev Notes: - /// - A default value is passed for `maxTrancheCount`. - /// - A dummy {LockupNFTDescriptor} is deployed so that the user does not have to provide one. - function deployLockupTranched(address initialAdmin) public returns (ISablierLockupTranched lockupTranched) { - uint256 maxTrancheCount = MAX_TRANCHE_COUNT; - lockupTranched = deployLockupTranched(initialAdmin, maxTrancheCount); - } - - /// @notice Deploys {SablierLockupTranched} from precompiled bytecode. - /// @dev A dummy {LockupNFTDescriptor} is deployed so that the user does not have to provide one. - function deployLockupTranched( - address initialAdmin, - uint256 maxTrancheCount - ) - public - returns (ISablierLockupTranched lockupTranched) - { - ILockupNFTDescriptor nftDescriptor = new LockupNFTDescriptor(); - lockupTranched = deployLockupTranched(initialAdmin, nftDescriptor, maxTrancheCount); + lockup = deployLockup(initialAdmin, nftDescriptor, MAX_COUNT); } - /// @notice Deploys {SablierLockupTranched} from precompiled bytecode. - /// @dev A default value is passed for `maxTrancheCount`. - function deployLockupTranched( - address initialAdmin, - ILockupNFTDescriptor nftDescriptor - ) - public - returns (ISablierLockupTranched lockupTranched) - { - lockupTranched = deployLockupTranched(initialAdmin, nftDescriptor, MAX_TRANCHE_COUNT); - } - - /// @notice Deploys {SablierLockupTranched} from precompiled bytecode. - function deployLockupTranched( + /// @notice Deploys {SablierLockup} from precompiled bytecode. + function deployLockup( address initialAdmin, ILockupNFTDescriptor nftDescriptor, - uint256 maxTrancheCount + uint256 maxCount ) public - returns (ISablierLockupTranched lockupTranched) + returns (ISablierLockup lockup) { - bytes memory creationBytecode = - bytes.concat(BYTECODE_LOCKUP_TRANCHED, abi.encode(initialAdmin, nftDescriptor, maxTrancheCount)); + bytes memory creationBytecode = bytes.concat(BYTECODE_LOCKUP, abi.encode(initialAdmin, nftDescriptor, maxCount)); assembly { - lockupTranched := create(0, add(creationBytecode, 0x20), mload(creationBytecode)) + lockup := create(0, add(creationBytecode, 0x20), mload(creationBytecode)) } - require( - address(lockupTranched) != address(0), "Lockup Precompiles: deployment failed for LockupTranched contract" - ); + require(address(lockup) != address(0), "Lockup Precompiles: deployment failed for Lockup contract"); } /// @notice Deploys {LockupNFTDescriptor} from precompiled bytecode. @@ -184,17 +90,10 @@ contract Precompiles { /// @notice Deploys all Core contracts. function deployCore(address initialAdmin) public - returns ( - ILockupNFTDescriptor nftDescriptor, - ISablierLockupDynamic lockupDynamic, - ISablierLockupLinear lockupLinear, - ISablierLockupTranched lockupTranched - ) + returns (ILockupNFTDescriptor nftDescriptor, ISablierLockup lockup) { nftDescriptor = deployNFTDescriptor(); - lockupDynamic = deployLockupDynamic(initialAdmin); - lockupLinear = deployLockupLinear(initialAdmin); - lockupTranched = deployLockupTranched(initialAdmin); + lockup = deployLockup(initialAdmin); } /*////////////////////////////////////////////////////////////////////////// @@ -234,24 +133,20 @@ contract Precompiles { /// @notice Deploys the entire Lockup Protocol from precompiled bytecode. /// /// 1. {LockupNFTDescriptor} - /// 2. {SablierLockupDynamic} - /// 3. {SablierLockupLinear} - /// 4. {SablierLockupTranched} + /// 2. {SablierLockup} /// 5. {SablierBatchLockup} /// 6. {SablierMerkleFactory} function deployProtocol(address initialAdmin) public returns ( ILockupNFTDescriptor nftDescriptor, - ISablierLockupDynamic lockupDynamic, - ISablierLockupLinear lockupLinear, - ISablierLockupTranched lockupTranched, + ISablierLockup lockup, ISablierBatchLockup batchLockup, ISablierMerkleFactory merkleFactory ) { // Deploy Core. - (nftDescriptor, lockupDynamic, lockupLinear, lockupTranched) = deployCore(initialAdmin); + (nftDescriptor, lockup) = deployCore(initialAdmin); // Deploy Periphery. (batchLockup, merkleFactory) = deployPeriphery(initialAdmin); diff --git a/script/Base.s.sol b/script/Base.s.sol index b60612601..9e2a23be9 100644 --- a/script/Base.s.sol +++ b/script/Base.s.sol @@ -12,7 +12,7 @@ contract BaseScript is Script { using Strings for uint256; using stdJson for string; - /// @dev The default value for `segmentCountMap` and `trancheCountMap`. + /// @dev The default value for `maxCountMap`. uint256 internal constant DEFAULT_MAX_COUNT = 500; /// @dev Included to enable compilation of the script without a $MNEMONIC environment variable. @@ -27,11 +27,8 @@ contract BaseScript is Script { /// @dev Used to derive the broadcaster's address if $EOA is not defined. string internal mnemonic; - /// @dev Maximum segment count mapped by the chain Id. - mapping(uint256 chainId => uint256 count) internal segmentCountMap; - - /// @dev Maximum tranche count mapped by the chain Id. - mapping(uint256 chainId => uint256 count) internal trancheCountMap; + /// @dev Maximum count for segments and tranches mapped by the chain Id. + mapping(uint256 chainId => uint256 count) internal maxCountMap; /// @dev Initializes the transaction broadcaster like this: /// @@ -49,15 +46,12 @@ contract BaseScript is Script { (broadcaster,) = deriveRememberKey({ mnemonic: mnemonic, index: 0 }); } - // Populate the segment and tranche count map. - populateSegmentAndTrancheCountMap(); + // Populate the max count map for segments and tranches. + populateMaxCountMap(); // If there is no maximum value set for a specific chain, use the default value. - if (segmentCountMap[block.chainid] == 0) { - segmentCountMap[block.chainid] = DEFAULT_MAX_COUNT; - } - if (trancheCountMap[block.chainid] == 0) { - trancheCountMap[block.chainid] = DEFAULT_MAX_COUNT; + if (maxCountMap[block.chainid] == 0) { + maxCountMap[block.chainid] = DEFAULT_MAX_COUNT; } } @@ -86,53 +80,42 @@ contract BaseScript is Script { return json.readString(".version"); } - /// @dev Populates the segment & tranche count map. Values can be updated using the `update-counts.sh` script. - function populateSegmentAndTrancheCountMap() internal { + /// @dev Updates max values for segments and tranches. Values can be updated using the `update-counts.sh` script. + function populateMaxCountMap() internal { // forgefmt: disable-start // Arbitrum chain ID - segmentCountMap[42161] = 1160; - trancheCountMap[42161] = 1200; + maxCountMap[42161] = 1160; // Avalanche chain ID. - segmentCountMap[43114] = 520; - trancheCountMap[43114] = 540; + maxCountMap[43114] = 520; // Base chain ID. - segmentCountMap[8453] = 2170; - trancheCountMap[8453] = 2270; + maxCountMap[8453] = 2170; // Blast chain ID. - segmentCountMap[81457] = 1080; - trancheCountMap[81457] = 1120; + maxCountMap[81457] = 1080; // BNB chain ID. - segmentCountMap[56] = 4820; - trancheCountMap[56] = 5130; + maxCountMap[56] = 4820; // Ethereum chain ID. - segmentCountMap[1] = 1080; - trancheCountMap[1] = 1120; + maxCountMap[1] = 1080; // Gnosis chain ID. - segmentCountMap[100] = 600; - trancheCountMap[100] = 620; + maxCountMap[100] = 600; // Optimism chain ID. - segmentCountMap[10] = 1080; - trancheCountMap[10] = 1120; + maxCountMap[10] = 1080; // Polygon chain ID. - segmentCountMap[137] = 1080; - trancheCountMap[137] = 1120; + maxCountMap[137] = 1080; // Scroll chain ID. - segmentCountMap[534352] = 330; - trancheCountMap[534352] = 340; + maxCountMap[534352] = 330; // Sepolia chain ID. - segmentCountMap[11155111] = 1080; - trancheCountMap[11155111] = 1120; + maxCountMap[11155111] = 1080; // forgefmt: disable-end } diff --git a/script/core/DeployCore.s.sol b/script/core/DeployCore.s.sol index b1a803176..cf5791f48 100644 --- a/script/core/DeployCore.s.sol +++ b/script/core/DeployCore.s.sol @@ -2,9 +2,7 @@ pragma solidity >=0.8.22 <0.9.0; import { LockupNFTDescriptor } from "../../src/core/LockupNFTDescriptor.sol"; -import { SablierLockupDynamic } from "../../src/core/SablierLockupDynamic.sol"; -import { SablierLockupLinear } from "../../src/core/SablierLockupLinear.sol"; -import { SablierLockupTranched } from "../../src/core/SablierLockupTranched.sol"; +import { SablierLockup } from "../../src/core/SablierLockup.sol"; import { BaseScript } from "../Base.s.sol"; @@ -14,16 +12,9 @@ contract DeployCore is BaseScript { public virtual broadcast - returns ( - LockupNFTDescriptor nftDescriptor, - SablierLockupDynamic lockupDynamic, - SablierLockupLinear lockupLinear, - SablierLockupTranched lockupTranched - ) + returns (LockupNFTDescriptor nftDescriptor, SablierLockup lockup) { nftDescriptor = new LockupNFTDescriptor(); - lockupDynamic = new SablierLockupDynamic(initialAdmin, nftDescriptor, segmentCountMap[block.chainid]); - lockupLinear = new SablierLockupLinear(initialAdmin, nftDescriptor); - lockupTranched = new SablierLockupTranched(initialAdmin, nftDescriptor, trancheCountMap[block.chainid]); + lockup = new SablierLockup(initialAdmin, nftDescriptor, maxCountMap[block.chainid]); } } diff --git a/script/core/DeployCore2.s.sol b/script/core/DeployCore2.s.sol deleted file mode 100644 index 027b8d51c..000000000 --- a/script/core/DeployCore2.s.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity >=0.8.22 <0.9.0; - -import { ILockupNFTDescriptor } from "../../src/core/interfaces/ILockupNFTDescriptor.sol"; -import { SablierLockupDynamic } from "../../src/core/SablierLockupDynamic.sol"; -import { SablierLockupLinear } from "../../src/core/SablierLockupLinear.sol"; -import { SablierLockupTranched } from "../../src/core/SablierLockupTranched.sol"; - -import { BaseScript } from "../Base.s.sol"; - -contract DeployCore2 is BaseScript { - function run( - address initialAdmin, - ILockupNFTDescriptor nftDescriptor - ) - public - virtual - broadcast - returns ( - SablierLockupDynamic lockupDynamic, - SablierLockupLinear lockupLinear, - SablierLockupTranched lockupTranched - ) - { - lockupDynamic = new SablierLockupDynamic(initialAdmin, nftDescriptor, segmentCountMap[block.chainid]); - lockupLinear = new SablierLockupLinear(initialAdmin, nftDescriptor); - lockupTranched = new SablierLockupTranched(initialAdmin, nftDescriptor, trancheCountMap[block.chainid]); - } -} diff --git a/script/core/DeployDeterministicCore.s.sol b/script/core/DeployDeterministicCore.s.sol index 13964749b..8b88358c6 100644 --- a/script/core/DeployDeterministicCore.s.sol +++ b/script/core/DeployDeterministicCore.s.sol @@ -2,10 +2,7 @@ pragma solidity >=0.8.22 <0.9.0; import { LockupNFTDescriptor } from "../../src/core/LockupNFTDescriptor.sol"; -import { SablierLockupDynamic } from "../../src/core/SablierLockupDynamic.sol"; -import { SablierLockupLinear } from "../../src/core/SablierLockupLinear.sol"; -import { SablierLockupTranched } from "../../src/core/SablierLockupTranched.sol"; - +import { SablierLockup } from "../../src/core/SablierLockup.sol"; import { BaseScript } from "../Base.s.sol"; /// @notice Deploys all Core contracts at deterministic addresses across chains. @@ -15,19 +12,10 @@ contract DeployDeterministicCore is BaseScript { public virtual broadcast - returns ( - LockupNFTDescriptor nftDescriptor, - SablierLockupDynamic lockupDynamic, - SablierLockupLinear lockupLinear, - SablierLockupTranched lockupTranched - ) + returns (LockupNFTDescriptor nftDescriptor, SablierLockup lockup) { bytes32 salt = constructCreate2Salt(); nftDescriptor = new LockupNFTDescriptor{ salt: salt }(); - lockupDynamic = - new SablierLockupDynamic{ salt: salt }(initialAdmin, nftDescriptor, segmentCountMap[block.chainid]); - lockupLinear = new SablierLockupLinear{ salt: salt }(initialAdmin, nftDescriptor); - lockupTranched = - new SablierLockupTranched{ salt: salt }(initialAdmin, nftDescriptor, trancheCountMap[block.chainid]); + lockup = new SablierLockup{ salt: salt }(initialAdmin, nftDescriptor, maxCountMap[block.chainid]); } } diff --git a/script/core/DeployDeterministicCore2.s.sol b/script/core/DeployDeterministicCore2.s.sol deleted file mode 100644 index f453fcc6a..000000000 --- a/script/core/DeployDeterministicCore2.s.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity >=0.8.22 <0.9.0; - -import { ILockupNFTDescriptor } from "../../src/core/interfaces/ILockupNFTDescriptor.sol"; -import { SablierLockupDynamic } from "../../src/core/SablierLockupDynamic.sol"; -import { SablierLockupLinear } from "../../src/core/SablierLockupLinear.sol"; -import { SablierLockupTranched } from "../../src/core/SablierLockupTranched.sol"; - -import { BaseScript } from "../Base.s.sol"; - -/// @dev Reverts if any contract has already been deployed. -contract DeployDeterministicCore2 is BaseScript { - function run( - address initialAdmin, - ILockupNFTDescriptor nftDescriptor - ) - public - virtual - broadcast - returns ( - SablierLockupDynamic lockupDynamic, - SablierLockupLinear lockupLinear, - SablierLockupTranched lockupTranched - ) - { - bytes32 salt = constructCreate2Salt(); - lockupDynamic = - new SablierLockupDynamic{ salt: salt }(initialAdmin, nftDescriptor, segmentCountMap[block.chainid]); - lockupLinear = new SablierLockupLinear{ salt: salt }(initialAdmin, nftDescriptor); - lockupTranched = - new SablierLockupTranched{ salt: salt }(initialAdmin, nftDescriptor, trancheCountMap[block.chainid]); - } -} diff --git a/script/core/DeployDeterministicLockupLinear.s.sol b/script/core/DeployDeterministicLockup.s.sol similarity index 51% rename from script/core/DeployDeterministicLockupLinear.s.sol rename to script/core/DeployDeterministicLockup.s.sol index a1e0d177b..00c60a440 100644 --- a/script/core/DeployDeterministicLockupLinear.s.sol +++ b/script/core/DeployDeterministicLockup.s.sol @@ -2,23 +2,22 @@ pragma solidity >=0.8.22 <0.9.0; import { ILockupNFTDescriptor } from "../../src/core/interfaces/ILockupNFTDescriptor.sol"; -import { SablierLockupLinear } from "../../src/core/SablierLockupLinear.sol"; - +import { SablierLockup } from "../../src/core/SablierLockup.sol"; import { BaseScript } from "../Base.s.sol"; -/// @dev Deploys {SablierLockupLinear} at a deterministic address across chains. +/// @notice Deploys {SablierLockup} at a deterministic address across chains. /// @dev Reverts if the contract has already been deployed. -contract DeployDeterministicLockupLinear is BaseScript { +contract DeployDeterministicLockup is BaseScript { function run( address initialAdmin, - ILockupNFTDescriptor initialNFTDescriptor + ILockupNFTDescriptor nftDescriptor ) public virtual broadcast - returns (SablierLockupLinear lockupLinear) + returns (SablierLockup lockup) { bytes32 salt = constructCreate2Salt(); - lockupLinear = new SablierLockupLinear{ salt: salt }(initialAdmin, initialNFTDescriptor); + lockup = new SablierLockup{ salt: salt }(initialAdmin, nftDescriptor, maxCountMap[block.chainid]); } } diff --git a/script/core/DeployDeterministicLockupDynamic.s.sol b/script/core/DeployDeterministicLockupDynamic.s.sol deleted file mode 100644 index 124d4fee1..000000000 --- a/script/core/DeployDeterministicLockupDynamic.s.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity >=0.8.22 <0.9.0; - -import { ILockupNFTDescriptor } from "../../src/core/interfaces/ILockupNFTDescriptor.sol"; -import { SablierLockupDynamic } from "../../src/core/SablierLockupDynamic.sol"; - -import { BaseScript } from "../Base.s.sol"; - -/// @notice Deploys {SablierLockupDynamic} at a deterministic address across chains. -/// @dev Reverts if the contract has already been deployed. -contract DeployDeterministicLockupDynamic is BaseScript { - function run( - address initialAdmin, - ILockupNFTDescriptor initialNFTDescriptor - ) - public - virtual - broadcast - returns (SablierLockupDynamic lockupDynamic) - { - bytes32 salt = constructCreate2Salt(); - lockupDynamic = - new SablierLockupDynamic{ salt: salt }(initialAdmin, initialNFTDescriptor, segmentCountMap[block.chainid]); - } -} diff --git a/script/core/DeployDeterministicLockupTranched.s.sol b/script/core/DeployDeterministicLockupTranched.s.sol deleted file mode 100644 index 4990a2a71..000000000 --- a/script/core/DeployDeterministicLockupTranched.s.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity >=0.8.22 <0.9.0; - -import { ILockupNFTDescriptor } from "../../src/core/interfaces/ILockupNFTDescriptor.sol"; -import { SablierLockupTranched } from "../../src/core/SablierLockupTranched.sol"; - -import { BaseScript } from "../Base.s.sol"; - -/// @dev Deploys {SablierLockupTranched} at a deterministic address across chains. -/// @dev Reverts if the contract has already been deployed. -contract DeployDeterministicLockupTranched is BaseScript { - function run( - address initialAdmin, - ILockupNFTDescriptor initialNFTDescriptor - ) - public - virtual - broadcast - returns (SablierLockupTranched lockupTranched) - { - bytes32 salt = constructCreate2Salt(); - lockupTranched = - new SablierLockupTranched{ salt: salt }(initialAdmin, initialNFTDescriptor, trancheCountMap[block.chainid]); - } -} diff --git a/script/core/DeployLockupLinear.s.sol b/script/core/DeployLockup.s.sol similarity index 50% rename from script/core/DeployLockupLinear.s.sol rename to script/core/DeployLockup.s.sol index 82ec8349b..bb45a3ed5 100644 --- a/script/core/DeployLockupLinear.s.sol +++ b/script/core/DeployLockup.s.sol @@ -2,20 +2,21 @@ pragma solidity >=0.8.22 <0.9.0; import { ILockupNFTDescriptor } from "../../src/core/interfaces/ILockupNFTDescriptor.sol"; -import { SablierLockupLinear } from "../../src/core/SablierLockupLinear.sol"; +import { SablierLockup } from "../../src/core/SablierLockup.sol"; import { BaseScript } from "../Base.s.sol"; -contract DeployLockupLinear is BaseScript { +/// @notice Deploys {SablierLockup} contract. +contract DeployLockup is BaseScript { function run( address initialAdmin, - ILockupNFTDescriptor initialNFTDescriptor + ILockupNFTDescriptor nftDescriptor ) public virtual broadcast - returns (SablierLockupLinear lockupLinear) + returns (SablierLockup lockup) { - lockupLinear = new SablierLockupLinear(initialAdmin, initialNFTDescriptor); + lockup = new SablierLockup(initialAdmin, nftDescriptor, maxCountMap[block.chainid]); } } diff --git a/script/core/DeployLockupDynamic.s.sol b/script/core/DeployLockupDynamic.s.sol deleted file mode 100644 index 1ee3f167c..000000000 --- a/script/core/DeployLockupDynamic.s.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity >=0.8.22 <0.9.0; - -import { ILockupNFTDescriptor } from "../../src/core/interfaces/ILockupNFTDescriptor.sol"; -import { SablierLockupDynamic } from "../../src/core/SablierLockupDynamic.sol"; - -import { BaseScript } from "../Base.s.sol"; - -contract DeployLockupDynamic is BaseScript { - function run( - address initialAdmin, - ILockupNFTDescriptor initialNFTDescriptor - ) - public - virtual - broadcast - returns (SablierLockupDynamic lockupDynamic) - { - lockupDynamic = new SablierLockupDynamic(initialAdmin, initialNFTDescriptor, segmentCountMap[block.chainid]); - } -} diff --git a/script/core/DeployLockupTranched.s.sol b/script/core/DeployLockupTranched.s.sol deleted file mode 100644 index 3a0c3d47d..000000000 --- a/script/core/DeployLockupTranched.s.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity >=0.8.22 <0.9.0; - -import { ILockupNFTDescriptor } from "../../src/core/interfaces/ILockupNFTDescriptor.sol"; -import { SablierLockupTranched } from "../../src/core/SablierLockupTranched.sol"; - -import { BaseScript } from "../Base.s.sol"; - -contract DeployLockupTranched is BaseScript { - function run( - address initialAdmin, - ILockupNFTDescriptor initialNFTDescriptor - ) - public - virtual - broadcast - returns (SablierLockupTranched lockupTranched) - { - lockupTranched = new SablierLockupTranched(initialAdmin, initialNFTDescriptor, trancheCountMap[block.chainid]); - } -} diff --git a/script/core/DeployNFTDescriptor.s.sol b/script/core/DeployNFTDescriptor.s.sol index 1cb0063f0..709c268a4 100644 --- a/script/core/DeployNFTDescriptor.s.sol +++ b/script/core/DeployNFTDescriptor.s.sol @@ -5,6 +5,7 @@ import { LockupNFTDescriptor } from "../../src/core/LockupNFTDescriptor.sol"; import { BaseScript } from "../Base.s.sol"; +/// @notice Deploys {LockupNFTDescriptor} contract. contract DeployNFTDescriptor is BaseScript { function run() public virtual broadcast returns (LockupNFTDescriptor nftDescriptor) { nftDescriptor = new LockupNFTDescriptor(); diff --git a/script/core/GenerateSVG.s.sol b/script/core/GenerateSVG.s.sol index 33b919841..49b32c8f9 100644 --- a/script/core/GenerateSVG.s.sol +++ b/script/core/GenerateSVG.s.sol @@ -13,7 +13,7 @@ contract GenerateSVG is BaseScript, LockupNFTDescriptor { using Strings for string; address internal constant DAI = address(uint160(uint256(keccak256("DAI")))); - address internal constant LOCKUP_LINEAR = address(uint160(uint256(keccak256("SablierLockupLinear")))); + address internal constant LOCKUP = address(uint160(uint256(keccak256("SablierLockup")))); /// @param progress The streamed amount as a numerical percentage with 4 implied decimals. /// @param status The status of the stream, as a string. @@ -31,14 +31,14 @@ contract GenerateSVG is BaseScript, LockupNFTDescriptor { { svg = NFTSVG.generateSVG( NFTSVG.SVGParams({ - accentColor: generateAccentColor({ sablier: LOCKUP_LINEAR, streamId: uint256(keccak256(msg.data)) }), + accentColor: generateAccentColor({ sablier: LOCKUP, streamId: uint256(keccak256(msg.data)) }), amount: string.concat(SVGElements.SIGN_GE, " ", amount), assetAddress: DAI.toHexString(), assetSymbol: "DAI", duration: calculateDurationInDays({ startTime: 0, endTime: duration * 1 days }), progress: stringifyPercentage(progress), progressNumerical: progress, - lockupAddress: LOCKUP_LINEAR.toHexString(), + lockupAddress: LOCKUP.toHexString(), lockupModel: "Lockup Linear", status: status }) diff --git a/script/core/Init.s.sol b/script/core/Init.s.sol index 021eeec87..1365fb9ba 100644 --- a/script/core/Init.s.sol +++ b/script/core/Init.s.sol @@ -7,9 +7,8 @@ import { ud60x18 } from "@prb/math/src/UD60x18.sol"; import { Solarray } from "solarray/src/Solarray.sol"; -import { ISablierLockupDynamic } from "../../src/core/interfaces/ISablierLockupDynamic.sol"; -import { ISablierLockupLinear } from "../../src/core/interfaces/ISablierLockupLinear.sol"; -import { Broker, LockupDynamic, LockupLinear } from "../../src/core/types/DataTypes.sol"; +import { ISablierLockup } from "../../src/core/interfaces/ISablierLockup.sol"; +import { Broker, Lockup, LockupDynamic, LockupLinear } from "../../src/core/types/DataTypes.sol"; import { BaseScript } from "../Base.s.sol"; @@ -19,14 +18,7 @@ interface IERC20Mint { /// @notice Initializes the protocol by creating some streams. contract Init is BaseScript { - function run( - ISablierLockupLinear lockupLinear, - ISablierLockupDynamic lockupDynamic, - IERC20 asset - ) - public - broadcast - { + function run(ISablierLockup lockup, IERC20 asset) public broadcast { address sender = broadcaster; address recipient = vm.addr(vm.deriveKey({ mnemonic: mnemonic, index: 1 })); @@ -38,8 +30,7 @@ contract Init is BaseScript { IERC20Mint(address(asset)).mint({ beneficiary: sender, value: 131_601.1e18 + 10_000e18 }); // Approve the Lockup contracts to transfer the ERC-20 assets from the sender. - asset.approve({ spender: address(lockupLinear), value: type(uint256).max }); - asset.approve({ spender: address(lockupDynamic), value: type(uint256).max }); + asset.approve({ spender: address(lockup), value: type(uint256).max }); // Create 7 Lockup Linear streams with various amounts and durations. // @@ -53,25 +44,25 @@ contract Init is BaseScript { uint40[] memory totalDurations = Solarray.uint40s(1 seconds, 1 hours, 24 hours, 1 weeks, 4 weeks, 12 weeks, 48 weeks); for (uint256 i = 0; i < totalDurations.length; ++i) { - lockupLinear.createWithDurations( - LockupLinear.CreateWithDurations({ + lockup.createWithDurationsLL( + Lockup.CreateWithDurations({ sender: sender, recipient: recipient, totalAmount: totalAmounts[i], asset: asset, cancelable: true, transferable: true, - durations: LockupLinear.Durations({ cliff: cliffDurations[i], total: totalDurations[i] }), broker: Broker(address(0), ud60x18(0)) - }) + }), + LockupLinear.Durations({ cliff: cliffDurations[i], total: totalDurations[i] }) ); } // Renounce the 5th stream. - lockupLinear.renounce({ streamId: 5 }); + lockup.renounce({ streamId: 5 }); // Cancel the 6th stream. - lockupLinear.cancel({ streamId: 6 }); + lockup.cancel({ streamId: 6 }); /*////////////////////////////////////////////////////////////////////////// LOCKUP-DYNAMIC @@ -83,17 +74,17 @@ contract Init is BaseScript { LockupDynamic.SegmentWithDuration({ amount: 2500e18, exponent: ud2x18(3.14e18), duration: 1 hours }); segments[1] = LockupDynamic.SegmentWithDuration({ amount: 7500e18, exponent: ud2x18(0.5e18), duration: 1 weeks }); - lockupDynamic.createWithDurations( - LockupDynamic.CreateWithDurations({ + lockup.createWithDurationsLD( + Lockup.CreateWithDurations({ sender: sender, recipient: recipient, totalAmount: 10_000e18, asset: asset, cancelable: true, transferable: true, - segments: segments, broker: Broker(address(0), ud60x18(0)) - }) + }), + segments ); } } diff --git a/script/periphery/CreateMerkleLL.s.sol b/script/periphery/CreateMerkleLL.s.sol index fd7fe8d89..38567cda3 100644 --- a/script/periphery/CreateMerkleLL.s.sol +++ b/script/periphery/CreateMerkleLL.s.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.22 <0.9.0; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { ISablierLockupLinear } from "../../src/core/interfaces/ISablierLockupLinear.sol"; +import { ISablierLockup } from "../../src/core/interfaces/ISablierLockup.sol"; import { ISablierMerkleFactory } from "../../src/periphery/interfaces/ISablierMerkleFactory.sol"; import { ISablierMerkleLL } from "../../src/periphery/interfaces/ISablierMerkleLL.sol"; import { MerkleBase, MerkleLL } from "../../src/periphery/types/DataTypes.sol"; @@ -29,7 +29,7 @@ contract CreateMerkleLL is BaseScript { // TODO: Update address once deployed. merkleLL = merkleFactory.createMerkleLL({ baseParams: baseParams, - lockupLinear: ISablierLockupLinear(0x3962f6585946823440d274aD7C719B02b49DE51E), + lockup: ISablierLockup(0x3962f6585946823440d274aD7C719B02b49DE51E), cancelable: true, transferable: true, schedule: MerkleLL.Schedule({ diff --git a/script/periphery/CreateMerkleLT.s.sol b/script/periphery/CreateMerkleLT.s.sol index 24846ec18..13f941885 100644 --- a/script/periphery/CreateMerkleLT.s.sol +++ b/script/periphery/CreateMerkleLT.s.sol @@ -4,7 +4,7 @@ pragma solidity >=0.8.22 <0.9.0; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UD2x18 } from "@prb/math/src/UD2x18.sol"; -import { ISablierLockupTranched } from "../../src/core/interfaces/ISablierLockupTranched.sol"; +import { ISablierLockup } from "../../src/core/interfaces/ISablierLockup.sol"; import { ISablierMerkleFactory } from "../../src/periphery/interfaces/ISablierMerkleFactory.sol"; import { ISablierMerkleLT } from "../../src/periphery/interfaces/ISablierMerkleLT.sol"; import { MerkleBase, MerkleLT } from "../../src/periphery/types/DataTypes.sol"; @@ -36,7 +36,7 @@ contract CreateMerkleLT is BaseScript { // TODO: Update address once deployed. merkleLT = merkleFactory.createMerkleLT({ baseParams: baseParams, - lockupTranched: ISablierLockupTranched(0xf86B359035208e4529686A1825F2D5BeE38c28A8), + lockup: ISablierLockup(0xf86B359035208e4529686A1825F2D5BeE38c28A8), cancelable: true, transferable: true, streamStartTime: 0, // i.e. block.timestamp diff --git a/script/protocol/DeployDeterministicProtocol.s.sol b/script/protocol/DeployDeterministicProtocol.s.sol index 5bb83108d..0d2ffa0c1 100644 --- a/script/protocol/DeployDeterministicProtocol.s.sol +++ b/script/protocol/DeployDeterministicProtocol.s.sol @@ -2,9 +2,7 @@ pragma solidity >=0.8.22 <0.9.0; import { LockupNFTDescriptor } from "../../src/core/LockupNFTDescriptor.sol"; -import { SablierLockupDynamic } from "../../src/core/SablierLockupDynamic.sol"; -import { SablierLockupLinear } from "../../src/core/SablierLockupLinear.sol"; -import { SablierLockupTranched } from "../../src/core/SablierLockupTranched.sol"; +import { SablierLockup } from "../../src/core/SablierLockup.sol"; import { SablierBatchLockup } from "../../src/periphery/SablierBatchLockup.sol"; import { SablierMerkleFactory } from "../../src/periphery/SablierMerkleFactory.sol"; @@ -19,17 +17,14 @@ contract DeployDeterministicProtocol is DeploymentLogger("deterministic") { broadcast returns ( LockupNFTDescriptor nftDescriptor, - SablierLockupDynamic lockupDynamic, - SablierLockupLinear lockupLinear, - SablierLockupTranched lockupTranched, + SablierLockup lockup, SablierBatchLockup batchLockup, SablierMerkleFactory merkleLockupFactory ) { address initialAdmin = adminMap[block.chainid]; - (nftDescriptor, lockupDynamic, lockupLinear, lockupTranched, batchLockup, merkleLockupFactory) = - _run(initialAdmin); + (nftDescriptor, lockup, batchLockup, merkleLockupFactory) = _run(initialAdmin); } /// @dev Deploys the protocol with the given `initialAdmin`. @@ -37,15 +32,12 @@ contract DeployDeterministicProtocol is DeploymentLogger("deterministic") { internal returns ( LockupNFTDescriptor nftDescriptor, - SablierLockupDynamic lockupDynamic, - SablierLockupLinear lockupLinear, - SablierLockupTranched lockupTranched, + SablierLockup lockup, SablierBatchLockup batchLockup, SablierMerkleFactory merkleLockupFactory ) { - (nftDescriptor, lockupDynamic, lockupLinear, lockupTranched, batchLockup, merkleLockupFactory) = - _run(initialAdmin); + (nftDescriptor, lockup, batchLockup, merkleLockupFactory) = _run(initialAdmin); } /// @dev Common logic for the run functions. @@ -53,9 +45,7 @@ contract DeployDeterministicProtocol is DeploymentLogger("deterministic") { internal returns ( LockupNFTDescriptor nftDescriptor, - SablierLockupDynamic lockupDynamic, - SablierLockupLinear lockupLinear, - SablierLockupTranched lockupTranched, + SablierLockup lockup, SablierBatchLockup batchLockup, SablierMerkleFactory merkleLockupFactory ) @@ -64,23 +54,14 @@ contract DeployDeterministicProtocol is DeploymentLogger("deterministic") { // Deploy Core. nftDescriptor = new LockupNFTDescriptor{ salt: salt }(); - lockupDynamic = - new SablierLockupDynamic{ salt: salt }(initialAdmin, nftDescriptor, segmentCountMap[block.chainid]); - lockupLinear = new SablierLockupLinear{ salt: salt }(initialAdmin, nftDescriptor); - lockupTranched = - new SablierLockupTranched{ salt: salt }(initialAdmin, nftDescriptor, trancheCountMap[block.chainid]); + lockup = new SablierLockup{ salt: salt }(initialAdmin, nftDescriptor, maxCountMap[block.chainid]); // Deploy Periphery. batchLockup = new SablierBatchLockup{ salt: salt }(); merkleLockupFactory = new SablierMerkleFactory{ salt: salt }(initialAdmin); appendToFileDeployedAddresses( - address(lockupDynamic), - address(lockupLinear), - address(lockupTranched), - address(nftDescriptor), - address(batchLockup), - address(merkleLockupFactory) + address(lockup), address(nftDescriptor), address(batchLockup), address(merkleLockupFactory) ); } } diff --git a/script/protocol/DeployProtocol.s.sol b/script/protocol/DeployProtocol.s.sol index 77906c74c..b5f58a622 100644 --- a/script/protocol/DeployProtocol.s.sol +++ b/script/protocol/DeployProtocol.s.sol @@ -2,9 +2,7 @@ pragma solidity >=0.8.22 <0.9.0; import { LockupNFTDescriptor } from "../../src/core/LockupNFTDescriptor.sol"; -import { SablierLockupDynamic } from "../../src/core/SablierLockupDynamic.sol"; -import { SablierLockupLinear } from "../../src/core/SablierLockupLinear.sol"; -import { SablierLockupTranched } from "../../src/core/SablierLockupTranched.sol"; +import { SablierLockup } from "../../src/core/SablierLockup.sol"; import { SablierBatchLockup } from "../../src/periphery/SablierBatchLockup.sol"; import { SablierMerkleFactory } from "../../src/periphery/SablierMerkleFactory.sol"; @@ -19,17 +17,14 @@ contract DeployProtocol is DeploymentLogger("non_deterministic") { broadcast returns ( LockupNFTDescriptor nftDescriptor, - SablierLockupDynamic lockupDynamic, - SablierLockupLinear lockupLinear, - SablierLockupTranched lockupTranched, + SablierLockup lockup, SablierBatchLockup batchLockup, SablierMerkleFactory merkleLockupFactory ) { address initialAdmin = adminMap[block.chainid]; - (nftDescriptor, lockupDynamic, lockupLinear, lockupTranched, batchLockup, merkleLockupFactory) = - _run(initialAdmin); + (nftDescriptor, lockup, batchLockup, merkleLockupFactory) = _run(initialAdmin); } /// @dev Deploys the protocol with the given `initialAdmin`. @@ -37,15 +32,12 @@ contract DeployProtocol is DeploymentLogger("non_deterministic") { internal returns ( LockupNFTDescriptor nftDescriptor, - SablierLockupDynamic lockupDynamic, - SablierLockupLinear lockupLinear, - SablierLockupTranched lockupTranched, + SablierLockup lockup, SablierBatchLockup batchLockup, SablierMerkleFactory merkleLockupFactory ) { - (nftDescriptor, lockupDynamic, lockupLinear, lockupTranched, batchLockup, merkleLockupFactory) = - _run(initialAdmin); + (nftDescriptor, lockup, batchLockup, merkleLockupFactory) = _run(initialAdmin); } /// @dev Common logic for the run functions. @@ -53,27 +45,18 @@ contract DeployProtocol is DeploymentLogger("non_deterministic") { internal returns ( LockupNFTDescriptor nftDescriptor, - SablierLockupDynamic lockupDynamic, - SablierLockupLinear lockupLinear, - SablierLockupTranched lockupTranched, + SablierLockup lockup, SablierBatchLockup batchLockup, SablierMerkleFactory merkleLockupFactory ) { nftDescriptor = new LockupNFTDescriptor(); - lockupDynamic = new SablierLockupDynamic(initialAdmin, nftDescriptor, segmentCountMap[block.chainid]); - lockupLinear = new SablierLockupLinear(initialAdmin, nftDescriptor); - lockupTranched = new SablierLockupTranched(initialAdmin, nftDescriptor, trancheCountMap[block.chainid]); + lockup = new SablierLockup(initialAdmin, nftDescriptor, maxCountMap[block.chainid]); batchLockup = new SablierBatchLockup(); merkleLockupFactory = new SablierMerkleFactory(initialAdmin); appendToFileDeployedAddresses( - address(lockupDynamic), - address(lockupLinear), - address(lockupTranched), - address(nftDescriptor), - address(batchLockup), - address(merkleLockupFactory) + address(lockup), address(nftDescriptor), address(batchLockup), address(merkleLockupFactory) ); } } diff --git a/script/protocol/DeploymentLogger.s.sol b/script/protocol/DeploymentLogger.s.sol index a989ebcb9..f3ec7d0bf 100644 --- a/script/protocol/DeploymentLogger.s.sol +++ b/script/protocol/DeploymentLogger.s.sol @@ -61,9 +61,7 @@ abstract contract DeploymentLogger is BaseScript { /// @dev Function to append the deployed addresses to the deployment file. function appendToFileDeployedAddresses( - address lockupDynamic, - address lockupLinear, - address lockupTranched, + address lockup, address nftDescriptor, address batchLockup, address merkleFactory @@ -76,26 +74,12 @@ abstract contract DeploymentLogger is BaseScript { string memory firstTwoLines = "| Contract | Address | Deployment |\n | :------- | :------ | :----------|"; _appendToFile(firstTwoLines); - string memory lockupDynamicLine = _getContractLine({ - contractName: "SablierLockupDynamic", - contractAddress: lockupDynamic.toHexString(), + string memory lockupLine = _getContractLine({ + contractName: "SablierLockup", + contractAddress: lockup.toHexString(), coreOrPeriphery: "core" }); - _appendToFile(lockupDynamicLine); - - string memory lockupLinearLine = _getContractLine({ - contractName: "SablierLockupLinear", - contractAddress: lockupLinear.toHexString(), - coreOrPeriphery: "core" - }); - _appendToFile(lockupLinearLine); - - string memory lockupTranchedLine = _getContractLine({ - contractName: "SablierLockupTranched", - contractAddress: lockupTranched.toHexString(), - coreOrPeriphery: "core" - }); - _appendToFile(lockupTranchedLine); + _appendToFile(lockupLine); string memory nftDescriptorLine = _getContractLine({ contractName: "SablierNFTDescriptor", diff --git a/shell/prepare-artifacts.sh b/shell/prepare-artifacts.sh index 8135739a3..6db458496 100755 --- a/shell/prepare-artifacts.sh +++ b/shell/prepare-artifacts.sh @@ -31,20 +31,18 @@ mkdir $artifacts \ core=./artifacts/core cp out-optimized/LockupNFTDescriptor.sol/LockupNFTDescriptor.json $core -cp out-optimized/SablierLockupDynamic.sol/SablierLockupDynamic.json $core -cp out-optimized/SablierLockupLinear.sol/SablierLockupLinear.json $core -cp out-optimized/SablierLockupTranched.sol/SablierLockupTranched.json $core +cp out-optimized/SablierLockup.sol/SablierLockup.json $core core_interfaces=./artifacts/core/interfaces cp out-optimized/ILockupNFTDescriptor.sol/ILockupNFTDescriptor.json $core_interfaces cp out-optimized/ISablierLockupRecipient.sol/ISablierLockupRecipient.json $core_interfaces +cp out-optimized/ISablierLockupBase.sol/ISablierLockupBase.json $core_interfaces cp out-optimized/ISablierLockup.sol/ISablierLockup.json $core_interfaces -cp out-optimized/ISablierLockupDynamic.sol/ISablierLockupDynamic.json $core_interfaces -cp out-optimized/ISablierLockupLinear.sol/ISablierLockupLinear.json $core_interfaces -cp out-optimized/ISablierLockupTranched.sol/ISablierLockupTranched.json $core_interfaces core_libraries=./artifacts/core/libraries cp out-optimized/Errors.sol/Errors.json $core_libraries +cp out-optimized/Helpers.sol/Helpers.json $core_libraries +cp out-optimized/VestingMath.sol/VestingMath.json $core_libraries ################################################ #### PERIPHERY #### diff --git a/shell/update-counts.sh b/shell/update-counts.sh index a865a3f4e..5a54701a1 100755 --- a/shell/update-counts.sh +++ b/shell/update-counts.sh @@ -43,8 +43,7 @@ update_counts() { } # Call the function with specific parameters for segments and tranches -update_counts "Segments" "segmentCountMap" -update_counts "Tranches" "trancheCountMap" +update_counts "maxCountMap" "maxCountMap" # Reformat the code with Forge forge fmt $BASE_SCRIPT diff --git a/shell/update-precompiles.sh b/shell/update-precompiles.sh index 1e42d0104..04f70e6f6 100755 --- a/shell/update-precompiles.sh +++ b/shell/update-precompiles.sh @@ -13,9 +13,7 @@ FOUNDRY_PROFILE=optimized forge build # Retrieve the raw bytecodes, removing the "0x" prefix batch_lockup=$(cat out-optimized/SablierBatchLockup.sol/SablierBatchLockup.json | jq -r '.bytecode.object' | cut -c 3-) -lockup_dynamic=$(cat out-optimized/SablierLockupDynamic.sol/SablierLockupDynamic.json | jq -r '.bytecode.object' | cut -c 3-) -lockup_linear=$(cat out-optimized/SablierLockupLinear.sol/SablierLockupLinear.json | jq -r '.bytecode.object' | cut -c 3-) -lockup_tranched=$(cat out-optimized/SablierLockupTranched.sol/SablierLockupTranched.json | jq -r '.bytecode.object' | cut -c 3-) +lockup=$(cat out-optimized/SablierLockup.sol/SablierLockup.json | jq -r '.bytecode.object' | cut -c 3-) merkle_factory=$(cat out-optimized/SablierMerkleFactory.sol/SablierMerkleFactory.json | jq -r '.bytecode.object' | cut -c 3-) nft_descriptor=$(cat out-optimized/LockupNFTDescriptor.sol/LockupNFTDescriptor.json | jq -r '.bytecode.object' | cut -c 3-) @@ -27,9 +25,7 @@ fi # Replace the current bytecodes sd "(BYTECODE_BATCH_LOCKUP =)[^;]+;" "\$1 hex\"$batch_lockup\";" $precompiles_path -sd "(BYTECODE_LOCKUP_DYNAMIC =)[^;]+;" "\$1 hex\"$lockup_dynamic\";" $precompiles_path -sd "(BYTECODE_LOCKUP_LINEAR =)[^;]+;" "\$1 hex\"$lockup_linear\";" $precompiles_path -sd "(BYTECODE_LOCKUP_TRANCHED =)[^;]+;" "\$1 hex\"$lockup_tranched\";" $precompiles_path +sd "(BYTECODE_LOCKUP =)[^;]+;" "\$1 hex\"$lockup\";" $precompiles_path sd "(BYTECODE_MERKLE_FACTORY =)[^;]+;" "\$1 hex\"$merkle_factory\";" $precompiles_path sd "(BYTECODE_NFT_DESCRIPTOR =)[^;]+;" "\$1 hex\"$nft_descriptor\";" $precompiles_path diff --git a/src/core/LockupNFTDescriptor.sol b/src/core/LockupNFTDescriptor.sol index ef9970a57..3a9cafc4c 100644 --- a/src/core/LockupNFTDescriptor.sol +++ b/src/core/LockupNFTDescriptor.sol @@ -8,21 +8,21 @@ import { Base64 } from "@openzeppelin/contracts/utils/Base64.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { ILockupNFTDescriptor } from "./interfaces/ILockupNFTDescriptor.sol"; import { ISablierLockup } from "./interfaces/ISablierLockup.sol"; +import { ISablierLockupBase } from "./interfaces/ISablierLockupBase.sol"; import { Errors } from "./libraries/Errors.sol"; import { NFTSVG } from "./libraries/NFTSVG.sol"; import { SVGElements } from "./libraries/SVGElements.sol"; import { Lockup } from "./types/DataTypes.sol"; - /* ██╗ ██████╗ ██████╗██╗ ██╗██╗ ██╗██████╗ ███╗ ██╗███████╗████████╗ ██║ ██╔═══██╗██╔════╝██║ ██╔╝██║ ██║██╔══██╗ ████╗ ██║██╔════╝╚══██╔══╝ -██║ ██║ ██║██║ █████╔╝ ██║ ██║██████╔╝ ██╔██╗ ██║█████╗ ██║ -██║ ██║ ██║██║ ██╔═██╗ ██║ ██║██╔═══╝ ██║╚██╗██║██╔══╝ ██║ -███████╗╚██████╔╝╚██████╗██║ ██╗╚██████╔╝██║ ██║ ╚████║██║ ██║ -╚══════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═══╝╚═╝ ╚═╝ +██║ ██║ ██║██║ █████╔╝ ██║ ██║██████╔╝ ██╔██╗ ██║█████╗ ██║ +██║ ██║ ██║██║ ██╔═██╗ ██║ ██║██╔═══╝ ██║╚██╗██║██╔══╝ ██║ +███████╗╚██████╔╝╚██████╗██║ ██╗╚██████╔╝██║ ██║ ╚████║██║ ██║ +╚══════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═══╝╚═╝ ╚═╝ -██████╗ ███████╗███████╗ ██████╗██████╗ ██╗██████╗ ████████╗ ██████╗ ██████╗ +██████╗ ███████╗███████╗ ██████╗██████╗ ██╗██████╗ ████████╗ ██████╗ ██████╗ ██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗██║██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗ ██║ ██║█████╗ ███████╗██║ ██████╔╝██║██████╔╝ ██║ ██║ ██║██████╔╝ ██║ ██║██╔══╝ ╚════██║██║ ██╔══██╗██║██╔═══╝ ██║ ██║ ██║██╔══██╗ @@ -99,7 +99,7 @@ contract LockupNFTDescriptor is ILockupNFTDescriptor { // Performs a low-level call to handle older deployments that miss the `isTransferable` function. (vars.success, vars.returnData) = - address(vars.lockup).staticcall(abi.encodeCall(ISablierLockup.isTransferable, (streamId))); + address(vars.lockup).staticcall(abi.encodeCall(ISablierLockupBase.isTransferable, (streamId))); // When the call has failed, the stream NFT is assumed to be transferable. vars.isTransferable = vars.success ? abi.decode(vars.returnData, (bool)) : true; @@ -345,7 +345,9 @@ contract LockupNFTDescriptor is ILockupNFTDescriptor { /// @dev Reverts if the symbol is unknown. function mapSymbol(IERC721Metadata sablier) internal view returns (string memory) { string memory symbol = sablier.symbol(); - if (symbol.equal("SAB-LOCKUP-LIN") || symbol.equal("SAB-V2-LOCKUP-LIN")) { + if (symbol.equal("SAB-LOCKUP")) { + return "Sablier Lockup"; + } else if (symbol.equal("SAB-LOCKUP-LIN") || symbol.equal("SAB-V2-LOCKUP-LIN")) { return "Sablier Lockup Linear"; } else if (symbol.equal("SAB-LOCKUP-DYN") || symbol.equal("SAB-V2-LOCKUP-DYN")) { return "Sablier Lockup Dynamic"; diff --git a/src/core/SablierLockup.sol b/src/core/SablierLockup.sol new file mode 100644 index 000000000..2c0fdefc9 --- /dev/null +++ b/src/core/SablierLockup.sol @@ -0,0 +1,513 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.8.22; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +import { SablierLockupBase } from "./abstracts/SablierLockupBase.sol"; +import { ILockupNFTDescriptor } from "./interfaces/ILockupNFTDescriptor.sol"; +import { ISablierLockup } from "./interfaces/ISablierLockup.sol"; +import { Errors } from "./libraries/Errors.sol"; +import { Helpers } from "./libraries/Helpers.sol"; +import { VestingMath } from "./libraries/VestingMath.sol"; +import { Lockup, LockupDynamic, LockupLinear, LockupTranched } from "./types/DataTypes.sol"; + +/* + +███████╗ █████╗ ██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██████╗ ██████╗██╗ ██╗██╗ ██╗██████╗ +██╔════╝██╔══██╗██╔══██╗██║ ██║██╔════╝██╔══██╗ ██║ ██╔═══██╗██╔════╝██║ ██╔╝██║ ██║██╔══██╗ +███████╗███████║██████╔╝██║ ██║█████╗ ██████╔╝ ██║ ██║ ██║██║ █████╔╝ ██║ ██║██████╔╝ +╚════██║██╔══██║██╔══██╗██║ ██║██╔══╝ ██╔══██╗ ██║ ██║ ██║██║ ██╔═██╗ ██║ ██║██╔═══╝ +███████║██║ ██║██████╔╝███████╗██║███████╗██║ ██║ ███████╗╚██████╔╝╚██████╗██║ ██╗╚██████╔╝██║ +╚══════╝╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝╚══════╝╚═╝ ╚═╝ ╚══════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ + +*/ + +/// @title SablierLockup +/// @notice See the documentation in {ISablierLockup}. +contract SablierLockup is ISablierLockup, SablierLockupBase { + using SafeERC20 for IERC20; + + /*////////////////////////////////////////////////////////////////////////// + STATE VARIABLES + //////////////////////////////////////////////////////////////////////////*/ + + /// @inheritdoc ISablierLockup + uint256 public immutable override MAX_COUNT; + + /// @dev Cliff timestamp mapped by stream IDs. This is used in Lockup Linear models. + mapping(uint256 streamId => uint40 cliffTime) internal _cliffs; + + /// @dev Stream segments mapped by stream IDs. This is used in Lockup Dynamic models. + mapping(uint256 streamId => LockupDynamic.Segment[] segments) internal _segments; + + /// @dev Stream tranches mapped by stream IDs. This is used in Lockup Tranched models. + mapping(uint256 streamId => LockupTranched.Tranche[] tranches) internal _tranches; + + /*////////////////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////////////////*/ + + /// @param initialAdmin The address of the initial contract admin. + /// @param initialNFTDescriptor The address of the NFT descriptor contract. + /// @param maxCount The maximum number of segments and tranched allowed in Lockup Dynamic and Lockup Tranched + /// models, respectively. + constructor( + address initialAdmin, + ILockupNFTDescriptor initialNFTDescriptor, + uint256 maxCount + ) + ERC721("Sablier Lockup NFT", "SAB-LOCKUP") + SablierLockupBase(initialAdmin, initialNFTDescriptor) + { + MAX_COUNT = maxCount; + nextStreamId = 1; + } + + /*////////////////////////////////////////////////////////////////////////// + USER-FACING CONSTANT FUNCTIONS + //////////////////////////////////////////////////////////////////////////*/ + + /// @inheritdoc ISablierLockup + function getCliffTime(uint256 streamId) external view override notNull(streamId) returns (uint40 cliffTime) { + if (_streams[streamId].lockupModel != Lockup.Model.LOCKUP_LINEAR) { + revert Errors.SablierLockup_NotExpectedModel(_streams[streamId].lockupModel, Lockup.Model.LOCKUP_LINEAR); + } + + cliffTime = _cliffs[streamId]; + } + + /// @inheritdoc ISablierLockup + function getSegments(uint256 streamId) + external + view + override + notNull(streamId) + returns (LockupDynamic.Segment[] memory segments) + { + if (_streams[streamId].lockupModel != Lockup.Model.LOCKUP_DYNAMIC) { + revert Errors.SablierLockup_NotExpectedModel(_streams[streamId].lockupModel, Lockup.Model.LOCKUP_DYNAMIC); + } + + segments = _segments[streamId]; + } + + /// @inheritdoc ISablierLockup + function getTranches(uint256 streamId) + external + view + override + notNull(streamId) + returns (LockupTranched.Tranche[] memory tranches) + { + if (_streams[streamId].lockupModel != Lockup.Model.LOCKUP_TRANCHED) { + revert Errors.SablierLockup_NotExpectedModel(_streams[streamId].lockupModel, Lockup.Model.LOCKUP_TRANCHED); + } + + tranches = _tranches[streamId]; + } + + /*////////////////////////////////////////////////////////////////////////// + USER-FACING NON-CONSTANT FUNCTIONS + //////////////////////////////////////////////////////////////////////////*/ + + /// @inheritdoc ISablierLockup + function createWithDurationsLD( + Lockup.CreateWithDurations calldata params, + LockupDynamic.SegmentWithDuration[] calldata segmentsWithDuration + ) + external + override + noDelegateCall + returns (uint256 streamId) + { + // Generate the canonical segments. + LockupDynamic.Segment[] memory segments = Helpers.calculateSegmentTimestamps(segmentsWithDuration); + + // Declare the timestamps for the stream. + Lockup.Timestamps memory timestamps = + Lockup.Timestamps({ start: uint40(block.timestamp), end: segments[segments.length - 1].timestamp }); + + // Checks, Effects and Interactions: create the stream. + streamId = _createLD( + Lockup.CreateWithTimestamps({ + sender: params.sender, + recipient: params.recipient, + totalAmount: params.totalAmount, + asset: params.asset, + cancelable: params.cancelable, + transferable: params.transferable, + timestamps: timestamps, + broker: params.broker + }), + segments + ); + } + + /// @inheritdoc ISablierLockup + function createWithDurationsLL( + Lockup.CreateWithDurations calldata params, + LockupLinear.Durations calldata durations + ) + external + override + noDelegateCall + returns (uint256 streamId) + { + // Set the current block timestamp as the stream's start time. + Lockup.Timestamps memory timestamps = Lockup.Timestamps({ start: uint40(block.timestamp), end: 0 }); + + uint40 cliffTime; + + // Calculate the cliff time and the end time. It is safe to use unchecked arithmetic because {_createLL} will + // nonetheless check that the end time is greater than the cliff time, and also that the cliff time, if set, + // is greater than or equal to the start time. + unchecked { + if (durations.cliff > 0) { + cliffTime = timestamps.start + durations.cliff; + } + timestamps.end = timestamps.start + durations.total; + } + + // Checks, Effects and Interactions: create the stream. + streamId = _createLL( + Lockup.CreateWithTimestamps({ + sender: params.sender, + recipient: params.recipient, + totalAmount: params.totalAmount, + asset: params.asset, + cancelable: params.cancelable, + transferable: params.transferable, + timestamps: timestamps, + broker: params.broker + }), + cliffTime + ); + } + + /// @inheritdoc ISablierLockup + function createWithDurationsLT( + Lockup.CreateWithDurations calldata params, + LockupTranched.TrancheWithDuration[] calldata tranchesWithDuration + ) + external + override + noDelegateCall + returns (uint256 streamId) + { + // Generate the canonical tranches. + LockupTranched.Tranche[] memory tranches = Helpers.calculateTrancheTimestamps(tranchesWithDuration); + + // Declare the timestamps for the stream. + Lockup.Timestamps memory timestamps = + Lockup.Timestamps({ start: uint40(block.timestamp), end: tranches[tranches.length - 1].timestamp }); + + // Checks, Effects and Interactions: create the stream. + streamId = _createLT( + Lockup.CreateWithTimestamps({ + sender: params.sender, + recipient: params.recipient, + totalAmount: params.totalAmount, + asset: params.asset, + cancelable: params.cancelable, + transferable: params.transferable, + timestamps: timestamps, + broker: params.broker + }), + tranches + ); + } + + /// @inheritdoc ISablierLockup + function createWithTimestampsLD( + Lockup.CreateWithTimestamps calldata params, + LockupDynamic.Segment[] calldata segments + ) + external + override + noDelegateCall + returns (uint256 streamId) + { + // Checks, Effects and Interactions: create the stream. + streamId = _createLD(params, segments); + } + + /// @inheritdoc ISablierLockup + function createWithTimestampsLL( + Lockup.CreateWithTimestamps calldata params, + uint40 cliffTime + ) + external + override + noDelegateCall + returns (uint256 streamId) + { + // Checks, Effects and Interactions: create the stream. + streamId = _createLL(params, cliffTime); + } + + /// @inheritdoc ISablierLockup + function createWithTimestampsLT( + Lockup.CreateWithTimestamps calldata params, + LockupTranched.Tranche[] calldata tranches + ) + external + override + noDelegateCall + returns (uint256 streamId) + { + // Checks, Effects and Interactions: create the stream. + streamId = _createLT(params, tranches); + } + + /*////////////////////////////////////////////////////////////////////////// + INTERNAL CONSTANT FUNCTIONS + //////////////////////////////////////////////////////////////////////////*/ + + /// @inheritdoc SablierLockupBase + function _calculateStreamedAmount(uint256 streamId) internal view override returns (uint128) { + // If the start time is in the future, return zero. + uint40 blockTimestamp = uint40(block.timestamp); + uint40 startTime = _streams[streamId].startTime; + if (startTime >= blockTimestamp) { + return 0; + } + + // If the end time is not in the future, return the deposited amount. + uint40 endTime = _streams[streamId].endTime; + uint128 depositedAmount = _streams[streamId].amounts.deposited; + if (endTime <= blockTimestamp) { + return depositedAmount; + } + + uint128 streamedAmount; + Lockup.Model lockupModel = _streams[streamId].lockupModel; + + // Calculate streamed amount for Lockup Dynamic model. + if (lockupModel == Lockup.Model.LOCKUP_DYNAMIC) { + streamedAmount = VestingMath.calculateLockupDynamicStreamedAmount({ + segments: _segments[streamId], + startTime: startTime, + withdrawnAmount: _streams[streamId].amounts.withdrawn + }); + } + // Calculate streamed amount for Lockup Linear model. + else if (lockupModel == Lockup.Model.LOCKUP_LINEAR) { + streamedAmount = VestingMath.calculateLockupLinearStreamedAmount({ + depositedAmount: depositedAmount, + startTime: startTime, + cliffTime: _cliffs[streamId], + endTime: endTime, + withdrawnAmount: _streams[streamId].amounts.withdrawn + }); + } + // Calculate streamed amount for Lockup Tranched model. + else if (lockupModel == Lockup.Model.LOCKUP_TRANCHED) { + streamedAmount = VestingMath.calculateLockupTranchedStreamedAmount({ tranches: _tranches[streamId] }); + } + + return streamedAmount; + } + + /*////////////////////////////////////////////////////////////////////////// + INTERNAL NON-CONSTANT FUNCTIONS + //////////////////////////////////////////////////////////////////////////*/ + + /// @dev Common logic for creating a stream. + function _create( + uint256 streamId, + Lockup.CreateWithTimestamps memory params, + Lockup.CreateAmounts memory createAmounts, + Lockup.Model lockupModel + ) + internal + { + // Effect: create the stream. + _streams[streamId] = Lockup.Stream({ + sender: params.sender, + startTime: params.timestamps.start, + endTime: params.timestamps.end, + isCancelable: params.cancelable, + wasCanceled: false, + asset: params.asset, + isDepleted: false, + isStream: true, + isTransferable: params.transferable, + lockupModel: lockupModel, + amounts: Lockup.Amounts({ deposited: createAmounts.deposit, withdrawn: 0, refunded: 0 }) + }); + + // Effect: mint the NFT to the recipient. + _mint({ to: params.recipient, tokenId: streamId }); + + unchecked { + // Effect: bump the next stream ID. + nextStreamId = streamId + 1; + } + + // Interaction: transfer the deposit amount. + params.asset.safeTransferFrom({ from: msg.sender, to: address(this), value: createAmounts.deposit }); + + // Interaction: pay the broker fee, if not zero. + if (createAmounts.brokerFee > 0) { + params.asset.safeTransferFrom({ from: msg.sender, to: params.broker.account, value: createAmounts.brokerFee }); + } + } + + /// @dev See the documentation for the user-facing functions that call this internal function. + function _createLD( + Lockup.CreateWithTimestamps memory params, + LockupDynamic.Segment[] memory segments + ) + internal + returns (uint256 streamId) + { + // Check: validate the user-provided parameters and segments. + Lockup.CreateAmounts memory createAmounts = Helpers.checkCreateLockupDynamic({ + sender: params.sender, + timestamps: params.timestamps, + totalAmount: params.totalAmount, + segments: segments, + maxCount: MAX_COUNT, + brokerFee: params.broker.fee, + maxBrokerFee: MAX_BROKER_FEE + }); + + // Load the stream ID in a variable. + streamId = nextStreamId; + + // Effect: store the segments. Since Solidity lacks a syntax for copying arrays of structs directly from + // memory to storage, a manual approach is necessary. See https://github.com/ethereum/solidity/issues/12783. + uint256 segmentCount = segments.length; + for (uint256 i = 0; i < segmentCount; ++i) { + _segments[streamId].push(segments[i]); + } + + // Effect: create the stream, mint the NFT and transfer the deposit amount. + _create({ + streamId: streamId, + params: params, + createAmounts: createAmounts, + lockupModel: Lockup.Model.LOCKUP_DYNAMIC + }); + + // Log the newly created stream. + emit ISablierLockup.CreateLockupDynamicStream({ + streamId: streamId, + funder: msg.sender, + sender: params.sender, + recipient: params.recipient, + amounts: createAmounts, + asset: params.asset, + cancelable: params.cancelable, + transferable: params.transferable, + timestamps: params.timestamps, + segments: segments, + broker: params.broker.account + }); + } + + /// @dev See the documentation for the user-facing functions that call this internal function. + function _createLL( + Lockup.CreateWithTimestamps memory params, + uint40 cliffTime + ) + internal + returns (uint256 streamId) + { + // Check: validate the user-provided parameters and cliff time. + Lockup.CreateAmounts memory createAmounts = Helpers.checkCreateLockupLinear({ + sender: params.sender, + timestamps: params.timestamps, + cliffTime: cliffTime, + totalAmount: params.totalAmount, + brokerFee: params.broker.fee, + maxBrokerFee: MAX_BROKER_FEE + }); + + // Load the stream ID in a variable. + streamId = nextStreamId; + + // Effect: update cliff time if its non-zero. + if (cliffTime > 0) { + _cliffs[streamId] = cliffTime; + } + + // Effect: create the stream, mint the NFT and transfer the deposit amount. + _create({ + streamId: streamId, + params: params, + createAmounts: createAmounts, + lockupModel: Lockup.Model.LOCKUP_LINEAR + }); + + // Log the newly created stream. + emit ISablierLockup.CreateLockupLinearStream({ + streamId: streamId, + funder: msg.sender, + sender: params.sender, + recipient: params.recipient, + amounts: createAmounts, + asset: params.asset, + cancelable: params.cancelable, + transferable: params.transferable, + timestamps: params.timestamps, + cliffTime: cliffTime, + broker: params.broker.account + }); + } + + /// @dev See the documentation for the user-facing functions that call this internal function. + function _createLT( + Lockup.CreateWithTimestamps memory params, + LockupTranched.Tranche[] memory tranches + ) + internal + returns (uint256 streamId) + { + // Check: validate the user-provided parameters and tranches. + Lockup.CreateAmounts memory createAmounts = Helpers.checkCreateLockupTranched({ + sender: params.sender, + timestamps: params.timestamps, + totalAmount: params.totalAmount, + tranches: tranches, + maxCount: MAX_COUNT, + brokerFee: params.broker.fee, + maxBrokerFee: MAX_BROKER_FEE + }); + + // Load the stream ID in a variable. + streamId = nextStreamId; + + // Effect: store the tranches. Since Solidity lacks a syntax for copying arrays of structs directly from + // memory to storage, a manual approach is necessary. See https://github.com/ethereum/solidity/issues/12783. + uint256 trancheCount = tranches.length; + for (uint256 i = 0; i < trancheCount; ++i) { + _tranches[streamId].push(tranches[i]); + } + + // Effect: create the stream, mint the NFT and transfer the deposit amount. + _create({ + streamId: streamId, + params: params, + createAmounts: createAmounts, + lockupModel: Lockup.Model.LOCKUP_TRANCHED + }); + + // Log the newly created stream. + emit ISablierLockup.CreateLockupTranchedStream({ + streamId: streamId, + funder: msg.sender, + sender: params.sender, + recipient: params.recipient, + amounts: createAmounts, + asset: params.asset, + cancelable: params.cancelable, + transferable: params.transferable, + timestamps: params.timestamps, + tranches: tranches, + broker: params.broker.account + }); + } +} diff --git a/src/core/SablierLockupDynamic.sol b/src/core/SablierLockupDynamic.sol deleted file mode 100644 index e79e746e2..000000000 --- a/src/core/SablierLockupDynamic.sol +++ /dev/null @@ -1,380 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.8.22; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import { PRBMathCastingUint128 as CastingUint128 } from "@prb/math/src/casting/Uint128.sol"; -import { PRBMathCastingUint40 as CastingUint40 } from "@prb/math/src/casting/Uint40.sol"; -import { SD59x18 } from "@prb/math/src/SD59x18.sol"; - -import { SablierLockup } from "./abstracts/SablierLockup.sol"; -import { ILockupNFTDescriptor } from "./interfaces/ILockupNFTDescriptor.sol"; -import { ISablierLockupDynamic } from "./interfaces/ISablierLockupDynamic.sol"; -import { Helpers } from "./libraries/Helpers.sol"; -import { Lockup, LockupDynamic } from "./types/DataTypes.sol"; - -/* - -███████╗ █████╗ ██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██████╗ ██████╗██╗ ██╗██╗ ██╗██████╗ -██╔════╝██╔══██╗██╔══██╗██║ ██║██╔════╝██╔══██╗ ██║ ██╔═══██╗██╔════╝██║ ██╔╝██║ ██║██╔══██╗ -███████╗███████║██████╔╝██║ ██║█████╗ ██████╔╝ ██║ ██║ ██║██║ █████╔╝ ██║ ██║██████╔╝ -╚════██║██╔══██║██╔══██╗██║ ██║██╔══╝ ██╔══██╗ ██║ ██║ ██║██║ ██╔═██╗ ██║ ██║██╔═══╝ -███████║██║ ██║██████╔╝███████╗██║███████╗██║ ██║ ███████╗╚██████╔╝╚██████╗██║ ██╗╚██████╔╝██║ -╚══════╝╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝╚══════╝╚═╝ ╚═╝ ╚══════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ - -██████╗ ██╗ ██╗███╗ ██╗ █████╗ ███╗ ███╗██╗ ██████╗ -██╔══██╗╚██╗ ██╔╝████╗ ██║██╔══██╗████╗ ████║██║██╔════╝ -██║ ██║ ╚████╔╝ ██╔██╗ ██║███████║██╔████╔██║██║██║ -██║ ██║ ╚██╔╝ ██║╚██╗██║██╔══██║██║╚██╔╝██║██║██║ -██████╔╝ ██║ ██║ ╚████║██║ ██║██║ ╚═╝ ██║██║╚██████╗ -╚═════╝ ╚═╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═════╝ - -*/ - -/// @title SablierLockupDynamic -/// @notice See the documentation in {ISablierLockupDynamic}. -contract SablierLockupDynamic is - ISablierLockupDynamic, // 5 inherited components - SablierLockup // 15 inherited components -{ - using CastingUint128 for uint128; - using CastingUint40 for uint40; - using SafeERC20 for IERC20; - - /*////////////////////////////////////////////////////////////////////////// - STATE VARIABLES - //////////////////////////////////////////////////////////////////////////*/ - - /// @inheritdoc ISablierLockupDynamic - uint256 public immutable override MAX_SEGMENT_COUNT; - - /// @dev Stream segments mapped by stream IDs. This complements the `_streams` mapping in {SablierLockup}. - mapping(uint256 id => LockupDynamic.Segment[] segments) internal _segments; - - /*////////////////////////////////////////////////////////////////////////// - CONSTRUCTOR - //////////////////////////////////////////////////////////////////////////*/ - - /// @param initialAdmin The address of the initial contract admin. - /// @param initialNFTDescriptor The address of the NFT descriptor contract. - /// @param maxSegmentCount The maximum number of segments allowed in a stream. - constructor( - address initialAdmin, - ILockupNFTDescriptor initialNFTDescriptor, - uint256 maxSegmentCount - ) - ERC721("Sablier Lockup Dynamic NFT", "SAB-LOCKUP-DYN") - SablierLockup(initialAdmin, initialNFTDescriptor) - { - MAX_SEGMENT_COUNT = maxSegmentCount; - nextStreamId = 1; - } - - /*////////////////////////////////////////////////////////////////////////// - USER-FACING CONSTANT FUNCTIONS - //////////////////////////////////////////////////////////////////////////*/ - - /// @inheritdoc ISablierLockupDynamic - function getSegments(uint256 streamId) - external - view - override - notNull(streamId) - returns (LockupDynamic.Segment[] memory segments) - { - segments = _segments[streamId]; - } - - /// @inheritdoc ISablierLockupDynamic - function getStream(uint256 streamId) - external - view - override - notNull(streamId) - returns (LockupDynamic.StreamLD memory stream) - { - // Retrieve the Lockup stream from storage. - Lockup.Stream memory lockupStream = _streams[streamId]; - - // Settled streams cannot be canceled. - if (_statusOf(streamId) == Lockup.Status.SETTLED) { - lockupStream.isCancelable = false; - } - - stream = LockupDynamic.StreamLD({ - amounts: lockupStream.amounts, - asset: lockupStream.asset, - endTime: lockupStream.endTime, - isCancelable: lockupStream.isCancelable, - isDepleted: lockupStream.isDepleted, - isStream: lockupStream.isStream, - isTransferable: lockupStream.isTransferable, - recipient: _ownerOf(streamId), - segments: _segments[streamId], - sender: lockupStream.sender, - startTime: lockupStream.startTime, - wasCanceled: lockupStream.wasCanceled - }); - } - - /// @inheritdoc ISablierLockupDynamic - function getTimestamps(uint256 streamId) - external - view - override - notNull(streamId) - returns (LockupDynamic.Timestamps memory timestamps) - { - timestamps = LockupDynamic.Timestamps({ start: _streams[streamId].startTime, end: _streams[streamId].endTime }); - } - - /*////////////////////////////////////////////////////////////////////////// - USER-FACING NON-CONSTANT FUNCTIONS - //////////////////////////////////////////////////////////////////////////*/ - - /// @inheritdoc ISablierLockupDynamic - function createWithDurations(LockupDynamic.CreateWithDurations calldata params) - external - override - noDelegateCall - returns (uint256 streamId) - { - // Generate the canonical segments. - LockupDynamic.Segment[] memory segments = Helpers.calculateSegmentTimestamps(params.segments); - - // Checks, Effects and Interactions: create the stream. - streamId = _create( - LockupDynamic.CreateWithTimestamps({ - sender: params.sender, - recipient: params.recipient, - totalAmount: params.totalAmount, - asset: params.asset, - cancelable: params.cancelable, - transferable: params.transferable, - startTime: uint40(block.timestamp), - segments: segments, - broker: params.broker - }) - ); - } - - /// @inheritdoc ISablierLockupDynamic - function createWithTimestamps(LockupDynamic.CreateWithTimestamps calldata params) - external - override - noDelegateCall - returns (uint256 streamId) - { - // Checks, Effects and Interactions: create the stream. - streamId = _create(params); - } - - /*////////////////////////////////////////////////////////////////////////// - INTERNAL CONSTANT FUNCTIONS - //////////////////////////////////////////////////////////////////////////*/ - - /// @inheritdoc SablierLockup - /// @dev The distribution function is: - /// - /// $$ - /// f(x) = x^{exp} * csa + \Sigma(esa) - /// $$ - /// - /// Where: - /// - /// - $x$ is the elapsed time divided by the total duration of the current segment. - /// - $exp$ is the current segment exponent. - /// - $csa$ is the current segment amount. - /// - $\Sigma(esa)$ is the sum of all vested segments' amounts. - function _calculateStreamedAmount(uint256 streamId) internal view override returns (uint128) { - // If the start time is in the future, return zero. - uint40 blockTimestamp = uint40(block.timestamp); - if (_streams[streamId].startTime >= blockTimestamp) { - return 0; - } - - // If the end time is not in the future, return the deposited amount. - uint40 endTime = _streams[streamId].endTime; - if (endTime <= blockTimestamp) { - return _streams[streamId].amounts.deposited; - } - - if (_segments[streamId].length > 1) { - // If there is more than one segment, it may be required to iterate over all of them. - return _calculateStreamedAmountForMultipleSegments(streamId); - } else { - // Otherwise, there is only one segment, and the calculation is simpler. - return _calculateStreamedAmountForOneSegment(streamId); - } - } - - /// @dev Calculates the streamed amount for a stream with multiple segments. - /// - /// Notes: - /// - /// 1. Normalization to 18 decimals is not needed because there is no mix of amounts with different decimals. - /// 2. The stream's start time must be in the past so that the calculations below do not overflow. - /// 3. The stream's end time must be in the future so that the loop below does not panic with an "index out of - /// bounds" error. - function _calculateStreamedAmountForMultipleSegments(uint256 streamId) internal view returns (uint128) { - unchecked { - uint40 blockTimestamp = uint40(block.timestamp); - Lockup.Stream memory stream = _streams[streamId]; - LockupDynamic.Segment[] memory segments = _segments[streamId]; - - // Sum the amounts in all segments that precede the block timestamp. - uint128 previousSegmentAmounts; - uint40 currentSegmentTimestamp = segments[0].timestamp; - uint256 index = 0; - while (currentSegmentTimestamp < blockTimestamp) { - previousSegmentAmounts += segments[index].amount; - index += 1; - currentSegmentTimestamp = segments[index].timestamp; - } - - // After exiting the loop, the current segment is at `index`. - SD59x18 currentSegmentAmount = segments[index].amount.intoSD59x18(); - SD59x18 currentSegmentExponent = segments[index].exponent.intoSD59x18(); - currentSegmentTimestamp = segments[index].timestamp; - - uint40 previousTimestamp; - if (index == 0) { - // When the current segment's index is equal to 0, the current segment is the first, so use the start - // time as the previous timestamp. - previousTimestamp = stream.startTime; - } else { - // Otherwise, when the current segment's index is greater than zero, it means that the segment is not - // the first. In this case, use the previous segment's timestamp. - previousTimestamp = segments[index - 1].timestamp; - } - - // Calculate how much time has passed since the segment started, and the total duration of the segment. - SD59x18 elapsedTime = (blockTimestamp - previousTimestamp).intoSD59x18(); - SD59x18 segmentDuration = (currentSegmentTimestamp - previousTimestamp).intoSD59x18(); - - // Divide the elapsed time by the total duration of the segment. - SD59x18 elapsedTimePercentage = elapsedTime.div(segmentDuration); - - // Calculate the streamed amount using the special formula. - SD59x18 multiplier = elapsedTimePercentage.pow(currentSegmentExponent); - SD59x18 segmentStreamedAmount = multiplier.mul(currentSegmentAmount); - - // Although the segment streamed amount should never exceed the total segment amount, this condition is - // checked without asserting to avoid locking assets in case of a bug. If this situation occurs, the - // amount streamed in the segment is considered zero (except for past withdrawals), and the segment is - // effectively voided. - if (segmentStreamedAmount.gt(currentSegmentAmount)) { - return previousSegmentAmounts > stream.amounts.withdrawn - ? previousSegmentAmounts - : stream.amounts.withdrawn; - } - - // Calculate the total streamed amount by adding the previous segment amounts and the amount streamed in - // the current segment. Casting to uint128 is safe due to the if statement above. - return previousSegmentAmounts + uint128(segmentStreamedAmount.intoUint256()); - } - } - - /// @dev Calculates the streamed amount for a stream with one segment. Normalization to 18 decimals is not - /// needed because there is no mix of amounts with different decimals. - function _calculateStreamedAmountForOneSegment(uint256 streamId) internal view returns (uint128) { - unchecked { - // Calculate how much time has passed since the stream started, and the stream's total duration. - SD59x18 elapsedTime = (uint40(block.timestamp) - _streams[streamId].startTime).intoSD59x18(); - SD59x18 totalDuration = (_streams[streamId].endTime - _streams[streamId].startTime).intoSD59x18(); - - // Divide the elapsed time by the stream's total duration. - SD59x18 elapsedTimePercentage = elapsedTime.div(totalDuration); - - // Cast the stream parameters to SD59x18. - SD59x18 exponent = _segments[streamId][0].exponent.intoSD59x18(); - SD59x18 depositedAmount = _streams[streamId].amounts.deposited.intoSD59x18(); - - // Calculate the streamed amount using the special formula. - SD59x18 multiplier = elapsedTimePercentage.pow(exponent); - SD59x18 streamedAmount = multiplier.mul(depositedAmount); - - // Although the streamed amount should never exceed the deposited amount, this condition is checked - // without asserting to avoid locking assets in case of a bug. If this situation occurs, the withdrawn - // amount is considered to be the streamed amount, and the stream is effectively frozen. - if (streamedAmount.gt(depositedAmount)) { - return _streams[streamId].amounts.withdrawn; - } - - // Cast the streamed amount to uint128. This is safe due to the check above. - return uint128(streamedAmount.intoUint256()); - } - } - - /*////////////////////////////////////////////////////////////////////////// - INTERNAL NON-CONSTANT FUNCTIONS - //////////////////////////////////////////////////////////////////////////*/ - - /// @dev See the documentation for the user-facing functions that call this internal function. - function _create(LockupDynamic.CreateWithTimestamps memory params) internal returns (uint256 streamId) { - // Check: verify the broker fee and calculate the amounts. - Lockup.CreateAmounts memory createAmounts = - Helpers.checkAndCalculateBrokerFee(params.totalAmount, params.broker.fee, MAX_BROKER_FEE); - - // Check: validate the user-provided parameters. - Helpers.checkCreateLockupDynamic( - params.sender, createAmounts.deposit, params.segments, MAX_SEGMENT_COUNT, params.startTime - ); - - // Load the stream ID in a variable. - streamId = nextStreamId; - - // Effect: create the stream. - Lockup.Stream storage stream = _streams[streamId]; - stream.amounts.deposited = createAmounts.deposit; - stream.asset = params.asset; - stream.isCancelable = params.cancelable; - stream.isStream = true; - stream.isTransferable = params.transferable; - stream.sender = params.sender; - stream.startTime = params.startTime; - - unchecked { - // The segment count cannot be zero at this point. - uint256 segmentCount = params.segments.length; - stream.endTime = params.segments[segmentCount - 1].timestamp; - - // Effect: store the segments. Since Solidity lacks a syntax for copying arrays of structs directly from - // memory to storage, a manual approach is necessary. See https://github.com/ethereum/solidity/issues/12783. - for (uint256 i = 0; i < segmentCount; ++i) { - _segments[streamId].push(params.segments[i]); - } - - // Effect: bump the next stream ID. - // Using unchecked arithmetic because these calculations cannot realistically overflow, ever. - nextStreamId = streamId + 1; - } - - // Effect: mint the NFT to the recipient. - _mint({ to: params.recipient, tokenId: streamId }); - - // Interaction: transfer the deposit amount. - params.asset.safeTransferFrom({ from: msg.sender, to: address(this), value: createAmounts.deposit }); - - // Interaction: pay the broker fee, if not zero. - if (createAmounts.brokerFee > 0) { - params.asset.safeTransferFrom({ from: msg.sender, to: params.broker.account, value: createAmounts.brokerFee }); - } - - // Log the newly created stream. - emit ISablierLockupDynamic.CreateLockupDynamicStream({ - streamId: streamId, - funder: msg.sender, - sender: params.sender, - recipient: params.recipient, - amounts: createAmounts, - asset: params.asset, - cancelable: params.cancelable, - transferable: params.transferable, - segments: params.segments, - timestamps: LockupDynamic.Timestamps({ start: stream.startTime, end: stream.endTime }), - broker: params.broker.account - }); - } -} diff --git a/src/core/SablierLockupLinear.sol b/src/core/SablierLockupLinear.sol deleted file mode 100644 index 8b684ff51..000000000 --- a/src/core/SablierLockupLinear.sol +++ /dev/null @@ -1,298 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.8.22; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import { UD60x18, ud } from "@prb/math/src/UD60x18.sol"; - -import { SablierLockup } from "./abstracts/SablierLockup.sol"; -import { SablierLockup } from "./abstracts/SablierLockup.sol"; -import { ILockupNFTDescriptor } from "./interfaces/ILockupNFTDescriptor.sol"; -import { ISablierLockupLinear } from "./interfaces/ISablierLockupLinear.sol"; -import { Helpers } from "./libraries/Helpers.sol"; -import { Lockup, LockupLinear } from "./types/DataTypes.sol"; - -/* - -███████╗ █████╗ ██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██████╗ ██████╗██╗ ██╗██╗ ██╗██████╗ -██╔════╝██╔══██╗██╔══██╗██║ ██║██╔════╝██╔══██╗ ██║ ██╔═══██╗██╔════╝██║ ██╔╝██║ ██║██╔══██╗ -███████╗███████║██████╔╝██║ ██║█████╗ ██████╔╝ ██║ ██║ ██║██║ █████╔╝ ██║ ██║██████╔╝ -╚════██║██╔══██║██╔══██╗██║ ██║██╔══╝ ██╔══██╗ ██║ ██║ ██║██║ ██╔═██╗ ██║ ██║██╔═══╝ -███████║██║ ██║██████╔╝███████╗██║███████╗██║ ██║ ███████╗╚██████╔╝╚██████╗██║ ██╗╚██████╔╝██║ -╚══════╝╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝╚══════╝╚═╝ ╚═╝ ╚══════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ - -██╗ ██╗███╗ ██╗███████╗ █████╗ ██████╗ -██║ ██║████╗ ██║██╔════╝██╔══██╗██╔══██╗ -██║ ██║██╔██╗ ██║█████╗ ███████║██████╔╝ -██║ ██║██║╚██╗██║██╔══╝ ██╔══██║██╔══██╗ -███████╗██║██║ ╚████║███████╗██║ ██║██║ ██║ -╚══════╝╚═╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ - -*/ - -/// @title SablierLockupLinear -/// @notice See the documentation in {ISablierLockupLinear}. -contract SablierLockupLinear is - ISablierLockupLinear, // 5 inherited components - SablierLockup // 15 inherited components -{ - using SafeERC20 for IERC20; - - /*////////////////////////////////////////////////////////////////////////// - STATE VARIABLES - //////////////////////////////////////////////////////////////////////////*/ - - /// @dev Cliff times mapped by stream IDs. This complements the `_streams` mapping in {SablierLockup}. - mapping(uint256 id => uint40 cliff) internal _cliffs; - - /*////////////////////////////////////////////////////////////////////////// - CONSTRUCTOR - //////////////////////////////////////////////////////////////////////////*/ - - /// @param initialAdmin The address of the initial contract admin. - /// @param initialNFTDescriptor The address of the initial NFT descriptor. - constructor( - address initialAdmin, - ILockupNFTDescriptor initialNFTDescriptor - ) - ERC721("Sablier Lockup Linear NFT", "SAB-LOCKUP-LIN") - SablierLockup(initialAdmin, initialNFTDescriptor) - { - nextStreamId = 1; - } - - /*////////////////////////////////////////////////////////////////////////// - USER-FACING CONSTANT FUNCTIONS - //////////////////////////////////////////////////////////////////////////*/ - - /// @inheritdoc ISablierLockupLinear - function getCliffTime(uint256 streamId) external view override notNull(streamId) returns (uint40 cliffTime) { - cliffTime = _cliffs[streamId]; - } - - /// @inheritdoc ISablierLockupLinear - function getStream(uint256 streamId) - external - view - override - notNull(streamId) - returns (LockupLinear.StreamLL memory stream) - { - // Retrieve the Lockup stream from storage. - Lockup.Stream memory lockupStream = _streams[streamId]; - - // Settled streams cannot be canceled. - if (_statusOf(streamId) == Lockup.Status.SETTLED) { - lockupStream.isCancelable = false; - } - - stream = LockupLinear.StreamLL({ - amounts: lockupStream.amounts, - asset: lockupStream.asset, - cliffTime: _cliffs[streamId], - endTime: lockupStream.endTime, - isCancelable: lockupStream.isCancelable, - isTransferable: lockupStream.isTransferable, - isDepleted: lockupStream.isDepleted, - isStream: lockupStream.isStream, - recipient: _ownerOf(streamId), - sender: lockupStream.sender, - startTime: lockupStream.startTime, - wasCanceled: lockupStream.wasCanceled - }); - } - - /// @inheritdoc ISablierLockupLinear - function getTimestamps(uint256 streamId) - external - view - override - notNull(streamId) - returns (LockupLinear.Timestamps memory timestamps) - { - timestamps = LockupLinear.Timestamps({ - start: _streams[streamId].startTime, - cliff: _cliffs[streamId], - end: _streams[streamId].endTime - }); - } - - /*////////////////////////////////////////////////////////////////////////// - USER-FACING NON-CONSTANT FUNCTIONS - //////////////////////////////////////////////////////////////////////////*/ - - /// @inheritdoc ISablierLockupLinear - function createWithDurations(LockupLinear.CreateWithDurations calldata params) - external - override - noDelegateCall - returns (uint256 streamId) - { - // Set the current block timestamp as the stream's start time. - LockupLinear.Timestamps memory timestamps; - timestamps.start = uint40(block.timestamp); - - // Calculate the cliff time and the end time. It is safe to use unchecked arithmetic because {_create} will - // nonetheless check that the end time is greater than the cliff time, and also that the cliff time, if set, - // is greater than or equal to the start time. - unchecked { - if (params.durations.cliff > 0) { - timestamps.cliff = timestamps.start + params.durations.cliff; - } - timestamps.end = timestamps.start + params.durations.total; - } - - // Checks, Effects and Interactions: create the stream. - streamId = _create( - LockupLinear.CreateWithTimestamps({ - sender: params.sender, - recipient: params.recipient, - totalAmount: params.totalAmount, - asset: params.asset, - cancelable: params.cancelable, - transferable: params.transferable, - timestamps: timestamps, - broker: params.broker - }) - ); - } - - /// @inheritdoc ISablierLockupLinear - function createWithTimestamps(LockupLinear.CreateWithTimestamps calldata params) - external - override - noDelegateCall - returns (uint256 streamId) - { - // Checks, Effects and Interactions: create the stream. - streamId = _create(params); - } - - /*////////////////////////////////////////////////////////////////////////// - INTERNAL CONSTANT FUNCTIONS - //////////////////////////////////////////////////////////////////////////*/ - - /// @inheritdoc SablierLockup - /// @dev The distribution function is: - /// - /// $$ - /// f(x) = x * d + c - /// $$ - /// - /// Where: - /// - /// - $x$ is the elapsed time divided by the stream's total duration. - /// - $d$ is the deposited amount. - /// - $c$ is the cliff amount. - function _calculateStreamedAmount(uint256 streamId) internal view override returns (uint128) { - uint256 cliffTime = uint256(_cliffs[streamId]); - uint256 startTime = uint256(_streams[streamId].startTime); - uint256 blockTimestamp = block.timestamp; - - // If the cliff time or the start time is in the future, return zero. - if (cliffTime > blockTimestamp || startTime >= blockTimestamp) { - return 0; - } - - // If the end time is not in the future, return the deposited amount. - uint256 endTime = uint256(_streams[streamId].endTime); - if (blockTimestamp >= endTime) { - return _streams[streamId].amounts.deposited; - } - - // In all other cases, calculate the amount streamed so far. Normalization to 18 decimals is not needed - // because there is no mix of amounts with different decimals. - unchecked { - // Calculate how much time has passed since the stream started, and the stream's total duration. - UD60x18 elapsedTime = ud(blockTimestamp - startTime); - UD60x18 totalDuration = ud(endTime - startTime); - - // Divide the elapsed time by the stream's total duration. - UD60x18 elapsedTimePercentage = elapsedTime.div(totalDuration); - - // Cast the deposited amount to UD60x18. - UD60x18 depositedAmount = ud(_streams[streamId].amounts.deposited); - - // Calculate the streamed amount by multiplying the elapsed time percentage by the deposited amount. - UD60x18 streamedAmount = elapsedTimePercentage.mul(depositedAmount); - - // Although the streamed amount should never exceed the deposited amount, this condition is checked - // without asserting to avoid locking assets in case of a bug. If this situation occurs, the withdrawn - // amount is considered to be the streamed amount, and the stream is effectively frozen. - if (streamedAmount.gt(depositedAmount)) { - return _streams[streamId].amounts.withdrawn; - } - - // Cast the streamed amount to uint128. This is safe due to the check above. - return uint128(streamedAmount.intoUint256()); - } - } - - /*////////////////////////////////////////////////////////////////////////// - INTERNAL NON-CONSTANT FUNCTIONS - //////////////////////////////////////////////////////////////////////////*/ - - /// @dev See the documentation for the user-facing functions that call this internal function. - function _create(LockupLinear.CreateWithTimestamps memory params) internal returns (uint256 streamId) { - // Check: verify the broker fee and calculate the amounts. - Lockup.CreateAmounts memory createAmounts = - Helpers.checkAndCalculateBrokerFee(params.totalAmount, params.broker.fee, MAX_BROKER_FEE); - - // Check: validate the user-provided parameters. - Helpers.checkCreateLockupLinear(params.sender, createAmounts.deposit, params.timestamps); - - // Load the stream ID. - streamId = nextStreamId; - - // Effect: create the stream. - _streams[streamId] = Lockup.Stream({ - amounts: Lockup.Amounts({ deposited: createAmounts.deposit, refunded: 0, withdrawn: 0 }), - asset: params.asset, - endTime: params.timestamps.end, - isCancelable: params.cancelable, - isDepleted: false, - isStream: true, - isTransferable: params.transferable, - sender: params.sender, - startTime: params.timestamps.start, - wasCanceled: false - }); - - // Effect: set the cliff time if it is greater than zero. - if (params.timestamps.cliff > 0) { - _cliffs[streamId] = params.timestamps.cliff; - } - - // Effect: bump the next stream ID. - // Using unchecked arithmetic because these calculations cannot realistically overflow, ever. - unchecked { - nextStreamId = streamId + 1; - } - - // Effect: mint the NFT to the recipient. - _mint({ to: params.recipient, tokenId: streamId }); - - // Interaction: transfer the deposit amount. - params.asset.safeTransferFrom({ from: msg.sender, to: address(this), value: createAmounts.deposit }); - - // Interaction: pay the broker fee, if not zero. - if (createAmounts.brokerFee > 0) { - params.asset.safeTransferFrom({ from: msg.sender, to: params.broker.account, value: createAmounts.brokerFee }); - } - - // Log the newly created stream. - emit ISablierLockupLinear.CreateLockupLinearStream({ - streamId: streamId, - funder: msg.sender, - sender: params.sender, - recipient: params.recipient, - amounts: createAmounts, - asset: params.asset, - cancelable: params.cancelable, - transferable: params.transferable, - timestamps: params.timestamps, - broker: params.broker.account - }); - } -} diff --git a/src/core/SablierLockupTranched.sol b/src/core/SablierLockupTranched.sol deleted file mode 100644 index f77e47e4d..000000000 --- a/src/core/SablierLockupTranched.sol +++ /dev/null @@ -1,284 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.8.22; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; - -import { SablierLockup } from "./abstracts/SablierLockup.sol"; -import { ILockupNFTDescriptor } from "./interfaces/ILockupNFTDescriptor.sol"; -import { ISablierLockupTranched } from "./interfaces/ISablierLockupTranched.sol"; -import { Helpers } from "./libraries/Helpers.sol"; -import { Lockup, LockupTranched } from "./types/DataTypes.sol"; - -/* - -███████╗ █████╗ ██████╗ ██╗ ██╗███████╗██████╗ ██╗ ██████╗ ██████╗██╗ ██╗██╗ ██╗██████╗ -██╔════╝██╔══██╗██╔══██╗██║ ██║██╔════╝██╔══██╗ ██║ ██╔═══██╗██╔════╝██║ ██╔╝██║ ██║██╔══██╗ -███████╗███████║██████╔╝██║ ██║█████╗ ██████╔╝ ██║ ██║ ██║██║ █████╔╝ ██║ ██║██████╔╝ -╚════██║██╔══██║██╔══██╗██║ ██║██╔══╝ ██╔══██╗ ██║ ██║ ██║██║ ██╔═██╗ ██║ ██║██╔═══╝ -███████║██║ ██║██████╔╝███████╗██║███████╗██║ ██║ ███████╗╚██████╔╝╚██████╗██║ ██╗╚██████╔╝██║ -╚══════╝╚═╝ ╚═╝╚═════╝ ╚══════╝╚═╝╚══════╝╚═╝ ╚═╝ ╚══════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ - -████████╗██████╗ █████╗ ███╗ ██╗ ██████╗██╗ ██╗███████╗██████╗ -╚══██╔══╝██╔══██╗██╔══██╗████╗ ██║██╔════╝██║ ██║██╔════╝██╔══██╗ - ██║ ██████╔╝███████║██╔██╗ ██║██║ ███████║█████╗ ██║ ██║ - ██║ ██╔══██╗██╔══██║██║╚██╗██║██║ ██╔══██║██╔══╝ ██║ ██║ - ██║ ██║ ██║██║ ██║██║ ╚████║╚██████╗██║ ██║███████╗██████╔╝ - ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═════╝ - -*/ - -/// @title SablierLockupTranched -/// @notice See the documentation in {ISablierLockupTranched}. -contract SablierLockupTranched is - ISablierLockupTranched, // 5 inherited components - SablierLockup // 15 inherited components -{ - using SafeERC20 for IERC20; - - /*////////////////////////////////////////////////////////////////////////// - STATE VARIABLES - //////////////////////////////////////////////////////////////////////////*/ - - /// @inheritdoc ISablierLockupTranched - uint256 public immutable override MAX_TRANCHE_COUNT; - - /// @dev Stream tranches mapped by stream IDs. This complements the `_streams` mapping in {SablierLockup}. - mapping(uint256 id => LockupTranched.Tranche[] tranches) internal _tranches; - - /*////////////////////////////////////////////////////////////////////////// - CONSTRUCTOR - //////////////////////////////////////////////////////////////////////////*/ - - /// @param initialAdmin The address of the initial contract admin. - /// @param initialNFTDescriptor The address of the NFT descriptor contract. - /// @param maxTrancheCount The maximum number of tranches allowed in a stream. - constructor( - address initialAdmin, - ILockupNFTDescriptor initialNFTDescriptor, - uint256 maxTrancheCount - ) - ERC721("Sablier Lockup Tranched NFT", "SAB-LOCKUP-TRA") - SablierLockup(initialAdmin, initialNFTDescriptor) - { - MAX_TRANCHE_COUNT = maxTrancheCount; - nextStreamId = 1; - } - - /*////////////////////////////////////////////////////////////////////////// - USER-FACING CONSTANT FUNCTIONS - //////////////////////////////////////////////////////////////////////////*/ - - /// @inheritdoc ISablierLockupTranched - function getStream(uint256 streamId) - external - view - override - notNull(streamId) - returns (LockupTranched.StreamLT memory stream) - { - // Retrieve the Lockup stream from storage. - Lockup.Stream memory lockupStream = _streams[streamId]; - - // Settled streams cannot be canceled. - if (_statusOf(streamId) == Lockup.Status.SETTLED) { - lockupStream.isCancelable = false; - } - - stream = LockupTranched.StreamLT({ - amounts: lockupStream.amounts, - asset: lockupStream.asset, - endTime: lockupStream.endTime, - isCancelable: lockupStream.isCancelable, - isDepleted: lockupStream.isDepleted, - isStream: lockupStream.isStream, - isTransferable: lockupStream.isTransferable, - recipient: _ownerOf(streamId), - sender: lockupStream.sender, - startTime: lockupStream.startTime, - tranches: _tranches[streamId], - wasCanceled: lockupStream.wasCanceled - }); - } - - /// @inheritdoc ISablierLockupTranched - function getTimestamps(uint256 streamId) - external - view - override - notNull(streamId) - returns (LockupTranched.Timestamps memory timestamps) - { - timestamps = LockupTranched.Timestamps({ start: _streams[streamId].startTime, end: _streams[streamId].endTime }); - } - - /// @inheritdoc ISablierLockupTranched - function getTranches(uint256 streamId) - external - view - override - notNull(streamId) - returns (LockupTranched.Tranche[] memory tranches) - { - tranches = _tranches[streamId]; - } - - /*////////////////////////////////////////////////////////////////////////// - USER-FACING NON-CONSTANT FUNCTIONS - //////////////////////////////////////////////////////////////////////////*/ - - /// @inheritdoc ISablierLockupTranched - function createWithDurations(LockupTranched.CreateWithDurations calldata params) - external - override - noDelegateCall - returns (uint256 streamId) - { - // Generate the canonical tranches. - LockupTranched.Tranche[] memory tranches = Helpers.calculateTrancheTimestamps(params.tranches); - - // Checks, Effects and Interactions: create the stream. - streamId = _create( - LockupTranched.CreateWithTimestamps({ - sender: params.sender, - recipient: params.recipient, - totalAmount: params.totalAmount, - asset: params.asset, - cancelable: params.cancelable, - transferable: params.transferable, - startTime: uint40(block.timestamp), - tranches: tranches, - broker: params.broker - }) - ); - } - - /// @inheritdoc ISablierLockupTranched - function createWithTimestamps(LockupTranched.CreateWithTimestamps calldata params) - external - override - noDelegateCall - returns (uint256 streamId) - { - // Checks, Effects and Interactions: create the stream. - streamId = _create(params); - } - - /*////////////////////////////////////////////////////////////////////////// - INTERNAL CONSTANT FUNCTIONS - //////////////////////////////////////////////////////////////////////////*/ - - /// @inheritdoc SablierLockup - /// @dev The distribution function is: - /// - /// $$ - /// f(x) = \Sigma(eta) - /// $$ - /// - /// Where: - /// - /// - $\Sigma(eta)$ is the sum of all vested tranches' amounts. - function _calculateStreamedAmount(uint256 streamId) internal view override returns (uint128) { - uint40 blockTimestamp = uint40(block.timestamp); - LockupTranched.Tranche[] memory tranches = _tranches[streamId]; - - // If the first tranche's timestamp is in the future, return zero. - if (tranches[0].timestamp > blockTimestamp) { - return 0; - } - - // If the end time is not in the future, return the deposited amount. - if (_streams[streamId].endTime <= blockTimestamp) { - return _streams[streamId].amounts.deposited; - } - - // Sum the amounts in all tranches that have already been vested. - // Using unchecked arithmetic is safe because the sum of the tranche amounts is equal to the total amount - // at this point. - uint128 streamedAmount = tranches[0].amount; - for (uint256 i = 1; i < tranches.length; ++i) { - // The loop breaks at the first tranche with a timestamp in the future. A tranche is considered vested if - // its timestamp is less than or equal to the block timestamp. - if (tranches[i].timestamp > blockTimestamp) { - break; - } - unchecked { - streamedAmount += tranches[i].amount; - } - } - - return streamedAmount; - } - - /*////////////////////////////////////////////////////////////////////////// - INTERNAL NON-CONSTANT FUNCTIONS - //////////////////////////////////////////////////////////////////////////*/ - - /// @dev See the documentation for the user-facing functions that call this internal function. - function _create(LockupTranched.CreateWithTimestamps memory params) internal returns (uint256 streamId) { - // Check: verify the broker fee and calculate the amounts. - Lockup.CreateAmounts memory createAmounts = - Helpers.checkAndCalculateBrokerFee(params.totalAmount, params.broker.fee, MAX_BROKER_FEE); - - // Check: validate the user-provided parameters. - Helpers.checkCreateLockupTranched( - params.sender, createAmounts.deposit, params.tranches, MAX_TRANCHE_COUNT, params.startTime - ); - - // Load the stream ID in a variable. - streamId = nextStreamId; - - // Effect: create the stream. - Lockup.Stream storage stream = _streams[streamId]; - stream.amounts.deposited = createAmounts.deposit; - stream.asset = params.asset; - stream.isCancelable = params.cancelable; - stream.isStream = true; - stream.isTransferable = params.transferable; - stream.sender = params.sender; - stream.startTime = params.startTime; - - unchecked { - // The tranche count cannot be zero at this point. - uint256 trancheCount = params.tranches.length; - stream.endTime = params.tranches[trancheCount - 1].timestamp; - - // Effect: store the tranches. Since Solidity lacks a syntax for copying arrays of structs directly from - // memory to storage, a manual approach is necessary. See https://github.com/ethereum/solidity/issues/12783. - for (uint256 i = 0; i < trancheCount; ++i) { - _tranches[streamId].push(params.tranches[i]); - } - - // Effect: bump the next stream ID. - // Using unchecked arithmetic because these calculations cannot realistically overflow, ever. - nextStreamId = streamId + 1; - } - - // Effect: mint the NFT to the recipient. - _mint({ to: params.recipient, tokenId: streamId }); - - // Interaction: transfer the deposit amount. - params.asset.safeTransferFrom({ from: msg.sender, to: address(this), value: createAmounts.deposit }); - - // Interaction: pay the broker fee, if not zero. - if (createAmounts.brokerFee > 0) { - params.asset.safeTransferFrom({ from: msg.sender, to: params.broker.account, value: createAmounts.brokerFee }); - } - - // Log the newly created stream. - emit ISablierLockupTranched.CreateLockupTranchedStream({ - streamId: streamId, - funder: msg.sender, - sender: params.sender, - recipient: params.recipient, - amounts: createAmounts, - asset: params.asset, - cancelable: params.cancelable, - transferable: params.transferable, - tranches: params.tranches, - timestamps: LockupTranched.Timestamps({ start: stream.startTime, end: stream.endTime }), - broker: params.broker.account - }); - } -} diff --git a/src/core/abstracts/SablierLockup.sol b/src/core/abstracts/SablierLockupBase.sol similarity index 85% rename from src/core/abstracts/SablierLockup.sol rename to src/core/abstracts/SablierLockupBase.sol index 5cb697173..f3c241e6f 100644 --- a/src/core/abstracts/SablierLockup.sol +++ b/src/core/abstracts/SablierLockupBase.sol @@ -7,8 +7,9 @@ import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { UD60x18 } from "@prb/math/src/UD60x18.sol"; + import { ILockupNFTDescriptor } from "./../interfaces/ILockupNFTDescriptor.sol"; -import { ISablierLockup } from "./../interfaces/ISablierLockup.sol"; +import { ISablierLockupBase } from "./../interfaces/ISablierLockupBase.sol"; import { ISablierLockupRecipient } from "./../interfaces/ISablierLockupRecipient.sol"; import { Errors } from "./../libraries/Errors.sol"; import { Lockup } from "./../types/DataTypes.sol"; @@ -16,13 +17,13 @@ import { Adminable } from "./Adminable.sol"; import { Batch } from "./Batch.sol"; import { NoDelegateCall } from "./NoDelegateCall.sol"; -/// @title SablierLockup -/// @notice See the documentation in {ISablierLockup}. -abstract contract SablierLockup is - Batch, // 0 inherited components +/// @title SablierLockupBase +/// @notice See the documentation in {SablierLockupBase}. +abstract contract SablierLockupBase is + Batch, // 1 inherited components NoDelegateCall, // 0 inherited components Adminable, // 1 inherited components - ISablierLockup, // 7 inherited components + ISablierLockupBase, // 6 inherited components ERC721 // 6 inherited components { using SafeERC20 for IERC20; @@ -31,13 +32,13 @@ abstract contract SablierLockup is STATE VARIABLES //////////////////////////////////////////////////////////////////////////*/ - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase UD60x18 public constant override MAX_BROKER_FEE = UD60x18.wrap(0.1e18); - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase uint256 public override nextStreamId; - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase ILockupNFTDescriptor public override nftDescriptor; /// @dev Mapping of contracts allowed to hook to Sablier when a stream is canceled or when assets are withdrawn. @@ -63,7 +64,7 @@ abstract contract SablierLockup is /// @dev Checks that `streamId` does not reference a null stream. modifier notNull(uint256 streamId) { if (!_streams[streamId].isStream) { - revert Errors.SablierLockup_Null(streamId); + revert Errors.SablierLockupBase_Null(streamId); } _; } @@ -72,12 +73,12 @@ abstract contract SablierLockup is USER-FACING CONSTANT FUNCTIONS //////////////////////////////////////////////////////////////////////////*/ - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function getAsset(uint256 streamId) external view override notNull(streamId) returns (IERC20 asset) { asset = _streams[streamId].asset; } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function getDepositedAmount(uint256 streamId) external view @@ -88,18 +89,29 @@ abstract contract SablierLockup is depositedAmount = _streams[streamId].amounts.deposited; } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function getEndTime(uint256 streamId) external view override notNull(streamId) returns (uint40 endTime) { endTime = _streams[streamId].endTime; } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase + function getLockupModel(uint256 streamId) + external + view + override + notNull(streamId) + returns (Lockup.Model lockupModel) + { + lockupModel = _streams[streamId].lockupModel; + } + + /// @inheritdoc ISablierLockupBase function getRecipient(uint256 streamId) external view override returns (address recipient) { // Check the stream NFT exists and return the owner, which is the stream's recipient. recipient = _requireOwned({ tokenId: streamId }); } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function getRefundedAmount(uint256 streamId) external view @@ -110,17 +122,17 @@ abstract contract SablierLockup is refundedAmount = _streams[streamId].amounts.refunded; } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function getSender(uint256 streamId) external view override notNull(streamId) returns (address sender) { sender = _streams[streamId].sender; } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function getStartTime(uint256 streamId) external view override notNull(streamId) returns (uint40 startTime) { startTime = _streams[streamId].startTime; } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function getWithdrawnAmount(uint256 streamId) external view @@ -131,46 +143,46 @@ abstract contract SablierLockup is withdrawnAmount = _streams[streamId].amounts.withdrawn; } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function isAllowedToHook(address recipient) external view returns (bool result) { result = _allowedToHook[recipient]; } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function isCancelable(uint256 streamId) external view override notNull(streamId) returns (bool result) { if (_statusOf(streamId) != Lockup.Status.SETTLED) { result = _streams[streamId].isCancelable; } } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function isCold(uint256 streamId) external view override notNull(streamId) returns (bool result) { Lockup.Status status = _statusOf(streamId); result = status == Lockup.Status.SETTLED || status == Lockup.Status.CANCELED || status == Lockup.Status.DEPLETED; } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function isDepleted(uint256 streamId) external view override notNull(streamId) returns (bool result) { result = _streams[streamId].isDepleted; } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function isStream(uint256 streamId) external view override returns (bool result) { result = _streams[streamId].isStream; } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function isTransferable(uint256 streamId) external view override notNull(streamId) returns (bool result) { result = _streams[streamId].isTransferable; } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function isWarm(uint256 streamId) external view override notNull(streamId) returns (bool result) { Lockup.Status status = _statusOf(streamId); result = status == Lockup.Status.PENDING || status == Lockup.Status.STREAMING; } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function refundableAmountOf(uint256 streamId) external view @@ -187,14 +199,14 @@ abstract contract SablierLockup is // Otherwise, the result is implicitly zero. } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function statusOf(uint256 streamId) external view override notNull(streamId) returns (Lockup.Status status) { status = _statusOf(streamId); } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function streamedAmountOf(uint256 streamId) - public + external view override notNull(streamId) @@ -218,12 +230,12 @@ abstract contract SablierLockup is uri = nftDescriptor.tokenURI({ sablier: this, streamId: streamId }); } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function wasCanceled(uint256 streamId) external view override notNull(streamId) returns (bool result) { result = _streams[streamId].wasCanceled; } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function withdrawableAmountOf(uint256 streamId) external view @@ -238,31 +250,31 @@ abstract contract SablierLockup is USER-FACING NON-CONSTANT FUNCTIONS //////////////////////////////////////////////////////////////////////////*/ - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function allowToHook(address recipient) external override onlyAdmin { // Check: non-zero code size. if (recipient.code.length == 0) { - revert Errors.SablierLockup_AllowToHookZeroCodeSize(recipient); + revert Errors.SablierLockupBase_AllowToHookZeroCodeSize(recipient); } // Check: recipients implements the ERC-165 interface ID required by {ISablierLockupRecipient}. bytes4 interfaceId = type(ISablierLockupRecipient).interfaceId; if (!ISablierLockupRecipient(recipient).supportsInterface(interfaceId)) { - revert Errors.SablierLockup_AllowToHookUnsupportedInterface(recipient); + revert Errors.SablierLockupBase_AllowToHookUnsupportedInterface(recipient); } // Effect: put the recipient on the allowlist. _allowedToHook[recipient] = true; // Log the allowlist addition. - emit ISablierLockup.AllowToHook({ admin: msg.sender, recipient: recipient }); + emit ISablierLockupBase.AllowToHook({ admin: msg.sender, recipient: recipient }); } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function burn(uint256 streamId) external override noDelegateCall notNull(streamId) { // Check: only depleted streams can be burned. if (!_streams[streamId].isDepleted) { - revert Errors.SablierLockup_StreamNotDepleted(streamId); + revert Errors.SablierLockupBase_StreamNotDepleted(streamId); } // Retrieve the current owner. @@ -272,32 +284,32 @@ abstract contract SablierLockup is // 1. NFT exists (see {IERC721.getApproved}). // 2. `msg.sender` is either the owner of the NFT or an approved third party. if (!_isCallerStreamRecipientOrApproved(streamId, currentRecipient)) { - revert Errors.SablierLockup_Unauthorized(streamId, msg.sender); + revert Errors.SablierLockupBase_Unauthorized(streamId, msg.sender); } // Effect: burn the NFT. _burn({ tokenId: streamId }); } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function cancel(uint256 streamId) public override noDelegateCall notNull(streamId) { // Check: the stream is neither depleted nor canceled. if (_streams[streamId].isDepleted) { - revert Errors.SablierLockup_StreamDepleted(streamId); + revert Errors.SablierLockupBase_StreamDepleted(streamId); } else if (_streams[streamId].wasCanceled) { - revert Errors.SablierLockup_StreamCanceled(streamId); + revert Errors.SablierLockupBase_StreamCanceled(streamId); } // Check: `msg.sender` is the stream's sender. if (!_isCallerStreamSender(streamId)) { - revert Errors.SablierLockup_Unauthorized(streamId, msg.sender); + revert Errors.SablierLockupBase_Unauthorized(streamId, msg.sender); } // Checks, Effects and Interactions: cancel the stream. _cancel(streamId); } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function cancelMultiple(uint256[] calldata streamIds) external override noDelegateCall { // Iterate over the provided array of stream IDs and cancel each stream. uint256 count = streamIds.length; @@ -307,41 +319,41 @@ abstract contract SablierLockup is } } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function renounce(uint256 streamId) external override noDelegateCall notNull(streamId) { // Check: the stream is not cold. Lockup.Status status = _statusOf(streamId); if (status == Lockup.Status.DEPLETED) { - revert Errors.SablierLockup_StreamDepleted(streamId); + revert Errors.SablierLockupBase_StreamDepleted(streamId); } else if (status == Lockup.Status.CANCELED) { - revert Errors.SablierLockup_StreamCanceled(streamId); + revert Errors.SablierLockupBase_StreamCanceled(streamId); } else if (status == Lockup.Status.SETTLED) { - revert Errors.SablierLockup_StreamSettled(streamId); + revert Errors.SablierLockupBase_StreamSettled(streamId); } // Check: `msg.sender` is the stream's sender. if (!_isCallerStreamSender(streamId)) { - revert Errors.SablierLockup_Unauthorized(streamId, msg.sender); + revert Errors.SablierLockupBase_Unauthorized(streamId, msg.sender); } // Checks and Effects: renounce the stream. _renounce(streamId); // Log the renouncement. - emit ISablierLockup.RenounceLockupStream(streamId); + emit ISablierLockupBase.RenounceLockupStream(streamId); // Emit an ERC-4906 event to trigger an update of the NFT metadata. emit MetadataUpdate({ _tokenId: streamId }); } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function setNFTDescriptor(ILockupNFTDescriptor newNFTDescriptor) external override onlyAdmin { // Effect: set the NFT descriptor. ILockupNFTDescriptor oldNftDescriptor = nftDescriptor; nftDescriptor = newNFTDescriptor; // Log the change of the NFT descriptor. - emit ISablierLockup.SetNFTDescriptor({ + emit ISablierLockupBase.SetNFTDescriptor({ admin: msg.sender, oldNFTDescriptor: oldNftDescriptor, newNFTDescriptor: newNFTDescriptor @@ -351,16 +363,16 @@ abstract contract SablierLockup is emit BatchMetadataUpdate({ _fromTokenId: 1, _toTokenId: nextStreamId - 1 }); } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function withdraw(uint256 streamId, address to, uint128 amount) public override noDelegateCall notNull(streamId) { // Check: the stream is not depleted. if (_streams[streamId].isDepleted) { - revert Errors.SablierLockup_StreamDepleted(streamId); + revert Errors.SablierLockupBase_StreamDepleted(streamId); } // Check: the withdrawal address is not zero. if (to == address(0)) { - revert Errors.SablierLockup_WithdrawToZeroAddress(streamId); + revert Errors.SablierLockupBase_WithdrawToZeroAddress(streamId); } // Retrieve the recipient from storage. @@ -369,18 +381,18 @@ abstract contract SablierLockup is // Check: `msg.sender` is neither the stream's recipient nor an approved third party, the withdrawal address // must be the recipient. if (to != recipient && !_isCallerStreamRecipientOrApproved(streamId, recipient)) { - revert Errors.SablierLockup_WithdrawalAddressNotRecipient(streamId, msg.sender, to); + revert Errors.SablierLockupBase_WithdrawalAddressNotRecipient(streamId, msg.sender, to); } // Check: the withdraw amount is not zero. if (amount == 0) { - revert Errors.SablierLockup_WithdrawAmountZero(streamId); + revert Errors.SablierLockupBase_WithdrawAmountZero(streamId); } // Check: the withdraw amount is not greater than the withdrawable amount. uint128 withdrawableAmount = _withdrawableAmountOf(streamId); if (amount > withdrawableAmount) { - revert Errors.SablierLockup_Overdraw(streamId, amount, withdrawableAmount); + revert Errors.SablierLockupBase_Overdraw(streamId, amount, withdrawableAmount); } // Effects and Interactions: make the withdrawal. @@ -400,18 +412,18 @@ abstract contract SablierLockup is // Check: the recipient's hook returned the correct selector. if (selector != ISablierLockupRecipient.onSablierLockupWithdraw.selector) { - revert Errors.SablierLockup_InvalidHookSelector(recipient); + revert Errors.SablierLockupBase_InvalidHookSelector(recipient); } } } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function withdrawMax(uint256 streamId, address to) external override returns (uint128 withdrawnAmount) { withdrawnAmount = _withdrawableAmountOf(streamId); withdraw({ streamId: streamId, to: to, amount: withdrawnAmount }); } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function withdrawMaxAndTransfer( uint256 streamId, address newRecipient @@ -427,7 +439,7 @@ abstract contract SablierLockup is // Check: `msg.sender` is neither the stream's recipient nor an approved third party. if (!_isCallerStreamRecipientOrApproved(streamId, currentRecipient)) { - revert Errors.SablierLockup_Unauthorized(streamId, msg.sender); + revert Errors.SablierLockupBase_Unauthorized(streamId, msg.sender); } // Skip the withdrawal if the withdrawable amount is zero. @@ -440,7 +452,7 @@ abstract contract SablierLockup is _transfer({ from: currentRecipient, to: newRecipient, tokenId: streamId }); } - /// @inheritdoc ISablierLockup + /// @inheritdoc ISablierLockupBase function withdrawMultiple( uint256[] calldata streamIds, uint128[] calldata amounts @@ -453,7 +465,7 @@ abstract contract SablierLockup is uint256 streamIdsCount = streamIds.length; uint256 amountsCount = amounts.length; if (streamIdsCount != amountsCount) { - revert Errors.SablierLockup_WithdrawArrayCountsNotEqual(streamIdsCount, amountsCount); + revert Errors.SablierLockupBase_WithdrawArrayCountsNotEqual(streamIdsCount, amountsCount); } // Iterate over the provided array of stream IDs, and withdraw from each stream to the recipient. @@ -537,12 +549,12 @@ abstract contract SablierLockup is // Check: the stream is not settled. if (streamedAmount >= amounts.deposited) { - revert Errors.SablierLockup_StreamSettled(streamId); + revert Errors.SablierLockupBase_StreamSettled(streamId); } // Check: the stream is cancelable. if (!_streams[streamId].isCancelable) { - revert Errors.SablierLockup_StreamNotCancelable(streamId); + revert Errors.SablierLockupBase_StreamNotCancelable(streamId); } // Calculate the sender's amount. @@ -579,7 +591,7 @@ abstract contract SablierLockup is asset.safeTransfer({ to: sender, value: senderAmount }); // Log the cancellation. - emit ISablierLockup.CancelLockupStream(streamId, sender, recipient, asset, senderAmount, recipientAmount); + emit ISablierLockupBase.CancelLockupStream(streamId, sender, recipient, asset, senderAmount, recipientAmount); // Emit an ERC-4906 event to trigger an update of the NFT metadata. emit MetadataUpdate({ _tokenId: streamId }); @@ -595,7 +607,7 @@ abstract contract SablierLockup is // Check: the recipient's hook returned the correct selector. if (selector != ISablierLockupRecipient.onSablierLockupCancel.selector) { - revert Errors.SablierLockup_InvalidHookSelector(recipient); + revert Errors.SablierLockupBase_InvalidHookSelector(recipient); } } } @@ -604,7 +616,7 @@ abstract contract SablierLockup is function _renounce(uint256 streamId) internal { // Check: the stream is cancelable. if (!_streams[streamId].isCancelable) { - revert Errors.SablierLockup_StreamNotCancelable(streamId); + revert Errors.SablierLockupBase_StreamNotCancelable(streamId); } // Effect: renounce the stream by making it not cancelable. @@ -625,7 +637,7 @@ abstract contract SablierLockup is address from = _ownerOf(streamId); if (from != address(0) && to != address(0) && !_streams[streamId].isTransferable) { - revert Errors.SablierLockup_NotTransferable(streamId); + revert Errors.SablierLockupBase_NotTransferable(streamId); } // Emit an ERC-4906 event to trigger an update of the NFT metadata. @@ -659,6 +671,6 @@ abstract contract SablierLockup is asset.safeTransfer({ to: to, value: amount }); // Log the withdrawal. - emit ISablierLockup.WithdrawFromLockupStream(streamId, to, asset, amount); + emit ISablierLockupBase.WithdrawFromLockupStream(streamId, to, asset, amount); } } diff --git a/src/core/interfaces/ISablierLockup.sol b/src/core/interfaces/ISablierLockup.sol index 977e54f01..80e100f1c 100644 --- a/src/core/interfaces/ISablierLockup.sol +++ b/src/core/interfaces/ISablierLockup.sol @@ -1,360 +1,276 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; -import { IERC4906 } from "@openzeppelin/contracts/interfaces/IERC4906.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; -import { UD60x18 } from "@prb/math/src/UD60x18.sol"; -import { Lockup } from "../types/DataTypes.sol"; -import { IAdminable } from "./IAdminable.sol"; -import { IBatch } from "./IBatch.sol"; -import { ILockupNFTDescriptor } from "./ILockupNFTDescriptor.sol"; +import { Lockup, LockupDynamic, LockupLinear, LockupTranched } from "../types/DataTypes.sol"; +import { ISablierLockupBase } from "./ISablierLockupBase.sol"; /// @title ISablierLockup -/// @notice Common logic between all Sablier Lockup contracts. -interface ISablierLockup is - IAdminable, // 0 inherited components - IBatch, // 0 inherited components - IERC4906, // 2 inherited components - IERC721Metadata // 2 inherited components -{ +/// @notice Creates and manages Lockup streams with various distribution models. +interface ISablierLockup is ISablierLockupBase { /*////////////////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////////////////*/ - /// @notice Emitted when the admin allows a new recipient contract to hook to Sablier. - /// @param admin The address of the current contract admin. - /// @param recipient The address of the recipient contract put on the allowlist. - event AllowToHook(address indexed admin, address recipient); - - /// @notice Emitted when a stream is canceled. - /// @param streamId The ID of the stream. - /// @param sender The address of the stream's sender. - /// @param recipient The address of the stream's recipient. - /// @param asset The contract address of the ERC-20 asset that has been distributed. - /// @param senderAmount The amount of assets refunded to the stream's sender, denoted in units of the asset's - /// decimals. - /// @param recipientAmount The amount of assets left for the stream's recipient to withdraw, denoted in units of the - /// asset's decimals. - event CancelLockupStream( + /// @notice Emitted when a stream is created using Lockup dynamic model. + /// @param streamId The ID of the newly created stream. + /// @param funder The address which has funded the stream. + /// @param sender The address distributing the assets, which is able to cancel the stream. + /// @param recipient The address receiving the assets, as well as the NFT owner. + /// @param amounts Struct encapsulating (i) the deposit amount, and (ii) the broker fee amount, both denoted + /// in units of the asset's decimals. + /// @param asset The contract address of the ERC-20 asset to be distributed. + /// @param cancelable Boolean indicating whether the stream is cancelable or not. + /// @param transferable Boolean indicating whether the stream NFT is transferable or not. + /// @param timestamps Struct encapsulating (i) the stream's start time and (ii) end time, all as Unix timestamps. + /// @param broker The address of the broker who has helped create the stream, e.g. a front-end website. + /// @param segments The segments the protocol uses to compose the dynamic distribution function. + event CreateLockupDynamicStream( uint256 streamId, + address funder, address indexed sender, address indexed recipient, + Lockup.CreateAmounts amounts, IERC20 indexed asset, - uint128 senderAmount, - uint128 recipientAmount + bool cancelable, + bool transferable, + Lockup.Timestamps timestamps, + LockupDynamic.Segment[] segments, + address broker ); - /// @notice Emitted when a sender gives up the right to cancel a stream. - /// @param streamId The ID of the stream. - event RenounceLockupStream(uint256 indexed streamId); - - /// @notice Emitted when the admin sets a new NFT descriptor contract. - /// @param admin The address of the current contract admin. - /// @param oldNFTDescriptor The address of the old NFT descriptor contract. - /// @param newNFTDescriptor The address of the new NFT descriptor contract. - event SetNFTDescriptor( - address indexed admin, ILockupNFTDescriptor oldNFTDescriptor, ILockupNFTDescriptor newNFTDescriptor + /// @notice Emitted when a stream is created using Lockup linear model. + /// @param streamId The ID of the newly created stream. + /// @param funder The address which funded the stream. + /// @param sender The address distributing the assets, which is able to to cancel the stream. + /// @param recipient The address receiving the assets, as well as the NFT owner. + /// @param amounts Struct encapsulating (i) the deposit amount, and (ii) the broker fee amount, both denoted + /// in units of the asset's decimals. + /// @param asset The contract address of the ERC-20 asset to be distributed. + /// @param cancelable Boolean indicating whether the stream is cancelable or not. + /// @param transferable Boolean indicating whether the stream NFT is transferable or not. + /// @param timestamps Struct encapsulating (i) the stream's start time and (ii) end time, all as Unix timestamps. + /// @param cliffTime The Unix timestamp for the cliff period's end. A value of zero means there is no cliff. + /// @param broker The address of the broker who has helped create the stream, e.g. a front-end website. + event CreateLockupLinearStream( + uint256 streamId, + address funder, + address indexed sender, + address indexed recipient, + Lockup.CreateAmounts amounts, + IERC20 indexed asset, + bool cancelable, + bool transferable, + Lockup.Timestamps timestamps, + uint40 cliffTime, + address broker ); - /// @notice Emitted when assets are withdrawn from a stream. - /// @param streamId The ID of the stream. - /// @param to The address that has received the withdrawn assets. - /// @param asset The contract address of the ERC-20 asset that has been withdrawn. - /// @param amount The amount of assets withdrawn, denoted in units of the asset's decimals. - event WithdrawFromLockupStream(uint256 indexed streamId, address indexed to, IERC20 indexed asset, uint128 amount); + /// @notice Emitted when a stream is created using Lockup tranched model. + /// @param streamId The ID of the newly created stream. + /// @param funder The address which has funded the stream. + /// @param sender The address distributing the assets, which is able to cancel the stream. + /// @param recipient The address receiving the assets, as well as the NFT owner. + /// @param amounts Struct encapsulating (i) the deposit amount, and (ii) the broker fee amount, both denoted + /// in units of the asset's decimals. + /// @param asset The contract address of the ERC-20 asset to be distributed. + /// @param cancelable Boolean indicating whether the stream is cancelable or not. + /// @param transferable Boolean indicating whether the stream NFT is transferable or not. + /// @param timestamps Struct encapsulating (i) the stream's start time and (ii) end time, both as Unix timestamps. + /// @param broker The address of the broker who has helped create the stream, e.g. a front-end website. + /// @param tranches The tranches the protocol uses to compose the tranched distribution function. + event CreateLockupTranchedStream( + uint256 streamId, + address funder, + address indexed sender, + address indexed recipient, + Lockup.CreateAmounts amounts, + IERC20 indexed asset, + bool cancelable, + bool transferable, + Lockup.Timestamps timestamps, + LockupTranched.Tranche[] tranches, + address broker + ); /*////////////////////////////////////////////////////////////////////////// CONSTANT FUNCTIONS //////////////////////////////////////////////////////////////////////////*/ - /// @notice Retrieves the maximum broker fee that can be charged by the broker, denoted as a fixed-point - /// number where 1e18 is 100%. - /// @dev This value is hard coded as a constant. - function MAX_BROKER_FEE() external view returns (UD60x18); - - /// @notice Retrieves the address of the ERC-20 asset being distributed. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - function getAsset(uint256 streamId) external view returns (IERC20 asset); - - /// @notice Retrieves the amount deposited in the stream, denoted in units of the asset's decimals. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - function getDepositedAmount(uint256 streamId) external view returns (uint128 depositedAmount); - - /// @notice Retrieves the stream's end time, which is a Unix timestamp. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - function getEndTime(uint256 streamId) external view returns (uint40 endTime); - - /// @notice Retrieves the stream's recipient. - /// @dev Reverts if the NFT has been burned. - /// @param streamId The stream ID for the query. - function getRecipient(uint256 streamId) external view returns (address recipient); - - /// @notice Retrieves the amount refunded to the sender after a cancellation, denoted in units of the asset's - /// decimals. This amount is always zero unless the stream was canceled. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - function getRefundedAmount(uint256 streamId) external view returns (uint128 refundedAmount); - - /// @notice Retrieves the stream's sender. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - function getSender(uint256 streamId) external view returns (address sender); - - /// @notice Retrieves the stream's start time, which is a Unix timestamp. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - function getStartTime(uint256 streamId) external view returns (uint40 startTime); - - /// @notice Retrieves the amount withdrawn from the stream, denoted in units of the asset's decimals. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - function getWithdrawnAmount(uint256 streamId) external view returns (uint128 withdrawnAmount); - - /// @notice Retrieves a flag indicating whether the provided address is a contract allowed to hook to Sablier - /// when a stream is canceled or when assets are withdrawn. - /// @dev See {ISablierLockupRecipient} for more information. - function isAllowedToHook(address recipient) external view returns (bool result); - - /// @notice Retrieves a flag indicating whether the stream can be canceled. When the stream is cold, this - /// flag is always `false`. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - function isCancelable(uint256 streamId) external view returns (bool result); - - /// @notice Retrieves a flag indicating whether the stream is cold, i.e. settled, canceled, or depleted. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - function isCold(uint256 streamId) external view returns (bool result); + /// @notice The maximum number of segments and tranches allowed in Dynamic and Tranched streams respectively. + /// @dev This is initialized at construction time and cannot be changed later. + function MAX_COUNT() external view returns (uint256); - /// @notice Retrieves a flag indicating whether the stream is depleted. - /// @dev Reverts if `streamId` references a null stream. + /// @notice Retrieves the stream's cliff time, which is a Unix timestamp. A value of zero means there is no cliff. + /// @dev Reverts if `streamId` references a null stream or a non Lockup Linear stream. /// @param streamId The stream ID for the query. - function isDepleted(uint256 streamId) external view returns (bool result); + function getCliffTime(uint256 streamId) external view returns (uint40 cliffTime); - /// @notice Retrieves a flag indicating whether the stream exists. - /// @dev Does not revert if `streamId` references a null stream. + /// @notice Retrieves the segments used to compose the dynamic distribution function. + /// @dev Reverts if `streamId` references a null stream or a non Lockup Dynamic stream. /// @param streamId The stream ID for the query. - function isStream(uint256 streamId) external view returns (bool result); - - /// @notice Retrieves a flag indicating whether the stream NFT can be transferred. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - function isTransferable(uint256 streamId) external view returns (bool result); - - /// @notice Retrieves a flag indicating whether the stream is warm, i.e. either pending or streaming. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - function isWarm(uint256 streamId) external view returns (bool result); - - /// @notice Counter for stream IDs, used in the create functions. - function nextStreamId() external view returns (uint256); - - /// @notice Contract that generates the non-fungible token URI. - function nftDescriptor() external view returns (ILockupNFTDescriptor); + function getSegments(uint256 streamId) external view returns (LockupDynamic.Segment[] memory segments); - /// @notice Calculates the amount that the sender would be refunded if the stream were canceled, denoted in units - /// of the asset's decimals. - /// @dev Reverts if `streamId` references a null stream. + /// @notice Retrieves the tranches used to compose the tranched distribution function. + /// @dev Reverts if `streamId` references a null stream or a non Lockup Tranched stream. /// @param streamId The stream ID for the query. - function refundableAmountOf(uint256 streamId) external view returns (uint128 refundableAmount); - - /// @notice Retrieves the stream's status. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - function statusOf(uint256 streamId) external view returns (Lockup.Status status); - - /// @notice Calculates the amount streamed to the recipient, denoted in units of the asset's decimals. - /// @dev Reverts if `streamId` references a null stream. - /// - /// Notes: - /// - Upon cancellation of the stream, the amount streamed is calculated as the difference between the deposited - /// amount and the refunded amount. Ultimately, when the stream becomes depleted, the streamed amount is equivalent - /// to the total amount withdrawn. - /// - /// @param streamId The stream ID for the query. - function streamedAmountOf(uint256 streamId) external view returns (uint128 streamedAmount); - - /// @notice Retrieves a flag indicating whether the stream was canceled. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - function wasCanceled(uint256 streamId) external view returns (bool result); - - /// @notice Calculates the amount that the recipient can withdraw from the stream, denoted in units of the asset's - /// decimals. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - function withdrawableAmountOf(uint256 streamId) external view returns (uint128 withdrawableAmount); + function getTranches(uint256 streamId) external view returns (LockupTranched.Tranche[] memory tranches); /*////////////////////////////////////////////////////////////////////////// NON-CONSTANT FUNCTIONS //////////////////////////////////////////////////////////////////////////*/ - /// @notice Allows a recipient contract to hook to Sablier when a stream is canceled or when assets are withdrawn. - /// Useful for implementing contracts that hold streams on behalf of users, such as vaults or staking contracts. - /// - /// @dev Emits an {AllowToHook} event. - /// - /// Notes: - /// - Does not revert if the contract is already on the allowlist. - /// - This is an irreversible operation. The contract cannot be removed from the allowlist. - /// - /// Requirements: - /// - `msg.sender` must be the contract admin. - /// - `recipient` must have a non-zero code size. - /// - `recipient` must implement {ISablierLockupRecipient}. - /// - /// @param recipient The address of the contract to allow for hooks. - function allowToHook(address recipient) external; - - /// @notice Burns the NFT associated with the stream. + /// @notice Creates a stream by setting the start time to `block.timestamp`, and the end time to the sum of + /// `block.timestamp` and all specified time durations. The segment timestamps are derived from these + /// durations. The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. /// - /// @dev Emits a {Transfer} event. + /// @dev Emits a {Transfer} and {CreateLockupDynamicStream} event. /// /// Requirements: - /// - Must not be delegate called. - /// - `streamId` must reference a depleted stream. - /// - The NFT must exist. - /// - `msg.sender` must be either the NFT owner or an approved third party. - /// - /// @param streamId The ID of the stream NFT to burn. - function burn(uint256 streamId) external; + /// - All requirements in {createWithTimestampsLD} must be met for the calculated parameters. + /// + /// @param params Struct encapsulating the function parameters, which are documented in {DataTypes}. + /// @param segmentsWithDuration Segments with durations used to compose the dynamic distribution function. Timestamps + /// are calculated by starting from `block.timestamp` and adding each duration to the previous timestamp. + /// @return streamId The ID of the newly created stream. + function createWithDurationsLD( + Lockup.CreateWithDurations calldata params, + LockupDynamic.SegmentWithDuration[] calldata segmentsWithDuration + ) + external + returns (uint256 streamId); - /// @notice Cancels the stream and refunds any remaining assets to the sender. + /// @notice Creates a stream by setting the start time to `block.timestamp`, and the end time to + /// the sum of `block.timestamp` and `durations.total`. The stream is funded by `msg.sender` and is wrapped in an + /// ERC-721 NFT. /// - /// @dev Emits a {Transfer}, {CancelLockupStream}, and {MetadataUpdate} event. - /// - /// Notes: - /// - If there any assets left for the recipient to withdraw, the stream is marked as canceled. Otherwise, the - /// stream is marked as depleted. - /// - This function attempts to invoke a hook on the recipient, if the resolved address is a contract. + /// @dev Emits a {Transfer} and {CreateLockupLinearStream} event. /// /// Requirements: - /// - Must not be delegate called. - /// - The stream must be warm and cancelable. - /// - `msg.sender` must be the stream's sender. - /// - /// @param streamId The ID of the stream to cancel. - function cancel(uint256 streamId) external; + /// - All requirements in {createWithTimestampsLL} must be met for the calculated parameters. + /// + /// @param params Struct encapsulating the function parameters, which are documented in {DataTypes}. + /// @param durations Struct encapsulating (i) cliff period duration and (ii) total stream duration, both in seconds. + /// @return streamId The ID of the newly created stream. + function createWithDurationsLL( + Lockup.CreateWithDurations calldata params, + LockupLinear.Durations calldata durations + ) + external + returns (uint256 streamId); - /// @notice Cancels multiple streams and refunds any remaining assets to the sender. + /// @notice Creates a stream by setting the start time to `block.timestamp`, and the end time to the sum of + /// `block.timestamp` and all specified time durations. The tranche timestamps are derived from these + /// durations. The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. /// - /// @dev Emits multiple {Transfer}, {CancelLockupStream}, and {MetadataUpdate} events. - /// - /// Notes: - /// - Refer to the notes in {cancel}. + /// @dev Emits a {Transfer} and {CreateLockupTrancheStream} event. /// /// Requirements: - /// - All requirements from {cancel} must be met for each stream. - /// - /// @param streamIds The IDs of the streams to cancel. - function cancelMultiple(uint256[] calldata streamIds) external; + /// - All requirements in {createWithTimestampsLT} must be met for the calculated parameters. + /// + /// @param params Struct encapsulating the function parameters, which are documented in {DataTypes}. + /// @param tranchesWithDuration Tranches with durations used to compose the tranched distribution function. + /// Timestamps are calculated by starting from `block.timestamp` and adding each duration to the previous timestamp. + /// @return streamId The ID of the newly created stream. + function createWithDurationsLT( + Lockup.CreateWithDurations calldata params, + LockupTranched.TrancheWithDuration[] calldata tranchesWithDuration + ) + external + returns (uint256 streamId); - /// @notice Removes the right of the stream's sender to cancel the stream. + /// @notice Creates a stream with the provided segment timestamps, implying the end time from the last timestamp. + /// The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. /// - /// @dev Emits a {RenounceLockupStream} and {MetadataUpdate} event. + /// @dev Emits a {Transfer} and {CreateLockupDynamicStream} event. /// /// Notes: - /// - This is an irreversible operation. + /// - As long as the segment timestamps are arranged in ascending order, it is not an error for some + /// of them to be in the past. /// /// Requirements: /// - Must not be delegate called. - /// - `streamId` must reference a warm stream. - /// - `msg.sender` must be the stream's sender. - /// - The stream must be cancelable. - /// - /// @param streamId The ID of the stream to renounce. - function renounce(uint256 streamId) external; - - /// @notice Sets a new NFT descriptor contract, which produces the URI describing the Sablier stream NFTs. - /// - /// @dev Emits a {SetNFTDescriptor} and {BatchMetadataUpdate} event. - /// - /// Notes: - /// - Does not revert if the NFT descriptor is the same. - /// - /// Requirements: - /// - `msg.sender` must be the contract admin. - /// - /// @param newNFTDescriptor The address of the new NFT descriptor contract. - function setNFTDescriptor(ILockupNFTDescriptor newNFTDescriptor) external; + /// - `params.totalAmount` must be greater than zero. + /// - If set, `params.broker.fee` must not be greater than `MAX_BROKER_FEE`. + /// - `params.timestamps.start` must be greater than zero and less than the first segment's timestamp. + /// - `segments` must have at least one segment, but not more than `MAX_COUNT`. + /// - The segment timestamps must be arranged in ascending order. + /// - `params.timestamps.end` must be equal to the last segment's timestamp. + /// - The sum of the segment amounts must equal the deposit amount. + /// - `params.recipient` must not be the zero address. + /// - `params.sender` must not be the zero address. + /// - `msg.sender` must have allowed this contract to spend at least `params.totalAmount` assets. + /// + /// @param params Struct encapsulating the function parameters, which are documented in {DataTypes}. + /// @param segments Segments used to compose the dynamic distribution function. + /// @return streamId The ID of the newly created stream. + function createWithTimestampsLD( + Lockup.CreateWithTimestamps calldata params, + LockupDynamic.Segment[] calldata segments + ) + external + returns (uint256 streamId); - /// @notice Withdraws the provided amount of assets from the stream to the `to` address. + /// @notice Creates a stream with the provided start time and end time. The stream is funded by `msg.sender` and is + /// wrapped in an ERC-721 NFT. /// - /// @dev Emits a {Transfer}, {WithdrawFromLockupStream}, and {MetadataUpdate} event. + /// @dev Emits a {Transfer} and {CreateLockupLinearStream} event. /// /// Notes: - /// - This function attempts to call a hook on the recipient of the stream, unless `msg.sender` is the recipient. + /// - A cliff time of zero means there is no cliff. + /// - As long as the times are ordered, it is not an error for the start or the cliff time to be in the past. /// /// Requirements: /// - Must not be delegate called. - /// - `streamId` must not reference a null or depleted stream. - /// - `to` must not be the zero address. - /// - `amount` must be greater than zero and must not exceed the withdrawable amount. - /// - `to` must be the recipient if `msg.sender` is not the stream's recipient or an approved third party. - /// - /// @param streamId The ID of the stream to withdraw from. - /// @param to The address receiving the withdrawn assets. - /// @param amount The amount to withdraw, denoted in units of the asset's decimals. - function withdraw(uint256 streamId, address to, uint128 amount) external; - - /// @notice Withdraws the maximum withdrawable amount from the stream to the provided address `to`. - /// - /// @dev Emits a {Transfer}, {WithdrawFromLockupStream}, and {MetadataUpdate} event. - /// - /// Notes: - /// - Refer to the notes in {withdraw}. - /// - /// Requirements: - /// - Refer to the requirements in {withdraw}. - /// - /// @param streamId The ID of the stream to withdraw from. - /// @param to The address receiving the withdrawn assets. - /// @return withdrawnAmount The amount withdrawn, denoted in units of the asset's decimals. - function withdrawMax(uint256 streamId, address to) external returns (uint128 withdrawnAmount); - - /// @notice Withdraws the maximum withdrawable amount from the stream to the current recipient, and transfers the - /// NFT to `newRecipient`. - /// - /// @dev Emits a {WithdrawFromLockupStream} and a {Transfer} event. - /// - /// Notes: - /// - If the withdrawable amount is zero, the withdrawal is skipped. - /// - Refer to the notes in {withdraw}. - /// - /// Requirements: - /// - `msg.sender` must be either the NFT owner or an approved third party. - /// - Refer to the requirements in {withdraw}. - /// - Refer to the requirements in {IERC721.transferFrom}. - /// - /// @param streamId The ID of the stream NFT to transfer. - /// @param newRecipient The address of the new owner of the stream NFT. - /// @return withdrawnAmount The amount withdrawn, denoted in units of the asset's decimals. - function withdrawMaxAndTransfer( - uint256 streamId, - address newRecipient + /// - `params.totalAmount` must be greater than zero. + /// - If set, `params.broker.fee` must not be greater than `MAX_BROKER_FEE`. + /// - `params.timestamps.start` must be greater than zero and less than `params.timestamps.end`. + /// - If set, `cliffTime` must be greater than `params.timestamps.start` and less than + /// `params.timestamps.end`. + /// - `params.recipient` must not be the zero address. + /// - `params.sender` must not be the zero address. + /// - `msg.sender` must have allowed this contract to spend at least `params.totalAmount` assets. + /// + /// @param params Struct encapsulating the function parameters, which are documented in {DataTypes}. + /// @param cliffTime The Unix timestamp for the cliff period's end. A value of zero means there is no cliff. + /// @return streamId The ID of the newly created stream. + function createWithTimestampsLL( + Lockup.CreateWithTimestamps calldata params, + uint40 cliffTime ) external - returns (uint128 withdrawnAmount); + returns (uint256 streamId); - /// @notice Withdraws assets from streams to the recipient of each stream. + /// @notice Creates a stream with the provided tranche timestamps, implying the end time from the last timestamp. + /// The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. /// - /// @dev Emits multiple {Transfer}, {WithdrawFromLockupStream}, and {MetadataUpdate} events. + /// @dev Emits a {Transfer} and {CreateLockupTrancheStream} event. /// /// Notes: - /// - This function attempts to call a hook on the recipient of each stream, unless `msg.sender` is the recipient. + /// - As long as the tranche timestamps are arranged in ascending order, it is not an error for some + /// of them to be in the past. /// /// Requirements: /// - Must not be delegate called. - /// - There must be an equal number of `streamIds` and `amounts`. - /// - Each stream ID in the array must not reference a null or depleted stream. - /// - Each amount in the array must be greater than zero and must not exceed the withdrawable amount. - /// - /// @param streamIds The IDs of the streams to withdraw from. - /// @param amounts The amounts to withdraw, denoted in units of the asset's decimals. - function withdrawMultiple(uint256[] calldata streamIds, uint128[] calldata amounts) external; + /// - `params.totalAmount` must be greater than zero. + /// - If set, `params.broker.fee` must not be greater than `MAX_BROKER_FEE`. + /// - `params.timestamps.start` must be greater than zero and less than the first tranche's timestamp. + /// - `tranches` must have at least one tranche, but not more than `MAX_COUNT`. + /// - The tranche timestamps must be arranged in ascending order. + /// - `params.timestamps.end` must be equal to the last tranche's timestamp. + /// - The sum of the tranche amounts must equal the deposit amount. + /// - `params.recipient` must not be the zero address. + /// - `params.sender` must not be the zero address. + /// - `msg.sender` must have allowed this contract to spend at least `params.totalAmount` assets. + /// + /// @param params Struct encapsulating the function parameters, which are documented in {DataTypes}. + /// @param tranches Tranches used to compose the tranched distribution function. + /// @return streamId The ID of the newly created stream. + function createWithTimestampsLT( + Lockup.CreateWithTimestamps calldata params, + LockupTranched.Tranche[] calldata tranches + ) + external + returns (uint256 streamId); } diff --git a/src/core/interfaces/ISablierLockupBase.sol b/src/core/interfaces/ISablierLockupBase.sol new file mode 100644 index 000000000..b0fb06e17 --- /dev/null +++ b/src/core/interfaces/ISablierLockupBase.sol @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity >=0.8.22; + +import { IERC4906 } from "@openzeppelin/contracts/interfaces/IERC4906.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; +import { UD60x18 } from "@prb/math/src/UD60x18.sol"; + +import { Lockup } from "../types/DataTypes.sol"; +import { IAdminable } from "./IAdminable.sol"; +import { IBatch } from "./IBatch.sol"; +import { ILockupNFTDescriptor } from "./ILockupNFTDescriptor.sol"; + +/// @title ISablierLockupBase +/// @notice Common logic between all Sablier Lockup contracts. +interface ISablierLockupBase is + IAdminable, // 0 inherited components + IBatch, // 0 inherited components + IERC4906, // 2 inherited components + IERC721Metadata // 2 inherited components +{ + /*////////////////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////////////////*/ + + /// @notice Emitted when the admin allows a new recipient contract to hook to Sablier. + /// @param admin The address of the current contract admin. + /// @param recipient The address of the recipient contract put on the allowlist. + event AllowToHook(address indexed admin, address recipient); + + /// @notice Emitted when a stream is canceled. + /// @param streamId The ID of the stream. + /// @param sender The address of the stream's sender. + /// @param recipient The address of the stream's recipient. + /// @param asset The contract address of the ERC-20 asset that has been distributed. + /// @param senderAmount The amount of assets refunded to the stream's sender, denoted in units of the asset's + /// decimals. + /// @param recipientAmount The amount of assets left for the stream's recipient to withdraw, denoted in units of the + /// asset's decimals. + event CancelLockupStream( + uint256 streamId, + address indexed sender, + address indexed recipient, + IERC20 indexed asset, + uint128 senderAmount, + uint128 recipientAmount + ); + + /// @notice Emitted when a sender gives up the right to cancel a stream. + /// @param streamId The ID of the stream. + event RenounceLockupStream(uint256 indexed streamId); + + /// @notice Emitted when the admin sets a new NFT descriptor contract. + /// @param admin The address of the current contract admin. + /// @param oldNFTDescriptor The address of the old NFT descriptor contract. + /// @param newNFTDescriptor The address of the new NFT descriptor contract. + event SetNFTDescriptor( + address indexed admin, ILockupNFTDescriptor oldNFTDescriptor, ILockupNFTDescriptor newNFTDescriptor + ); + + /// @notice Emitted when assets are withdrawn from a stream. + /// @param streamId The ID of the stream. + /// @param to The address that has received the withdrawn assets. + /// @param asset The contract address of the ERC-20 asset that has been withdrawn. + /// @param amount The amount of assets withdrawn, denoted in units of the asset's decimals. + event WithdrawFromLockupStream(uint256 indexed streamId, address indexed to, IERC20 indexed asset, uint128 amount); + + /*////////////////////////////////////////////////////////////////////////// + CONSTANT FUNCTIONS + //////////////////////////////////////////////////////////////////////////*/ + + /// @notice Retrieves the maximum broker fee that can be charged by the broker, denoted as a fixed-point + /// number where 1e18 is 100%. + /// @dev This value is hard coded as a constant. + function MAX_BROKER_FEE() external view returns (UD60x18); + + /// @notice Retrieves the address of the ERC-20 asset being distributed. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function getAsset(uint256 streamId) external view returns (IERC20 asset); + + /// @notice Retrieves the amount deposited in the stream, denoted in units of the asset's decimals. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function getDepositedAmount(uint256 streamId) external view returns (uint128 depositedAmount); + + /// @notice Retrieves the stream's end time, which is a Unix timestamp. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function getEndTime(uint256 streamId) external view returns (uint40 endTime); + + /// @notice Retrieves the distribution models used to create the stream. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function getLockupModel(uint256 streamId) external view returns (Lockup.Model lockupModel); + + /// @notice Retrieves the stream's recipient. + /// @dev Reverts if the NFT has been burned. + /// @param streamId The stream ID for the query. + function getRecipient(uint256 streamId) external view returns (address recipient); + + /// @notice Retrieves the amount refunded to the sender after a cancellation, denoted in units of the asset's + /// decimals. This amount is always zero unless the stream was canceled. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function getRefundedAmount(uint256 streamId) external view returns (uint128 refundedAmount); + + /// @notice Retrieves the stream's sender. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function getSender(uint256 streamId) external view returns (address sender); + + /// @notice Retrieves the stream's start time, which is a Unix timestamp. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function getStartTime(uint256 streamId) external view returns (uint40 startTime); + + /// @notice Retrieves the amount withdrawn from the stream, denoted in units of the asset's decimals. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function getWithdrawnAmount(uint256 streamId) external view returns (uint128 withdrawnAmount); + + /// @notice Retrieves a flag indicating whether the provided address is a contract allowed to hook to Sablier + /// when a stream is canceled or when assets are withdrawn. + /// @dev See {ISablierLockupRecipient} for more information. + function isAllowedToHook(address recipient) external view returns (bool result); + + /// @notice Retrieves a flag indicating whether the stream can be canceled. When the stream is cold, this + /// flag is always `false`. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function isCancelable(uint256 streamId) external view returns (bool result); + + /// @notice Retrieves a flag indicating whether the stream is cold, i.e. settled, canceled, or depleted. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function isCold(uint256 streamId) external view returns (bool result); + + /// @notice Retrieves a flag indicating whether the stream is depleted. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function isDepleted(uint256 streamId) external view returns (bool result); + + /// @notice Retrieves a flag indicating whether the stream exists. + /// @dev Does not revert if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function isStream(uint256 streamId) external view returns (bool result); + + /// @notice Retrieves a flag indicating whether the stream NFT can be transferred. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function isTransferable(uint256 streamId) external view returns (bool result); + + /// @notice Retrieves a flag indicating whether the stream is warm, i.e. either pending or streaming. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function isWarm(uint256 streamId) external view returns (bool result); + + /// @notice Counter for stream IDs, used in the create functions. + function nextStreamId() external view returns (uint256); + + /// @notice Contract that generates the non-fungible token URI. + function nftDescriptor() external view returns (ILockupNFTDescriptor); + + /// @notice Calculates the amount that the sender would be refunded if the stream were canceled, denoted in units + /// of the asset's decimals. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function refundableAmountOf(uint256 streamId) external view returns (uint128 refundableAmount); + + /// @notice Retrieves the stream's status. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function statusOf(uint256 streamId) external view returns (Lockup.Status status); + + /// @notice Calculates the amount streamed to the recipient, denoted in units of the asset's decimals. + /// @dev Reverts if `streamId` references a null stream. + /// + /// Notes: + /// - Upon cancellation of the stream, the amount streamed is calculated as the difference between the deposited + /// amount and the refunded amount. Ultimately, when the stream becomes depleted, the streamed amount is equivalent + /// to the total amount withdrawn. + /// + /// @param streamId The stream ID for the query. + function streamedAmountOf(uint256 streamId) external view returns (uint128 streamedAmount); + + /// @notice Retrieves a flag indicating whether the stream was canceled. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function wasCanceled(uint256 streamId) external view returns (bool result); + + /// @notice Calculates the amount that the recipient can withdraw from the stream, denoted in units of the asset's + /// decimals. + /// @dev Reverts if `streamId` references a null stream. + /// @param streamId The stream ID for the query. + function withdrawableAmountOf(uint256 streamId) external view returns (uint128 withdrawableAmount); + + /*////////////////////////////////////////////////////////////////////////// + NON-CONSTANT FUNCTIONS + //////////////////////////////////////////////////////////////////////////*/ + + /// @notice Allows a recipient contract to hook to Sablier when a stream is canceled or when assets are withdrawn. + /// Useful for implementing contracts that hold streams on behalf of users, such as vaults or staking contracts. + /// + /// @dev Emits an {AllowToHook} event. + /// + /// Notes: + /// - Does not revert if the contract is already on the allowlist. + /// - This is an irreversible operation. The contract cannot be removed from the allowlist. + /// + /// Requirements: + /// - `msg.sender` must be the contract admin. + /// - `recipient` must have a non-zero code size. + /// - `recipient` must implement {ISablierLockupRecipient}. + /// + /// @param recipient The address of the contract to allow for hooks. + function allowToHook(address recipient) external; + + /// @notice Burns the NFT associated with the stream. + /// + /// @dev Emits a {Transfer} event. + /// + /// Requirements: + /// - Must not be delegate called. + /// - `streamId` must reference a depleted stream. + /// - The NFT must exist. + /// - `msg.sender` must be either the NFT owner or an approved third party. + /// + /// @param streamId The ID of the stream NFT to burn. + function burn(uint256 streamId) external; + + /// @notice Cancels the stream and refunds any remaining assets to the sender. + /// + /// @dev Emits a {Transfer}, {CancelLockupStream}, and {MetadataUpdate} event. + /// + /// Notes: + /// - If there any assets left for the recipient to withdraw, the stream is marked as canceled. Otherwise, the + /// stream is marked as depleted. + /// - This function attempts to invoke a hook on the recipient, if the resolved address is a contract. + /// + /// Requirements: + /// - Must not be delegate called. + /// - The stream must be warm and cancelable. + /// - `msg.sender` must be the stream's sender. + /// + /// @param streamId The ID of the stream to cancel. + function cancel(uint256 streamId) external; + + /// @notice Cancels multiple streams and refunds any remaining assets to the sender. + /// + /// @dev Emits multiple {Transfer}, {CancelLockupStream}, and {MetadataUpdate} events. + /// + /// Notes: + /// - Refer to the notes in {cancel}. + /// + /// Requirements: + /// - All requirements from {cancel} must be met for each stream. + /// + /// @param streamIds The IDs of the streams to cancel. + function cancelMultiple(uint256[] calldata streamIds) external; + + /// @notice Removes the right of the stream's sender to cancel the stream. + /// + /// @dev Emits a {RenounceLockupStream} and {MetadataUpdate} event. + /// + /// Notes: + /// - This is an irreversible operation. + /// + /// Requirements: + /// - Must not be delegate called. + /// - `streamId` must reference a warm stream. + /// - `msg.sender` must be the stream's sender. + /// - The stream must be cancelable. + /// + /// @param streamId The ID of the stream to renounce. + function renounce(uint256 streamId) external; + + /// @notice Sets a new NFT descriptor contract, which produces the URI describing the Sablier stream NFTs. + /// + /// @dev Emits a {SetNFTDescriptor} and {BatchMetadataUpdate} event. + /// + /// Notes: + /// - Does not revert if the NFT descriptor is the same. + /// + /// Requirements: + /// - `msg.sender` must be the contract admin. + /// + /// @param newNFTDescriptor The address of the new NFT descriptor contract. + function setNFTDescriptor(ILockupNFTDescriptor newNFTDescriptor) external; + + /// @notice Withdraws the provided amount of assets from the stream to the `to` address. + /// + /// @dev Emits a {Transfer}, {WithdrawFromLockupStream}, and {MetadataUpdate} event. + /// + /// Notes: + /// - This function attempts to call a hook on the recipient of the stream, unless `msg.sender` is the recipient. + /// + /// Requirements: + /// - Must not be delegate called. + /// - `streamId` must not reference a null or depleted stream. + /// - `to` must not be the zero address. + /// - `amount` must be greater than zero and must not exceed the withdrawable amount. + /// - `to` must be the recipient if `msg.sender` is not the stream's recipient or an approved third party. + /// + /// @param streamId The ID of the stream to withdraw from. + /// @param to The address receiving the withdrawn assets. + /// @param amount The amount to withdraw, denoted in units of the asset's decimals. + function withdraw(uint256 streamId, address to, uint128 amount) external; + + /// @notice Withdraws the maximum withdrawable amount from the stream to the provided address `to`. + /// + /// @dev Emits a {Transfer}, {WithdrawFromLockupStream}, and {MetadataUpdate} event. + /// + /// Notes: + /// - Refer to the notes in {withdraw}. + /// + /// Requirements: + /// - Refer to the requirements in {withdraw}. + /// + /// @param streamId The ID of the stream to withdraw from. + /// @param to The address receiving the withdrawn assets. + /// @return withdrawnAmount The amount withdrawn, denoted in units of the asset's decimals. + function withdrawMax(uint256 streamId, address to) external returns (uint128 withdrawnAmount); + + /// @notice Withdraws the maximum withdrawable amount from the stream to the current recipient, and transfers the + /// NFT to `newRecipient`. + /// + /// @dev Emits a {WithdrawFromLockupStream} and a {Transfer} event. + /// + /// Notes: + /// - If the withdrawable amount is zero, the withdrawal is skipped. + /// - Refer to the notes in {withdraw}. + /// + /// Requirements: + /// - `msg.sender` must be either the NFT owner or an approved third party. + /// - Refer to the requirements in {withdraw}. + /// - Refer to the requirements in {IERC721.transferFrom}. + /// + /// @param streamId The ID of the stream NFT to transfer. + /// @param newRecipient The address of the new owner of the stream NFT. + /// @return withdrawnAmount The amount withdrawn, denoted in units of the asset's decimals. + function withdrawMaxAndTransfer( + uint256 streamId, + address newRecipient + ) + external + returns (uint128 withdrawnAmount); + + /// @notice Withdraws assets from streams to the recipient of each stream. + /// + /// @dev Emits multiple {Transfer}, {WithdrawFromLockupStream}, and {MetadataUpdate} events. + /// + /// Notes: + /// - This function attempts to call a hook on the recipient of each stream, unless `msg.sender` is the recipient. + /// + /// Requirements: + /// - Must not be delegate called. + /// - There must be an equal number of `streamIds` and `amounts`. + /// - Each stream ID in the array must not reference a null or depleted stream. + /// - Each amount in the array must be greater than zero and must not exceed the withdrawable amount. + /// + /// @param streamIds The IDs of the streams to withdraw from. + /// @param amounts The amounts to withdraw, denoted in units of the asset's decimals. + function withdrawMultiple(uint256[] calldata streamIds, uint128[] calldata amounts) external; +} diff --git a/src/core/interfaces/ISablierLockupDynamic.sol b/src/core/interfaces/ISablierLockupDynamic.sol deleted file mode 100644 index 06b8f938f..000000000 --- a/src/core/interfaces/ISablierLockupDynamic.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity >=0.8.22; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -import { Lockup, LockupDynamic } from "../types/DataTypes.sol"; -import { ISablierLockup } from "./ISablierLockup.sol"; - -/// @title ISablierLockupDynamic -/// @notice Creates and manages Lockup streams with a dynamic distribution function. -interface ISablierLockupDynamic is ISablierLockup { - /*////////////////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////////////////*/ - - /// @notice Emitted when a stream is created. - /// @param streamId The ID of the newly created stream. - /// @param funder The address which has funded the stream. - /// @param sender The address distributing the assets, which is able to cancel the stream. - /// @param recipient The address receiving the assets, as well as the NFT owner. - /// @param amounts Struct encapsulating (i) the deposit amount, and (ii) the broker fee amount, both denoted - /// in units of the asset's decimals. - /// @param asset The contract address of the ERC-20 asset to be distributed. - /// @param cancelable Boolean indicating whether the stream is cancelable or not. - /// @param transferable Boolean indicating whether the stream NFT is transferable or not. - /// @param segments The segments the protocol uses to compose the dynamic distribution function. - /// @param timestamps Struct encapsulating (i) the stream's start time and (ii) end time, both as Unix timestamps. - /// @param broker The address of the broker who has helped create the stream, e.g. a front-end website. - event CreateLockupDynamicStream( - uint256 streamId, - address funder, - address indexed sender, - address indexed recipient, - Lockup.CreateAmounts amounts, - IERC20 indexed asset, - bool cancelable, - bool transferable, - LockupDynamic.Segment[] segments, - LockupDynamic.Timestamps timestamps, - address broker - ); - - /*////////////////////////////////////////////////////////////////////////// - CONSTANT FUNCTIONS - //////////////////////////////////////////////////////////////////////////*/ - - /// @notice The maximum number of segments allowed in a stream. - /// @dev This is initialized at construction time and cannot be changed later. - function MAX_SEGMENT_COUNT() external view returns (uint256); - - /// @notice Retrieves the segments used to compose the dynamic distribution function. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - function getSegments(uint256 streamId) external view returns (LockupDynamic.Segment[] memory segments); - - /// @notice Retrieves the full stream details. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - /// @return stream See the documentation in {DataTypes}. - function getStream(uint256 streamId) external view returns (LockupDynamic.StreamLD memory stream); - - /// @notice Retrieves the stream's start and end timestamps. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - /// @return timestamps See the documentation in {DataTypes}. - function getTimestamps(uint256 streamId) external view returns (LockupDynamic.Timestamps memory timestamps); - - /*////////////////////////////////////////////////////////////////////////// - NON-CONSTANT FUNCTIONS - //////////////////////////////////////////////////////////////////////////*/ - - /// @notice Creates a stream by setting the start time to `block.timestamp`, and the end time to the sum of - /// `block.timestamp` and all specified time durations. The segment timestamps are derived from these - /// durations. The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. - /// - /// @dev Emits a {Transfer} and {CreateLockupDynamicStream} event. - /// - /// Requirements: - /// - All requirements in {createWithTimestamps} must be met for the calculated parameters. - /// - /// @param params Struct encapsulating the function parameters, which are documented in {DataTypes}. - /// @return streamId The ID of the newly created stream. - function createWithDurations(LockupDynamic.CreateWithDurations calldata params) - external - returns (uint256 streamId); - - /// @notice Creates a stream with the provided segment timestamps, implying the end time from the last timestamp. - /// The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. - /// - /// @dev Emits a {Transfer} and {CreateLockupDynamicStream} event. - /// - /// Notes: - /// - As long as the segment timestamps are arranged in ascending order, it is not an error for some - /// of them to be in the past. - /// - /// Requirements: - /// - Must not be delegate called. - /// - `params.totalAmount` must be greater than zero. - /// - If set, `params.broker.fee` must not be greater than `MAX_BROKER_FEE`. - /// - `params.startTime` must be greater than zero and less than the first segment's timestamp. - /// - `params.segments` must have at least one segment, but not more than `MAX_SEGMENT_COUNT`. - /// - The segment timestamps must be arranged in ascending order. - /// - The sum of the segment amounts must equal the deposit amount. - /// - `params.recipient` must not be the zero address. - /// - `params.sender` must not be the zero address. - /// - `msg.sender` must have allowed this contract to spend at least `params.totalAmount` assets. - /// - /// @param params Struct encapsulating the function parameters, which are documented in {DataTypes}. - /// @return streamId The ID of the newly created stream. - function createWithTimestamps(LockupDynamic.CreateWithTimestamps calldata params) - external - returns (uint256 streamId); -} diff --git a/src/core/interfaces/ISablierLockupLinear.sol b/src/core/interfaces/ISablierLockupLinear.sol deleted file mode 100644 index 538558499..000000000 --- a/src/core/interfaces/ISablierLockupLinear.sol +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity >=0.8.22; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -import { Lockup, LockupLinear } from "../types/DataTypes.sol"; -import { ISablierLockup } from "./ISablierLockup.sol"; - -/// @title ISablierLockupLinear -/// @notice Creates and manages Lockup streams with a linear distribution function. -interface ISablierLockupLinear is ISablierLockup { - /*////////////////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////////////////*/ - - /// @notice Emitted when a stream is created. - /// @param streamId The ID of the newly created stream. - /// @param funder The address which funded the stream. - /// @param sender The address distributing the assets, which is able to to cancel the stream. - /// @param recipient The address receiving the assets, as well as the NFT owner. - /// @param amounts Struct encapsulating (i) the deposit amount, and (ii) the broker fee amount, both denoted - /// in units of the asset's decimals. - /// @param asset The contract address of the ERC-20 asset to be distributed. - /// @param cancelable Boolean indicating whether the stream is cancelable or not. - /// @param transferable Boolean indicating whether the stream NFT is transferable or not. - /// @param timestamps Struct encapsulating (i) the stream's start time, (ii) cliff time, and (iii) end time, all as - /// Unix timestamps. - /// @param broker The address of the broker who has helped create the stream, e.g. a front-end website. - event CreateLockupLinearStream( - uint256 streamId, - address funder, - address indexed sender, - address indexed recipient, - Lockup.CreateAmounts amounts, - IERC20 indexed asset, - bool cancelable, - bool transferable, - LockupLinear.Timestamps timestamps, - address broker - ); - - /*////////////////////////////////////////////////////////////////////////// - CONSTANT FUNCTIONS - //////////////////////////////////////////////////////////////////////////*/ - - /// @notice Retrieves the stream's cliff time, which is a Unix timestamp. A value of zero means there - /// is no cliff. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - function getCliffTime(uint256 streamId) external view returns (uint40 cliffTime); - - /// @notice Retrieves the full stream details. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - /// @return stream See the documentation in {DataTypes}. - function getStream(uint256 streamId) external view returns (LockupLinear.StreamLL memory stream); - - /// @notice Retrieves the stream's start, cliff and end timestamps. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - /// @return timestamps See the documentation in {DataTypes}. - function getTimestamps(uint256 streamId) external view returns (LockupLinear.Timestamps memory timestamps); - - /*////////////////////////////////////////////////////////////////////////// - NON-CONSTANT FUNCTIONS - //////////////////////////////////////////////////////////////////////////*/ - - /// @notice Creates a stream by setting the start time to `block.timestamp`, and the end time to - /// the sum of `block.timestamp` and `params.durations.total`. The stream is funded by `msg.sender` and is wrapped - /// in an ERC-721 NFT. - /// - /// @dev Emits a {Transfer} and {CreateLockupLinearStream} event. - /// - /// Requirements: - /// - All requirements in {createWithTimestamps} must be met for the calculated parameters. - /// - /// @param params Struct encapsulating the function parameters, which are documented in {DataTypes}. - /// @return streamId The ID of the newly created stream. - function createWithDurations(LockupLinear.CreateWithDurations calldata params) - external - returns (uint256 streamId); - - /// @notice Creates a stream with the provided start time and end time. The stream is funded by `msg.sender` and is - /// wrapped in an ERC-721 NFT. - /// - /// @dev Emits a {Transfer} and {CreateLockupLinearStream} event. - /// - /// Notes: - /// - A cliff time of zero means there is no cliff. - /// - As long as the times are ordered, it is not an error for the start or the cliff time to be in the past. - /// - /// Requirements: - /// - Must not be delegate called. - /// - `params.totalAmount` must be greater than zero. - /// - If set, `params.broker.fee` must not be greater than `MAX_BROKER_FEE`. - /// - `params.timestamps.start` must be greater than zero and less than `params.timestamps.end`. - /// - If set, `params.timestamps.cliff` must be greater than `params.timestamps.start` and less than - /// `params.timestamps.end`. - /// - `params.recipient` must not be the zero address. - /// - `params.sender` must not be the zero address. - /// - `msg.sender` must have allowed this contract to spend at least `params.totalAmount` assets. - /// - /// @param params Struct encapsulating the function parameters, which are documented in {DataTypes}. - /// @return streamId The ID of the newly created stream. - function createWithTimestamps(LockupLinear.CreateWithTimestamps calldata params) - external - returns (uint256 streamId); -} diff --git a/src/core/interfaces/ISablierLockupTranched.sol b/src/core/interfaces/ISablierLockupTranched.sol deleted file mode 100644 index 833bbfd60..000000000 --- a/src/core/interfaces/ISablierLockupTranched.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -pragma solidity >=0.8.22; - -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -import { Lockup, LockupTranched } from "../types/DataTypes.sol"; -import { ISablierLockup } from "./ISablierLockup.sol"; - -/// @title ISablierLockupTranched -/// @notice Creates and manages Lockup streams with a tranched distribution function. -interface ISablierLockupTranched is ISablierLockup { - /*////////////////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////////////////*/ - - /// @notice Emitted when a stream is created. - /// @param streamId The ID of the newly created stream. - /// @param funder The address which has funded the stream. - /// @param sender The address distributing the assets, which is able to cancel the stream. - /// @param recipient The address receiving the assets, as well as the NFT owner. - /// @param amounts Struct encapsulating (i) the deposit amount, and (ii) the broker fee amount, both denoted - /// in units of the asset's decimals. - /// @param asset The contract address of the ERC-20 asset to be distributed. - /// @param cancelable Boolean indicating whether the stream is cancelable or not. - /// @param transferable Boolean indicating whether the stream NFT is transferable or not. - /// @param tranches The tranches the protocol uses to compose the tranched distribution function. - /// @param timestamps Struct encapsulating (i) the stream's start time and (ii) end time, both as Unix timestamps. - /// @param broker The address of the broker who has helped create the stream, e.g. a front-end website. - event CreateLockupTranchedStream( - uint256 streamId, - address funder, - address indexed sender, - address indexed recipient, - Lockup.CreateAmounts amounts, - IERC20 indexed asset, - bool cancelable, - bool transferable, - LockupTranched.Tranche[] tranches, - LockupTranched.Timestamps timestamps, - address broker - ); - - /*////////////////////////////////////////////////////////////////////////// - CONSTANT FUNCTIONS - //////////////////////////////////////////////////////////////////////////*/ - - /// @notice The maximum number of tranches allowed in a stream. - /// @dev This is initialized at construction time and cannot be changed later. - function MAX_TRANCHE_COUNT() external view returns (uint256); - - /// @notice Retrieves the full stream details. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - /// @return stream See the documentation in {DataTypes}. - function getStream(uint256 streamId) external view returns (LockupTranched.StreamLT memory stream); - - /// @notice Retrieves the stream's start and end timestamps. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - /// @return timestamps See the documentation in {DataTypes}. - function getTimestamps(uint256 streamId) external view returns (LockupTranched.Timestamps memory timestamps); - - /// @notice Retrieves the tranches used to compose the tranched distribution function. - /// @dev Reverts if `streamId` references a null stream. - /// @param streamId The stream ID for the query. - function getTranches(uint256 streamId) external view returns (LockupTranched.Tranche[] memory tranches); - - /*////////////////////////////////////////////////////////////////////////// - NON-CONSTANT FUNCTIONS - //////////////////////////////////////////////////////////////////////////*/ - - /// @notice Creates a stream by setting the start time to `block.timestamp`, and the end time to the sum of - /// `block.timestamp` and all specified time durations. The tranche timestamps are derived from these - /// durations. The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. - /// - /// @dev Emits a {Transfer} and {CreateLockupTrancheStream} event. - /// - /// Requirements: - /// - All requirements in {createWithTimestamps} must be met for the calculated parameters. - /// - /// @param params Struct encapsulating the function parameters, which are documented in {DataTypes}. - /// @return streamId The ID of the newly created stream. - function createWithDurations(LockupTranched.CreateWithDurations calldata params) - external - returns (uint256 streamId); - - /// @notice Creates a stream with the provided tranche timestamps, implying the end time from the last timestamp. - /// The stream is funded by `msg.sender` and is wrapped in an ERC-721 NFT. - /// - /// @dev Emits a {Transfer} and {CreateLockupTrancheStream} event. - /// - /// Notes: - /// - As long as the tranche timestamps are arranged in ascending order, it is not an error for some - /// of them to be in the past. - /// - /// Requirements: - /// - Must not be delegate called. - /// - `params.totalAmount` must be greater than zero. - /// - If set, `params.broker.fee` must not be greater than `MAX_BROKER_FEE`. - /// - `params.startTime` must be greater than zero and less than the first tranche's timestamp. - /// - `params.tranches` must have at least one tranche, but not more than `MAX_TRANCHE_COUNT`. - /// - The tranche timestamps must be arranged in ascending order. - /// - The sum of the tranche amounts must equal the deposit amount. - /// - `params.recipient` must not be the zero address. - /// - `params.sender` must not be the zero address. - /// - `msg.sender` must have allowed this contract to spend at least `params.totalAmount` assets. - /// - /// @param params Struct encapsulating the function parameters, which are documented in {DataTypes}. - /// @return streamId The ID of the newly created stream. - function createWithTimestamps(LockupTranched.CreateWithTimestamps calldata params) - external - returns (uint256 streamId); -} diff --git a/src/core/libraries/Errors.sol b/src/core/libraries/Errors.sol index e8974095a..2dbe9d376 100644 --- a/src/core/libraries/Errors.sol +++ b/src/core/libraries/Errors.sol @@ -4,6 +4,8 @@ pragma solidity >=0.8.22; import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; import { UD60x18 } from "@prb/math/src/UD60x18.sol"; +import { Lockup } from "../types/DataTypes.sol"; + /// @title Errors /// @notice Library containing all custom errors the protocol may revert with. library Errors { @@ -28,133 +30,128 @@ library Errors { error LockupNFTDescriptor_UnknownNFT(IERC721Metadata nft, string symbol); /*////////////////////////////////////////////////////////////////////////// - SABLIER-LOCKUP + HELPERS //////////////////////////////////////////////////////////////////////////*/ - /// @notice Thrown when trying to allow to hook a contract that doesn't implement the interface correctly. - error SablierLockup_AllowToHookUnsupportedInterface(address recipient); + /// @notice Thrown when the broker fee exceeds the maximum allowed fee. + error SablierHelpers_BrokerFeeTooHigh(UD60x18 brokerFee, UD60x18 maxBrokerFee); - /// @notice Thrown when trying to allow to hook an address with no code. - error SablierLockup_AllowToHookZeroCodeSize(address recipient); + /// @notice Thrown when trying to create a linear stream with a cliff time not strictly less than the end time. + error SablierHelpers_CliffTimeNotLessThanEndTime(uint40 cliffTime, uint40 endTime); - /// @notice Thrown when the broker fee exceeds the maximum allowed fee. - error SablierLockup_BrokerFeeTooHigh(UD60x18 brokerFee, UD60x18 maxBrokerFee); + /// @notice Thrown when trying to create a dynamic stream with a deposit amount not equal to the sum of the segment + /// amounts. + error SablierHelpers_DepositAmountNotEqualToSegmentAmountsSum(uint128 depositAmount, uint128 segmentAmountsSum); + + /// @notice Thrown when trying to create a tranched stream with a deposit amount not equal to the sum of the tranche + /// amounts. + error SablierHelpers_DepositAmountNotEqualToTrancheAmountsSum(uint128 depositAmount, uint128 trancheAmountsSum); /// @notice Thrown when trying to create a stream with a zero deposit amount. - error SablierLockup_DepositAmountZero(); + error SablierHelpers_DepositAmountZero(); - /// @notice Thrown when the hook does not return the correct selector. - error SablierLockup_InvalidHookSelector(address recipient); + /// @notice Thrown when trying to create a dynamic stream with end time not equal to the last segment's timestamp. + error SablierHelpers_EndTimeNotEqualToLastSegmentTimestamp(uint40 endTime, uint40 lastSegmentTimestamp); - /// @notice Thrown when trying to transfer Stream NFT when transferability is disabled. - error SablierLockup_NotTransferable(uint256 tokenId); + /// @notice Thrown when trying to create a tranched stream with end time not equal to the last tranche's timestamp. + error SablierHelpers_EndTimeNotEqualToLastTrancheTimestamp(uint40 endTime, uint40 lastTrancheTimestamp); - /// @notice Thrown when the ID references a null stream. - error SablierLockup_Null(uint256 streamId); + /// @notice Thrown when trying to create a dynamic stream with more segments than the maximum allowed. + error SablierHelpers_SegmentCountTooHigh(uint256 count); - /// @notice Thrown when trying to withdraw an amount greater than the withdrawable amount. - error SablierLockup_Overdraw(uint256 streamId, uint128 amount, uint128 withdrawableAmount); + /// @notice Thrown when trying to create a dynamic stream with no segments. + error SablierHelpers_SegmentCountZero(); + + /// @notice Thrown when trying to create a dynamic stream with unordered segment timestamps. + error SablierHelpers_SegmentTimestampsNotOrdered(uint256 index, uint40 previousTimestamp, uint40 currentTimestamp); /// @notice Thrown when trying to create a stream with the sender as the zero address. - error SablierLockup_SenderZeroAddress(); + error SablierHelpers_SenderZeroAddress(); - /// @notice Thrown when trying to create a stream with a zero start time. - error SablierLockup_StartTimeZero(); + /// @notice Thrown when trying to create a linear stream with a start time not strictly less than the cliff time, + /// when the cliff time does not have a zero value. + error SablierHelpers_StartTimeNotLessThanCliffTime(uint40 startTime, uint40 cliffTime); - /// @notice Thrown when trying to cancel or renounce a canceled stream. - error SablierLockup_StreamCanceled(uint256 streamId); + /// @notice Thrown when trying to create a linear stream with a start time not strictly less than the end time. + error SablierHelpers_StartTimeNotLessThanEndTime(uint40 startTime, uint40 endTime); - /// @notice Thrown when trying to cancel, renounce, or withdraw from a depleted stream. - error SablierLockup_StreamDepleted(uint256 streamId); + /// @notice Thrown when trying to create a dynamic stream with a start time not strictly less than the first + /// segment timestamp. + error SablierHelpers_StartTimeNotLessThanFirstSegmentTimestamp(uint40 startTime, uint40 firstSegmentTimestamp); - /// @notice Thrown when trying to cancel or renounce a stream that is not cancelable. - error SablierLockup_StreamNotCancelable(uint256 streamId); + /// @notice Thrown when trying to create a tranched stream with a start time not strictly less than the first + /// tranche timestamp. + error SablierHelpers_StartTimeNotLessThanFirstTrancheTimestamp(uint40 startTime, uint40 firstTrancheTimestamp); - /// @notice Thrown when trying to burn a stream that is not depleted. - error SablierLockup_StreamNotDepleted(uint256 streamId); + /// @notice Thrown when trying to create a stream with a zero start time. + error SablierHelpers_StartTimeZero(); - /// @notice Thrown when trying to cancel or renounce a settled stream. - error SablierLockup_StreamSettled(uint256 streamId); + /// @notice Thrown when trying to create a tranched stream with more tranches than the maximum allowed. + error SablierHelpers_TrancheCountTooHigh(uint256 count); - /// @notice Thrown when `msg.sender` lacks authorization to perform an action. - error SablierLockup_Unauthorized(uint256 streamId, address caller); + /// @notice Thrown when trying to create a tranched stream with no tranches. + error SablierHelpers_TrancheCountZero(); - /// @notice Thrown when trying to withdraw to an address other than the recipient's. - error SablierLockup_WithdrawalAddressNotRecipient(uint256 streamId, address caller, address to); + /// @notice Thrown when trying to create a tranched stream with unordered tranche timestamps. + error SablierHelpers_TrancheTimestampsNotOrdered(uint256 index, uint40 previousTimestamp, uint40 currentTimestamp); - /// @notice Thrown when trying to withdraw zero assets from a stream. - error SablierLockup_WithdrawAmountZero(uint256 streamId); + /*////////////////////////////////////////////////////////////////////////// + SABLIER-LOCKUP-BASE + //////////////////////////////////////////////////////////////////////////*/ - /// @notice Thrown when trying to withdraw from multiple streams and the number of stream IDs does - /// not match the number of withdraw amounts. - error SablierLockup_WithdrawArrayCountsNotEqual(uint256 streamIdsCount, uint256 amountsCount); + /// @notice Thrown when trying to allow to hook a contract that doesn't implement the interface correctly. + error SablierLockupBase_AllowToHookUnsupportedInterface(address recipient); - /// @notice Thrown when trying to withdraw to the zero address. - error SablierLockup_WithdrawToZeroAddress(uint256 streamId); + /// @notice Thrown when trying to allow to hook an address with no code. + error SablierLockupBase_AllowToHookZeroCodeSize(address recipient); - /*////////////////////////////////////////////////////////////////////////// - SABLIER-LOCKUP-DYNAMIC - //////////////////////////////////////////////////////////////////////////*/ + /// @notice Thrown when the hook does not return the correct selector. + error SablierLockupBase_InvalidHookSelector(address recipient); - /// @notice Thrown when trying to create a stream with a deposit amount not equal to the sum of the - /// segment amounts. - error SablierLockupDynamic_DepositAmountNotEqualToSegmentAmountsSum( - uint128 depositAmount, uint128 segmentAmountsSum - ); + /// @notice Thrown when trying to transfer Stream NFT when transferability is disabled. + error SablierLockupBase_NotTransferable(uint256 tokenId); - /// @notice Thrown when trying to create a stream with more segments than the maximum allowed. - error SablierLockupDynamic_SegmentCountTooHigh(uint256 count); + /// @notice Thrown when the ID references a null stream. + error SablierLockupBase_Null(uint256 streamId); - /// @notice Thrown when trying to create a stream with no segments. - error SablierLockupDynamic_SegmentCountZero(); + /// @notice Thrown when trying to withdraw an amount greater than the withdrawable amount. + error SablierLockupBase_Overdraw(uint256 streamId, uint128 amount, uint128 withdrawableAmount); - /// @notice Thrown when trying to create a stream with unordered segment timestamps. - error SablierLockupDynamic_SegmentTimestampsNotOrdered( - uint256 index, uint40 previousTimestamp, uint40 currentTimestamp - ); + /// @notice Thrown when trying to cancel or renounce a canceled stream. + error SablierLockupBase_StreamCanceled(uint256 streamId); - /// @notice Thrown when trying to create a stream with a start time not strictly less than the first - /// segment timestamp. - error SablierLockupDynamic_StartTimeNotLessThanFirstSegmentTimestamp(uint40 startTime, uint40 firstSegmentTimestamp); + /// @notice Thrown when trying to cancel, renounce, or withdraw from a depleted stream. + error SablierLockupBase_StreamDepleted(uint256 streamId); - /*////////////////////////////////////////////////////////////////////////// - SABLIER-LOCKUP-LINEAR - //////////////////////////////////////////////////////////////////////////*/ + /// @notice Thrown when trying to cancel or renounce a stream that is not cancelable. + error SablierLockupBase_StreamNotCancelable(uint256 streamId); - /// @notice Thrown when trying to create a stream with a cliff time not strictly less than the end time. - error SablierLockupLinear_CliffTimeNotLessThanEndTime(uint40 cliffTime, uint40 endTime); + /// @notice Thrown when trying to burn a stream that is not depleted. + error SablierLockupBase_StreamNotDepleted(uint256 streamId); - /// @notice Thrown when trying to create a stream with a start time not strictly less than the cliff time, when the - /// cliff time does not have a zero value. - error SablierLockupLinear_StartTimeNotLessThanCliffTime(uint40 startTime, uint40 cliffTime); + /// @notice Thrown when trying to cancel or renounce a settled stream. + error SablierLockupBase_StreamSettled(uint256 streamId); - /// @notice Thrown when trying to create a stream with a start time not strictly less than the end time. - error SablierLockupLinear_StartTimeNotLessThanEndTime(uint40 startTime, uint40 endTime); + /// @notice Thrown when `msg.sender` lacks authorization to perform an action. + error SablierLockupBase_Unauthorized(uint256 streamId, address caller); - /*////////////////////////////////////////////////////////////////////////// - SABLIER-LOCKUP-TRANCHE - //////////////////////////////////////////////////////////////////////////*/ + /// @notice Thrown when trying to withdraw to an address other than the recipient's. + error SablierLockupBase_WithdrawalAddressNotRecipient(uint256 streamId, address caller, address to); - /// @notice Thrown when trying to create a stream with a deposit amount not equal to the sum of the - /// tranche amounts. - error SablierLockupTranched_DepositAmountNotEqualToTrancheAmountsSum( - uint128 depositAmount, uint128 trancheAmountsSum - ); + /// @notice Thrown when trying to withdraw zero assets from a stream. + error SablierLockupBase_WithdrawAmountZero(uint256 streamId); - /// @notice Thrown when trying to create a stream with a start time not strictly less than the first - /// tranche timestamp. - error SablierLockupTranched_StartTimeNotLessThanFirstTrancheTimestamp( - uint40 startTime, uint40 firstTrancheTimestamp - ); + /// @notice Thrown when trying to withdraw from multiple streams and the number of stream IDs does + /// not match the number of withdraw amounts. + error SablierLockupBase_WithdrawArrayCountsNotEqual(uint256 streamIdsCount, uint256 amountsCount); - /// @notice Thrown when trying to create a stream with more tranches than the maximum allowed. - error SablierLockupTranched_TrancheCountTooHigh(uint256 count); + /// @notice Thrown when trying to withdraw to the zero address. + error SablierLockupBase_WithdrawToZeroAddress(uint256 streamId); - /// @notice Thrown when trying to create a stream with no tranches. - error SablierLockupTranched_TrancheCountZero(); + /*////////////////////////////////////////////////////////////////////////// + SABLIER-LOCKUP + //////////////////////////////////////////////////////////////////////////*/ - /// @notice Thrown when trying to create a stream with unordered tranche timestamps. - error SablierLockupTranched_TrancheTimestampsNotOrdered( - uint256 index, uint40 previousTimestamp, uint40 currentTimestamp - ); + /// @notice Thrown when a function is called on a stream that does not use the expected Lockup model. + error SablierLockup_NotExpectedModel(Lockup.Model actualLockupModel, Lockup.Model expectedLockupModel); } diff --git a/src/core/libraries/Helpers.sol b/src/core/libraries/Helpers.sol index 93fe7cb4d..e9af67208 100644 --- a/src/core/libraries/Helpers.sol +++ b/src/core/libraries/Helpers.sol @@ -2,258 +2,268 @@ pragma solidity >=0.8.22; import { UD60x18, ud } from "@prb/math/src/UD60x18.sol"; - -import { Lockup, LockupDynamic, LockupLinear, LockupTranched } from "../types/DataTypes.sol"; +import { Lockup, LockupDynamic, LockupTranched } from "./../types/DataTypes.sol"; import { Errors } from "./Errors.sol"; /// @title Helpers -/// @notice Library with helper functions needed across the Lockup contracts. +/// @notice Library with functions needed to validate input parameters across lockup streams. library Helpers { /*////////////////////////////////////////////////////////////////////////// - INTERNAL CONSTANT FUNCTIONS + CONSTANT FUNCTIONS //////////////////////////////////////////////////////////////////////////*/ /// @dev Calculate the timestamps and return the segments. - function calculateSegmentTimestamps(LockupDynamic.SegmentWithDuration[] memory segments) - internal + function calculateSegmentTimestamps(LockupDynamic.SegmentWithDuration[] memory segmentsWithDuration) + public view returns (LockupDynamic.Segment[] memory segmentsWithTimestamps) { - uint256 segmentCount = segments.length; + uint256 segmentCount = segmentsWithDuration.length; segmentsWithTimestamps = new LockupDynamic.Segment[](segmentCount); // Make the block timestamp the stream's start time. uint40 startTime = uint40(block.timestamp); - // It is safe to use unchecked arithmetic because {SablierLockupDynamic-_create} will nonetheless check the - // correctness of the calculated segment timestamps. + // It is safe to use unchecked arithmetic because {SablierLockup._createLD} will nonetheless + // check the correctness of the calculated segment timestamps. unchecked { // The first segment is precomputed because it is needed in the for loop below. segmentsWithTimestamps[0] = LockupDynamic.Segment({ - amount: segments[0].amount, - exponent: segments[0].exponent, - timestamp: startTime + segments[0].duration + amount: segmentsWithDuration[0].amount, + exponent: segmentsWithDuration[0].exponent, + timestamp: startTime + segmentsWithDuration[0].duration }); // Copy the segment amounts and exponents, and calculate the segment timestamps. for (uint256 i = 1; i < segmentCount; ++i) { segmentsWithTimestamps[i] = LockupDynamic.Segment({ - amount: segments[i].amount, - exponent: segments[i].exponent, - timestamp: segmentsWithTimestamps[i - 1].timestamp + segments[i].duration + amount: segmentsWithDuration[i].amount, + exponent: segmentsWithDuration[i].exponent, + timestamp: segmentsWithTimestamps[i - 1].timestamp + segmentsWithDuration[i].duration }); } } } /// @dev Calculate the timestamps and return the tranches. - function calculateTrancheTimestamps(LockupTranched.TrancheWithDuration[] memory tranches) - internal + function calculateTrancheTimestamps(LockupTranched.TrancheWithDuration[] memory tranchesWithDuration) + public view returns (LockupTranched.Tranche[] memory tranchesWithTimestamps) { - uint256 trancheCount = tranches.length; + uint256 trancheCount = tranchesWithDuration.length; tranchesWithTimestamps = new LockupTranched.Tranche[](trancheCount); // Make the block timestamp the stream's start time. uint40 startTime = uint40(block.timestamp); - // It is safe to use unchecked arithmetic because {SablierLockupTranched-_create} will nonetheless check the + // It is safe to use unchecked arithmetic because {SablierLockup-_createLT} will nonetheless check the // correctness of the calculated tranche timestamps. unchecked { // The first tranche is precomputed because it is needed in the for loop below. - tranchesWithTimestamps[0] = - LockupTranched.Tranche({ amount: tranches[0].amount, timestamp: startTime + tranches[0].duration }); + tranchesWithTimestamps[0] = LockupTranched.Tranche({ + amount: tranchesWithDuration[0].amount, + timestamp: startTime + tranchesWithDuration[0].duration + }); // Copy the tranche amounts and calculate the tranche timestamps. for (uint256 i = 1; i < trancheCount; ++i) { tranchesWithTimestamps[i] = LockupTranched.Tranche({ - amount: tranches[i].amount, - timestamp: tranchesWithTimestamps[i - 1].timestamp + tranches[i].duration + amount: tranchesWithDuration[i].amount, + timestamp: tranchesWithTimestamps[i - 1].timestamp + tranchesWithDuration[i].duration }); } } } - /// @dev Checks the broker fee is not greater than `maxBrokerFee`, and then calculates the broker fee amount and the - /// deposit amount from the total amount. - function checkAndCalculateBrokerFee( + /// @dev Checks the parameters of the {SablierLockup-_createLD} function. + function checkCreateLockupDynamic( + address sender, + Lockup.Timestamps memory timestamps, uint128 totalAmount, + LockupDynamic.Segment[] memory segments, + uint256 maxCount, UD60x18 brokerFee, UD60x18 maxBrokerFee ) - internal + public pure - returns (Lockup.CreateAmounts memory amounts) + returns (Lockup.CreateAmounts memory createAmounts) { - // When the total amount is zero, the broker fee is also zero. - if (totalAmount == 0) { - return Lockup.CreateAmounts(0, 0); - } - - // Check: the broker fee is not greater than `maxBrokerFee`. - if (brokerFee.gt(maxBrokerFee)) { - revert Errors.SablierLockup_BrokerFeeTooHigh(brokerFee, maxBrokerFee); - } + // Check: verify the broker fee and calculate the amounts. + createAmounts = _checkAndCalculateBrokerFee(totalAmount, brokerFee, maxBrokerFee); - // Calculate the broker fee amount. - // The cast to uint128 is safe because the maximum fee is hard coded. - amounts.brokerFee = uint128(ud(totalAmount).mul(brokerFee).intoUint256()); + // Check: validate the user-provided common parameters. + _checkCreateStream(sender, createAmounts.deposit, timestamps.start); - // Assert that the total amount is strictly greater than the broker fee amount. - assert(totalAmount > amounts.brokerFee); - - // Calculate the deposit amount (the amount to stream, net of the broker fee). - amounts.deposit = totalAmount - amounts.brokerFee; + // Check: validate the user-provided segments. + _checkSegments(segments, createAmounts.deposit, timestamps, maxCount); } - /// @dev Checks the parameters of the {SablierLockupDynamic-_create} function. - function checkCreateLockupDynamic( + /// @dev Checks the parameters of the {SablierLockup-_createLL} function. + function checkCreateLockupLinear( address sender, - uint128 depositAmount, - LockupDynamic.Segment[] memory segments, - uint256 maxSegmentCount, - uint40 startTime + Lockup.Timestamps memory timestamps, + uint40 cliffTime, + uint128 totalAmount, + UD60x18 brokerFee, + UD60x18 maxBrokerFee ) - internal + public pure + returns (Lockup.CreateAmounts memory createAmounts) { - // Check: the sender is not the zero address. - if (sender == address(0)) { - revert Errors.SablierLockup_SenderZeroAddress(); - } + // Check: verify the broker fee and calculate the amounts. + createAmounts = _checkAndCalculateBrokerFee(totalAmount, brokerFee, maxBrokerFee); - // Check: the deposit amount is not zero. - if (depositAmount == 0) { - revert Errors.SablierLockup_DepositAmountZero(); - } + // Check: validate the user-provided common parameters. + _checkCreateStream(sender, createAmounts.deposit, timestamps.start); - // Check: the start time is not zero. - if (startTime == 0) { - revert Errors.SablierLockup_StartTimeZero(); - } + // Check: validate the user-provided cliff and end times. + _checkCliffAndEndTime(timestamps, cliffTime); + } - // Check: the segment count is not zero. - uint256 segmentCount = segments.length; - if (segmentCount == 0) { - revert Errors.SablierLockupDynamic_SegmentCountZero(); - } + /// @dev Checks the parameters of the {SablierLockup-_createLT} function. + function checkCreateLockupTranched( + address sender, + Lockup.Timestamps memory timestamps, + uint128 totalAmount, + LockupTranched.Tranche[] memory tranches, + uint256 maxCount, + UD60x18 brokerFee, + UD60x18 maxBrokerFee + ) + public + pure + returns (Lockup.CreateAmounts memory createAmounts) + { + // Check: verify the broker fee and calculate the amounts. + createAmounts = _checkAndCalculateBrokerFee(totalAmount, brokerFee, maxBrokerFee); - // Check: the segment count is not greater than the maximum allowed. - if (segmentCount > maxSegmentCount) { - revert Errors.SablierLockupDynamic_SegmentCountTooHigh(segmentCount); - } + // Check: validate the user-provided common parameters. + _checkCreateStream(sender, createAmounts.deposit, timestamps.start); - // Check: requirements of segments. - _checkSegments(segments, depositAmount, startTime); + // Check: validate the user-provided segments. + _checkTranches(tranches, createAmounts.deposit, timestamps, maxCount); } - /// @dev Checks the parameters of the {SablierLockupLinear-_create} function. - function checkCreateLockupLinear( - address sender, - uint128 depositAmount, - LockupLinear.Timestamps memory timestamps + /*////////////////////////////////////////////////////////////////////////// + PRIVATE CONSTANT FUNCTIONS + //////////////////////////////////////////////////////////////////////////*/ + + /// @dev Checks the broker fee is not greater than `maxBrokerFee`, and then calculates the broker fee amount and + /// the deposit amount from the total amount. + function _checkAndCalculateBrokerFee( + uint128 totalAmount, + UD60x18 brokerFee, + UD60x18 maxBrokerFee ) - internal + private pure + returns (Lockup.CreateAmounts memory amounts) { - // Check: the sender is not the zero address. - if (sender == address(0)) { - revert Errors.SablierLockup_SenderZeroAddress(); + // When the total amount is zero, the broker fee is also zero. + if (totalAmount == 0) { + return Lockup.CreateAmounts(0, 0); } - // Check: the deposit amount is not zero. - if (depositAmount == 0) { - revert Errors.SablierLockup_DepositAmountZero(); + // If the broker fee is zero, the deposit amount is the total amount. + if (brokerFee.isZero()) { + return Lockup.CreateAmounts(totalAmount, 0); } - // Check: the start time is not zero. - if (timestamps.start == 0) { - revert Errors.SablierLockup_StartTimeZero(); + // Check: the broker fee is not greater than `maxBrokerFee`. + if (brokerFee.gt(maxBrokerFee)) { + revert Errors.SablierHelpers_BrokerFeeTooHigh(brokerFee, maxBrokerFee); } + // Calculate the broker fee amount. + amounts.brokerFee = ud(totalAmount).mul(brokerFee).intoUint128(); + + // Assert that the total amount is strictly greater than the broker fee amount. + assert(totalAmount > amounts.brokerFee); + + // Calculate the deposit amount (the amount to stream, net of the broker fee). + amounts.deposit = totalAmount - amounts.brokerFee; + } + + /// @dev Checks the user-provided cliff and end times of a lockup linear stream. + function _checkCliffAndEndTime(Lockup.Timestamps memory timestamps, uint40 cliffTime) private pure { // Since a cliff time of zero means there is no cliff, the following checks are performed only if it's not zero. - if (timestamps.cliff > 0) { + if (cliffTime > 0) { // Check: the start time is strictly less than the cliff time. - if (timestamps.start >= timestamps.cliff) { - revert Errors.SablierLockupLinear_StartTimeNotLessThanCliffTime(timestamps.start, timestamps.cliff); + if (timestamps.start >= cliffTime) { + revert Errors.SablierHelpers_StartTimeNotLessThanCliffTime(timestamps.start, cliffTime); } // Check: the cliff time is strictly less than the end time. - if (timestamps.cliff >= timestamps.end) { - revert Errors.SablierLockupLinear_CliffTimeNotLessThanEndTime(timestamps.cliff, timestamps.end); + if (cliffTime >= timestamps.end) { + revert Errors.SablierHelpers_CliffTimeNotLessThanEndTime(cliffTime, timestamps.end); } } // Check: the start time is strictly less than the end time. if (timestamps.start >= timestamps.end) { - revert Errors.SablierLockupLinear_StartTimeNotLessThanEndTime(timestamps.start, timestamps.end); + revert Errors.SablierHelpers_StartTimeNotLessThanEndTime(timestamps.start, timestamps.end); } } - /// @dev Checks the parameters of the {SablierLockupTranched-_create} function. - function checkCreateLockupTranched( - address sender, - uint128 depositAmount, - LockupTranched.Tranche[] memory tranches, - uint256 maxTrancheCount, - uint40 startTime - ) - internal - pure - { + /// @dev Checks the user-provided common parameters across lockup streams. + function _checkCreateStream(address sender, uint128 depositAmount, uint40 startTime) private pure { // Check: the sender is not the zero address. if (sender == address(0)) { - revert Errors.SablierLockup_SenderZeroAddress(); + revert Errors.SablierHelpers_SenderZeroAddress(); } // Check: the deposit amount is not zero. if (depositAmount == 0) { - revert Errors.SablierLockup_DepositAmountZero(); + revert Errors.SablierHelpers_DepositAmountZero(); } // Check: the start time is not zero. if (startTime == 0) { - revert Errors.SablierLockup_StartTimeZero(); - } - - // Check: the tranche count is not zero. - uint256 trancheCount = tranches.length; - if (trancheCount == 0) { - revert Errors.SablierLockupTranched_TrancheCountZero(); + revert Errors.SablierHelpers_StartTimeZero(); } - - // Check: the tranche count is not greater than the maximum allowed. - if (trancheCount > maxTrancheCount) { - revert Errors.SablierLockupTranched_TrancheCountTooHigh(trancheCount); - } - - // Check: requirements of tranches. - _checkTranches(tranches, depositAmount, startTime); } - /*////////////////////////////////////////////////////////////////////////// - PRIVATE CONSTANT FUNCTIONS - //////////////////////////////////////////////////////////////////////////*/ - - /// @dev Checks that: + /// @dev Checks: /// /// 1. The first timestamp is strictly greater than the start time. /// 2. The timestamps are ordered chronologically. /// 3. There are no duplicate timestamps. /// 4. The deposit amount is equal to the sum of all segment amounts. + /// 5. The end time equals the last segment's timestamp. function _checkSegments( LockupDynamic.Segment[] memory segments, uint128 depositAmount, - uint40 startTime + Lockup.Timestamps memory timestamps, + uint256 maxSegmentCount ) private pure { + // Check: the segment count is not zero. + uint256 segmentCount = segments.length; + if (segmentCount == 0) { + revert Errors.SablierHelpers_SegmentCountZero(); + } + + // Check: the segment count is not greater than the maximum allowed. + if (segmentCount > maxSegmentCount) { + revert Errors.SablierHelpers_SegmentCountTooHigh(segmentCount); + } + // Check: the start time is strictly less than the first segment timestamp. - if (startTime >= segments[0].timestamp) { - revert Errors.SablierLockupDynamic_StartTimeNotLessThanFirstSegmentTimestamp( - startTime, segments[0].timestamp + if (timestamps.start >= segments[0].timestamp) { + revert Errors.SablierHelpers_StartTimeNotLessThanFirstSegmentTimestamp( + timestamps.start, segments[0].timestamp + ); + } + + // Check: the end time equals the last segment's timestamp. + if (timestamps.end != segments[segmentCount - 1].timestamp) { + revert Errors.SablierHelpers_EndTimeNotEqualToLastSegmentTimestamp( + timestamps.end, segments[segmentCount - 1].timestamp ); } @@ -266,15 +276,14 @@ library Helpers { // // 1. Calculate the sum of all segment amounts. // 2. Check that the timestamps are ordered. - uint256 count = segments.length; - for (uint256 index = 0; index < count; ++index) { + for (uint256 index = 0; index < segmentCount; ++index) { // Add the current segment amount to the sum. segmentAmountsSum += segments[index].amount; // Check: the current timestamp is strictly greater than the previous timestamp. currentSegmentTimestamp = segments[index].timestamp; if (currentSegmentTimestamp <= previousSegmentTimestamp) { - revert Errors.SablierLockupDynamic_SegmentTimestampsNotOrdered( + revert Errors.SablierHelpers_SegmentTimestampsNotOrdered( index, previousSegmentTimestamp, currentSegmentTimestamp ); } @@ -285,30 +294,48 @@ library Helpers { // Check: the deposit amount is equal to the segment amounts sum. if (depositAmount != segmentAmountsSum) { - revert Errors.SablierLockupDynamic_DepositAmountNotEqualToSegmentAmountsSum( - depositAmount, segmentAmountsSum - ); + revert Errors.SablierHelpers_DepositAmountNotEqualToSegmentAmountsSum(depositAmount, segmentAmountsSum); } } - /// @dev Checks that: + /// @dev Checks: /// /// 1. The first timestamp is strictly greater than the start time. /// 2. The timestamps are ordered chronologically. /// 3. There are no duplicate timestamps. /// 4. The deposit amount is equal to the sum of all tranche amounts. + /// 5. The end time equals the last tranche's timestamp. function _checkTranches( LockupTranched.Tranche[] memory tranches, uint128 depositAmount, - uint40 startTime + Lockup.Timestamps memory timestamps, + uint256 maxTrancheCount ) private pure { + // Check: the tranche count is not zero. + uint256 trancheCount = tranches.length; + if (trancheCount == 0) { + revert Errors.SablierHelpers_TrancheCountZero(); + } + + // Check: the tranche count is not greater than the maximum allowed. + if (trancheCount > maxTrancheCount) { + revert Errors.SablierHelpers_TrancheCountTooHigh(trancheCount); + } + // Check: the start time is strictly less than the first tranche timestamp. - if (startTime >= tranches[0].timestamp) { - revert Errors.SablierLockupTranched_StartTimeNotLessThanFirstTrancheTimestamp( - startTime, tranches[0].timestamp + if (timestamps.start >= tranches[0].timestamp) { + revert Errors.SablierHelpers_StartTimeNotLessThanFirstTrancheTimestamp( + timestamps.start, tranches[0].timestamp + ); + } + + // Check: the end time equals the tranche's timestamp. + if (timestamps.end != tranches[trancheCount - 1].timestamp) { + revert Errors.SablierHelpers_EndTimeNotEqualToLastTrancheTimestamp( + timestamps.end, tranches[trancheCount - 1].timestamp ); } @@ -321,15 +348,14 @@ library Helpers { // // 1. Calculate the sum of all tranche amounts. // 2. Check that the timestamps are ordered. - uint256 count = tranches.length; - for (uint256 index = 0; index < count; ++index) { + for (uint256 index = 0; index < trancheCount; ++index) { // Add the current tranche amount to the sum. trancheAmountsSum += tranches[index].amount; // Check: the current timestamp is strictly greater than the previous timestamp. currentTrancheTimestamp = tranches[index].timestamp; if (currentTrancheTimestamp <= previousTrancheTimestamp) { - revert Errors.SablierLockupTranched_TrancheTimestampsNotOrdered( + revert Errors.SablierHelpers_TrancheTimestampsNotOrdered( index, previousTrancheTimestamp, currentTrancheTimestamp ); } @@ -340,9 +366,7 @@ library Helpers { // Check: the deposit amount is equal to the tranche amounts sum. if (depositAmount != trancheAmountsSum) { - revert Errors.SablierLockupTranched_DepositAmountNotEqualToTrancheAmountsSum( - depositAmount, trancheAmountsSum - ); + revert Errors.SablierHelpers_DepositAmountNotEqualToTrancheAmountsSum(depositAmount, trancheAmountsSum); } } } diff --git a/src/core/libraries/VestingMath.sol b/src/core/libraries/VestingMath.sol new file mode 100644 index 000000000..f45f7ea08 --- /dev/null +++ b/src/core/libraries/VestingMath.sol @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity >=0.8.22; + +import { PRBMathCastingUint128 as CastingUint128 } from "@prb/math/src/casting/Uint128.sol"; +import { PRBMathCastingUint40 as CastingUint40 } from "@prb/math/src/casting/Uint40.sol"; +import { SD59x18 } from "@prb/math/src/SD59x18.sol"; +import { UD60x18, ud } from "@prb/math/src/UD60x18.sol"; +import { LockupDynamic, LockupTranched } from "./../types/DataTypes.sol"; + +/// @title VestingMath +/// @notice Library with functions needed to calculate vested amount across lockup streams. +library VestingMath { + using CastingUint128 for uint128; + using CastingUint40 for uint40; + + /// @notice Calculates the streamed amount for a Lockup dynamic stream. + /// @dev Lockup dynamic model uses the following distribution function: + /// + /// $$ + /// f(x) = x^{exp} * csa + \Sigma(esa) + /// $$ + /// + /// Where: + /// + /// - $x$ is the elapsed time divided by the total duration of the current segment. + /// - $exp$ is the current segment exponent. + /// - $csa$ is the current segment amount. + /// - $\Sigma(esa)$ is the sum of all vested segments' amounts. + /// + /// Notes: + /// 1. Normalization to 18 decimals is not needed because there is no mix of amounts with different decimals. + /// 2. The stream's start time must be in the past so that the calculations below do not overflow. + /// 3. The stream's end time must be in the future so that the loop below does not panic with an "index out of + /// bounds" error. + function calculateLockupDynamicStreamedAmount( + LockupDynamic.Segment[] memory segments, + uint40 startTime, + uint128 withdrawnAmount + ) + public + view + returns (uint128) + { + unchecked { + uint40 blockTimestamp = uint40(block.timestamp); + + // Sum the amounts in all segments that precede the block timestamp. + uint128 previousSegmentAmounts; + uint40 currentSegmentTimestamp = segments[0].timestamp; + uint256 index = 0; + while (currentSegmentTimestamp < blockTimestamp) { + previousSegmentAmounts += segments[index].amount; + index += 1; + currentSegmentTimestamp = segments[index].timestamp; + } + + // After exiting the loop, the current segment is at `index`. + SD59x18 currentSegmentAmount = segments[index].amount.intoSD59x18(); + SD59x18 currentSegmentExponent = segments[index].exponent.intoSD59x18(); + currentSegmentTimestamp = segments[index].timestamp; + + uint40 previousTimestamp; + if (index == 0) { + // When the current segment's index is equal to 0, the current segment is the first, so use the start + // time as the previous timestamp. + previousTimestamp = startTime; + } else { + // Otherwise, when the current segment's index is greater than zero, it means that the segment is not + // the first. In this case, use the previous segment's timestamp. + previousTimestamp = segments[index - 1].timestamp; + } + + // Calculate how much time has passed since the segment started, and the total duration of the segment. + SD59x18 elapsedTime = (blockTimestamp - previousTimestamp).intoSD59x18(); + SD59x18 segmentDuration = (currentSegmentTimestamp - previousTimestamp).intoSD59x18(); + + // Divide the elapsed time by the total duration of the segment. + SD59x18 elapsedTimePercentage = elapsedTime.div(segmentDuration); + + // Calculate the streamed amount using the special formula. + SD59x18 multiplier = elapsedTimePercentage.pow(currentSegmentExponent); + SD59x18 segmentStreamedAmount = multiplier.mul(currentSegmentAmount); + + // Although the segment streamed amount should never exceed the total segment amount, this condition is + // checked without asserting to avoid locking assets in case of a bug. If this situation occurs, the + // amount streamed in the segment is considered zero (except for past withdrawals), and the segment is + // effectively voided. + if (segmentStreamedAmount.gt(currentSegmentAmount)) { + return previousSegmentAmounts > withdrawnAmount ? previousSegmentAmounts : withdrawnAmount; + } + + // Calculate the total streamed amount by adding the previous segment amounts and the amount streamed in + // the current segment. Casting to uint128 is safe due to the if statement above. + return previousSegmentAmounts + uint128(segmentStreamedAmount.intoUint256()); + } + } + + /// @notice Calculates the streamed amount for a Lockup linear stream. + /// @dev Lockup linear model uses the following distribution function: + /// + /// $$ + /// f(x) = x * d + c + /// $$ + /// + /// Where: + /// + /// - $x$ is the elapsed time divided by the stream's total duration. + /// - $d$ is the deposited amount. + /// - $c$ is the cliff amount. + function calculateLockupLinearStreamedAmount( + uint128 depositedAmount, + uint40 startTime, + uint40 cliffTime, + uint40 endTime, + uint128 withdrawnAmount + ) + public + view + returns (uint128) + { + uint256 blockTimestamp = block.timestamp; + + // If the cliff time is in the future, return zero. + if (cliffTime > blockTimestamp) { + return 0; + } + + // In all other cases, calculate the amount streamed so far. Normalization to 18 decimals is not needed + // because there is no mix of amounts with different decimals. + unchecked { + // Calculate how much time has passed since the stream started, and the stream's total duration. + UD60x18 elapsedTime = ud(blockTimestamp - startTime); + UD60x18 totalDuration = ud(endTime - startTime); + + // Divide the elapsed time by the stream's total duration. + UD60x18 elapsedTimePercentage = elapsedTime.div(totalDuration); + + // Cast the deposited amount to UD60x18. + UD60x18 depositedAmountUD60x18 = ud(depositedAmount); + + // Calculate the streamed amount by multiplying the elapsed time percentage by the deposited amount. + UD60x18 streamedAmount = elapsedTimePercentage.mul(depositedAmountUD60x18); + + // Although the streamed amount should never exceed the deposited amount, this condition is checked + // without asserting to avoid locking assets in case of a bug. If this situation occurs, the withdrawn + // amount is considered to be the streamed amount, and the stream is effectively frozen. + if (streamedAmount.gt(depositedAmountUD60x18)) { + return withdrawnAmount; + } + + // Cast the streamed amount to uint128. This is safe due to the check above. + return uint128(streamedAmount.intoUint256()); + } + } + + /// @notice Calculates the streamed amount for a Lockup tranched stream. + /// @dev Lockup tranched model uses the following distribution function: + /// + /// $$ + /// f(x) = \Sigma(eta) + /// $$ + /// + /// Where: + /// + /// - $\Sigma(eta)$ is the sum of all vested tranches' amounts. + function calculateLockupTranchedStreamedAmount(LockupTranched.Tranche[] memory tranches) + public + view + returns (uint128) + { + uint256 blockTimestamp = block.timestamp; + + // If the first tranche's timestamp is in the future, return zero. + if (tranches[0].timestamp > blockTimestamp) { + return 0; + } + + // Sum the amounts in all tranches that have already been vested. + // Using unchecked arithmetic is safe because the sum of the tranche amounts is equal to the total amount + // at this point. + uint128 streamedAmount = tranches[0].amount; + uint256 tranchesCount = tranches.length; + for (uint256 i = 1; i < tranchesCount; ++i) { + // The loop breaks at the first tranche with a timestamp in the future. A tranche is considered vested if + // its timestamp is less than or equal to the block timestamp. + if (tranches[i].timestamp > blockTimestamp) { + break; + } + unchecked { + streamedAmount += tranches[i].amount; + } + } + + return streamedAmount; + } +} diff --git a/src/core/types/DataTypes.sol b/src/core/types/DataTypes.sol index ca31592cb..8808aa4d2 100644 --- a/src/core/types/DataTypes.sol +++ b/src/core/types/DataTypes.sol @@ -5,8 +5,6 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UD2x18 } from "@prb/math/src/UD2x18.sol"; import { UD60x18 } from "@prb/math/src/UD60x18.sol"; -// DataTypes.sol -// // This file defines all structs used in Lockup, most of which are organized under three namespaces: // // - Lockup @@ -26,7 +24,7 @@ struct Broker { UD60x18 fee; } -/// @notice Namespace for the structs used in both {SablierLockupLinear} and {SablierLockupDynamic}. +/// @notice Namespace for the structs used in all Lockup models. library Lockup { /// @notice Struct encapsulating the deposit, withdrawn, and refunded amounts, all denoted in units of the asset's /// decimals. @@ -52,6 +50,58 @@ library Lockup { uint128 brokerFee; } + /// @notice Struct encapsulating the parameters of the `createWithDurations` functions. + /// @param sender The address distributing the assets, with the ability to cancel the stream. It doesn't have to be + /// the same as `msg.sender`. + /// @param recipient The address receiving the assets, as well as the NFT owner. + /// @param totalAmount The total amount, including the deposit and any broker fee, denoted in units of the asset's + /// decimals. + /// @param asset The contract address of the ERC-20 asset to be distributed. + /// @param cancelable Indicates if the stream is cancelable. + /// @param transferable Indicates if the stream NFT is transferable. + /// @param broker Struct encapsulating (i) the address of the broker assisting in creating the stream, and (ii) the + /// percentage fee paid to the broker from `totalAmount`, denoted as a fixed-point number. Both can be set to zero. + struct CreateWithDurations { + address sender; + address recipient; + uint128 totalAmount; + IERC20 asset; + bool cancelable; + bool transferable; + Broker broker; + } + + /// @notice Struct encapsulating the parameters of the `createWithTimestamps` functions. + /// @param sender The address distributing the assets, with the ability to cancel the stream. It doesn't have to be + /// the same as `msg.sender`. + /// @param recipient The address receiving the assets, as well as the NFT owner. + /// @param totalAmount The total amount, including the deposit and any broker fee, denoted in units of the asset's + /// decimals. + /// @param asset The contract address of the ERC-20 asset to be distributed. + /// @param cancelable Indicates if the stream is cancelable. + /// @param transferable Indicates if the stream NFT is transferable. + /// @param timestamps Struct encapsulating (i) the stream's start time and (ii) end time, both as Unix timestamps. + /// @param broker Struct encapsulating (i) the address of the broker assisting in creating the stream, and (ii) the + /// percentage fee paid to the broker from `totalAmount`, denoted as a fixed-point number. Both can be set to zero. + struct CreateWithTimestamps { + address sender; + address recipient; + uint128 totalAmount; + IERC20 asset; + bool cancelable; + bool transferable; + Timestamps timestamps; + Broker broker; + } + + /// @notice Enum representing the different distribution models used to create lockup streams. + /// @dev These distribution models determine the vesting function used in the calculations of the unlocked assets. + enum Model { + LOCKUP_LINEAR, + LOCKUP_DYNAMIC, + LOCKUP_TRANCHED + } + /// @notice Enum representing the different statuses of a stream. /// @dev The status can have a "temperature": /// 1. Warm: Pending, Streaming. The passage of time alone can change the status. @@ -71,7 +121,7 @@ library Lockup { DEPLETED } - /// @notice A common data structure to be stored in all {SablierLockup} models. + /// @notice A common data structure to be stored in all Lockup models. /// @dev The fields are arranged like this to save gas via tight variable packing. /// @param sender The address distributing the assets, with the ability to cancel the stream. /// @param startTime The Unix timestamp indicating the stream's start. @@ -82,6 +132,7 @@ library Lockup { /// @param isDepleted Boolean indicating if the stream is depleted. /// @param isStream Boolean indicating if the struct entity exists. /// @param isTransferable Boolean indicating if the stream NFT is transferable. + /// @param lockupModel The distribution model of the stream. /// @param amounts Struct encapsulating the deposit, withdrawn, and refunded amounts, both denoted in units of the /// asset's decimals. struct Stream { @@ -96,63 +147,23 @@ library Lockup { bool isDepleted; bool isStream; bool isTransferable; + Model lockupModel; // slot 2 and 3 Amounts amounts; } -} -/// @notice Namespace for the structs used in {SablierLockupDynamic}. -library LockupDynamic { - /// @notice Struct encapsulating the parameters of the {SablierLockupDynamic.createWithDurations} function. - /// @param sender The address distributing the assets, which is able to cancel the stream. It doesn't have to be the - /// same as `msg.sender`. - /// @param recipient The address receiving the assets, as well as the NFT owner. - /// @param totalAmount The total amount, including the deposit and any broker fee, denoted in units of the asset's - /// decimals. - /// @param asset The contract address of the ERC-20 asset to be distributed. - /// @param cancelable Indicates if the stream is cancelable. - /// @param transferable Indicates if the stream NFT is transferable. - /// @param segments Segments with durations used to compose the dynamic distribution function. Timestamps are - /// calculated by starting from `block.timestamp` and adding each duration to the previous timestamp. - /// @param broker Struct encapsulating (i) the address of the broker assisting in creating the stream, and (ii) the - /// percentage fee paid to the broker from `totalAmount`, denoted as a fixed-point number. Both can be set to zero. - struct CreateWithDurations { - address sender; - address recipient; - uint128 totalAmount; - IERC20 asset; - bool cancelable; - bool transferable; - SegmentWithDuration[] segments; - Broker broker; - } - - /// @notice Struct encapsulating the parameters of the {SablierLockupDynamic.createWithTimestamps} function. - /// @param sender The address distributing the assets, which is able to cancel the stream. It doesn't have to be the - /// same as `msg.sender`. - /// @param recipient The address receiving the assets, as well as the NFT owner. - /// @param totalAmount The total amount, including the deposit and any broker fee, denoted in units of the asset's - /// decimals. - /// @param asset The contract address of the ERC-20 asset to be distributed. - /// @param cancelable Indicates if the stream is cancelable. - /// @param transferable Indicates if the stream NFT is transferable. - /// @param startTime The Unix timestamp indicating the stream's start. - /// @param segments Segments used to compose the dynamic distribution function. - /// @param broker Struct encapsulating (i) the address of the broker assisting in creating the stream, and (ii) the - /// percentage fee paid to the broker from `totalAmount`, denoted as a fixed-point number. Both can be set to zero. - struct CreateWithTimestamps { - address sender; - address recipient; - uint128 totalAmount; - IERC20 asset; - bool cancelable; - bool transferable; - uint40 startTime; - Segment[] segments; - Broker broker; + /// @notice Struct encapsulating the Lockup timestamps. + /// @param start The Unix timestamp for the stream's start. + /// @param end The Unix timestamp for the stream's end. + struct Timestamps { + uint40 start; + uint40 end; } +} - /// @notice Segment struct used in the Lockup Dynamic stream. +/// @notice Namespace for the structs used only in Lockup Dynamic model. +library LockupDynamic { + /// @notice Segment struct to be stored in the Lockup Dynamic model. /// @param amount The amount of assets streamed in the segment, denoted in units of the asset's decimals. /// @param exponent The exponent of the segment, denoted as a fixed-point number. /// @param timestamp The Unix timestamp indicating the segment's end. @@ -163,7 +174,7 @@ library LockupDynamic { uint40 timestamp; } - /// @notice Segment struct used at runtime in {SablierLockupDynamic.createWithDurations}. + /// @notice Segment struct used at runtime in {SablierLockup.createWithDurationsLD} function. /// @param amount The amount of assets streamed in the segment, denoted in units of the asset's decimals. /// @param exponent The exponent of the segment, denoted as a fixed-point number. /// @param duration The time difference in seconds between the segment and the previous one. @@ -172,195 +183,23 @@ library LockupDynamic { UD2x18 exponent; uint40 duration; } - - /// @notice Struct encapsulating the full details of a stream. - /// @dev Extends `Lockup.Stream` by including the recipient and the segments. - struct StreamLD { - address sender; - address recipient; - uint40 startTime; - uint40 endTime; - bool isCancelable; - bool wasCanceled; - IERC20 asset; - bool isDepleted; - bool isStream; - bool isTransferable; - Lockup.Amounts amounts; - Segment[] segments; - } - - /// @notice Struct encapsulating the LockupDynamic timestamps. - /// @param start The Unix timestamp indicating the stream's start. - /// @param end The Unix timestamp indicating the stream's end. - struct Timestamps { - uint40 start; - uint40 end; - } } -/// @notice Namespace for the structs used in {SablierLockupLinear}. +/// @notice Namespace for the structs used only in Lockup Linear model. library LockupLinear { - /// @notice Struct encapsulating the parameters of the {SablierLockupLinear.createWithDurations} function. - /// @param sender The address distributing the assets, with the ability to cancel the stream. It doesn't have to be - /// the same as `msg.sender`. - /// @param recipient The address receiving the assets, as well as the NFT owner. - /// @param totalAmount The total amount, including the deposit and any broker fee, denoted in units of the asset's - /// decimals. - /// @param asset The contract address of the ERC-20 asset to be distributed. - /// @param cancelable Indicates if the stream is cancelable. - /// @param transferable Indicates if the stream NFT is transferable. - /// @param durations Struct encapsulating (i) cliff period duration and (ii) total stream duration, both in seconds. - /// @param broker Struct encapsulating (i) the address of the broker assisting in creating the stream, and (ii) the - /// percentage fee paid to the broker from `totalAmount`, denoted as a fixed-point number. Both can be set to zero. - struct CreateWithDurations { - address sender; - address recipient; - uint128 totalAmount; - IERC20 asset; - bool cancelable; - bool transferable; - Durations durations; - Broker broker; - } - - /// @notice Struct encapsulating the parameters of the {SablierLockupLinear.createWithTimestamps} function. - /// @param sender The address distributing the assets, with the ability to cancel the stream. It doesn't have to be - /// the same as `msg.sender`. - /// @param recipient The address receiving the assets, as well as the NFT owner. - /// @param totalAmount The total amount, including the deposit and any broker fee, denoted in units of the asset's - /// decimals. - /// @param asset The contract address of the ERC-20 asset to be distributed. - /// @param cancelable Indicates if the stream is cancelable. - /// @param transferable Indicates if the stream NFT is transferable. - /// @param timestamps Struct encapsulating (i) the stream's start time, (ii) cliff time, and (iii) end time, all as - /// Unix timestamps. - /// @param broker Struct encapsulating (i) the address of the broker assisting in creating the stream, and (ii) the - /// percentage fee paid to the broker from `totalAmount`, denoted as a fixed-point number. Both can be set to zero. - struct CreateWithTimestamps { - address sender; - address recipient; - uint128 totalAmount; - IERC20 asset; - bool cancelable; - bool transferable; - Timestamps timestamps; - Broker broker; - } - - /// @notice Struct encapsulating the cliff duration and the total duration. + /// @notice Struct encapsulating the cliff duration and the total duration used at runtime in + /// {SablierLockup.createWithDurationsLL} function. /// @param cliff The cliff duration in seconds. /// @param total The total duration in seconds. struct Durations { uint40 cliff; uint40 total; } - - /// @notice Struct encapsulating the full details of a stream. - /// @dev Extends `Lockup.Stream` by including the recipient and the cliff time. - struct StreamLL { - address sender; - address recipient; - uint40 startTime; - bool isCancelable; - bool wasCanceled; - IERC20 asset; - uint40 endTime; - bool isDepleted; - bool isStream; - bool isTransferable; - Lockup.Amounts amounts; - uint40 cliffTime; - } - - /// @notice Struct encapsulating the LockupLinear timestamps. - /// @param start The Unix timestamp for the stream's start. - /// @param cliff The Unix timestamp for the cliff period's end. A value of zero means there is no cliff. - /// @param end The Unix timestamp for the stream's end. - struct Timestamps { - uint40 start; - uint40 cliff; - uint40 end; - } } -/// @notice Namespace for the structs used in {SablierLockupTranched}. +/// @notice Namespace for the structs used only in Lockup Tranched model. library LockupTranched { - /// @notice Struct encapsulating the parameters of the {SablierLockupTranched.createWithDurations} function. - /// @param sender The address distributing the assets, with the ability to cancel the stream. It doesn't have to be - /// the same as `msg.sender`. - /// @param recipient The address receiving the assets, as well as the NFT owner. - /// @param totalAmount The total amount, including the deposit and any broker fee, denoted in units of the asset's - /// decimals. - /// @param asset The contract address of the ERC-20 asset to be distributed. - /// @param cancelable Indicates if the stream is cancelable. - /// @param transferable Indicates if the stream NFT is transferable. - /// @param tranches Tranches with durations used to compose the tranched distribution function. Timestamps are - /// calculated by starting from `block.timestamp` and adding each duration to the previous timestamp. - /// @param broker Struct encapsulating (i) the address of the broker assisting in creating the stream, and (ii) the - /// percentage fee paid to the broker from `totalAmount`, denoted as a fixed-point number. Both can be set to zero. - struct CreateWithDurations { - address sender; - address recipient; - uint128 totalAmount; - IERC20 asset; - bool cancelable; - bool transferable; - TrancheWithDuration[] tranches; - Broker broker; - } - - /// @notice Struct encapsulating the parameters of the {SablierLockupTranched.createWithTimestamps} function. - /// @param sender The address distributing the assets, with the ability to cancel the stream. It doesn't have to be - /// the same as `msg.sender`. - /// @param recipient The address receiving the assets, as well as the NFT owner. - /// @param totalAmount The total amount, including the deposit and any broker fee, denoted in units of the asset's - /// decimals. - /// @param asset The contract address of the ERC-20 asset to be distributed. - /// @param cancelable Indicates if the stream is cancelable. - /// @param transferable Indicates if the stream NFT is transferable. - /// @param startTime The Unix timestamp indicating the stream's start. - /// @param tranches Tranches used to compose the tranched distribution function. - /// @param broker Struct encapsulating (i) the address of the broker assisting in creating the stream, and (ii) the - /// percentage fee paid to the broker from `totalAmount`, denoted as a fixed-point number. Both can be set to zero. - struct CreateWithTimestamps { - address sender; - address recipient; - uint128 totalAmount; - IERC20 asset; - bool cancelable; - bool transferable; - uint40 startTime; - Tranche[] tranches; - Broker broker; - } - - /// @notice Struct encapsulating the full details of a stream. - /// @dev Extends `Lockup.Stream` by including the recipient and the tranches. - struct StreamLT { - address sender; - address recipient; - uint40 startTime; - uint40 endTime; - bool isCancelable; - bool wasCanceled; - IERC20 asset; - bool isDepleted; - bool isStream; - bool isTransferable; - Lockup.Amounts amounts; - Tranche[] tranches; - } - - /// @notice Struct encapsulating the LockupTranched timestamps. - /// @param start The Unix timestamp indicating the stream's start. - /// @param end The Unix timestamp indicating the stream's end. - struct Timestamps { - uint40 start; - uint40 end; - } - - /// @notice Tranche struct used in the Lockup Tranched stream. + /// @notice Tranche struct to be stored in the Lockup Tranched model. /// @param amount The amount of assets to be unlocked in the tranche, denoted in units of the asset's decimals. /// @param timestamp The Unix timestamp indicating the tranche's end. struct Tranche { @@ -369,7 +208,7 @@ library LockupTranched { uint40 timestamp; } - /// @notice Tranche struct used at runtime in {SablierLockupTranched.createWithDurations}. + /// @notice Tranche struct used at runtime in {SablierLockup.createWithDurationsLT} function. /// @param amount The amount of assets to be unlocked in the tranche, denoted in units of the asset's decimals. /// @param duration The time difference in seconds between the tranche and the previous one. struct TrancheWithDuration { diff --git a/src/periphery/SablierBatchLockup.sol b/src/periphery/SablierBatchLockup.sol index 73fe519f5..2797839f5 100644 --- a/src/periphery/SablierBatchLockup.sol +++ b/src/periphery/SablierBatchLockup.sol @@ -4,10 +4,8 @@ pragma solidity >=0.8.22; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { ISablierLockupDynamic } from "../core/interfaces/ISablierLockupDynamic.sol"; -import { ISablierLockupLinear } from "../core/interfaces/ISablierLockupLinear.sol"; -import { ISablierLockupTranched } from "../core/interfaces/ISablierLockupTranched.sol"; -import { LockupDynamic, LockupLinear, LockupTranched } from "../core/types/DataTypes.sol"; +import { ISablierLockup } from "../core/interfaces/ISablierLockup.sol"; +import { Lockup } from "../core/types/DataTypes.sol"; import { ISablierBatchLockup } from "./interfaces/ISablierBatchLockup.sol"; import { Errors } from "./libraries/Errors.sol"; @@ -24,7 +22,7 @@ contract SablierBatchLockup is ISablierBatchLockup { /// @inheritdoc ISablierBatchLockup function createWithDurationsLD( - ISablierLockupDynamic lockupDynamic, + ISablierLockup lockup, IERC20 asset, BatchLockup.CreateWithDurationsLD[] calldata batch ) @@ -48,31 +46,31 @@ contract SablierBatchLockup is ISablierBatchLockup { } } - // Perform the ERC-20 transfer and approve {SablierLockupDynamic} to spend the amount of assets. - _handleTransfer(address(lockupDynamic), asset, transferAmount); + // Perform the ERC-20 transfer and approve {SablierLockup} to spend the amount of assets. + _handleTransfer(address(lockup), asset, transferAmount); // Create a stream for each element in the parameter array. streamIds = new uint256[](batchSize); for (i = 0; i < batchSize; ++i) { // Create the stream. - streamIds[i] = lockupDynamic.createWithDurations( - LockupDynamic.CreateWithDurations({ + streamIds[i] = lockup.createWithDurationsLD( + Lockup.CreateWithDurations({ sender: batch[i].sender, recipient: batch[i].recipient, totalAmount: batch[i].totalAmount, asset: asset, cancelable: batch[i].cancelable, transferable: batch[i].transferable, - segments: batch[i].segments, broker: batch[i].broker - }) + }), + batch[i].segmentsWithDuration ); } } /// @inheritdoc ISablierBatchLockup function createWithTimestampsLD( - ISablierLockupDynamic lockupDynamic, + ISablierLockup lockup, IERC20 asset, BatchLockup.CreateWithTimestampsLD[] calldata batch ) @@ -96,25 +94,32 @@ contract SablierBatchLockup is ISablierBatchLockup { } } - // Perform the ERC-20 transfer and approve {SablierLockupDynamic} to spend the amount of assets. - _handleTransfer(address(lockupDynamic), asset, transferAmount); + // Perform the ERC-20 transfer and approve {SablierLockup} to spend the amount of assets. + _handleTransfer(address(lockup), asset, transferAmount); + + uint40 endTime; // Create a stream for each element in the parameter array. streamIds = new uint256[](batchSize); for (i = 0; i < batchSize; ++i) { + // Calculate the end time of the stream. + unchecked { + endTime = batch[i].segments[batch[i].segments.length - 1].timestamp; + } + // Create the stream. - streamIds[i] = lockupDynamic.createWithTimestamps( - LockupDynamic.CreateWithTimestamps({ + streamIds[i] = lockup.createWithTimestampsLD( + Lockup.CreateWithTimestamps({ sender: batch[i].sender, recipient: batch[i].recipient, totalAmount: batch[i].totalAmount, asset: asset, cancelable: batch[i].cancelable, transferable: batch[i].transferable, - startTime: batch[i].startTime, - segments: batch[i].segments, + timestamps: Lockup.Timestamps({ start: batch[i].startTime, end: endTime }), broker: batch[i].broker - }) + }), + batch[i].segments ); } } @@ -125,7 +130,7 @@ contract SablierBatchLockup is ISablierBatchLockup { /// @inheritdoc ISablierBatchLockup function createWithDurationsLL( - ISablierLockupLinear lockupLinear, + ISablierLockup lockup, IERC20 asset, BatchLockup.CreateWithDurationsLL[] calldata batch ) @@ -149,31 +154,31 @@ contract SablierBatchLockup is ISablierBatchLockup { } } - // Perform the ERC-20 transfer and approve {SablierLockupLinear} to spend the amount of assets. - _handleTransfer(address(lockupLinear), asset, transferAmount); + // Perform the ERC-20 transfer and approve {SablierLockup} to spend the amount of assets. + _handleTransfer(address(lockup), asset, transferAmount); // Create a stream for each element in the parameter array. streamIds = new uint256[](batchSize); for (i = 0; i < batchSize; ++i) { // Create the stream. - streamIds[i] = lockupLinear.createWithDurations( - LockupLinear.CreateWithDurations({ + streamIds[i] = lockup.createWithDurationsLL( + Lockup.CreateWithDurations({ sender: batch[i].sender, recipient: batch[i].recipient, totalAmount: batch[i].totalAmount, asset: asset, cancelable: batch[i].cancelable, transferable: batch[i].transferable, - durations: batch[i].durations, broker: batch[i].broker - }) + }), + batch[i].durations ); } } /// @inheritdoc ISablierBatchLockup function createWithTimestampsLL( - ISablierLockupLinear lockupLinear, + ISablierLockup lockup, IERC20 asset, BatchLockup.CreateWithTimestampsLL[] calldata batch ) @@ -197,15 +202,15 @@ contract SablierBatchLockup is ISablierBatchLockup { } } - // Perform the ERC-20 transfer and approve {SablierLockupLinear} to spend the amount of assets. - _handleTransfer(address(lockupLinear), asset, transferAmount); + // Perform the ERC-20 transfer and approve {SablierLockup} to spend the amount of assets. + _handleTransfer(address(lockup), asset, transferAmount); // Create a stream for each element in the parameter array. streamIds = new uint256[](batchSize); for (i = 0; i < batchSize; ++i) { // Create the stream. - streamIds[i] = lockupLinear.createWithTimestamps( - LockupLinear.CreateWithTimestamps({ + streamIds[i] = lockup.createWithTimestampsLL( + Lockup.CreateWithTimestamps({ sender: batch[i].sender, recipient: batch[i].recipient, totalAmount: batch[i].totalAmount, @@ -214,7 +219,8 @@ contract SablierBatchLockup is ISablierBatchLockup { transferable: batch[i].transferable, timestamps: batch[i].timestamps, broker: batch[i].broker - }) + }), + batch[i].cliffTime ); } } @@ -225,7 +231,7 @@ contract SablierBatchLockup is ISablierBatchLockup { /// @inheritdoc ISablierBatchLockup function createWithDurationsLT( - ISablierLockupTranched lockupTranched, + ISablierLockup lockup, IERC20 asset, BatchLockup.CreateWithDurationsLT[] calldata batch ) @@ -249,31 +255,31 @@ contract SablierBatchLockup is ISablierBatchLockup { } } - // Perform the ERC-20 transfer and approve {SablierLockupTranched} to spend the amount of assets. - _handleTransfer(address(lockupTranched), asset, transferAmount); + // Perform the ERC-20 transfer and approve {SablierLockup} to spend the amount of assets. + _handleTransfer(address(lockup), asset, transferAmount); // Create a stream for each element in the parameter array. streamIds = new uint256[](batchSize); for (i = 0; i < batchSize; ++i) { // Create the stream. - streamIds[i] = lockupTranched.createWithDurations( - LockupTranched.CreateWithDurations({ + streamIds[i] = lockup.createWithDurationsLT( + Lockup.CreateWithDurations({ sender: batch[i].sender, recipient: batch[i].recipient, totalAmount: batch[i].totalAmount, asset: asset, cancelable: batch[i].cancelable, transferable: batch[i].transferable, - tranches: batch[i].tranches, broker: batch[i].broker - }) + }), + batch[i].tranchesWithDuration ); } } /// @inheritdoc ISablierBatchLockup function createWithTimestampsLT( - ISablierLockupTranched lockupTranched, + ISablierLockup lockup, IERC20 asset, BatchLockup.CreateWithTimestampsLT[] calldata batch ) @@ -297,25 +303,32 @@ contract SablierBatchLockup is ISablierBatchLockup { } } - // Perform the ERC-20 transfer and approve {SablierLockupTranched} to spend the amount of assets. - _handleTransfer(address(lockupTranched), asset, transferAmount); + // Perform the ERC-20 transfer and approve {SablierLockup} to spend the amount of assets. + _handleTransfer(address(lockup), asset, transferAmount); + + uint40 endTime; // Create a stream for each element in the parameter array. streamIds = new uint256[](batchSize); for (i = 0; i < batchSize; ++i) { + // Calculate the end time of the stream. + unchecked { + endTime = batch[i].tranches[batch[i].tranches.length - 1].timestamp; + } + // Create the stream. - streamIds[i] = lockupTranched.createWithTimestamps( - LockupTranched.CreateWithTimestamps({ + streamIds[i] = lockup.createWithTimestampsLT( + Lockup.CreateWithTimestamps({ sender: batch[i].sender, recipient: batch[i].recipient, totalAmount: batch[i].totalAmount, asset: asset, cancelable: batch[i].cancelable, transferable: batch[i].transferable, - startTime: batch[i].startTime, - tranches: batch[i].tranches, + timestamps: Lockup.Timestamps({ start: batch[i].startTime, end: endTime }), broker: batch[i].broker - }) + }), + batch[i].tranches ); } } @@ -328,20 +341,20 @@ contract SablierBatchLockup is ISablierBatchLockup { /// is insufficient, this function approves Lockup to spend the exact `amount`. /// The {SafeERC20.forceApprove} function is used to handle special ERC-20 assets (e.g. USDT) that require the /// current allowance to be zero before setting it to a non-zero value. - function _approve(address lockupContract, IERC20 asset, uint256 amount) internal { - uint256 allowance = asset.allowance({ owner: address(this), spender: lockupContract }); + function _approve(address lockup, IERC20 asset, uint256 amount) internal { + uint256 allowance = asset.allowance({ owner: address(this), spender: lockup }); if (allowance < amount) { - asset.forceApprove({ spender: lockupContract, value: amount }); + asset.forceApprove({ spender: lockup, value: amount }); } } /// @dev Helper function to transfer assets from the caller to the batchLockup contract and approve the Lockup /// contract. - function _handleTransfer(address lockupContract, IERC20 asset, uint256 amount) internal { + function _handleTransfer(address lockup, IERC20 asset, uint256 amount) internal { // Transfer the assets to the batchLockup contract. asset.safeTransferFrom({ from: msg.sender, to: address(this), value: amount }); // Approve the Lockup contract to spend funds. - _approve(lockupContract, asset, amount); + _approve(lockup, asset, amount); } } diff --git a/src/periphery/SablierMerkleFactory.sol b/src/periphery/SablierMerkleFactory.sol index ecc82b88c..3cd80bff1 100644 --- a/src/periphery/SablierMerkleFactory.sol +++ b/src/periphery/SablierMerkleFactory.sol @@ -4,8 +4,7 @@ pragma solidity >=0.8.22; import { uUNIT } from "@prb/math/src/UD2x18.sol"; import { Adminable } from "../core/abstracts/Adminable.sol"; -import { ISablierLockupLinear } from "../core/interfaces/ISablierLockupLinear.sol"; -import { ISablierLockupTranched } from "../core/interfaces/ISablierLockupTranched.sol"; +import { ISablierLockup } from "../core/interfaces/ISablierLockup.sol"; import { ISablierMerkleBase } from "./interfaces/ISablierMerkleBase.sol"; import { ISablierMerkleFactory } from "./interfaces/ISablierMerkleFactory.sol"; @@ -157,7 +156,7 @@ contract SablierMerkleFactory is /// @inheritdoc ISablierMerkleFactory function createMerkleLL( MerkleBase.ConstructorParams memory baseParams, - ISablierLockupLinear lockupLinear, + ISablierLockup lockup, bool cancelable, bool transferable, MerkleLL.Schedule memory schedule, @@ -178,7 +177,7 @@ contract SablierMerkleFactory is abi.encode(baseParams.ipfsCID), baseParams.merkleRoot, bytes32(abi.encodePacked(baseParams.name)), - lockupLinear, + lockup, cancelable, transferable, abi.encode(schedule) @@ -189,14 +188,13 @@ contract SablierMerkleFactory is uint256 sablierFee = _computeSablierFeeForUser(msg.sender); // Deploy the MerkleLL contract with CREATE2. - merkleLL = - new SablierMerkleLL{ salt: salt }(baseParams, lockupLinear, cancelable, transferable, schedule, sablierFee); + merkleLL = new SablierMerkleLL{ salt: salt }(baseParams, lockup, cancelable, transferable, schedule, sablierFee); // Log the creation of the MerkleLL contract, including some metadata that is not stored on-chain. emit CreateMerkleLL( merkleLL, baseParams, - lockupLinear, + lockup, cancelable, transferable, schedule, @@ -209,7 +207,7 @@ contract SablierMerkleFactory is /// @inheritdoc ISablierMerkleFactory function createMerkleLT( MerkleBase.ConstructorParams memory baseParams, - ISablierLockupTranched lockupTranched, + ISablierLockup lockup, bool cancelable, bool transferable, uint40 streamStartTime, @@ -236,14 +234,14 @@ contract SablierMerkleFactory is // Deploy the MerkleLT contract. merkleLT = _deployMerkleLT( - baseParams, lockupTranched, cancelable, transferable, streamStartTime, tranchesWithPercentages, sablierFee + baseParams, lockup, cancelable, transferable, streamStartTime, tranchesWithPercentages, sablierFee ); // Log the creation of the MerkleLT contract, including some metadata that is not stored on-chain. emit CreateMerkleLT( merkleLT, baseParams, - lockupTranched, + lockup, cancelable, transferable, streamStartTime, @@ -268,7 +266,7 @@ contract SablierMerkleFactory is /// @dev We need a separate function to prevent the stack too deep error. function _deployMerkleLT( MerkleBase.ConstructorParams memory baseParams, - ISablierLockupTranched lockupTranched, + ISablierLockup lockup, bool cancelable, bool transferable, uint40 streamStartTime, @@ -288,7 +286,7 @@ contract SablierMerkleFactory is abi.encode(baseParams.ipfsCID), baseParams.merkleRoot, bytes32(abi.encodePacked(baseParams.name)), - lockupTranched, + lockup, cancelable, transferable, streamStartTime, @@ -298,7 +296,7 @@ contract SablierMerkleFactory is // Deploy the MerkleLT contract with CREATE2. merkleLT = new SablierMerkleLT{ salt: salt }( - baseParams, lockupTranched, cancelable, transferable, streamStartTime, tranchesWithPercentages, sablierFee + baseParams, lockup, cancelable, transferable, streamStartTime, tranchesWithPercentages, sablierFee ); } } diff --git a/src/periphery/SablierMerkleLL.sol b/src/periphery/SablierMerkleLL.sol index be4433e4c..92a27b914 100644 --- a/src/periphery/SablierMerkleLL.sol +++ b/src/periphery/SablierMerkleLL.sol @@ -5,8 +5,8 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { ud } from "@prb/math/src/UD60x18.sol"; -import { ISablierLockupLinear } from "../core/interfaces/ISablierLockupLinear.sol"; -import { Broker, LockupLinear } from "../core/types/DataTypes.sol"; +import { ISablierLockup } from "../core/interfaces/ISablierLockup.sol"; +import { Broker, Lockup } from "../core/types/DataTypes.sol"; import { SablierMerkleBase } from "./abstracts/SablierMerkleBase.sol"; import { ISablierMerkleLL } from "./interfaces/ISablierMerkleLL.sol"; @@ -28,7 +28,7 @@ contract SablierMerkleLL is bool public immutable override CANCELABLE; /// @inheritdoc ISablierMerkleLL - ISablierLockupLinear public immutable override LOCKUP_LINEAR; + ISablierLockup public immutable override LOCKUP; /// @inheritdoc ISablierMerkleLL bool public immutable override TRANSFERABLE; @@ -44,7 +44,7 @@ contract SablierMerkleLL is /// contract. constructor( MerkleBase.ConstructorParams memory baseParams, - ISablierLockupLinear lockupLinear, + ISablierLockup lockup, bool cancelable, bool transferable, MerkleLL.Schedule memory schedule_, @@ -53,12 +53,12 @@ contract SablierMerkleLL is SablierMerkleBase(baseParams, sablierFee) { CANCELABLE = cancelable; - LOCKUP_LINEAR = lockupLinear; + LOCKUP = lockup; TRANSFERABLE = transferable; schedule = schedule_; // Max approve the Lockup contract to spend funds from the MerkleLL contract. - ASSET.forceApprove(address(LOCKUP_LINEAR), type(uint256).max); + ASSET.forceApprove(address(LOCKUP), type(uint256).max); } /*////////////////////////////////////////////////////////////////////////// @@ -68,25 +68,27 @@ contract SablierMerkleLL is /// @inheritdoc SablierMerkleBase function _claim(uint256 index, address recipient, uint128 amount) internal override { // Calculate the timestamps for the stream. - LockupLinear.Timestamps memory timestamps; + Lockup.Timestamps memory timestamps; if (schedule.startTime == 0) { timestamps.start = uint40(block.timestamp); } else { timestamps.start = schedule.startTime; } + uint40 cliffTime; + // It is safe to use unchecked arithmetic because the `createWithTimestamps` function in the Lockup contract // will nonetheless make the relevant checks. unchecked { if (schedule.cliffDuration > 0) { - timestamps.cliff = timestamps.start + schedule.cliffDuration; + cliffTime = timestamps.start + schedule.cliffDuration; } timestamps.end = timestamps.start + schedule.totalDuration; } - // Interaction: create the stream via {SablierLockupLinear}. - uint256 streamId = LOCKUP_LINEAR.createWithTimestamps( - LockupLinear.CreateWithTimestamps({ + // Interaction: create the stream via {SablierLockup}. + uint256 streamId = LOCKUP.createWithTimestampsLL( + Lockup.CreateWithTimestamps({ sender: admin, recipient: recipient, totalAmount: amount, @@ -95,7 +97,8 @@ contract SablierMerkleLL is transferable: TRANSFERABLE, timestamps: timestamps, broker: Broker({ account: address(0), fee: ud(0) }) - }) + }), + cliffTime ); // Log the claim. diff --git a/src/periphery/SablierMerkleLT.sol b/src/periphery/SablierMerkleLT.sol index f851e2706..9df43efd8 100644 --- a/src/periphery/SablierMerkleLT.sol +++ b/src/periphery/SablierMerkleLT.sol @@ -6,8 +6,8 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { uUNIT } from "@prb/math/src/UD2x18.sol"; import { UD60x18, ud60x18, ZERO } from "@prb/math/src/UD60x18.sol"; -import { ISablierLockupTranched } from "../core/interfaces/ISablierLockupTranched.sol"; -import { Broker, LockupTranched } from "../core/types/DataTypes.sol"; +import { ISablierLockup } from "../core/interfaces/ISablierLockup.sol"; +import { Broker, Lockup, LockupTranched } from "../core/types/DataTypes.sol"; import { SablierMerkleBase } from "./abstracts/SablierMerkleBase.sol"; import { ISablierMerkleLT } from "./interfaces/ISablierMerkleLT.sol"; @@ -30,7 +30,7 @@ contract SablierMerkleLT is bool public immutable override CANCELABLE; /// @inheritdoc ISablierMerkleLT - ISablierLockupTranched public immutable override LOCKUP_TRANCHED; + ISablierLockup public immutable override LOCKUP; /// @inheritdoc ISablierMerkleLT uint40 public immutable override STREAM_START_TIME; @@ -52,7 +52,7 @@ contract SablierMerkleLT is /// contract. constructor( MerkleBase.ConstructorParams memory baseParams, - ISablierLockupTranched lockupTranched, + ISablierLockup lockup, bool cancelable, bool transferable, uint40 streamStartTime, @@ -62,7 +62,7 @@ contract SablierMerkleLT is SablierMerkleBase(baseParams, sablierFee) { CANCELABLE = cancelable; - LOCKUP_TRANCHED = lockupTranched; + LOCKUP = lockup; STREAM_START_TIME = streamStartTime; TRANSFERABLE = transferable; @@ -78,7 +78,7 @@ contract SablierMerkleLT is TOTAL_PERCENTAGE = totalPercentage; // Max approve the Lockup contract to spend funds from the MerkleLT contract. - ASSET.forceApprove(address(LOCKUP_TRANCHED), type(uint256).max); + ASSET.forceApprove(address(LOCKUP), type(uint256).max); } /*////////////////////////////////////////////////////////////////////////// @@ -104,19 +104,25 @@ contract SablierMerkleLT is // Calculate the tranches based on the unlock percentages. (uint40 startTime, LockupTranched.Tranche[] memory tranches) = _calculateStartTimeAndTranches(amount); - // Interaction: create the stream via {SablierLockupTranched}. - uint256 streamId = LOCKUP_TRANCHED.createWithTimestamps( - LockupTranched.CreateWithTimestamps({ + // Calculate the stream's end time. + uint40 endTime; + unchecked { + endTime = tranches[tranches.length - 1].timestamp; + } + + // Interaction: create the stream via {SablierLockup-createWithTimestampsLT}. + uint256 streamId = LOCKUP.createWithTimestampsLT( + Lockup.CreateWithTimestamps({ sender: admin, recipient: recipient, totalAmount: amount, asset: ASSET, cancelable: CANCELABLE, - startTime: startTime, transferable: TRANSFERABLE, - tranches: tranches, + timestamps: Lockup.Timestamps({ start: startTime, end: endTime }), broker: Broker({ account: address(0), fee: ZERO }) - }) + }), + tranches ); // Log the claim. diff --git a/src/periphery/interfaces/ISablierBatchLockup.sol b/src/periphery/interfaces/ISablierBatchLockup.sol index 3ef10202e..db2cc457e 100644 --- a/src/periphery/interfaces/ISablierBatchLockup.sol +++ b/src/periphery/interfaces/ISablierBatchLockup.sol @@ -3,9 +3,7 @@ pragma solidity >=0.8.22; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { ISablierLockupDynamic } from "../../core/interfaces/ISablierLockupDynamic.sol"; -import { ISablierLockupLinear } from "../../core/interfaces/ISablierLockupLinear.sol"; -import { ISablierLockupTranched } from "../../core/interfaces/ISablierLockupTranched.sol"; +import { ISablierLockup } from "../../core/interfaces/ISablierLockup.sol"; import { BatchLockup } from "../types/DataTypes.sol"; @@ -16,38 +14,38 @@ interface ISablierBatchLockup { SABLIER-LOCKUP-DYNAMIC //////////////////////////////////////////////////////////////////////////*/ - /// @notice Creates a batch of Lockup Dynamic streams using `createWithDurations`. + /// @notice Creates a batch of Lockup Dynamic streams using `createWithDurationsLD`. /// /// @dev Requirements: /// - There must be at least one element in `batch`. - /// - All requirements from {ISablierLockupDynamic.createWithDurations} must be met for each stream. + /// - All requirements from {ISablierLockup.createWithDurationsLD} must be met for each stream. /// - /// @param lockupDynamic The address of the {SablierLockupDynamic} contract. + /// @param lockup The address of the {SablierLockup} contract. /// @param asset The contract address of the ERC-20 asset to be distributed. /// @param batch An array of structs, each encapsulating a subset of the parameters of - /// {SablierLockupDynamic.createWithDurations}. + /// {SablierLockup.createWithDurationsLD}. /// @return streamIds The ids of the newly created streams. function createWithDurationsLD( - ISablierLockupDynamic lockupDynamic, + ISablierLockup lockup, IERC20 asset, BatchLockup.CreateWithDurationsLD[] calldata batch ) external returns (uint256[] memory streamIds); - /// @notice Creates a batch of Lockup Dynamic streams using `createWithTimestamps`. + /// @notice Creates a batch of Lockup Dynamic streams using `createWithTimestampsLD`. /// /// @dev Requirements: /// - There must be at least one element in `batch`. - /// - All requirements from {ISablierLockupDynamic.createWithTimestamps} must be met for each stream. + /// - All requirements from {ISablierLockup.createWithTimestampsLD} must be met for each stream. /// - /// @param lockupDynamic The address of the {SablierLockupDynamic} contract. + /// @param lockup The address of the {SablierLockup} contract. /// @param asset The contract address of the ERC-20 asset to be distributed. /// @param batch An array of structs, each encapsulating a subset of the parameters of - /// {SablierLockupDynamic.createWithTimestamps}. + /// {SablierLockup.createWithTimestampsLD}. /// @return streamIds The ids of the newly created streams. function createWithTimestampsLD( - ISablierLockupDynamic lockupDynamic, + ISablierLockup lockup, IERC20 asset, BatchLockup.CreateWithTimestampsLD[] calldata batch ) @@ -58,38 +56,38 @@ interface ISablierBatchLockup { SABLIER-LOCKUP-LINEAR //////////////////////////////////////////////////////////////////////////*/ - /// @notice Creates a batch of LockupLinear streams using `createWithDurations`. + /// @notice Creates a batch of Lockup Linear streams using `createWithDurationsLL`. /// /// @dev Requirements: /// - There must be at least one element in `batch`. - /// - All requirements from {ISablierLockupLinear.createWithDurations} must be met for each stream. + /// - All requirements from {ISablierLockup.createWithDurationsLL} must be met for each stream. /// - /// @param lockupLinear The address of the {SablierLockupLinear} contract. + /// @param lockup The address of the {SablierLockup} contract. /// @param asset The contract address of the ERC-20 asset to be distributed. /// @param batch An array of structs, each encapsulating a subset of the parameters of - /// {SablierLockupLinear.createWithDurations}. + /// {SablierLockup.createWithDurationsLL}. /// @return streamIds The ids of the newly created streams. function createWithDurationsLL( - ISablierLockupLinear lockupLinear, + ISablierLockup lockup, IERC20 asset, BatchLockup.CreateWithDurationsLL[] calldata batch ) external returns (uint256[] memory streamIds); - /// @notice Creates a batch of LockupLinear streams using `createWithTimestamps`. + /// @notice Creates a batch of Lockup Linear streams using `createWithTimestampsLL`. /// /// @dev Requirements: /// - There must be at least one element in `batch`. - /// - All requirements from {ISablierLockupLinear.createWithTimestamps} must be met for each stream. + /// - All requirements from {ISablierLockup.createWithTimestampsLL} must be met for each stream. /// - /// @param lockupLinear The address of the {SablierLockupLinear} contract. + /// @param lockup The address of the {SablierLockup} contract. /// @param asset The contract address of the ERC-20 asset to be distributed. /// @param batch An array of structs, each encapsulating a subset of the parameters of - /// {SablierLockupLinear.createWithTimestamps}. + /// {SablierLockup.createWithTimestampsLL}. /// @return streamIds The ids of the newly created streams. function createWithTimestampsLL( - ISablierLockupLinear lockupLinear, + ISablierLockup lockup, IERC20 asset, BatchLockup.CreateWithTimestampsLL[] calldata batch ) @@ -100,38 +98,38 @@ interface ISablierBatchLockup { SABLIER-LOCKUP-TRANCHED //////////////////////////////////////////////////////////////////////////*/ - /// @notice Creates a batch of LockupTranched streams using `createWithDurations`. + /// @notice Creates a batch of Lockup Tranched streams using `createWithDurationsLT`. /// /// @dev Requirements: /// - There must be at least one element in `batch`. - /// - All requirements from {ISablierLockupTranched.createWithDurations} must be met for each stream. + /// - All requirements from {ISablierLockup.createWithDurationsLT} must be met for each stream. /// - /// @param lockupTranched The address of the {SablierLockupTranched} contract. + /// @param lockup The address of the {SablierLockup} contract. /// @param asset The contract address of the ERC-20 asset to be distributed. /// @param batch An array of structs, each encapsulating a subset of the parameters of - /// {SablierLockupTranched.createWithDurations}. + /// {SablierLockup.createWithDurationsLT}. /// @return streamIds The ids of the newly created streams. function createWithDurationsLT( - ISablierLockupTranched lockupTranched, + ISablierLockup lockup, IERC20 asset, BatchLockup.CreateWithDurationsLT[] calldata batch ) external returns (uint256[] memory streamIds); - /// @notice Creates a batch of LockupTranched streams using `createWithTimestamps`. + /// @notice Creates a batch of Lockup Tranched streams using `createWithTimestampsLT`. /// /// @dev Requirements: /// - There must be at least one element in `batch`. - /// - All requirements from {ISablierLockupTranched.createWithTimestamps} must be met for each stream. + /// - All requirements from {ISablierLockup.createWithTimestampsLT} must be met for each stream. /// - /// @param lockupTranched The address of the {SablierLockupTranched} contract. + /// @param lockup The address of the {SablierLockup} contract. /// @param asset The contract address of the ERC-20 asset to be distributed. /// @param batch An array of structs, each encapsulating a subset of the parameters of - /// {SablierLockupTranched.createWithTimestamps}. + /// {SablierLockup.createWithTimestampsLT}. /// @return streamIds The ids of the newly created streams. function createWithTimestampsLT( - ISablierLockupTranched lockupTranched, + ISablierLockup lockup, IERC20 asset, BatchLockup.CreateWithTimestampsLT[] calldata batch ) diff --git a/src/periphery/interfaces/ISablierMerkleFactory.sol b/src/periphery/interfaces/ISablierMerkleFactory.sol index 304648996..47fb2ed24 100644 --- a/src/periphery/interfaces/ISablierMerkleFactory.sol +++ b/src/periphery/interfaces/ISablierMerkleFactory.sol @@ -2,8 +2,7 @@ pragma solidity >=0.8.22; import { IAdminable } from "../../core/interfaces/IAdminable.sol"; -import { ISablierLockupLinear } from "../../core/interfaces/ISablierLockupLinear.sol"; -import { ISablierLockupTranched } from "../../core/interfaces/ISablierLockupTranched.sol"; +import { ISablierLockup } from "../../core/interfaces/ISablierLockup.sol"; import { ISablierMerkleBase } from "../interfaces/ISablierMerkleBase.sol"; import { MerkleBase, MerkleFactory, MerkleLL, MerkleLT } from "../types/DataTypes.sol"; @@ -36,7 +35,7 @@ interface ISablierMerkleFactory is IAdminable { event CreateMerkleLL( ISablierMerkleLL indexed merkleLL, MerkleBase.ConstructorParams baseParams, - ISablierLockupLinear lockupLinear, + ISablierLockup lockup, bool cancelable, bool transferable, MerkleLL.Schedule schedule, @@ -49,7 +48,7 @@ interface ISablierMerkleFactory is IAdminable { event CreateMerkleLT( ISablierMerkleLT indexed merkleLT, MerkleBase.ConstructorParams baseParams, - ISablierLockupTranched lockupTranched, + ISablierLockup lockup, bool cancelable, bool transferable, uint40 streamStartTime, @@ -120,7 +119,7 @@ interface ISablierMerkleFactory is IAdminable { external returns (ISablierMerkleInstant merkleInstant); - /// @notice Creates a new Merkle Lockup campaign with a LockupLinear distribution. + /// @notice Creates a new Merkle Lockup campaign with a Lockup Linear distribution. /// /// @dev Emits a {CreateMerkleLL} event. /// @@ -130,7 +129,7 @@ interface ISablierMerkleFactory is IAdminable { /// /// @param baseParams Struct encapsulating the {SablierMerkleBase} parameters, which are documented in /// {DataTypes}. - /// @param lockupLinear The address of the {SablierLockupLinear} contract. + /// @param lockup The address of the {SablierLockup} contract. /// @param cancelable Indicates if the stream will be cancelable after claiming. /// @param transferable Indicates if the stream will be transferable after claiming. /// @param schedule The time variables to construct the stream timestamps. @@ -139,7 +138,7 @@ interface ISablierMerkleFactory is IAdminable { /// @return merkleLL The address of the newly created Merkle Lockup contract. function createMerkleLL( MerkleBase.ConstructorParams memory baseParams, - ISablierLockupLinear lockupLinear, + ISablierLockup lockup, bool cancelable, bool transferable, MerkleLL.Schedule memory schedule, @@ -149,7 +148,7 @@ interface ISablierMerkleFactory is IAdminable { external returns (ISablierMerkleLL merkleLL); - /// @notice Creates a new Merkle Lockup campaign with a LockupTranched distribution. + /// @notice Creates a new Merkle Lockup campaign with a Lockup Tranched distribution. /// /// @dev Emits a {CreateMerkleLT} event. /// @@ -159,7 +158,7 @@ interface ISablierMerkleFactory is IAdminable { /// /// @param baseParams Struct encapsulating the {SablierMerkleBase} parameters, which are documented in /// {DataTypes}. - /// @param lockupTranched The address of the {SablierLockupTranched} contract. + /// @param lockup The address of the {SablierLockup} contract. /// @param cancelable Indicates if the stream will be cancelable after claiming. /// @param transferable Indicates if the stream will be transferable after claiming. /// @param streamStartTime The start time of the streams created through {SablierMerkleBase.claim}. @@ -169,7 +168,7 @@ interface ISablierMerkleFactory is IAdminable { /// @return merkleLT The address of the newly created Merkle Lockup contract. function createMerkleLT( MerkleBase.ConstructorParams memory baseParams, - ISablierLockupTranched lockupTranched, + ISablierLockup lockup, bool cancelable, bool transferable, uint40 streamStartTime, diff --git a/src/periphery/interfaces/ISablierMerkleLL.sol b/src/periphery/interfaces/ISablierMerkleLL.sol index aee6630b2..2390bf022 100644 --- a/src/periphery/interfaces/ISablierMerkleLL.sol +++ b/src/periphery/interfaces/ISablierMerkleLL.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; -import { ISablierLockupLinear } from "../../core/interfaces/ISablierLockupLinear.sol"; +import { ISablierLockup } from "../../core/interfaces/ISablierLockup.sol"; import { ISablierMerkleBase } from "./ISablierMerkleBase.sol"; /// @title ISablierMerkleLL -/// @notice Merkle Lockup campaign that creates LockupLinear streams. +/// @notice Merkle Lockup campaign that creates Lockup Linear streams. interface ISablierMerkleLL is ISablierMerkleBase { /*////////////////////////////////////////////////////////////////////////// EVENTS @@ -23,15 +23,15 @@ interface ISablierMerkleLL is ISablierMerkleBase { /// @dev This is an immutable state variable. function CANCELABLE() external returns (bool); - /// @notice The address of the {SablierLockupLinear} contract. - function LOCKUP_LINEAR() external view returns (ISablierLockupLinear); + /// @notice The address of the {SablierLockup} contract. + function LOCKUP() external view returns (ISablierLockup); /// @notice A flag indicating whether the stream NFTs are transferable. /// @dev This is an immutable state variable. function TRANSFERABLE() external returns (bool); /// @notice The start time, cliff duration and the end duration used to calculate the time variables in - /// `LockupLinear.CreateWithTimestamps`. + /// `Lockup.CreateWithTimestampsLL`. /// @dev A start time value of zero will be considered as `block.timestamp`. function schedule() external view returns (uint40 startTime, uint40 cliffDuration, uint40 endDuration); } diff --git a/src/periphery/interfaces/ISablierMerkleLT.sol b/src/periphery/interfaces/ISablierMerkleLT.sol index 2bdb39bea..450c85288 100644 --- a/src/periphery/interfaces/ISablierMerkleLT.sol +++ b/src/periphery/interfaces/ISablierMerkleLT.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity >=0.8.22; -import { ISablierLockupTranched } from "./../../core/interfaces/ISablierLockupTranched.sol"; +import { ISablierLockup } from "./../../core/interfaces/ISablierLockup.sol"; import { MerkleLT } from "./../types/DataTypes.sol"; import { ISablierMerkleBase } from "./ISablierMerkleBase.sol"; /// @title ISablierMerkleLT -/// @notice Merkle Lockup campaign that creates LockupTranched streams. +/// @notice Merkle Lockup campaign that creates Lockup Tranched streams. interface ISablierMerkleLT is ISablierMerkleBase { /*////////////////////////////////////////////////////////////////////////// EVENTS @@ -23,8 +23,8 @@ interface ISablierMerkleLT is ISablierMerkleBase { /// @dev This is an immutable state variable. function CANCELABLE() external returns (bool); - /// @notice The address of the {SablierLockupTranched} contract. - function LOCKUP_TRANCHED() external view returns (ISablierLockupTranched); + /// @notice The address of the {SablierLockup} contract. + function LOCKUP() external view returns (ISablierLockup); /// @notice The start time of the streams created through {SablierMerkleBase.claim} function. /// @dev A start time value of zero will be considered as `block.timestamp`. diff --git a/src/periphery/types/DataTypes.sol b/src/periphery/types/DataTypes.sol index 5a1db1e3b..4670bac9b 100644 --- a/src/periphery/types/DataTypes.sol +++ b/src/periphery/types/DataTypes.sol @@ -4,23 +4,21 @@ pragma solidity >=0.8.22; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { UD2x18 } from "@prb/math/src/UD2x18.sol"; -import { Broker, LockupDynamic, LockupLinear, LockupTranched } from "../../core/types/DataTypes.sol"; +import { Broker, Lockup, LockupDynamic, LockupLinear, LockupTranched } from "../../core/types/DataTypes.sol"; library BatchLockup { - /// @notice A struct encapsulating all parameters of {SablierLockupDynamic.createWithDurations} except for the - /// asset. + /// @notice A struct encapsulating all parameters of {SablierLockup.createWithDurationsLD} except for the asset. struct CreateWithDurationsLD { address sender; address recipient; uint128 totalAmount; bool cancelable; bool transferable; - LockupDynamic.SegmentWithDuration[] segments; + LockupDynamic.SegmentWithDuration[] segmentsWithDuration; Broker broker; } - /// @notice A struct encapsulating all parameters of {SablierLockupLinear.createWithDurations} except for the - /// asset. + /// @notice A struct encapsulating all parameters of {SablierLockup.createWithDurationsLL} except for the asset. struct CreateWithDurationsLL { address sender; address recipient; @@ -31,20 +29,18 @@ library BatchLockup { Broker broker; } - /// @notice A struct encapsulating all parameters of {SablierLockupTranched.createWithDurations} except for the - /// asset. + /// @notice A struct encapsulating all parameters of {SablierLockup.createWithDurationsLT} except for the asset. struct CreateWithDurationsLT { address sender; address recipient; uint128 totalAmount; bool cancelable; bool transferable; - LockupTranched.TrancheWithDuration[] tranches; + LockupTranched.TrancheWithDuration[] tranchesWithDuration; Broker broker; } - /// @notice A struct encapsulating all parameters of {SablierLockupDynamic.createWithTimestamps} except for the - /// asset. + /// @notice A struct encapsulating all parameters of {SablierLockup.createWithTimestampsLD} except for the asset. struct CreateWithTimestampsLD { address sender; address recipient; @@ -56,20 +52,19 @@ library BatchLockup { Broker broker; } - /// @notice A struct encapsulating all parameters of {SablierLockupLinear.createWithTimestamps} except for the - /// asset. + /// @notice A struct encapsulating all parameters of {SablierLockup.createWithTimestampsLL} except for the asset. struct CreateWithTimestampsLL { address sender; address recipient; uint128 totalAmount; bool cancelable; bool transferable; - LockupLinear.Timestamps timestamps; + Lockup.Timestamps timestamps; + uint40 cliffTime; Broker broker; } - /// @notice A struct encapsulating all parameters of {SablierLockupTranched.createWithTimestamps} except for the - /// asset. + /// @notice A struct encapsulating all parameters of {SablierLockup.createWithTimestampsLT} except for the asset. struct CreateWithTimestampsLT { address sender; address recipient; @@ -113,7 +108,7 @@ library MerkleFactory { library MerkleLL { /// @notice Struct encapsulating the start time, cliff duration and the end duration used to construct the time - /// variables in `LockupLinear.CreateWithTimestamps`. + /// variables in `Lockup.CreateWithTimestampsLL`. /// @dev A start time value of zero will be considered as `block.timestamp`. /// @param startTime The start time of the stream. /// @param cliffDuration The duration of the cliff.