Address Contract Partially Verified
Address
0xd53006d1e3110fD319a79AEEc4c527a0d265E080
Balance
0.000200000 ETH ($0.39)
Nonce
1
Code Size
17344 bytes
Creator
0x00000010...C487 at tx 0x0094f059...a26c4c
Indexed Transactions
Index loading...
Contract Bytecode
17344 bytes
0x60806040526004361061020a575f3560e01c8063791b98bc11610113578063c4e833ce1161009d578063e1b4af691161006d578063e1b4af69146106c4578063e5a6b10f14610868578063ecaf76af1461089b578063f6dbee1e146108ce578063fc0c546a146108e7575f80fd5b8063c4e833ce146106e3578063d3decc68146107e3578063dc4c90d314610816578063dc98354e14610849575f80fd5b80639f063efc116100e35780639f063efc146104bc578063abf4fde41461061c578063af7a40ca1461064f578063b47b2fb114610682578063b6a8b0fa146106c4575f80fd5b8063791b98bc146105a35780637c121574146105d65780637d9f6db5146105ea5780638fd3ab8014610608575f80fd5b80634216204411610194578063575e24b411610164578063575e24b4146104725780636c2bbe7e146104bc5780636f0d5e68146104fb5780636fe7e6eb1461053e5780637164cf9b1461055d575f80fd5b806342162044146103995780634b9bf62b146103e4578063532cce181461042b578063570ca7351461043f575f80fd5b8063259982e5116101da578063259982e5146102e357806329db1be61461031b5780632c9dc6ab1461034e578063325564ec1461036f578063331f2f6514610385575f80fd5b806303d41eb614610215578063146278341461026557806318160ddd146102b057806321d0ee70146102e3575f80fd5b3661021157005b5f80fd5b348015610220575f80fd5b506102487f000000000000000000000000000000000000000000e1d1ffa7888968b100000081565b6040516001600160801b0390911681526020015b60405180910390f35b348015610270575f80fd5b506102987f0000000000000000000000007d6decf157e1329a20c4596eaf78d387e896aa4e81565b6040516001600160a01b03909116815260200161025c565b3480156102bb575f80fd5b506102487f000000000000000000000000000000000000000005e177fdb238e9649c00000081565b3480156102ee575f80fd5b506103026102fd366004613a66565b61091a565b6040516001600160e01b0319909116815260200161025c565b348015610326575f80fd5b506102987f000000000000000000000000a27ec0006e59f245217ff08cd52a7e8b169e62d281565b348015610359575f80fd5b5061036261097d565b60405161025c9190613b0a565b34801561037a575f80fd5b50610383610a09565b005b348015610390575f80fd5b50610383610a89565b3480156103a4575f80fd5b506103cc7f00000000000000000000000000000000000000000000000000000000016d874d81565b6040516001600160401b03909116815260200161025c565b3480156103ef575f80fd5b506104177f00000000000000000000000000000000000000000000000000000000000001f481565b60405162ffffff909116815260200161025c565b348015610436575f80fd5b50610383610dee565b34801561044a575f80fd5b506102987f00000000000000000000000013620833364653fa125ccdd7cf54b9e4a22ab6d981565b34801561047d575f80fd5b5061049161048c366004613b2c565b610fda565b604080516001600160e01b03199094168452602084019290925262ffffff169082015260600161025c565b3480156104c7575f80fd5b506104db6104d6366004613b85565b611044565b604080516001600160e01b0319909316835260208301919091520161025c565b348015610506575f80fd5b5061052e7f000000000000000000000000000000000000000000000000000000000000000081565b604051901515815260200161025c565b348015610549575f80fd5b50610302610558366004613c1d565b6110ae565b348015610568575f80fd5b506105907f000000000000000000000000000000000000000000000000000000000000000a81565b60405160029190910b815260200161025c565b3480156105ae575f80fd5b506102987f000000000000000000000000bd216513d74c8cf14cf4747e6aaa6420ff64ee9e81565b3480156105e1575f80fd5b50610383611104565b3480156105f5575f80fd5b505f54610298906001600160a01b031681565b348015610613575f80fd5b506103836112e4565b348015610627575f80fd5b506102987f0000000000000000000000000000ccadf55c911a2fbc0bb9d2942aa77c6faa1d81565b34801561065a575f80fd5b506102987f000000000000000000000000662de311f94bdbb571d95b5909e9cc6a25a6802a81565b34801561068d575f80fd5b506106a161069c366004613c76565b6113b6565b604080516001600160e01b03199093168352600f9190910b60208301520161025c565b3480156106cf575f80fd5b506103026106de366004613cf6565b61141e565b3480156106ee575f80fd5b506107d6604080516101c0810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e08101829052610100810182905261012081018290526101408101829052610160810182905261018081018290526101a081019190915250604080516101c08101825260018082525f60208301819052928201839052606082018390526080820183905260a0820183905260c082015260e08101829052610100810182905261012081018290526101408101829052610160810182905261018081018290526101a081019190915290565b60405161025c9190613d4f565b3480156107ee575f80fd5b506103cc7f00000000000000000000000000000000000000000000000000000000016da36d81565b348015610821575f80fd5b506102987f000000000000000000000000000000000004444c5dc75cb358380d2e3de08a9081565b348015610854575f80fd5b50610302610863366004613e70565b611481565b348015610873575f80fd5b506102987f000000000000000000000000000000000000000000000000000000000000000081565b3480156108a6575f80fd5b5061052e7f000000000000000000000000000000000000000000000000000000000000000081565b3480156108d9575f80fd5b5060025461052e9060ff1681565b3480156108f2575f80fd5b506102987f0000000000000000000000004b00c30ceba3f188407c6e6741cc5b43561f1f6e81565b5f336001600160a01b037f000000000000000000000000000000000004444c5dc75cb358380d2e3de08a9016146109645760405163570c108560e11b815260040160405180910390fd5b61097186868686866114e0565b90505b95945050505050565b6001805461098a90613eb7565b80601f01602080910402602001604051908101604052809291908181526020018280546109b690613eb7565b8015610a015780601f106109d857610100808354040283529160200191610a01565b820191905f5260205f20905b8154815290600101906020018083116109e457829003601f168201915b505050505081565b336001600160a01b037f0000000000000000000000007d6decf157e1329a20c4596eaf78d387e896aa4e1614610a5257604051632d5be4cb60e21b815260040160405180910390fd5b6002805460ff191660011790556040517fb5c3906b3448ef0ef60eb4570be19d593afecadbccff2f8a7de28d4098aa3f7b905f90a1565b6040516370a0823160e01b81523060048201527f000000000000000000000000000000000000000005e177fdb238e9649c0000006001600160801b0316907f0000000000000000000000004b00c30ceba3f188407c6e6741cc5b43561f1f6e6001600160a01b0316906370a0823190602401602060405180830381865afa158015610b16573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b3a9190613ee9565b1015610c17576040516370a0823160e01b81523060048201527f000000000000000000000000000000000000000005e177fdb238e9649c000000907f0000000000000000000000004b00c30ceba3f188407c6e6741cc5b43561f1f6e6001600160a01b0316906370a0823190602401602060405180830381865afa158015610bc4573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610be89190613ee9565b604051631ad2e45b60e21b81526001600160801b03909216600483015260248201526044015b60405180910390fd5b5f610c627f000000000000000000000000000000000000000000e1d1ffa7888968b10000007f000000000000000000000000000000000000000005e177fdb238e9649c000000613f14565b60405162ddc14160e21b81529091505f906001600160a01b037f0000000000000000000000000000ccadf55c911a2fbc0bb9d2942aa77c6faa1d1690630377050490610cd9907f0000000000000000000000004b00c30ceba3f188407c6e6741cc5b43561f1f6e9086906001908790600401613f33565b6020604051808303815f875af1158015610cf5573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d199190614005565b9050610d586001600160a01b037f0000000000000000000000004b00c30ceba3f188407c6e6741cc5b43561f1f6e16826001600160801b0385166114fa565b806001600160a01b031663331f2f656040518163ffffffff1660e01b81526004015f604051808303815f87803b158015610d90575f80fd5b505af1158015610da2573d5f803e3d5ffd5b50505f80546001600160a01b0319166001600160a01b03851690811782556040519093507f8a8cc462d00726e0f8c031dd2d6b9dcdf0794fb27a88579830dadee27d43ea7c9250a25050565b7f00000000000000000000000000000000000000000000000000000000016da36d6001600160401b0316431015610e6957604051634740621760e11b81526001600160401b037f00000000000000000000000000000000000000000000000000000000016da36d166004820152436024820152604401610c0e565b336001600160a01b037f00000000000000000000000013620833364653fa125ccdd7cf54b9e4a22ab6d91614610ee3576040516311ce341560e21b81523360048201526001600160a01b037f00000000000000000000000013620833364653fa125ccdd7cf54b9e4a22ab6d9166024820152604401610c0e565b5f610f176001600160a01b037f0000000000000000000000004b00c30ceba3f188407c6e6741cc5b43561f1f6e163061159f565b90508015610fd757610f736001600160a01b037f0000000000000000000000004b00c30ceba3f188407c6e6741cc5b43561f1f6e167f00000000000000000000000013620833364653fa125ccdd7cf54b9e4a22ab6d9836114fa565b7f00000000000000000000000013620833364653fa125ccdd7cf54b9e4a22ab6d96001600160a01b03167fdd9a81eb1b5197489c3ccfdab7b542e2e6dbdcf4120324e2688fab56fd23f98b82604051610fce91815260200190565b60405180910390a25b50565b5f8080336001600160a01b037f000000000000000000000000000000000004444c5dc75cb358380d2e3de08a9016146110265760405163570c108560e11b815260040160405180910390fd5b6110338888888888611630565b925092509250955095509592505050565b5f80336001600160a01b037f000000000000000000000000000000000004444c5dc75cb358380d2e3de08a90161461108f5760405163570c108560e11b815260040160405180910390fd5b61109e89898989898989611671565b9150915097509795505050505050565b5f336001600160a01b037f000000000000000000000000000000000004444c5dc75cb358380d2e3de08a9016146110f85760405163570c108560e11b815260040160405180910390fd5b610974858585856114e0565b7f00000000000000000000000000000000000000000000000000000000016da36d6001600160401b031643101561117f57604051634740621760e11b81526001600160401b037f00000000000000000000000000000000000000000000000000000000016da36d166004820152436024820152604401610c0e565b336001600160a01b037f00000000000000000000000013620833364653fa125ccdd7cf54b9e4a22ab6d916146111f9576040516311ce341560e21b81523360048201526001600160a01b037f00000000000000000000000013620833364653fa125ccdd7cf54b9e4a22ab6d9166024820152604401610c0e565b5f61122d6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163061159f565b90508015610fd7576112896001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167f00000000000000000000000013620833364653fa125ccdd7cf54b9e4a22ab6d9836114fa565b7f00000000000000000000000013620833364653fa125ccdd7cf54b9e4a22ab6d96001600160a01b03167f053dfa7183794b221b03c5109dfb5a07b67d719cb3e98262d48bc66b2a132ad382604051610fce91815260200190565b6112ec61168c565b5f6112f56118d9565b90505f61130182611ca7565b90505f61130d83611ed8565b905061131983826120aa565b8160405161136d919081516001600160a01b03908116825260208084015182169083015260408084015162ffffff169083015260608084015160020b90830152608092830151169181019190915260a00190565b60405190819003812084516001600160a01b03168252907f95cb306ff00e5e11a80ded9ef46c964545cdfb5c889f7f6a0711669d129f120f9060200160405180910390a2505050565b5f80336001600160a01b037f000000000000000000000000000000000004444c5dc75cb358380d2e3de08a9016146114015760405163570c108560e11b815260040160405180910390fd5b61140f888888888888611671565b91509150965096945050505050565b5f336001600160a01b037f000000000000000000000000000000000004444c5dc75cb358380d2e3de08a9016146114685760405163570c108560e11b815260040160405180910390fd5b6114768787878787876114e0565b979650505050505050565b5f336001600160a01b037f000000000000000000000000000000000004444c5dc75cb358380d2e3de08a9016146114cb5760405163570c108560e11b815260040160405180910390fd5b6114d68484846122b7565b90505b9392505050565b5f604051630a85dc2960e01b815260040160405180910390fd5b5f6001600160a01b03841661152f575f805f8085875af190508061152a5761152a835f633d2cec6f60e21b612302565b611599565b60405163a9059cbb60e01b81526001600160a01b038416600482015282602482015260205f6044835f895af13d15601f3d1160015f511416171691505f81525f60208201525f60408201525080611599576115998463a9059cbb60e01b633c9fd93960e21b612302565b50505050565b5f6001600160a01b0383166115bf57506001600160a01b0381163161162a565b6040516370a0823160e01b81526001600160a01b0383811660048301528416906370a0823190602401602060405180830381865afa158015611603573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116279190613ee9565b90505b92915050565b6002545f908190819060ff1661165957604051637657d18960e01b815260040160405180910390fd5b506315d7892d60e21b975f9750879650945050505050565b5f80604051630a85dc2960e01b815260040160405180910390fd5b7f00000000000000000000000000000000000000000000000000000000016d874d6001600160401b03164310156117075760405163a06fc0c960e01b81526001600160401b037f00000000000000000000000000000000000000000000000000000000016d874d166004820152436024820152604401610c0e565b5f8054906101000a90046001600160a01b03166001600160a01b031663c2c4c5c16040518163ffffffff1660e01b815260040160c0604051808303815f875af1158015611756573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061177a919061405c565b505f805f9054906101000a90046001600160a01b03166001600160a01b031663998ba4fc6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156117cb573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117ef9190613ee9565b90506001600160801b0381111561182957604051634defa31760e01b8152600481018290526001600160801b036024820152604401610c0e565b805f03611849576040516310ef230360e11b815260040160405180910390fd5b8061187d6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163061159f565b1015610fd757806118b76001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163061159f565b604051632bcaebd360e01b815260048101929092526024820152604401610c0e565b6040805160e0810182525f8082526020808301829052828401829052606083018290526080830182905260a0830182905260c0830182905281548451632662e93f60e21b81529451939492936001600160a01b039091169263998ba4fc9260048083019391928290030181865afa158015611956573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061197a9190613ee9565b90505f7f000000000000000000000000a27ec0006e59f245217ff08cd52a7e8b169e62d290505f611a51826001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316105f8054906101000a90046001600160a01b03166001600160a01b03166332a0f2d76040518163ffffffff1660e01b8152600401602060405180830381865afa158015611a27573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a4b9190613ee9565b9061237a565b9050611a5c81612441565b6001600160a01b039081168552611abf90829085908581167f0000000000000000000000000000000000000000000000000000000000000000909116107f000000000000000000000000000000000000000000e1d1ffa7888968b10000006124d8565b6001600160801b03908116606088015290811660408701521660208501528351611be990611b14611b0f7f000000000000000000000000000000000000000000000000000000000000000a6125be565b6125df565b611b40611b0f7f000000000000000000000000000000000000000000000000000000000000000a612897565b856001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031610611b83578760200151611b89565b87606001515b6001600160801b0316866001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031610611bd5578860600151611bdb565b88602001515b6001600160801b03166128af565b6001600160801b031660808501527f00000000000000000000000000000000000000000000000000000000000000008015611c59575083602001516001600160801b03167f000000000000000000000000000000000000000000e1d1ffa7888968b10000006001600160801b0316115b80611c9857507f00000000000000000000000000000000000000000000000000000000000000008015611c9857505f84604001516001600160801b0316115b151560a0850152509192915050565b6040805160a0810182525f808252602082018190529181018290526060810182905260808101829052907f000000000000000000000000a27ec0006e59f245217ff08cd52a7e8b169e62d290506040518060a00160405280826001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031610611d3e5782611d60565b7f00000000000000000000000000000000000000000000000000000000000000005b6001600160a01b03168152602001826001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031610611dcd577f0000000000000000000000000000000000000000000000000000000000000000611dcf565b825b6001600160a01b031681526020017f00000000000000000000000000000000000000000000000000000000000001f462ffffff1681526020017f000000000000000000000000000000000000000000000000000000000000000a60020b8152602001306001600160a01b031681525091507f000000000000000000000000000000000004444c5dc75cb358380d2e3de08a906001600160a01b0316636276cbbe83855f01516040518363ffffffff1660e01b8152600401611e91929190614134565b6020604051808303815f875af1158015611ead573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611ed1919061415a565b5050919050565b606080805f7f000000000000000000000000a27ec0006e59f245217ff08cd52a7e8b169e62d290505f6040518061010001604052807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602001836001600160a01b031681526020017f00000000000000000000000000000000000000000000000000000000000001f462ffffff1681526020017f000000000000000000000000000000000000000000000000000000000000000a60020b8152602001875f01516001600160a01b0316815260200187608001516001600160801b031681526020017f000000000000000000000000662de311f94bdbb571d95b5909e9cc6a25a6802a6001600160a01b03168152602001306001600160a01b031681525090508560a00151156120525761202181876020015188606001516005612964565b809450819550505061203e81858589602001518a604001516129a1565b805160051460c0890152909450925061206d565b61206781876020015188606001516004612964565b90945092505b612078818585612a27565b60405191955093506120909085908590602001614175565b604051602081830303815290604052945050505050919050565b5f6120b483612a41565b90506121136001600160a01b037f0000000000000000000000004b00c30ceba3f188407c6e6741cc5b43561f1f6e167f000000000000000000000000bd216513d74c8cf14cf4747e6aaa6420ff64ee9e6001600160801b0384166114fa565b5f61211d84612abd565b90506001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000166121d95760405163dd46508f60e01b81526001600160a01b037f000000000000000000000000bd216513d74c8cf14cf4747e6aaa6420ff64ee9e169063dd46508f906001600160801b038416906121a690879042906004016141ea565b5f604051808303818588803b1580156121bd575f80fd5b505af11580156121cf573d5f803e3d5ffd5b5050505050611599565b6122366001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000167f000000000000000000000000bd216513d74c8cf14cf4747e6aaa6420ff64ee9e6001600160801b0384166114fa565b60405163dd46508f60e01b81526001600160a01b037f000000000000000000000000bd216513d74c8cf14cf4747e6aaa6420ff64ee9e169063dd46508f9061228490869042906004016141ea565b5f604051808303815f87803b15801561229b575f80fd5b505af11580156122ad573d5f803e3d5ffd5b5050505050505050565b5f6001600160a01b03841630146122f257604051638cbcc02560e01b81526001600160a01b0385166004820152306024820152604401610c0e565b50636e4c1aa760e11b9392505050565b6040516390bfb86560e01b8082526001600160a01b03851660048301526001600160e01b031984166024830152608060448301526020601f3d018190040260a0810160648401523d608484015290913d5f60a483013e60048260a4018201526001600160e01b031984168260c4018201528160e40181fd5b5f825f0361239e5760405163d173d09f60e01b815260048101849052602401610c0e565b81156124065760a06123b484600160c01b61421f565b901c156123f3576123c983600160c01b61421f565b6040516387ebe85d60e01b815260048101919091526001600160a01b036024820152604401610c0e565b611627600160c01b600160601b85612afd565b60a083901c15612439576040516387ebe85d60e01b8152600481018490526001600160a01b036024820152604401610c0e565b505060601b90565b5f61244b82612b99565b90506401000276a36001600160a01b0382161080612485575073fffd8963efd1fc6a506488495d951d5263988d266001600160a01b038216115b156124d357604051633393dca160e11b81526001600160a01b03821660048201526401000276a3602482015273fffd8963efd1fc6a506488495d951d5263988d266044820152606401610c0e565b919050565b5f805f80856124fe576124f9876001600160801b0316600160c01b8a612afd565b612516565b61251688886001600160801b0316600160c01b612afd565b9050846001600160801b03168111156125ac575f8661254c5761254789876001600160801b0316600160c01b612afd565b612564565b612564866001600160801b0316600160c01b8b612afd565b90506001600160801b0381111561259457604051600162d630f360e01b0319815260048101829052602401610c0e565b9150816125a18189613f14565b9350859450506125b3565b8691508093505b509450945094915050565b5f81600281900b620d89e719816125d7576125d761420b565b050292915050565b60020b5f60ff82901d80830118620d89e8811115612608576126086345c3193d60e11b84612cf1565b7001fffcb933bd6fad37aa2d162d1a5940016001821602600160801b186002821615612644576ffff97272373d413259a46990580e213a0260801c5b6004821615612663576ffff2e50f5f656932ef12357cf3c7fdcc0260801c5b6008821615612682576fffe5caca7e10e4e61c3624eaa0941cd00260801c5b60108216156126a1576fffcb9843d60f6159c9db58835c9266440260801c5b60208216156126c0576fff973b41fa98c081472e6896dfb254c00260801c5b60408216156126df576fff2ea16466c96a3843ec78b326b528610260801c5b60808216156126fe576ffe5dee046a99a2a811c461f1969c30530260801c5b61010082161561271e576ffcbe86c7900a88aedcffc83b479aa3a40260801c5b61020082161561273e576ff987a7253ac413176f2b074cf7815e540260801c5b61040082161561275e576ff3392b0822b70005940c7a398e4b70f30260801c5b61080082161561277e576fe7159475a2c29b7443b29c7fa6e889d90260801c5b61100082161561279e576fd097f3bdfd2022b8845ad8f792aa58250260801c5b6120008216156127be576fa9f746462d870fdf8a65dc1f90e061e50260801c5b6140008216156127de576f70d869a156d2a1b890bb3df62baf32f70260801c5b6180008216156127fe576f31be135f97d08fd981231505542fcfa60260801c5b6201000082161561281f576f09aa508b5b7a84e1c677de54f3e99bc90260801c5b6202000082161561283f576e5d6af8dedb81196699c329225ee6040260801c5b6204000082161561285e576d2216e584f5fa1ea926041bedfe980260801c5b6208000082161561287b576b048a170391f7dc42444e8fa20260801c5b5f841315612887575f19045b63ffffffff0160201c9392505050565b5f81600281900b620d89e8816125d7576125d761420b565b5f836001600160a01b0316856001600160a01b031611156128ce579293925b846001600160a01b0316866001600160a01b0316116128f9576128f2858585612d00565b9050610974565b836001600160a01b0316866001600160a01b03161015612959575f61291f878686612d00565b90505f61292d878986612d61565b9050806001600160801b0316826001600160801b03161061294e5780612950565b815b92505050610974565b610971858584612d61565b604080518082019091526001600160801b038085168252831660208201526060908190612992878286612d9d565b92509250505b94509492505050565b6060805f80846001600160801b0316116129e4576129df857f000000000000000000000000000000000000000000e1d1ffa7888968b1000000613f14565b6129e6565b835b604080518082019091526001600160801b03808316825286161560208201819052919250612a168a828b8b612edb565b945094505050509550959350505050565b606080612a35858585613108565b91509150935093915050565b5f81602001516001600160801b03167f000000000000000000000000000000000000000000e1d1ffa7888968b10000006001600160801b0316118015612a8857508160c001515b612a9657816020015161162a565b7f000000000000000000000000000000000000000000e1d1ffa7888968b100000092915050565b5f8082604001516001600160801b0316118015612adb57508160c001515b612ae957816060015161162a565b8160400151826060015161162a919061423e565b5f838302815f1985870982811083820303915050808411612b1c575f80fd5b805f03612b2e575082900490506114d9565b5f848688095f868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a02909103029181900381900460010186841190950394909402919094039290920491909117919091029150509392505050565b5f60018211612ba6575090565b816001600160801b8210612bbf5760809190911c9060401b5b680100000000000000008210612bda5760409190911c9060201b5b6401000000008210612bf15760209190911c9060101b5b620100008210612c065760109190911c9060081b5b6101008210612c1a5760089190911c9060041b5b60108210612c2d5760049190911c9060021b5b60048210612c395760011b5b600302600190811c90818581612c5157612c5161420b565b048201901c90506001818581612c6957612c6961420b565b048201901c90506001818581612c8157612c8161420b565b048201901c90506001818581612c9957612c9961420b565b048201901c90506001818581612cb157612cb161420b565b048201901c90506001818581612cc957612cc961420b565b048201901c9050612ce8818581612ce257612ce261420b565b04821190565b90039392505050565b815f528060020b60045260245ffd5b5f826001600160a01b0316846001600160a01b03161115612d1f579192915b5f612d41856001600160a01b0316856001600160a01b0316600160601b612afd565b9050610974612d5c84838888036001600160a01b0316612afd565b613165565b5f826001600160a01b0316846001600160a01b03161115612d80579192915b6114d6612d5c83600160601b8787036001600160a01b0316612afd565b6060805f85602001516001600160a01b0316865f01516001600160a01b03161090505f6040518060400160405280612dd889606001516125be565b60020b8152602001612ded8960600151612897565b60020b81525090505f6040518060a0016040528084612e10578960200151612e13565b89515b6001600160a01b0316815260200184612e2d578951612e33565b89602001515b6001600160a01b03168152602001896040015162ffffff168152602001896060015160020b81526020018960e001516001600160a01b03168152509050612ea760408051600160f91b6020820152600b60f81b60218201819052602282015281516003818303018152602390910190915290565b9450612ece818385898c60c001518d60a001518d613186909695949392919063ffffffff16565b9350505050935093915050565b6060805f86602001516001600160a01b0316875f01516001600160a01b03161090505f8660200151151582151514612f2457612f1f8860800151896060015161335e565b612f36565b612f36886080015189606001516133dc565b805190915060020b158015612f505750602081015160020b155b15612f65575050600483525082905081612998565b5f612fcd8960800151612f7a845f01516125df565b612f8785602001516125df565b8b60200151151587151514612f9d578b51612f9f565b5f5b6001600160801b03168c60200151151588151514612fbd575f611bdb565b8c516001600160801b03166128af565b90506001600160801b0381161580613042575061301e896060015160020b5f60029190910b620d89e719818107929092129181900591909103620d89e891909105036001016001600160801b030490565b6001600160801b0316818a60a00151613037919061423e565b6001600160801b0316115b156130595760048652868694509450505050612998565b5f6040518060a0016040528085613074578b60200151613077565b8b515b6001600160a01b0316815260200185613091578b51613097565b8b602001515b6001600160a01b031681526020018b6040015162ffffff1681526020018b6060015160020b81526020018b60e001516001600160a01b031681525090506130dd8861344f565b60c08b01519096506130f9908a908390869088908c90886134ac565b94505050505094509492505050565b6020830151835160609182916001600160a01b0391821691161061312b85613589565b925061315a8161313f578660200151613142565b86515b8261314e578751613154565b87602001515b8661359f565b915050935093915050565b806001600160801b03811681146124d3576124d36393dafdf160e01b61363e565b60606004841415801561319a575060058414155b156131bb57604051631e1a3c5b60e01b815260048101859052602401610c0e565b836001600160401b038111156131d3576131d3614020565b60405190808252806020026020018201604052801561320657816020015b60608152602001906001900390816131f15790505b5090505f8561321657885161321c565b88602001515b90505f8661322e578960200151613231565b89515b88516020808b0151604080515f8152928301815293945061325f938d93928991889188918d9190810161425d565b604051602081830303815290604052835f81518110613280576132806142dd565b6020908102919091018101919091528951604080516001600160a01b0390921692820192909252600160ff1b918101919091525f6060820152608001604051602081830303815290604052836001815181106132de576132de6142dd565b60200260200101819052508860200151600160ff1b5f604051602001613324939291906001600160a01b0393909316835260208301919091521515604082015260600190565b60405160208183030381529060405283600281518110613346576133466142dd565b60200260200101819052505050979650505050505050565b604080518082019091525f80825260208201525f61337b84613646565b9050600283900b61338f82620d89e86142f1565b60020b1361339d575061162a565b60405180604001604052806133be858460020b6138d690919063ffffffff16565b60020b81526020016133cf85612897565b60020b9052949350505050565b604080518082019091525f80825260208201525f6133f984613646565b9050600283900b61340e620d89e719836142f1565b60020b121561341d575061162a565b6040518060400160405280613431856125be565b60020b81526020016133cf858460020b61391190919063ffffffff16565b606061345d60016004614316565b8251146134825781516040516311d8e0a760e11b8152600401610c0e91815260200190565b816002604051602001613496929190614329565b6040516020818303038152906040529050919050565b606060058451146134d5578351604051631e1a3c5b60e01b8152600401610c0e91815260200190565b60208801511515851515145f816134ed5789516134ef565b5f5b6001600160801b031690505f82613506575f613509565b8a515b89516020808c0151604080515f815292830181526001600160801b03949094169450613542938e93928a91889188918e91908101614353565b60408051601f198184030181529190528761355f60016004614316565b8151811061356f5761356f6142dd565b602090810291909101015250949998505050505050505050565b6060816011604051602001613496929190614329565b606060048251141580156135b557506005825114155b156135d8578151604051631e1a3c5b60e01b8152600401610c0e91815260200190565b604080516001600160a01b038087166020830152851691810191909152306060820152608001604051602081830303815290604052826001845161361c9190614316565b8151811061362c5761362c6142dd565b60209081029190910101525092915050565b805f5260045ffd5b5f73fffd8963efd1fc6a506488495d951d51639616826401000276a21983016001600160a01b03161115613685576136856318521d4960e21b83613949565b640100000000600160c01b03602083901b16805f6136a28261395e565b60ff169050608081106136bd57607f810383901c91506136c7565b80607f0383901b91505b908002607f81811c60ff83811c9190911c800280831c81831c1c800280841c81841c1c800280851c81851c1c800280861c81861c1c800280871c81871c1c800280881c81881c1c800280891c81891c1c8002808a1c818a1c1c8002808b1c818b1c1c8002808c1c818c1c1c8002808d1c818d1c1c8002808e1c9c81901c9c909c1c80029c8d901c9e9d607f198f0160401b60c09190911c678000000000000000161760c19b909b1c674000000000000000169a909a1760c29990991c672000000000000000169890981760c39790971c671000000000000000169690961760c49590951c670800000000000000169490941760c59390931c670400000000000000169290921760c69190911c670200000000000000161760c79190911c670100000000000000161760c89190911c6680000000000000161760c99190911c6640000000000000161760ca9190911c6620000000000000161760cb9190911c6610000000000000161760cc9190911c6608000000000000161760cd9190911c66040000000000001617693627a301d71055774c8581026f028f6481ab7f045a5af012a19d003aa9198101608090811d906fdb2df09e81959a81455e260799a0632f8301901d600281810b9083900b146138c757886001600160a01b03166138ac826125df565b6001600160a01b031611156138c157816138c9565b806138c9565b815b9998505050505050505050565b5f808260020b8460020b816138ed576138ed61420b565b0790505f8160020b121561390357808403613909565b80838501035b949350505050565b5f808260020b8460020b816139285761392861420b565b0790505f8160020b1215613940578281850303613909565b90920392915050565b815f526001600160a01b03811660045260245ffd5b5f80821161396a575f80fd5b507f0706060506020500060203020504000106050205030304010505030400000000601f6f8421084210842108cc6318c6db6d54be6001600160801b03841160071b84811c6001600160401b031060061b1784811c63ffffffff1060051b1784811c61ffff1060041b1784811c60ff1060031b1793841c1c161a1790565b6001600160a01b0381168114610fd7575f80fd5b5f60a08284031215613a0c575f80fd5b50919050565b5f60808284031215613a0c575f80fd5b5f8083601f840112613a32575f80fd5b5081356001600160401b03811115613a48575f80fd5b602083019150836020828501011115613a5f575f80fd5b9250929050565b5f805f805f6101608688031215613a7b575f80fd5b8535613a86816139e8565b9450613a9587602088016139fc565b9350613aa48760c08801613a12565b92506101408601356001600160401b03811115613abf575f80fd5b613acb88828901613a22565b969995985093965092949392505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f6114d96020830184613adc565b5f60608284031215613a0c575f80fd5b5f805f805f6101408688031215613b41575f80fd5b8535613b4c816139e8565b9450613b5b87602088016139fc565b9350613b6a8760c08801613b1c565b92506101208601356001600160401b03811115613abf575f80fd5b5f805f805f805f6101a0888a031215613b9c575f80fd5b8735613ba7816139e8565b9650613bb68960208a016139fc565b9550613bc58960c08a01613a12565b9450610140880135935061016088013592506101808801356001600160401b03811115613bf0575f80fd5b613bfc8a828b01613a22565b989b979a50959850939692959293505050565b8060020b8114610fd7575f80fd5b5f805f806101008587031215613c31575f80fd5b8435613c3c816139e8565b9350613c4b86602087016139fc565b925060c0850135613c5b816139e8565b915060e0850135613c6b81613c0f565b939692955090935050565b5f805f805f806101608789031215613c8c575f80fd5b8635613c97816139e8565b9550613ca688602089016139fc565b9450613cb58860c08901613b1c565b935061012087013592506101408701356001600160401b03811115613cd8575f80fd5b613ce489828a01613a22565b979a9699509497509295939492505050565b5f805f805f806101208789031215613d0c575f80fd5b8635613d17816139e8565b9550613d2688602089016139fc565b945060c0870135935060e087013592506101008701356001600160401b03811115613cd8575f80fd5b8151151581526101c081016020830151613d6d602084018215159052565b506040830151613d81604084018215159052565b506060830151613d95606084018215159052565b506080830151613da9608084018215159052565b5060a0830151613dbd60a084018215159052565b5060c0830151613dd160c084018215159052565b5060e0830151613de560e084018215159052565b50610100830151613dfb61010084018215159052565b50610120830151613e1161012084018215159052565b50610140830151613e2761014084018215159052565b50610160830151613e3d61016084018215159052565b50610180830151613e5361018084018215159052565b506101a0830151613e696101a084018215159052565b5092915050565b5f805f60e08486031215613e82575f80fd5b8335613e8d816139e8565b9250613e9c85602086016139fc565b915060c0840135613eac816139e8565b809150509250925092565b600181811c90821680613ecb57607f821691505b602082108103613a0c57634e487b7160e01b5f52602260045260245ffd5b5f60208284031215613ef9575f80fd5b5051919050565b634e487b7160e01b5f52601160045260245ffd5b6001600160801b03828116828216039081111561162a5761162a613f00565b6001600160a01b03851681526001600160801b03841660208201526080604082015282545f908190600181811c90821680613f6f57607f821691505b602082108103613f8d57634e487b7160e01b5f52602260045260245ffd5b6080860182905260a08601818015613fac5760018114613fc257613fee565b60ff198516825283151560051b82019550613fee565b5f8a8152602090205f5b85811015613fe857815484820152600190910190602001613fcc565b83019650505b505050505060609290920192909252949350505050565b5f60208284031215614015575f80fd5b81516114d9816139e8565b634e487b7160e01b5f52604160045260245ffd5b805162ffffff811681146124d3575f80fd5b80516001600160401b03811681146124d3575f80fd5b5f60c082840312801561406d575f80fd5b5060405160c081016001600160401b038111828210171561409c57634e487b7160e01b5f52604160045260245ffd5b6040908152835182526020808501519083015283810151908201526140c360608401614034565b60608201526140d460808401614046565b60808201526140e560a08401614046565b60a08201529392505050565b80516001600160a01b03908116835260208083015182169084015260408083015162ffffff169084015260608083015160020b9084015260809182015116910152565b60c0810161414282856140f1565b6001600160a01b039290921660a09190910152919050565b5f6020828403121561416a575f80fd5b81516114d981613c0f565b604081525f6141876040830185613adc565b828103602084015280845180835260208301915060208160051b840101602087015f5b838110156141dc57601f198684030185526141c6838351613adc565b60209586019590935091909101906001016141aa565b509098975050505050505050565b604081525f6141fc6040830185613adc565b90508260208301529392505050565b634e487b7160e01b5f52601260045260245ffd5b5f8261423957634e487b7160e01b5f52601260045260245ffd5b500490565b6001600160801b03818116838216019081111561162a5761162a613f00565b614267818a6140f1565b8760020b60a08201528660020b60c08201526001600160801b03861660e08201526001600160801b0385166101008201526001600160801b03841661012082015260018060a01b0383166101408201526101806101608201525f6142cf610180830184613adc565b9a9950505050505050505050565b634e487b7160e01b5f52603260045260245ffd5b600282810b9082900b03627fffff198112627fffff8213171561162a5761162a613f00565b8181038181111561162a5761162a613f00565b5f83518060208601845e60f89390931b6001600160f81b0319169190920190815260010192915050565b61435d818a6140f1565b8760020b60a08201528660020b60c08201526001600160801b03861660e0820152846101008201528361012082015260018060a01b0383166101408201526101806101608201525f6142cf610180830184613adc56fea164736f6c634300081a000a
Verified Source Code Partial Match
Compiler: v0.8.26+commit.8a97fa7a
EVM: cancun
Optimization: Yes (200 runs)
VirtualLBPStrategyBasic.sol 102 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {LBPStrategyBasic} from "./LBPStrategyBasic.sol";
import {IVirtualERC20} from "../interfaces/external/IVirtualERC20.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
import {MigratorParameters} from "../types/MigratorParameters.sol";
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol";
import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
import {ModifyLiquidityParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
/// @title VirtualLBPStrategyBasic
/// @notice Strategy for distributing virtual tokens to a v4 pool
/// Virtual tokens are ERC20 tokens that wrap an underlying token.
contract VirtualLBPStrategyBasic is LBPStrategyBasic {
/// @notice Emitted when migration is approved by the governance address
event MigrationApproved();
/// @notice Emitted when the governance address is set
/// @param governance The address of the governance address
event GovernanceSet(address governance);
/// @notice Error thrown when migration is not approved yet by the governance address
error MigrationNotApproved();
/// @notice Error thrown when the caller is not the governance address
error NotGovernance();
/// @notice The address of Aztec Governance
address public immutable GOVERNANCE;
/// @notice The address of the underlying token that is being distributed - used in the migrated pool
address public immutable UNDERLYING_TOKEN;
/// @notice Whether migration is approved by Governance
bool public isMigrationApproved = false;
constructor(
address _token,
uint128 _totalSupply,
MigratorParameters memory _migratorParams,
bytes memory _auctionParams,
IPositionManager _positionManager,
IPoolManager _poolManager,
address _governance
)
// Underlying strategy
LBPStrategyBasic(_token, _totalSupply, _migratorParams, _auctionParams, _positionManager, _poolManager)
{
UNDERLYING_TOKEN = IVirtualERC20(_token).UNDERLYING_TOKEN_ADDRESS();
GOVERNANCE = _governance;
emit GovernanceSet(_governance);
}
/// @notice Approves migration of the virtual token to the v4 pool
/// @dev Only callable by the governance address
function approveMigration() external {
if (msg.sender != GOVERNANCE) revert NotGovernance();
isMigrationApproved = true;
emit MigrationApproved();
}
/// @notice Returns the permissions for the hook
/// @dev Has permissions for before initialize, before swap and before remove liquidity
function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
return Hooks.Permissions({
beforeInitialize: true,
beforeAddLiquidity: false,
beforeSwap: true,
beforeSwapReturnDelta: false,
afterSwap: false,
afterInitialize: false,
beforeRemoveLiquidity: false,
afterAddLiquidity: false,
afterRemoveLiquidity: false,
beforeDonate: false,
afterDonate: false,
afterSwapReturnDelta: false,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}
/// @notice Validates that migration is approved before swapping on the pool and returns a zero delta
/// @dev Reverts if migration is not approved
function _beforeSwap(address, PoolKey calldata, SwapParams calldata, bytes calldata)
internal
view
override
returns (bytes4, BeforeSwapDelta, uint24)
{
if (!isMigrationApproved) revert MigrationNotApproved();
return (IHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0);
}
/// @notice Returns the address of the underlying token
function getPoolToken() internal view override returns (address) {
return UNDERLYING_TOKEN;
}
}
LBPStrategyBasic.sol 457 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {
IContinuousClearingAuction,
AuctionParameters
} from "continuous-clearing-auction/src/interfaces/IContinuousClearingAuction.sol";
import {ContinuousClearingAuction} from "continuous-clearing-auction/src/ContinuousClearingAuction.sol";
import {
IContinuousClearingAuctionFactory
} from "continuous-clearing-auction/src/interfaces/IContinuousClearingAuctionFactory.sol";
import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol";
import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol";
import {ActionConstants} from "@uniswap/v4-periphery/src/libraries/ActionConstants.sol";
import {LiquidityAmounts} from "@uniswap/v4-periphery/src/libraries/LiquidityAmounts.sol";
import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
import {IERC20} from "@openzeppelin-latest/contracts/token/ERC20/IERC20.sol";
import {IDistributionContract} from "../interfaces/IDistributionContract.sol";
import {MigratorParameters} from "../types/MigratorParameters.sol";
import {ILBPStrategyBasic} from "../interfaces/ILBPStrategyBasic.sol";
import {HookBasic} from "../utils/HookBasic.sol";
import {TokenPricing} from "../libraries/TokenPricing.sol";
import {StrategyPlanner} from "../libraries/StrategyPlanner.sol";
import {BasePositionParams, FullRangeParams, OneSidedParams} from "../types/PositionTypes.sol";
import {ParamsBuilder} from "../libraries/ParamsBuilder.sol";
import {MigrationData} from "../types/MigrationData.sol";
import {TokenDistribution} from "../libraries/TokenDistribution.sol";
/// @title LBPStrategyBasic
/// @notice Basic Strategy to distribute tokens and raise funds from an auction to a v4 pool
/// @custom:security-contact [email protected]
contract LBPStrategyBasic is ILBPStrategyBasic, HookBasic {
using CurrencyLibrary for Currency;
using StrategyPlanner for BasePositionParams;
using TokenDistribution for uint128;
using TokenPricing for uint256;
/// @notice The token that is being distributed
address public immutable token;
/// @notice The currency that the auction raised funds in
address public immutable currency;
/// @notice The LP fee that the v4 pool will use expressed in hundredths of a bip (1e6 = 100%)
uint24 public immutable poolLPFee;
/// @notice The tick spacing that the v4 pool will use
int24 public immutable poolTickSpacing;
/// @notice The supply of the token that was sent to this contract to be distributed
uint128 public immutable totalSupply;
/// @notice The remaining supply of the token that was not sent to the auction
uint128 public immutable reserveSupply;
/// @notice The address that will receive the position
address public immutable positionRecipient;
/// @notice The block number at which migration is allowed
uint64 public immutable migrationBlock;
/// @notice The auction factory that will be used to create the auction
address public immutable auctionFactory;
/// @notice The operator that can sweep currency and tokens from the pool after sweepBlock
address public immutable operator;
/// @notice The block number at which the operator can sweep currency and tokens from the pool
uint64 public immutable sweepBlock;
/// @notice Whether to create a one sided position in the token after the full range position
bool public immutable createOneSidedTokenPosition;
/// @notice Whether to create a one sided position in the currency after the full range position
bool public immutable createOneSidedCurrencyPosition;
/// @notice The position manager that will be used to create the position
IPositionManager public immutable positionManager;
/// @notice The auction that will be used to create the auction
IContinuousClearingAuction public auction;
bytes public auctionParameters;
constructor(
address _token,
uint128 _totalSupply,
MigratorParameters memory _migratorParams,
bytes memory _auctionParams,
IPositionManager _positionManager,
IPoolManager _poolManager
) HookBasic(_poolManager) {
_validateMigratorParams(_totalSupply, _migratorParams);
_validateAuctionParams(_auctionParams, _migratorParams);
auctionParameters = _auctionParams;
token = _token;
currency = _migratorParams.currency;
totalSupply = _totalSupply;
// Calculate tokens reserved for liquidity by subtracting tokens allocated for auction
// e.g. if tokenSplitToAuction = 5e6 (50%), then half goes to auction and half is reserved
reserveSupply = _totalSupply.calculateReserveSupply(_migratorParams.tokenSplitToAuction);
positionManager = _positionManager;
positionRecipient = _migratorParams.positionRecipient;
migrationBlock = _migratorParams.migrationBlock;
auctionFactory = _migratorParams.auctionFactory;
poolLPFee = _migratorParams.poolLPFee;
poolTickSpacing = _migratorParams.poolTickSpacing;
operator = _migratorParams.operator;
sweepBlock = _migratorParams.sweepBlock;
createOneSidedTokenPosition = _migratorParams.createOneSidedTokenPosition;
createOneSidedCurrencyPosition = _migratorParams.createOneSidedCurrencyPosition;
}
/// @notice Gets the address of the token that will be used to create the pool
/// @return The address of the token that will be used to create the pool
function getPoolToken() internal view virtual returns (address) {
return token;
}
/// @inheritdoc IDistributionContract
function onTokensReceived() external {
if (IERC20(token).balanceOf(address(this)) < totalSupply) {
revert InvalidAmountReceived(totalSupply, IERC20(token).balanceOf(address(this)));
}
uint128 auctionSupply = totalSupply - reserveSupply;
IContinuousClearingAuction _auction = IContinuousClearingAuction(
address(
IContinuousClearingAuctionFactory(auctionFactory)
.initializeDistribution(token, auctionSupply, auctionParameters, bytes32(0))
)
);
Currency.wrap(token).transfer(address(_auction), auctionSupply);
_auction.onTokensReceived();
auction = _auction;
emit AuctionCreated(address(_auction));
}
/// @inheritdoc ILBPStrategyBasic
function migrate() external {
_validateMigration();
MigrationData memory data = _prepareMigrationData();
PoolKey memory key = _initializePool(data);
bytes memory plan = _createPositionPlan(data);
_transferAssetsAndExecutePlan(data, plan);
emit Migrated(key, data.sqrtPriceX96);
}
/// @inheritdoc ILBPStrategyBasic
function sweepToken() external {
if (block.number < sweepBlock) revert SweepNotAllowed(sweepBlock, block.number);
if (msg.sender != operator) revert NotOperator(msg.sender, operator);
uint256 tokenBalance = Currency.wrap(token).balanceOf(address(this));
if (tokenBalance > 0) {
Currency.wrap(token).transfer(operator, tokenBalance);
emit TokensSwept(operator, tokenBalance);
}
}
/// @inheritdoc ILBPStrategyBasic
function sweepCurrency() external {
if (block.number < sweepBlock) revert SweepNotAllowed(sweepBlock, block.number);
if (msg.sender != operator) revert NotOperator(msg.sender, operator);
uint256 currencyBalance = Currency.wrap(currency).balanceOf(address(this));
if (currencyBalance > 0) {
Currency.wrap(currency).transfer(operator, currencyBalance);
emit CurrencySwept(operator, currencyBalance);
}
}
/// @notice Validates the migrator parameters and reverts if any are invalid. Continues if all are valid
/// @param _totalSupply The total supply of the token that was sent to this contract to be distributed
/// @param migratorParams The migrator parameters that will be used to create the v4 pool and position
function _validateMigratorParams(uint128 _totalSupply, MigratorParameters memory migratorParams) private pure {
// sweep block validation (cannot be before or equal to the migration block)
if (migratorParams.sweepBlock <= migratorParams.migrationBlock) {
revert InvalidSweepBlock(migratorParams.sweepBlock, migratorParams.migrationBlock);
}
// token split validation (cannot be greater than or equal to 100%)
else if (migratorParams.tokenSplitToAuction >= TokenDistribution.MAX_TOKEN_SPLIT) {
revert TokenSplitTooHigh(migratorParams.tokenSplitToAuction, TokenDistribution.MAX_TOKEN_SPLIT);
}
// tick spacing validation (cannot be greater than the v4 max tick spacing or less than the v4 min tick spacing)
else if (
migratorParams.poolTickSpacing > TickMath.MAX_TICK_SPACING
|| migratorParams.poolTickSpacing < TickMath.MIN_TICK_SPACING
) {
revert InvalidTickSpacing(
migratorParams.poolTickSpacing, TickMath.MIN_TICK_SPACING, TickMath.MAX_TICK_SPACING
);
}
// fee validation (cannot be greater than the v4 max fee)
else if (migratorParams.poolLPFee > LPFeeLibrary.MAX_LP_FEE) {
revert InvalidFee(migratorParams.poolLPFee, LPFeeLibrary.MAX_LP_FEE);
}
// position recipient validation (cannot be zero address, address(1), or address(2) which are reserved addresses on the position manager)
else if (
migratorParams.positionRecipient == address(0)
|| migratorParams.positionRecipient == ActionConstants.MSG_SENDER
|| migratorParams.positionRecipient == ActionConstants.ADDRESS_THIS
) {
revert InvalidPositionRecipient(migratorParams.positionRecipient);
}
// auction supply validation (cannot be zero)
else if (_totalSupply.calculateAuctionSupply(migratorParams.tokenSplitToAuction) == 0) {
revert AuctionSupplyIsZero();
}
}
/// @notice Validates that the auction parameters are valid
/// @dev Ensures that the `fundsRecipient` is set to ActionConstants.MSG_SENDER
/// and that the auction concludes before the configured migration block.
/// @param auctionParams The auction parameters that will be used to create the auction
/// @param migratorParams The migrator parameters that will be used to create the v4 pool and position
function _validateAuctionParams(bytes memory auctionParams, MigratorParameters memory migratorParams) private pure {
AuctionParameters memory _auctionParams = abi.decode(auctionParams, (AuctionParameters));
if (_auctionParams.fundsRecipient != ActionConstants.MSG_SENDER) {
revert InvalidFundsRecipient(_auctionParams.fundsRecipient, ActionConstants.MSG_SENDER);
} else if (_auctionParams.endBlock >= migratorParams.migrationBlock) {
revert InvalidEndBlock(_auctionParams.endBlock, migratorParams.migrationBlock);
} else if (_auctionParams.currency != migratorParams.currency) {
revert InvalidCurrency(_auctionParams.currency, migratorParams.currency);
}
}
/// @notice Validates migration timing and currency balance
function _validateMigration() private {
if (block.number < migrationBlock) {
revert MigrationNotAllowed(migrationBlock, block.number);
}
// call checkpoint to get the final currency raised and clearing price
auction.checkpoint();
uint256 currencyAmount = auction.currencyRaised();
// cannot create a v4 pool with more than type(uint128).max currency amount
if (currencyAmount > type(uint128).max) {
revert CurrencyAmountTooHigh(currencyAmount, type(uint128).max);
}
// cannot create a v4 pool with no currency raised
if (currencyAmount == 0) {
revert NoCurrencyRaised();
}
if (Currency.wrap(currency).balanceOf(address(this)) < currencyAmount) {
revert InsufficientCurrency(currencyAmount, Currency.wrap(currency).balanceOf(address(this)));
}
}
/// @notice Prepares all migration data including prices, amounts, and liquidity calculations
/// @return data MigrationData struct containing all calculated values
function _prepareMigrationData() private view returns (MigrationData memory data) {
uint128 currencyRaised = uint128(auction.currencyRaised()); // already validated to be less than or equal to type(uint128).max
address poolToken = getPoolToken();
uint256 priceX192 = auction.clearingPrice().convertToPriceX192(currency < poolToken);
data.sqrtPriceX96 = priceX192.convertToSqrtPriceX96();
(data.initialTokenAmount, data.leftoverCurrency, data.initialCurrencyAmount) =
priceX192.calculateAmounts(currencyRaised, currency < poolToken, reserveSupply);
data.liquidity = LiquidityAmounts.getLiquidityForAmounts(
data.sqrtPriceX96,
TickMath.getSqrtPriceAtTick(TickMath.minUsableTick(poolTickSpacing)),
TickMath.getSqrtPriceAtTick(TickMath.maxUsableTick(poolTickSpacing)),
currency < poolToken ? data.initialCurrencyAmount : data.initialTokenAmount,
currency < poolToken ? data.initialTokenAmount : data.initialCurrencyAmount
);
// Determine if we should create a one-sided position in tokens if createOneSidedTokenPosition is set OR
// if we should create a one-sided position in currency if createOneSidedCurrencyPosition is set and there is leftover currency
data.shouldCreateOneSided = createOneSidedTokenPosition && reserveSupply > data.initialTokenAmount
|| createOneSidedCurrencyPosition && data.leftoverCurrency > 0;
return data;
}
/// @notice Initializes the pool with the calculated price
/// @param data Migration data containing the sqrt price
/// @return key The pool key for the initialized pool
function _initializePool(MigrationData memory data) private returns (PoolKey memory key) {
address poolToken = getPoolToken();
key = PoolKey({
currency0: Currency.wrap(currency < poolToken ? currency : poolToken),
currency1: Currency.wrap(currency < poolToken ? poolToken : currency),
fee: poolLPFee,
tickSpacing: poolTickSpacing,
hooks: IHooks(address(this))
});
// Initialize the pool with the starting price determined by the auction
// Will revert if:
// - Pool is already initialized
// - Initial price is not set (sqrtPriceX96 = 0)
poolManager.initialize(key, data.sqrtPriceX96);
return key;
}
/// @notice Creates the position plan based on migration data
/// @param data Migration data with all necessary parameters
/// @return plan The encoded position plan
function _createPositionPlan(MigrationData memory data) private view returns (bytes memory plan) {
bytes memory actions;
bytes[] memory params;
address poolToken = getPoolToken();
// Create base parameters
BasePositionParams memory baseParams = BasePositionParams({
currency: currency,
poolToken: poolToken,
poolLPFee: poolLPFee,
poolTickSpacing: poolTickSpacing,
initialSqrtPriceX96: data.sqrtPriceX96,
liquidity: data.liquidity,
positionRecipient: positionRecipient,
hooks: IHooks(address(this))
});
if (data.shouldCreateOneSided) {
(actions, params) = _createFullRangePositionPlan(
baseParams,
data.initialTokenAmount,
data.initialCurrencyAmount,
ParamsBuilder.FULL_RANGE_WITH_ONE_SIDED_SIZE
);
(actions, params) = _createOneSidedPositionPlan(
baseParams, actions, params, data.initialTokenAmount, data.leftoverCurrency
);
// shouldCreatedOneSided could be true, but if the one sided position is not valid, only a full range position will be created and there will be no one sided params
data.hasOneSidedParams = params.length == ParamsBuilder.FULL_RANGE_WITH_ONE_SIDED_SIZE;
} else {
(actions, params) = _createFullRangePositionPlan(
baseParams, data.initialTokenAmount, data.initialCurrencyAmount, ParamsBuilder.FULL_RANGE_SIZE
);
}
(actions, params) = _createFinalTakePairPlan(baseParams, actions, params);
return abi.encode(actions, params);
}
/// @notice Transfers assets to position manager and executes the position plan
/// @param data Migration data with amounts and flags
/// @param plan The encoded position plan to execute
function _transferAssetsAndExecutePlan(MigrationData memory data, bytes memory plan) private {
// Calculate token amount to transfer
uint128 tokenTransferAmount = _getTokenTransferAmount(data);
// Transfer tokens to position manager
Currency.wrap(token).transfer(address(positionManager), tokenTransferAmount);
// Calculate currency amount and execute plan
uint128 currencyTransferAmount = _getCurrencyTransferAmount(data);
if (Currency.wrap(currency).isAddressZero()) {
// Native currency: send as value with modifyLiquidities call
positionManager.modifyLiquidities{value: currencyTransferAmount}(plan, block.timestamp);
} else {
// Non-native currency: transfer first, then call modifyLiquidities
Currency.wrap(currency).transfer(address(positionManager), currencyTransferAmount);
positionManager.modifyLiquidities(plan, block.timestamp);
}
}
/// @notice Calculates the amount of tokens to transfer
/// @param data Migration data
/// @return The amount of tokens to transfer to the position manager
function _getTokenTransferAmount(MigrationData memory data) private view returns (uint128) {
// hasOneSidedParams can only be true if shouldCreateOneSided is true
return
(reserveSupply > data.initialTokenAmount && data.hasOneSidedParams)
? reserveSupply
: data.initialTokenAmount;
}
/// @notice Calculates the amount of currency to transfer
/// @param data Migration data
/// @return The amount of currency to transfer to the position manager
function _getCurrencyTransferAmount(MigrationData memory data) private pure returns (uint128) {
// hasOneSidedParams can only be true if shouldCreateOneSided is true
return (data.leftoverCurrency > 0 && data.hasOneSidedParams)
? data.initialCurrencyAmount + data.leftoverCurrency
: data.initialCurrencyAmount;
}
/// @notice Creates the plan for creating a full range v4 position using the position manager
/// @param baseParams The base parameters for the position
/// @param tokenAmount The amount of token to be used to create the position
/// @param currencyAmount The amount of currency to be used to create the position
/// @param paramsArraySize The size of the parameters array (either 5 or 8)
/// @return The actions and parameters for the position
function _createFullRangePositionPlan(
BasePositionParams memory baseParams,
uint128 tokenAmount,
uint128 currencyAmount,
uint256 paramsArraySize
) private pure returns (bytes memory, bytes[] memory) {
// Create full range specific parameters
FullRangeParams memory fullRangeParams =
FullRangeParams({tokenAmount: tokenAmount, currencyAmount: currencyAmount});
// Plan the full range position
return baseParams.planFullRangePosition(fullRangeParams, paramsArraySize);
}
/// @notice Creates the plan for creating a one sided v4 position using the position manager along with the full range position
/// @param baseParams The base parameters for the position
/// @param actions The existing actions for the full range position which may be extended with the new actions for the one sided position
/// @param params The existing parameters for the full range position which may be extended with the new parameters for the one sided position
/// @param tokenAmount The amount of token to be used to create the position
/// @param leftoverCurrency The amount of currency that was leftover from the full range position
/// @return The actions and parameters needed to create the full range position and the one sided position
function _createOneSidedPositionPlan(
BasePositionParams memory baseParams,
bytes memory actions,
bytes[] memory params,
uint128 tokenAmount,
uint128 leftoverCurrency
) private view returns (bytes memory, bytes[] memory) {
// reserveSupply - tokenAmount will not underflow because of validation in TokenPricing.calculateAmounts()
uint128 amount = leftoverCurrency > 0 ? leftoverCurrency : reserveSupply - tokenAmount;
bool inToken = leftoverCurrency == 0;
// Create one-sided specific parameters
OneSidedParams memory oneSidedParams = OneSidedParams({amount: amount, inToken: inToken});
// Plan the one-sided position
return baseParams.planOneSidedPosition(oneSidedParams, actions, params);
}
/// @notice Creates the plan for taking the pair using the position manager
/// @param baseParams The base parameters for the position
/// @param actions The existing actions for the position which may be extended with the new actions for the final take pair
/// @param params The existing parameters for the position which may be extended with the new parameters for the final take pair
/// @return The actions and parameters needed to take the pair using the position manager
function _createFinalTakePairPlan(BasePositionParams memory baseParams, bytes memory actions, bytes[] memory params)
private
view
returns (bytes memory, bytes[] memory)
{
return baseParams.planFinalTakePair(actions, params);
}
/// @notice Receives native currency
receive() external payable {}
}
IVirtualERC20.sol 7 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IVirtualERC20 {
function UNDERLYING_TOKEN_ADDRESS() external view returns (address);
function transfer(address to, uint256 amount) external returns (bool);
}
IPoolManager.sol 217 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Currency} from "../types/Currency.sol";
import {PoolKey} from "../types/PoolKey.sol";
import {IHooks} from "./IHooks.sol";
import {IERC6909Claims} from "./external/IERC6909Claims.sol";
import {IProtocolFees} from "./IProtocolFees.sol";
import {BalanceDelta} from "../types/BalanceDelta.sol";
import {PoolId} from "../types/PoolId.sol";
import {IExtsload} from "./IExtsload.sol";
import {IExttload} from "./IExttload.sol";
import {ModifyLiquidityParams, SwapParams} from "../types/PoolOperation.sol";
/// @notice Interface for the PoolManager
interface IPoolManager is IProtocolFees, IERC6909Claims, IExtsload, IExttload {
/// @notice Thrown when a currency is not netted out after the contract is unlocked
error CurrencyNotSettled();
/// @notice Thrown when trying to interact with a non-initialized pool
error PoolNotInitialized();
/// @notice Thrown when unlock is called, but the contract is already unlocked
error AlreadyUnlocked();
/// @notice Thrown when a function is called that requires the contract to be unlocked, but it is not
error ManagerLocked();
/// @notice Pools are limited to type(int16).max tickSpacing in #initialize, to prevent overflow
error TickSpacingTooLarge(int24 tickSpacing);
/// @notice Pools must have a positive non-zero tickSpacing passed to #initialize
error TickSpacingTooSmall(int24 tickSpacing);
/// @notice PoolKey must have currencies where address(currency0) < address(currency1)
error CurrenciesOutOfOrderOrEqual(address currency0, address currency1);
/// @notice Thrown when a call to updateDynamicLPFee is made by an address that is not the hook,
/// or on a pool that does not have a dynamic swap fee.
error UnauthorizedDynamicLPFeeUpdate();
/// @notice Thrown when trying to swap amount of 0
error SwapAmountCannotBeZero();
///@notice Thrown when native currency is passed to a non native settlement
error NonzeroNativeValue();
/// @notice Thrown when `clear` is called with an amount that is not exactly equal to the open currency delta.
error MustClearExactPositiveDelta();
/// @notice Emitted when a new pool is initialized
/// @param id The abi encoded hash of the pool key struct for the new pool
/// @param currency0 The first currency of the pool by address sort order
/// @param currency1 The second currency of the pool by address sort order
/// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
/// @param tickSpacing The minimum number of ticks between initialized ticks
/// @param hooks The hooks contract address for the pool, or address(0) if none
/// @param sqrtPriceX96 The price of the pool on initialization
/// @param tick The initial tick of the pool corresponding to the initialized price
event Initialize(
PoolId indexed id,
Currency indexed currency0,
Currency indexed currency1,
uint24 fee,
int24 tickSpacing,
IHooks hooks,
uint160 sqrtPriceX96,
int24 tick
);
/// @notice Emitted when a liquidity position is modified
/// @param id The abi encoded hash of the pool key struct for the pool that was modified
/// @param sender The address that modified the pool
/// @param tickLower The lower tick of the position
/// @param tickUpper The upper tick of the position
/// @param liquidityDelta The amount of liquidity that was added or removed
/// @param salt The extra data to make positions unique
event ModifyLiquidity(
PoolId indexed id, address indexed sender, int24 tickLower, int24 tickUpper, int256 liquidityDelta, bytes32 salt
);
/// @notice Emitted for swaps between currency0 and currency1
/// @param id The abi encoded hash of the pool key struct for the pool that was modified
/// @param sender The address that initiated the swap call, and that received the callback
/// @param amount0 The delta of the currency0 balance of the pool
/// @param amount1 The delta of the currency1 balance of the pool
/// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96
/// @param liquidity The liquidity of the pool after the swap
/// @param tick The log base 1.0001 of the price of the pool after the swap
/// @param fee The swap fee in hundredths of a bip
event Swap(
PoolId indexed id,
address indexed sender,
int128 amount0,
int128 amount1,
uint160 sqrtPriceX96,
uint128 liquidity,
int24 tick,
uint24 fee
);
/// @notice Emitted for donations
/// @param id The abi encoded hash of the pool key struct for the pool that was donated to
/// @param sender The address that initiated the donate call
/// @param amount0 The amount donated in currency0
/// @param amount1 The amount donated in currency1
event Donate(PoolId indexed id, address indexed sender, uint256 amount0, uint256 amount1);
/// @notice All interactions on the contract that account deltas require unlocking. A caller that calls `unlock` must implement
/// `IUnlockCallback(msg.sender).unlockCallback(data)`, where they interact with the remaining functions on this contract.
/// @dev The only functions callable without an unlocking are `initialize` and `updateDynamicLPFee`
/// @param data Any data to pass to the callback, via `IUnlockCallback(msg.sender).unlockCallback(data)`
/// @return The data returned by the call to `IUnlockCallback(msg.sender).unlockCallback(data)`
function unlock(bytes calldata data) external returns (bytes memory);
/// @notice Initialize the state for a given pool ID
/// @dev A swap fee totaling MAX_SWAP_FEE (100%) makes exact output swaps impossible since the input is entirely consumed by the fee
/// @param key The pool key for the pool to initialize
/// @param sqrtPriceX96 The initial square root price
/// @return tick The initial tick of the pool
function initialize(PoolKey memory key, uint160 sqrtPriceX96) external returns (int24 tick);
/// @notice Modify the liquidity for the given pool
/// @dev Poke by calling with a zero liquidityDelta
/// @param key The pool to modify liquidity in
/// @param params The parameters for modifying the liquidity
/// @param hookData The data to pass through to the add/removeLiquidity hooks
/// @return callerDelta The balance delta of the caller of modifyLiquidity. This is the total of both principal, fee deltas, and hook deltas if applicable
/// @return feesAccrued The balance delta of the fees generated in the liquidity range. Returned for informational purposes
/// @dev Note that feesAccrued can be artificially inflated by a malicious actor and integrators should be careful using the value
/// For pools with a single liquidity position, actors can donate to themselves to inflate feeGrowthGlobal (and consequently feesAccrued)
/// atomically donating and collecting fees in the same unlockCallback may make the inflated value more extreme
function modifyLiquidity(PoolKey memory key, ModifyLiquidityParams memory params, bytes calldata hookData)
external
returns (BalanceDelta callerDelta, BalanceDelta feesAccrued);
/// @notice Swap against the given pool
/// @param key The pool to swap in
/// @param params The parameters for swapping
/// @param hookData The data to pass through to the swap hooks
/// @return swapDelta The balance delta of the address swapping
/// @dev Swapping on low liquidity pools may cause unexpected swap amounts when liquidity available is less than amountSpecified.
/// Additionally note that if interacting with hooks that have the BEFORE_SWAP_RETURNS_DELTA_FLAG or AFTER_SWAP_RETURNS_DELTA_FLAG
/// the hook may alter the swap input/output. Integrators should perform checks on the returned swapDelta.
function swap(PoolKey memory key, SwapParams memory params, bytes calldata hookData)
external
returns (BalanceDelta swapDelta);
/// @notice Donate the given currency amounts to the in-range liquidity providers of a pool
/// @dev Calls to donate can be frontrun adding just-in-time liquidity, with the aim of receiving a portion donated funds.
/// Donors should keep this in mind when designing donation mechanisms.
/// @dev This function donates to in-range LPs at slot0.tick. In certain edge-cases of the swap algorithm, the `sqrtPrice` of
/// a pool can be at the lower boundary of tick `n`, but the `slot0.tick` of the pool is already `n - 1`. In this case a call to
/// `donate` would donate to tick `n - 1` (slot0.tick) not tick `n` (getTickAtSqrtPrice(slot0.sqrtPriceX96)).
/// Read the comments in `Pool.swap()` for more information about this.
/// @param key The key of the pool to donate to
/// @param amount0 The amount of currency0 to donate
/// @param amount1 The amount of currency1 to donate
/// @param hookData The data to pass through to the donate hooks
/// @return BalanceDelta The delta of the caller after the donate
function donate(PoolKey memory key, uint256 amount0, uint256 amount1, bytes calldata hookData)
external
returns (BalanceDelta);
/// @notice Writes the current ERC20 balance of the specified currency to transient storage
/// This is used to checkpoint balances for the manager and derive deltas for the caller.
/// @dev This MUST be called before any ERC20 tokens are sent into the contract, but can be skipped
/// for native tokens because the amount to settle is determined by the sent value.
/// However, if an ERC20 token has been synced and not settled, and the caller instead wants to settle
/// native funds, this function can be called with the native currency to then be able to settle the native currency
function sync(Currency currency) external;
/// @notice Called by the user to net out some value owed to the user
/// @dev Will revert if the requested amount is not available, consider using `mint` instead
/// @dev Can also be used as a mechanism for free flash loans
/// @param currency The currency to withdraw from the pool manager
/// @param to The address to withdraw to
/// @param amount The amount of currency to withdraw
function take(Currency currency, address to, uint256 amount) external;
/// @notice Called by the user to pay what is owed
/// @return paid The amount of currency settled
function settle() external payable returns (uint256 paid);
/// @notice Called by the user to pay on behalf of another address
/// @param recipient The address to credit for the payment
/// @return paid The amount of currency settled
function settleFor(address recipient) external payable returns (uint256 paid);
/// @notice WARNING - Any currency that is cleared, will be non-retrievable, and locked in the contract permanently.
/// A call to clear will zero out a positive balance WITHOUT a corresponding transfer.
/// @dev This could be used to clear a balance that is considered dust.
/// Additionally, the amount must be the exact positive balance. This is to enforce that the caller is aware of the amount being cleared.
function clear(Currency currency, uint256 amount) external;
/// @notice Called by the user to move value into ERC6909 balance
/// @param to The address to mint the tokens to
/// @param id The currency address to mint to ERC6909s, as a uint256
/// @param amount The amount of currency to mint
/// @dev The id is converted to a uint160 to correspond to a currency address
/// If the upper 12 bytes are not 0, they will be 0-ed out
function mint(address to, uint256 id, uint256 amount) external;
/// @notice Called by the user to move value from ERC6909 balance
/// @param from The address to burn the tokens from
/// @param id The currency address to burn from ERC6909s, as a uint256
/// @param amount The amount of currency to burn
/// @dev The id is converted to a uint160 to correspond to a currency address
/// If the upper 12 bytes are not 0, they will be 0-ed out
function burn(address from, uint256 id, uint256 amount) external;
/// @notice Updates the pools lp fees for the a pool that has enabled dynamic lp fees.
/// @dev A swap fee totaling MAX_SWAP_FEE (100%) makes exact output swaps impossible since the input is entirely consumed by the fee
/// @param key The key of the pool to update dynamic LP fees for
/// @param newDynamicLPFee The new dynamic pool LP fee
function updateDynamicLPFee(PoolKey memory key, uint24 newDynamicLPFee) external;
}
IPositionManager.sol 68 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PositionInfo} from "../libraries/PositionInfoLibrary.sol";
import {INotifier} from "./INotifier.sol";
import {IImmutableState} from "./IImmutableState.sol";
import {IERC721Permit_v4} from "./IERC721Permit_v4.sol";
import {IEIP712_v4} from "./IEIP712_v4.sol";
import {IMulticall_v4} from "./IMulticall_v4.sol";
import {IPoolInitializer_v4} from "./IPoolInitializer_v4.sol";
import {IUnorderedNonce} from "./IUnorderedNonce.sol";
import {IPermit2Forwarder} from "./IPermit2Forwarder.sol";
/// @title IPositionManager
/// @notice Interface for the PositionManager contract
interface IPositionManager is
INotifier,
IImmutableState,
IERC721Permit_v4,
IEIP712_v4,
IMulticall_v4,
IPoolInitializer_v4,
IUnorderedNonce,
IPermit2Forwarder
{
/// @notice Thrown when the caller is not approved to modify a position
error NotApproved(address caller);
/// @notice Thrown when the block.timestamp exceeds the user-provided deadline
error DeadlinePassed(uint256 deadline);
/// @notice Thrown when calling transfer, subscribe, or unsubscribe when the PoolManager is unlocked.
/// @dev This is to prevent hooks from being able to trigger notifications at the same time the position is being modified.
error PoolManagerMustBeLocked();
/// @notice Unlocks Uniswap v4 PoolManager and batches actions for modifying liquidity
/// @dev This is the standard entrypoint for the PositionManager
/// @param unlockData is an encoding of actions, and parameters for those actions
/// @param deadline is the deadline for the batched actions to be executed
function modifyLiquidities(bytes calldata unlockData, uint256 deadline) external payable;
/// @notice Batches actions for modifying liquidity without unlocking v4 PoolManager
/// @dev This must be called by a contract that has already unlocked the v4 PoolManager
/// @param actions the actions to perform
/// @param params the parameters to provide for the actions
function modifyLiquiditiesWithoutUnlock(bytes calldata actions, bytes[] calldata params) external payable;
/// @notice Used to get the ID that will be used for the next minted liquidity position
/// @return uint256 The next token ID
function nextTokenId() external view returns (uint256);
/// @notice Returns the liquidity of a position
/// @param tokenId the ERC721 tokenId
/// @return liquidity the position's liquidity, as a liquidityAmount
/// @dev this value can be processed as an amount0 and amount1 by using the LiquidityAmounts library
function getPositionLiquidity(uint256 tokenId) external view returns (uint128 liquidity);
/// @notice Returns the pool key and position info of a position
/// @param tokenId the ERC721 tokenId
/// @return poolKey the pool key of the position
/// @return PositionInfo a uint256 packed value holding information about the position including the range (tickLower, tickUpper)
function getPoolAndPositionInfo(uint256 tokenId) external view returns (PoolKey memory, PositionInfo);
/// @notice Returns the position info of a position
/// @param tokenId the ERC721 tokenId
/// @return a uint256 packed value holding information about the position including the range (tickLower, tickUpper)
function positionInfo(uint256 tokenId) external view returns (PositionInfo);
}
MigratorParameters.sol 18 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title MigratorParameters
/// @notice Parameters for the LBPStrategyBasic contract
struct MigratorParameters {
uint64 migrationBlock; // block number when the migration can begin
address currency; // the currency that the token will be paired with in the v4 pool (currency that the auction raised funds in)
uint24 poolLPFee; // the LP fee that the v4 pool will use
int24 poolTickSpacing; // the tick spacing that the v4 pool will use
uint24 tokenSplitToAuction; // the percentage of the total supply of the token that will be sent to the auction, expressed in mps (1e7 = 100%)
address auctionFactory; // the Auction factory that will be used to create the auction
address positionRecipient; // the address that will receive the position
uint64 sweepBlock; // the block number when the operator can sweep currency and tokens from the pool
address operator; // the address that is able to sweep currency and tokens from the pool
bool createOneSidedTokenPosition; // whether to try to create a one-sided position in the token after the full range position or not
bool createOneSidedCurrencyPosition; // whether to try to create a one-sided position in the currency after the full range position or not
}
Hooks.sol 340 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "../types/PoolKey.sol";
import {IHooks} from "../interfaces/IHooks.sol";
import {SafeCast} from "./SafeCast.sol";
import {LPFeeLibrary} from "./LPFeeLibrary.sol";
import {BalanceDelta, toBalanceDelta, BalanceDeltaLibrary} from "../types/BalanceDelta.sol";
import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "../types/BeforeSwapDelta.sol";
import {IPoolManager} from "../interfaces/IPoolManager.sol";
import {ModifyLiquidityParams, SwapParams} from "../types/PoolOperation.sol";
import {ParseBytes} from "./ParseBytes.sol";
import {CustomRevert} from "./CustomRevert.sol";
/// @notice V4 decides whether to invoke specific hooks by inspecting the least significant bits
/// of the address that the hooks contract is deployed to.
/// For example, a hooks contract deployed to address: 0x0000000000000000000000000000000000002400
/// has the lowest bits '10 0100 0000 0000' which would cause the 'before initialize' and 'after add liquidity' hooks to be used.
library Hooks {
using LPFeeLibrary for uint24;
using Hooks for IHooks;
using SafeCast for int256;
using BeforeSwapDeltaLibrary for BeforeSwapDelta;
using ParseBytes for bytes;
using CustomRevert for bytes4;
uint160 internal constant ALL_HOOK_MASK = uint160((1 << 14) - 1);
uint160 internal constant BEFORE_INITIALIZE_FLAG = 1 << 13;
uint160 internal constant AFTER_INITIALIZE_FLAG = 1 << 12;
uint160 internal constant BEFORE_ADD_LIQUIDITY_FLAG = 1 << 11;
uint160 internal constant AFTER_ADD_LIQUIDITY_FLAG = 1 << 10;
uint160 internal constant BEFORE_REMOVE_LIQUIDITY_FLAG = 1 << 9;
uint160 internal constant AFTER_REMOVE_LIQUIDITY_FLAG = 1 << 8;
uint160 internal constant BEFORE_SWAP_FLAG = 1 << 7;
uint160 internal constant AFTER_SWAP_FLAG = 1 << 6;
uint160 internal constant BEFORE_DONATE_FLAG = 1 << 5;
uint160 internal constant AFTER_DONATE_FLAG = 1 << 4;
uint160 internal constant BEFORE_SWAP_RETURNS_DELTA_FLAG = 1 << 3;
uint160 internal constant AFTER_SWAP_RETURNS_DELTA_FLAG = 1 << 2;
uint160 internal constant AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG = 1 << 1;
uint160 internal constant AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG = 1 << 0;
struct Permissions {
bool beforeInitialize;
bool afterInitialize;
bool beforeAddLiquidity;
bool afterAddLiquidity;
bool beforeRemoveLiquidity;
bool afterRemoveLiquidity;
bool beforeSwap;
bool afterSwap;
bool beforeDonate;
bool afterDonate;
bool beforeSwapReturnDelta;
bool afterSwapReturnDelta;
bool afterAddLiquidityReturnDelta;
bool afterRemoveLiquidityReturnDelta;
}
/// @notice Thrown if the address will not lead to the specified hook calls being called
/// @param hooks The address of the hooks contract
error HookAddressNotValid(address hooks);
/// @notice Hook did not return its selector
error InvalidHookResponse();
/// @notice Additional context for ERC-7751 wrapped error when a hook call fails
error HookCallFailed();
/// @notice The hook's delta changed the swap from exactIn to exactOut or vice versa
error HookDeltaExceedsSwapAmount();
/// @notice Utility function intended to be used in hook constructors to ensure
/// the deployed hooks address causes the intended hooks to be called
/// @param permissions The hooks that are intended to be called
/// @dev permissions param is memory as the function will be called from constructors
function validateHookPermissions(IHooks self, Permissions memory permissions) internal pure {
if (
permissions.beforeInitialize != self.hasPermission(BEFORE_INITIALIZE_FLAG)
|| permissions.afterInitialize != self.hasPermission(AFTER_INITIALIZE_FLAG)
|| permissions.beforeAddLiquidity != self.hasPermission(BEFORE_ADD_LIQUIDITY_FLAG)
|| permissions.afterAddLiquidity != self.hasPermission(AFTER_ADD_LIQUIDITY_FLAG)
|| permissions.beforeRemoveLiquidity != self.hasPermission(BEFORE_REMOVE_LIQUIDITY_FLAG)
|| permissions.afterRemoveLiquidity != self.hasPermission(AFTER_REMOVE_LIQUIDITY_FLAG)
|| permissions.beforeSwap != self.hasPermission(BEFORE_SWAP_FLAG)
|| permissions.afterSwap != self.hasPermission(AFTER_SWAP_FLAG)
|| permissions.beforeDonate != self.hasPermission(BEFORE_DONATE_FLAG)
|| permissions.afterDonate != self.hasPermission(AFTER_DONATE_FLAG)
|| permissions.beforeSwapReturnDelta != self.hasPermission(BEFORE_SWAP_RETURNS_DELTA_FLAG)
|| permissions.afterSwapReturnDelta != self.hasPermission(AFTER_SWAP_RETURNS_DELTA_FLAG)
|| permissions.afterAddLiquidityReturnDelta != self.hasPermission(AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)
|| permissions.afterRemoveLiquidityReturnDelta
!= self.hasPermission(AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)
) {
HookAddressNotValid.selector.revertWith(address(self));
}
}
/// @notice Ensures that the hook address includes at least one hook flag or dynamic fees, or is the 0 address
/// @param self The hook to verify
/// @param fee The fee of the pool the hook is used with
/// @return bool True if the hook address is valid
function isValidHookAddress(IHooks self, uint24 fee) internal pure returns (bool) {
// The hook can only have a flag to return a hook delta on an action if it also has the corresponding action flag
if (!self.hasPermission(BEFORE_SWAP_FLAG) && self.hasPermission(BEFORE_SWAP_RETURNS_DELTA_FLAG)) return false;
if (!self.hasPermission(AFTER_SWAP_FLAG) && self.hasPermission(AFTER_SWAP_RETURNS_DELTA_FLAG)) return false;
if (!self.hasPermission(AFTER_ADD_LIQUIDITY_FLAG) && self.hasPermission(AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG))
{
return false;
}
if (
!self.hasPermission(AFTER_REMOVE_LIQUIDITY_FLAG)
&& self.hasPermission(AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)
) return false;
// If there is no hook contract set, then fee cannot be dynamic
// If a hook contract is set, it must have at least 1 flag set, or have a dynamic fee
return address(self) == address(0)
? !fee.isDynamicFee()
: (uint160(address(self)) & ALL_HOOK_MASK > 0 || fee.isDynamicFee());
}
/// @notice performs a hook call using the given calldata on the given hook that doesn't return a delta
/// @return result The complete data returned by the hook
function callHook(IHooks self, bytes memory data) internal returns (bytes memory result) {
bool success;
assembly ("memory-safe") {
success := call(gas(), self, 0, add(data, 0x20), mload(data), 0, 0)
}
// Revert with FailedHookCall, containing any error message to bubble up
if (!success) CustomRevert.bubbleUpAndRevertWith(address(self), bytes4(data), HookCallFailed.selector);
// The call was successful, fetch the returned data
assembly ("memory-safe") {
// allocate result byte array from the free memory pointer
result := mload(0x40)
// store new free memory pointer at the end of the array padded to 32 bytes
mstore(0x40, add(result, and(add(returndatasize(), 0x3f), not(0x1f))))
// store length in memory
mstore(result, returndatasize())
// copy return data to result
returndatacopy(add(result, 0x20), 0, returndatasize())
}
// Length must be at least 32 to contain the selector. Check expected selector and returned selector match.
if (result.length < 32 || result.parseSelector() != data.parseSelector()) {
InvalidHookResponse.selector.revertWith();
}
}
/// @notice performs a hook call using the given calldata on the given hook
/// @return int256 The delta returned by the hook
function callHookWithReturnDelta(IHooks self, bytes memory data, bool parseReturn) internal returns (int256) {
bytes memory result = callHook(self, data);
// If this hook wasn't meant to return something, default to 0 delta
if (!parseReturn) return 0;
// A length of 64 bytes is required to return a bytes4, and a 32 byte delta
if (result.length != 64) InvalidHookResponse.selector.revertWith();
return result.parseReturnDelta();
}
/// @notice modifier to prevent calling a hook if they initiated the action
modifier noSelfCall(IHooks self) {
if (msg.sender != address(self)) {
_;
}
}
/// @notice calls beforeInitialize hook if permissioned and validates return value
function beforeInitialize(IHooks self, PoolKey memory key, uint160 sqrtPriceX96) internal noSelfCall(self) {
if (self.hasPermission(BEFORE_INITIALIZE_FLAG)) {
self.callHook(abi.encodeCall(IHooks.beforeInitialize, (msg.sender, key, sqrtPriceX96)));
}
}
/// @notice calls afterInitialize hook if permissioned and validates return value
function afterInitialize(IHooks self, PoolKey memory key, uint160 sqrtPriceX96, int24 tick)
internal
noSelfCall(self)
{
if (self.hasPermission(AFTER_INITIALIZE_FLAG)) {
self.callHook(abi.encodeCall(IHooks.afterInitialize, (msg.sender, key, sqrtPriceX96, tick)));
}
}
/// @notice calls beforeModifyLiquidity hook if permissioned and validates return value
function beforeModifyLiquidity(
IHooks self,
PoolKey memory key,
ModifyLiquidityParams memory params,
bytes calldata hookData
) internal noSelfCall(self) {
if (params.liquidityDelta > 0 && self.hasPermission(BEFORE_ADD_LIQUIDITY_FLAG)) {
self.callHook(abi.encodeCall(IHooks.beforeAddLiquidity, (msg.sender, key, params, hookData)));
} else if (params.liquidityDelta <= 0 && self.hasPermission(BEFORE_REMOVE_LIQUIDITY_FLAG)) {
self.callHook(abi.encodeCall(IHooks.beforeRemoveLiquidity, (msg.sender, key, params, hookData)));
}
}
/// @notice calls afterModifyLiquidity hook if permissioned and validates return value
function afterModifyLiquidity(
IHooks self,
PoolKey memory key,
ModifyLiquidityParams memory params,
BalanceDelta delta,
BalanceDelta feesAccrued,
bytes calldata hookData
) internal returns (BalanceDelta callerDelta, BalanceDelta hookDelta) {
if (msg.sender == address(self)) return (delta, BalanceDeltaLibrary.ZERO_DELTA);
callerDelta = delta;
if (params.liquidityDelta > 0) {
if (self.hasPermission(AFTER_ADD_LIQUIDITY_FLAG)) {
hookDelta = BalanceDelta.wrap(
self.callHookWithReturnDelta(
abi.encodeCall(
IHooks.afterAddLiquidity, (msg.sender, key, params, delta, feesAccrued, hookData)
),
self.hasPermission(AFTER_ADD_LIQUIDITY_RETURNS_DELTA_FLAG)
)
);
callerDelta = callerDelta - hookDelta;
}
} else {
if (self.hasPermission(AFTER_REMOVE_LIQUIDITY_FLAG)) {
hookDelta = BalanceDelta.wrap(
self.callHookWithReturnDelta(
abi.encodeCall(
IHooks.afterRemoveLiquidity, (msg.sender, key, params, delta, feesAccrued, hookData)
),
self.hasPermission(AFTER_REMOVE_LIQUIDITY_RETURNS_DELTA_FLAG)
)
);
callerDelta = callerDelta - hookDelta;
}
}
}
/// @notice calls beforeSwap hook if permissioned and validates return value
function beforeSwap(IHooks self, PoolKey memory key, SwapParams memory params, bytes calldata hookData)
internal
returns (int256 amountToSwap, BeforeSwapDelta hookReturn, uint24 lpFeeOverride)
{
amountToSwap = params.amountSpecified;
if (msg.sender == address(self)) return (amountToSwap, BeforeSwapDeltaLibrary.ZERO_DELTA, lpFeeOverride);
if (self.hasPermission(BEFORE_SWAP_FLAG)) {
bytes memory result = callHook(self, abi.encodeCall(IHooks.beforeSwap, (msg.sender, key, params, hookData)));
// A length of 96 bytes is required to return a bytes4, a 32 byte delta, and an LP fee
if (result.length != 96) InvalidHookResponse.selector.revertWith();
// dynamic fee pools that want to override the cache fee, return a valid fee with the override flag. If override flag
// is set but an invalid fee is returned, the transaction will revert. Otherwise the current LP fee will be used
if (key.fee.isDynamicFee()) lpFeeOverride = result.parseFee();
// skip this logic for the case where the hook return is 0
if (self.hasPermission(BEFORE_SWAP_RETURNS_DELTA_FLAG)) {
hookReturn = BeforeSwapDelta.wrap(result.parseReturnDelta());
// any return in unspecified is passed to the afterSwap hook for handling
int128 hookDeltaSpecified = hookReturn.getSpecifiedDelta();
// Update the swap amount according to the hook's return, and check that the swap type doesn't change (exact input/output)
if (hookDeltaSpecified != 0) {
bool exactInput = amountToSwap < 0;
amountToSwap += hookDeltaSpecified;
if (exactInput ? amountToSwap > 0 : amountToSwap < 0) {
HookDeltaExceedsSwapAmount.selector.revertWith();
}
}
}
}
}
/// @notice calls afterSwap hook if permissioned and validates return value
function afterSwap(
IHooks self,
PoolKey memory key,
SwapParams memory params,
BalanceDelta swapDelta,
bytes calldata hookData,
BeforeSwapDelta beforeSwapHookReturn
) internal returns (BalanceDelta, BalanceDelta) {
if (msg.sender == address(self)) return (swapDelta, BalanceDeltaLibrary.ZERO_DELTA);
int128 hookDeltaSpecified = beforeSwapHookReturn.getSpecifiedDelta();
int128 hookDeltaUnspecified = beforeSwapHookReturn.getUnspecifiedDelta();
if (self.hasPermission(AFTER_SWAP_FLAG)) {
hookDeltaUnspecified += self.callHookWithReturnDelta(
abi.encodeCall(IHooks.afterSwap, (msg.sender, key, params, swapDelta, hookData)),
self.hasPermission(AFTER_SWAP_RETURNS_DELTA_FLAG)
).toInt128();
}
BalanceDelta hookDelta;
if (hookDeltaUnspecified != 0 || hookDeltaSpecified != 0) {
hookDelta = (params.amountSpecified < 0 == params.zeroForOne)
? toBalanceDelta(hookDeltaSpecified, hookDeltaUnspecified)
: toBalanceDelta(hookDeltaUnspecified, hookDeltaSpecified);
// the caller has to pay for (or receive) the hook's delta
swapDelta = swapDelta - hookDelta;
}
return (swapDelta, hookDelta);
}
/// @notice calls beforeDonate hook if permissioned and validates return value
function beforeDonate(IHooks self, PoolKey memory key, uint256 amount0, uint256 amount1, bytes calldata hookData)
internal
noSelfCall(self)
{
if (self.hasPermission(BEFORE_DONATE_FLAG)) {
self.callHook(abi.encodeCall(IHooks.beforeDonate, (msg.sender, key, amount0, amount1, hookData)));
}
}
/// @notice calls afterDonate hook if permissioned and validates return value
function afterDonate(IHooks self, PoolKey memory key, uint256 amount0, uint256 amount1, bytes calldata hookData)
internal
noSelfCall(self)
{
if (self.hasPermission(AFTER_DONATE_FLAG)) {
self.callHook(abi.encodeCall(IHooks.afterDonate, (msg.sender, key, amount0, amount1, hookData)));
}
}
function hasPermission(IHooks self, uint160 flag) internal pure returns (bool) {
return uint160(address(self)) & flag != 0;
}
}
IHooks.sol 152 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "../types/PoolKey.sol";
import {BalanceDelta} from "../types/BalanceDelta.sol";
import {ModifyLiquidityParams, SwapParams} from "../types/PoolOperation.sol";
import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol";
/// @notice V4 decides whether to invoke specific hooks by inspecting the least significant bits
/// of the address that the hooks contract is deployed to.
/// For example, a hooks contract deployed to address: 0x0000000000000000000000000000000000002400
/// has the lowest bits '10 0100 0000 0000' which would cause the 'before initialize' and 'after add liquidity' hooks to be used.
/// See the Hooks library for the full spec.
/// @dev Should only be callable by the v4 PoolManager.
interface IHooks {
/// @notice The hook called before the state of a pool is initialized
/// @param sender The initial msg.sender for the initialize call
/// @param key The key for the pool being initialized
/// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96
/// @return bytes4 The function selector for the hook
function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96) external returns (bytes4);
/// @notice The hook called after the state of a pool is initialized
/// @param sender The initial msg.sender for the initialize call
/// @param key The key for the pool being initialized
/// @param sqrtPriceX96 The sqrt(price) of the pool as a Q64.96
/// @param tick The current tick after the state of a pool is initialized
/// @return bytes4 The function selector for the hook
function afterInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96, int24 tick)
external
returns (bytes4);
/// @notice The hook called before liquidity is added
/// @param sender The initial msg.sender for the add liquidity call
/// @param key The key for the pool
/// @param params The parameters for adding liquidity
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook
/// @return bytes4 The function selector for the hook
function beforeAddLiquidity(
address sender,
PoolKey calldata key,
ModifyLiquidityParams calldata params,
bytes calldata hookData
) external returns (bytes4);
/// @notice The hook called after liquidity is added
/// @param sender The initial msg.sender for the add liquidity call
/// @param key The key for the pool
/// @param params The parameters for adding liquidity
/// @param delta The caller's balance delta after adding liquidity; the sum of principal delta, fees accrued, and hook delta
/// @param feesAccrued The fees accrued since the last time fees were collected from this position
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
function afterAddLiquidity(
address sender,
PoolKey calldata key,
ModifyLiquidityParams calldata params,
BalanceDelta delta,
BalanceDelta feesAccrued,
bytes calldata hookData
) external returns (bytes4, BalanceDelta);
/// @notice The hook called before liquidity is removed
/// @param sender The initial msg.sender for the remove liquidity call
/// @param key The key for the pool
/// @param params The parameters for removing liquidity
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook
/// @return bytes4 The function selector for the hook
function beforeRemoveLiquidity(
address sender,
PoolKey calldata key,
ModifyLiquidityParams calldata params,
bytes calldata hookData
) external returns (bytes4);
/// @notice The hook called after liquidity is removed
/// @param sender The initial msg.sender for the remove liquidity call
/// @param key The key for the pool
/// @param params The parameters for removing liquidity
/// @param delta The caller's balance delta after removing liquidity; the sum of principal delta, fees accrued, and hook delta
/// @param feesAccrued The fees accrued since the last time fees were collected from this position
/// @param hookData Arbitrary data handed into the PoolManager by the liquidity provider to be be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return BalanceDelta The hook's delta in token0 and token1. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
function afterRemoveLiquidity(
address sender,
PoolKey calldata key,
ModifyLiquidityParams calldata params,
BalanceDelta delta,
BalanceDelta feesAccrued,
bytes calldata hookData
) external returns (bytes4, BalanceDelta);
/// @notice The hook called before a swap
/// @param sender The initial msg.sender for the swap call
/// @param key The key for the pool
/// @param params The parameters for the swap
/// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return BeforeSwapDelta The hook's delta in specified and unspecified currencies. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
/// @return uint24 Optionally override the lp fee, only used if three conditions are met: 1. the Pool has a dynamic fee, 2. the value's 2nd highest bit is set (23rd bit, 0x400000), and 3. the value is less than or equal to the maximum fee (1 million)
function beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData)
external
returns (bytes4, BeforeSwapDelta, uint24);
/// @notice The hook called after a swap
/// @param sender The initial msg.sender for the swap call
/// @param key The key for the pool
/// @param params The parameters for the swap
/// @param delta The amount owed to the caller (positive) or owed to the pool (negative)
/// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return int128 The hook's delta in unspecified currency. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
function afterSwap(
address sender,
PoolKey calldata key,
SwapParams calldata params,
BalanceDelta delta,
bytes calldata hookData
) external returns (bytes4, int128);
/// @notice The hook called before donate
/// @param sender The initial msg.sender for the donate call
/// @param key The key for the pool
/// @param amount0 The amount of token0 being donated
/// @param amount1 The amount of token1 being donated
/// @param hookData Arbitrary data handed into the PoolManager by the donor to be be passed on to the hook
/// @return bytes4 The function selector for the hook
function beforeDonate(
address sender,
PoolKey calldata key,
uint256 amount0,
uint256 amount1,
bytes calldata hookData
) external returns (bytes4);
/// @notice The hook called after donate
/// @param sender The initial msg.sender for the donate call
/// @param key The key for the pool
/// @param amount0 The amount of token0 being donated
/// @param amount1 The amount of token1 being donated
/// @param hookData Arbitrary data handed into the PoolManager by the donor to be be passed on to the hook
/// @return bytes4 The function selector for the hook
function afterDonate(
address sender,
PoolKey calldata key,
uint256 amount0,
uint256 amount1,
bytes calldata hookData
) external returns (bytes4);
}
BeforeSwapDelta.sol 38 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// Return type of the beforeSwap hook.
// Upper 128 bits is the delta in specified tokens. Lower 128 bits is delta in unspecified tokens (to match the afterSwap hook)
type BeforeSwapDelta is int256;
// Creates a BeforeSwapDelta from specified and unspecified
function toBeforeSwapDelta(int128 deltaSpecified, int128 deltaUnspecified)
pure
returns (BeforeSwapDelta beforeSwapDelta)
{
assembly ("memory-safe") {
beforeSwapDelta := or(shl(128, deltaSpecified), and(sub(shl(128, 1), 1), deltaUnspecified))
}
}
/// @notice Library for getting the specified and unspecified deltas from the BeforeSwapDelta type
library BeforeSwapDeltaLibrary {
/// @notice A BeforeSwapDelta of 0
BeforeSwapDelta public constant ZERO_DELTA = BeforeSwapDelta.wrap(0);
/// extracts int128 from the upper 128 bits of the BeforeSwapDelta
/// returned by beforeSwap
function getSpecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128 deltaSpecified) {
assembly ("memory-safe") {
deltaSpecified := sar(128, delta)
}
}
/// extracts int128 from the lower 128 bits of the BeforeSwapDelta
/// returned by beforeSwap and afterSwap
function getUnspecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128 deltaUnspecified) {
assembly ("memory-safe") {
deltaUnspecified := signextend(15, delta)
}
}
}
PoolOperation.sol 26 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {PoolKey} from "../types/PoolKey.sol";
import {BalanceDelta} from "../types/BalanceDelta.sol";
/// @notice Parameter struct for `ModifyLiquidity` pool operations
struct ModifyLiquidityParams {
// the lower and upper tick of the position
int24 tickLower;
int24 tickUpper;
// how to modify the liquidity
int256 liquidityDelta;
// a value to set if you want unique liquidity positions at the same range
bytes32 salt;
}
/// @notice Parameter struct for `Swap` pool operations
struct SwapParams {
/// Whether to swap token0 for token1 or vice versa
bool zeroForOne;
/// The desired input amount if negative (exactIn), or the desired output amount if positive (exactOut)
int256 amountSpecified;
/// The sqrt price at which, if reached, the swap will stop executing
uint160 sqrtPriceLimitX96;
}
PoolKey.sol 22 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Currency} from "./Currency.sol";
import {IHooks} from "../interfaces/IHooks.sol";
import {PoolIdLibrary} from "./PoolId.sol";
using PoolIdLibrary for PoolKey global;
/// @notice Returns the key for identifying a pool
struct PoolKey {
/// @notice The lower currency of the pool, sorted numerically
Currency currency0;
/// @notice The higher currency of the pool, sorted numerically
Currency currency1;
/// @notice The pool LP fee, capped at 1_000_000. If the highest bit is 1, the pool has a dynamic fee and must be exactly equal to 0x800000
uint24 fee;
/// @notice Ticks that involve positions must be a multiple of tick spacing
int24 tickSpacing;
/// @notice The hooks of the pool
IHooks hooks;
}
IContinuousClearingAuction.sol 221 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Checkpoint} from '../libraries/CheckpointLib.sol';
import {ValueX7} from '../libraries/ValueX7Lib.sol';
import {IBidStorage} from './IBidStorage.sol';
import {ICheckpointStorage} from './ICheckpointStorage.sol';
import {IStepStorage} from './IStepStorage.sol';
import {ITickStorage} from './ITickStorage.sol';
import {ITokenCurrencyStorage} from './ITokenCurrencyStorage.sol';
import {IValidationHook} from './IValidationHook.sol';
import {IDistributionContract} from './external/IDistributionContract.sol';
/// @notice Parameters for the auction
/// @dev token and totalSupply are passed as constructor arguments
struct AuctionParameters {
address currency; // token to raise funds in. Use address(0) for ETH
address tokensRecipient; // address to receive leftover tokens
address fundsRecipient; // address to receive all raised funds
uint64 startBlock; // Block which the first step starts
uint64 endBlock; // When the auction finishes
uint64 claimBlock; // Block when the auction can claimed
uint256 tickSpacing; // Fixed granularity for prices
address validationHook; // Optional hook called before a bid
uint256 floorPrice; // Starting floor price for the auction
uint128 requiredCurrencyRaised; // Amount of currency required to be raised for the auction to graduate
bytes auctionStepsData; // Packed bytes describing token issuance schedule
}
/// @notice Interface for the ContinuousClearingAuction contract
interface IContinuousClearingAuction is
IDistributionContract,
ICheckpointStorage,
ITickStorage,
IStepStorage,
ITokenCurrencyStorage,
IBidStorage
{
/// @notice Error thrown when the amount received is invalid
error InvalidTokenAmountReceived();
/// @notice Error thrown when an invalid value is deposited
error InvalidAmount();
/// @notice Error thrown when the bid owner is the zero address
error BidOwnerCannotBeZeroAddress();
/// @notice Error thrown when the bid price is below the clearing price
error BidMustBeAboveClearingPrice();
/// @notice Error thrown when the bid price is too high given the auction's total supply
/// @param maxPrice The price of the bid
/// @param maxBidPrice The max price allowed for a bid
error InvalidBidPriceTooHigh(uint256 maxPrice, uint256 maxBidPrice);
/// @notice Error thrown when the bid amount is too small
error BidAmountTooSmall();
/// @notice Error thrown when msg.value is non zero when currency is not ETH
error CurrencyIsNotNative();
/// @notice Error thrown when the auction is not started
error AuctionNotStarted();
/// @notice Error thrown when the tokens required for the auction have not been received
error TokensNotReceived();
/// @notice Error thrown when the claim block is before the end block
error ClaimBlockIsBeforeEndBlock();
/// @notice Error thrown when the floor price plus tick spacing is greater than the maximum bid price
error FloorPriceAndTickSpacingGreaterThanMaxBidPrice(uint256 nextTick, uint256 maxBidPrice);
/// @notice Error thrown when the floor price plus tick spacing would overflow a uint256
error FloorPriceAndTickSpacingTooLarge();
/// @notice Error thrown when the bid has already been exited
error BidAlreadyExited();
/// @notice Error thrown when the bid is higher than the clearing price
error CannotExitBid();
/// @notice Error thrown when the bid cannot be partially exited before the end block
error CannotPartiallyExitBidBeforeEndBlock();
/// @notice Error thrown when the last fully filled checkpoint hint is invalid
error InvalidLastFullyFilledCheckpointHint();
/// @notice Error thrown when the outbid block checkpoint hint is invalid
error InvalidOutbidBlockCheckpointHint();
/// @notice Error thrown when the bid is not claimable
error NotClaimable();
/// @notice Error thrown when the bids are not owned by the same owner
error BatchClaimDifferentOwner(address expectedOwner, address receivedOwner);
/// @notice Error thrown when the bid has not been exited
error BidNotExited();
/// @notice Error thrown when the bid cannot be partially exited before the auction has graduated
error CannotPartiallyExitBidBeforeGraduation();
/// @notice Error thrown when the token transfer fails
error TokenTransferFailed();
/// @notice Error thrown when the auction is not over
error AuctionIsNotOver();
/// @notice Error thrown when the bid is too large
error InvalidBidUnableToClear();
/// @notice Error thrown when the auction has sold the entire total supply of tokens
error AuctionSoldOut();
/// @notice Emitted when the tokens are received
/// @param totalSupply The total supply of tokens received
event TokensReceived(uint256 totalSupply);
/// @notice Emitted when a bid is submitted
/// @param id The id of the bid
/// @param owner The owner of the bid
/// @param price The price of the bid
/// @param amount The amount of the bid
event BidSubmitted(uint256 indexed id, address indexed owner, uint256 price, uint128 amount);
/// @notice Emitted when a new checkpoint is created
/// @param blockNumber The block number of the checkpoint
/// @param clearingPrice The clearing price of the checkpoint
/// @param cumulativeMps The cumulative percentage of total tokens allocated across all previous steps, represented in ten-millionths of the total supply (1e7 = 100%)
event CheckpointUpdated(uint256 blockNumber, uint256 clearingPrice, uint24 cumulativeMps);
/// @notice Emitted when the clearing price is updated
/// @param blockNumber The block number when the clearing price was updated
/// @param clearingPrice The new clearing price
event ClearingPriceUpdated(uint256 blockNumber, uint256 clearingPrice);
/// @notice Emitted when a bid is exited
/// @param bidId The id of the bid
/// @param owner The owner of the bid
/// @param tokensFilled The amount of tokens filled
/// @param currencyRefunded The amount of currency refunded
event BidExited(uint256 indexed bidId, address indexed owner, uint256 tokensFilled, uint256 currencyRefunded);
/// @notice Emitted when a bid is claimed
/// @param bidId The id of the bid
/// @param owner The owner of the bid
/// @param tokensFilled The amount of tokens claimed
event TokensClaimed(uint256 indexed bidId, address indexed owner, uint256 tokensFilled);
/// @notice Submit a new bid
/// @param maxPrice The maximum price the bidder is willing to pay
/// @param amount The amount of the bid
/// @param owner The owner of the bid
/// @param prevTickPrice The price of the previous tick
/// @param hookData Additional data to pass to the hook required for validation
/// @return bidId The id of the bid
function submitBid(uint256 maxPrice, uint128 amount, address owner, uint256 prevTickPrice, bytes calldata hookData)
external
payable
returns (uint256 bidId);
/// @notice Submit a new bid without specifying the previous tick price
/// @dev It is NOT recommended to use this function unless you are sure that `maxPrice` is already initialized
/// as this function will iterate through every tick starting from the floor price if it is not.
/// @param maxPrice The maximum price the bidder is willing to pay
/// @param amount The amount of the bid
/// @param owner The owner of the bid
/// @param hookData Additional data to pass to the hook required for validation
/// @return bidId The id of the bid
function submitBid(uint256 maxPrice, uint128 amount, address owner, bytes calldata hookData)
external
payable
returns (uint256 bidId);
/// @notice Register a new checkpoint
/// @dev This function is called every time a new bid is submitted above the current clearing price
/// @dev If the auction is over, it returns the final checkpoint
/// @return _checkpoint The checkpoint at the current block
function checkpoint() external returns (Checkpoint memory _checkpoint);
/// @notice Whether the auction has graduated as of the given checkpoint
/// @dev The auction is considered graduated if the currency raised is greater than or equal to the required currency raised
/// @dev Be aware that the latest checkpoint may be out of date
/// @return bool True if the auction has graduated, false otherwise
function isGraduated() external view returns (bool);
/// @notice Get the currency raised at the last checkpointed block
/// @dev This may be less than the balance of this contract if there are outstanding refunds for bidders
/// @dev Be aware that the latest checkpoint may be out of date
/// @return The currency raised
function currencyRaised() external view returns (uint256);
/// @notice Exit a bid
/// @dev This function can only be used for bids where the max price is above the final clearing price after the auction has ended
/// @param bidId The id of the bid
function exitBid(uint256 bidId) external;
/// @notice Exit a bid which has been partially filled
/// @dev This function can be used only for partially filled bids. For fully filled bids, `exitBid` must be used
/// @param bidId The id of the bid
/// @param lastFullyFilledCheckpointBlock The last checkpointed block where the clearing price is strictly < bid.maxPrice
/// @param outbidBlock The first checkpointed block where the clearing price is strictly > bid.maxPrice, or 0 if the bid is partially filled at the end of the auction
function exitPartiallyFilledBid(uint256 bidId, uint64 lastFullyFilledCheckpointBlock, uint64 outbidBlock) external;
/// @notice Claim tokens after the auction's claim block
/// @notice The bid must be exited before claiming tokens
/// @dev Anyone can claim tokens for any bid, the tokens are transferred to the bid owner
/// @param bidId The id of the bid
function claimTokens(uint256 bidId) external;
/// @notice Claim tokens for multiple bids
/// @dev Anyone can claim tokens for bids of the same owner, the tokens are transferred to the owner
/// @dev A TokensClaimed event is emitted for each bid but only one token transfer will be made
/// @param owner The owner of the bids
/// @param bidIds The ids of the bids
function claimTokensBatch(address owner, uint256[] calldata bidIds) external;
/// @notice Withdraw all of the currency raised
/// @dev Can be called by anyone after the auction has ended
function sweepCurrency() external;
/// @notice The block at which the auction can be claimed
function claimBlock() external view returns (uint64);
/// @notice The address of the validation hook for the auction
function validationHook() external view returns (IValidationHook);
/// @notice Sweep any leftover tokens to the tokens recipient
/// @dev This function can only be called after the auction has ended
function sweepUnsoldTokens() external;
/// @notice The currency raised as of the last checkpoint
function currencyRaisedQ96_X7() external view returns (ValueX7);
/// @notice The sum of demand in ticks above the clearing price
function sumCurrencyDemandAboveClearingQ96() external view returns (uint256);
/// @notice The total currency raised as of the last checkpoint
function totalClearedQ96_X7() external view returns (ValueX7);
/// @notice The total tokens cleared as of the last checkpoint in uint256 representation
function totalCleared() external view returns (uint256);
}
ContinuousClearingAuction.sol 727 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {BidStorage} from './BidStorage.sol';
import {Checkpoint, CheckpointStorage} from './CheckpointStorage.sol';
import {StepStorage} from './StepStorage.sol';
import {Tick, TickStorage} from './TickStorage.sol';
import {TokenCurrencyStorage} from './TokenCurrencyStorage.sol';
import {AuctionParameters, IContinuousClearingAuction} from './interfaces/IContinuousClearingAuction.sol';
import {IValidationHook} from './interfaces/IValidationHook.sol';
import {IDistributionContract} from './interfaces/external/IDistributionContract.sol';
import {Bid, BidLib} from './libraries/BidLib.sol';
import {CheckpointLib} from './libraries/CheckpointLib.sol';
import {ConstantsLib} from './libraries/ConstantsLib.sol';
import {Currency, CurrencyLibrary} from './libraries/CurrencyLibrary.sol';
import {FixedPoint96} from './libraries/FixedPoint96.sol';
import {MaxBidPriceLib} from './libraries/MaxBidPriceLib.sol';
import {AuctionStep, StepLib} from './libraries/StepLib.sol';
import {ValidationHookLib} from './libraries/ValidationHookLib.sol';
import {ValueX7, ValueX7Lib} from './libraries/ValueX7Lib.sol';
import {FixedPointMathLib} from 'solady/utils/FixedPointMathLib.sol';
import {SafeTransferLib} from 'solady/utils/SafeTransferLib.sol';
/// @title ContinuousClearingAuction
/// @custom:security-contact [email protected]
/// @notice Implements a time weighted uniform clearing price auction
/// @dev Can be constructed directly or through the ContinuousClearingAuctionFactory. In either case, users must validate
/// that the auction parameters are correct and not incorrectly set.
contract ContinuousClearingAuction is
BidStorage,
CheckpointStorage,
StepStorage,
TickStorage,
TokenCurrencyStorage,
IContinuousClearingAuction
{
using FixedPointMathLib for *;
using CurrencyLibrary for Currency;
using BidLib for *;
using StepLib for *;
using CheckpointLib for Checkpoint;
using ValidationHookLib for IValidationHook;
using ValueX7Lib for *;
/// @notice The maximum price which a bid can be submitted at
/// @dev Set during construction to type(uint256).max / TOTAL_SUPPLY
uint256 public immutable MAX_BID_PRICE;
/// @notice The block at which purchased tokens can be claimed
uint64 internal immutable CLAIM_BLOCK;
/// @notice An optional hook to be called before a bid is registered
IValidationHook internal immutable VALIDATION_HOOK;
/// @notice The total currency raised in the auction in Q96 representation, scaled up by X7
ValueX7 internal $currencyRaisedQ96_X7;
/// @notice The total tokens sold in the auction so far, in Q96 representation, scaled up by X7
ValueX7 internal $totalClearedQ96_X7;
/// @notice The sum of currency demand in ticks above the clearing price
/// @dev This will increase every time a new bid is submitted, and decrease when bids are outbid.
uint256 internal $sumCurrencyDemandAboveClearingQ96;
/// @notice Whether the TOTAL_SUPPLY of tokens has been received
bool private $_tokensReceived;
constructor(address _token, uint128 _totalSupply, AuctionParameters memory _parameters)
StepStorage(_parameters.auctionStepsData, _parameters.startBlock, _parameters.endBlock)
TokenCurrencyStorage(
_token,
_parameters.currency,
_totalSupply,
_parameters.tokensRecipient,
_parameters.fundsRecipient,
_parameters.requiredCurrencyRaised
)
TickStorage(_parameters.tickSpacing, _parameters.floorPrice)
{
CLAIM_BLOCK = _parameters.claimBlock;
VALIDATION_HOOK = IValidationHook(_parameters.validationHook);
if (CLAIM_BLOCK < END_BLOCK) revert ClaimBlockIsBeforeEndBlock();
// See MaxBidPriceLib library for more details on the bid price calculations.
MAX_BID_PRICE = MaxBidPriceLib.maxBidPrice(TOTAL_SUPPLY);
// The floor price and tick spacing must allow for at least one tick above the floor price to be initialized
if (_parameters.tickSpacing > MAX_BID_PRICE || _parameters.floorPrice > MAX_BID_PRICE - _parameters.tickSpacing)
{
revert FloorPriceAndTickSpacingGreaterThanMaxBidPrice(
_parameters.floorPrice + _parameters.tickSpacing, MAX_BID_PRICE
);
}
}
/// @notice Modifier for functions which can only be called after the auction is over
modifier onlyAfterAuctionIsOver() {
if (block.number < END_BLOCK) revert AuctionIsNotOver();
_;
}
/// @notice Modifier for claim related functions which can only be called after the claim block
modifier onlyAfterClaimBlock() {
if (block.number < CLAIM_BLOCK) revert NotClaimable();
_;
}
/// @notice Modifier for functions which can only be called after the auction is started and the tokens have been received
modifier onlyActiveAuction() {
_onlyActiveAuction();
_;
}
/// @notice Internal function to check if the auction is active
/// @dev Submitting bids or checkpointing is not allowed unless the auction is active
function _onlyActiveAuction() internal view {
if (block.number < START_BLOCK) revert AuctionNotStarted();
if (!$_tokensReceived) revert TokensNotReceived();
}
/// @notice Modifier for functions which require the latest checkpoint to be up to date
modifier ensureEndBlockIsCheckpointed() {
if ($lastCheckpointedBlock != END_BLOCK) {
checkpoint();
}
_;
}
/// @inheritdoc IDistributionContract
function onTokensReceived() external {
// Don't check balance or emit the TokensReceived event if the tokens have already been received
if ($_tokensReceived) return;
// Use the normal totalSupply value instead of the Q96 value
if (TOKEN.balanceOf(address(this)) < TOTAL_SUPPLY) {
revert InvalidTokenAmountReceived();
}
$_tokensReceived = true;
emit TokensReceived(TOTAL_SUPPLY);
}
/// @inheritdoc IContinuousClearingAuction
function isGraduated() external view returns (bool) {
return _isGraduated();
}
/// @notice Whether the auction has graduated as of the given checkpoint
/// @dev The auction is considered `graudated` if the currency raised is greater than or equal to the required currency raised
function _isGraduated() internal view returns (bool) {
return ValueX7.unwrap($currencyRaisedQ96_X7) >= ValueX7.unwrap(REQUIRED_CURRENCY_RAISED_Q96_X7);
}
/// @inheritdoc IContinuousClearingAuction
function currencyRaised() external view returns (uint256) {
return _currencyRaised();
}
/// @notice Return the currency raised in uint256 representation
/// @return The currency raised
function _currencyRaised() internal view returns (uint256) {
return $currencyRaisedQ96_X7.divUint256(FixedPoint96.Q96).scaleDownToUint256();
}
/// @notice Return a new checkpoint after advancing the current checkpoint by some `mps`
/// This function updates the cumulative values of the checkpoint, and
/// requires that the clearing price is up to date
/// @param _checkpoint The checkpoint to sell tokens at its clearing price
/// @param deltaMps The number of mps to sell
/// @return The checkpoint with all cumulative values updated
function _sellTokensAtClearingPrice(Checkpoint memory _checkpoint, uint24 deltaMps)
internal
returns (Checkpoint memory)
{
// Advance the auction by selling an additional `deltaMps` share of TOTAL_SUPPLY at the current clearing price.
//
// Units and scaling:
// - Prices: Q96 (× 2^96)
// - Demand: Q96
// - deltaMps: X7 (milli-basis points, 1e7 = 100%)
// - Currency/Token flows in this function: Q96*X7 (demand or currency × deltaMps)
//
// Algorithm overview:
// 1) Assume all demand is strictly above the clearing price: contribution = sumAboveClearingQ96 × deltaMps.
// 2) If the clearing price is exactly on an initialized tick that has demand, account for the partially filled
// bids at the clearing tick. There are two ways to derive the at-clearing contribution when the price is
// not rounded up:
// (A) total implied currency at the rounded-up price − contribution from above-clearing
// (B) tick demand at clearing × deltaMps
// If the clearing price was rounded up to the tick boundary, (A) can exceed (B); cap with min(A, B).
uint256 priceQ96 = _checkpoint.clearingPrice;
uint256 deltaMpsU = uint256(deltaMps);
uint256 sumAboveQ96 = $sumCurrencyDemandAboveClearingQ96;
// Base case: demand strictly above the clearing price only
// Most bids are strictly above clearing; contribution = (Q96 demand above clearing) × (X7 delta)
uint256 currencyFromAboveQ96X7;
unchecked {
currencyFromAboveQ96X7 = sumAboveQ96 * deltaMpsU;
}
// Special case: clearing price equals a tick with demand (partially filled tick)
// Bidders at that tick can be partially filled over this increment. Split into:
// - (1) above-clearing contribution (already computed) and
// - (2) at-clearing contribution.
if (priceQ96 % TICK_SPACING == 0) {
uint256 demandAtPriceQ96 = _getTick(priceQ96).currencyDemandQ96;
if (demandAtPriceQ96 > 0) {
uint256 currencyRaisedAboveClearingQ96X7 = currencyFromAboveQ96X7;
// (A) Total implied currency at the (rounded-up) clearing price for this delta:
// TOTAL_SUPPLY × priceQ96 (Q96) × deltaMps (X7) = Q96*X7
// Note: on a tick boundary we use the rounded-up clearing price, which can slightly overestimate.
uint256 totalCurrencyForDeltaQ96X7;
unchecked {
totalCurrencyForDeltaQ96X7 = (uint256(TOTAL_SUPPLY) * priceQ96) * deltaMpsU;
}
// Portion attributable to the clearing tick by subtraction: A − above-clearing
uint256 demandAtClearingQ96X7 = totalCurrencyForDeltaQ96X7 - currencyRaisedAboveClearingQ96X7;
// (B) Expected currency from bids at the clearing tick, scaling the tick demand by deltaMps
uint256 expectedAtClearingTickQ96X7;
unchecked {
expectedAtClearingTickQ96X7 = demandAtPriceQ96 * deltaMpsU;
}
// If price was rounded up, (A) can exceed (B). In that case, at-clearing contribution is bounded by actual
// tick demand; take min((A), (B)). If the price was not rounded up, (A) == (B).
uint256 currencyAtClearingTickQ96X7 =
FixedPointMathLib.min(demandAtClearingQ96X7, expectedAtClearingTickQ96X7);
// Actual currency raised across this delta = above-clearing + at-clearing
currencyFromAboveQ96X7 = currencyAtClearingTickQ96X7 + currencyRaisedAboveClearingQ96X7;
// Track cumulative currency raised exactly at this clearing price (used for partial exits)
_checkpoint.currencyRaisedAtClearingPriceQ96_X7 = ValueX7.wrap(
ValueX7.unwrap(_checkpoint.currencyRaisedAtClearingPriceQ96_X7) + currencyAtClearingTickQ96X7
);
}
}
// Convert currency to tokens at price, rounding up, and update global cleared tokens.
// Intentional round-up leaves a small amount of dust to sweep, ensuring cleared tokens never exceed TOTAL_SUPPLY
// even when using rounded-up clearing prices on tick boundaries.
uint256 tokensClearedQ96X7 = currencyFromAboveQ96X7.fullMulDivUp(FixedPoint96.Q96, priceQ96);
$totalClearedQ96_X7 = ValueX7.wrap(ValueX7.unwrap($totalClearedQ96_X7) + tokensClearedQ96X7);
// Update global currency raised
$currencyRaisedQ96_X7 = ValueX7.wrap(ValueX7.unwrap($currencyRaisedQ96_X7) + currencyFromAboveQ96X7);
_checkpoint.cumulativeMps += deltaMps;
// Harmonic-mean accumulator: add (mps / price) using the rounded-up clearing price for this increment
_checkpoint.cumulativeMpsPerPrice += CheckpointLib.getMpsPerPrice(deltaMps, priceQ96);
return _checkpoint;
}
/// @notice Fast forward to the start of the current step and return the number of `mps` sold since the last checkpoint
/// @param _blockNumber The current block number
/// @param _lastCheckpointedBlock The block number of the last checkpointed block
/// @return step The current step in the auction which contains `_blockNumber`
/// @return deltaMps The number of `mps` sold between the last checkpointed block and the start of the current step
function _advanceToStartOfCurrentStep(uint64 _blockNumber, uint64 _lastCheckpointedBlock)
internal
returns (AuctionStep memory step, uint24 deltaMps)
{
// Advance the current step until the current block is within the step
// Start at the larger of the last checkpointed block or the start block of the current step
step = $step;
uint64 start = uint64(FixedPointMathLib.max(step.startBlock, _lastCheckpointedBlock));
uint64 end = step.endBlock;
uint24 mps = step.mps;
while (_blockNumber > end) {
uint64 blockDelta = end - start;
unchecked {
deltaMps += uint24(blockDelta * mps);
}
start = end;
if (end == END_BLOCK) break;
step = _advanceStep();
mps = step.mps;
end = step.endBlock;
}
}
/// @notice Iterate to find the tick where the total demand at and above it is strictly less than the remaining supply in the auction
/// @dev If the loop reaches the highest tick in the book, `nextActiveTickPrice` will be set to MAX_TICK_PTR
/// @param _checkpoint The latest checkpoint
/// @return The new clearing price
function _iterateOverTicksAndFindClearingPrice(Checkpoint memory _checkpoint) internal returns (uint256) {
// The clearing price can never be lower than the last checkpoint.
// If the clearing price is zero, set it to the floor price
uint256 minimumClearingPrice = _checkpoint.clearingPrice.coalesce(FLOOR_PRICE);
// If there are no more remaining mps in the auction, we don't need to iterate over ticks
// and we can return the minimum clearing price above
if (_checkpoint.remainingMpsInAuction() == 0) {
return minimumClearingPrice;
}
// Place state variables on the stack to save gas
bool updateStateVariables;
uint256 sumCurrencyDemandAboveClearingQ96_ = $sumCurrencyDemandAboveClearingQ96;
uint256 nextActiveTickPrice_ = $nextActiveTickPrice;
/**
* We have the current demand above the clearing price, and we want to see if it is enough to fully purchase
* all of the remaining supply being sold at the nextActiveTickPrice. We only need to check `nextActiveTickPrice`
* because we know that there are no bids in between the current clearing price and that price.
*
* Observe that we need a certain amount of collective demand to increase the auction from the floor price.
* - This is equal to `totalSupply * floorPrice`
*
* If the auction was fully subscribed in the first block which it was active, then the total CURRENCY REQUIRED
* at any given price is equal to totalSupply * p', where p' is that price.
*/
uint256 clearingPrice = sumCurrencyDemandAboveClearingQ96_.divUp(TOTAL_SUPPLY);
while (
// Loop while the currency amount above the clearing price is greater than the required currency at `nextActiveTickPrice_`
(nextActiveTickPrice_ != MAX_TICK_PTR
&& sumCurrencyDemandAboveClearingQ96_ >= TOTAL_SUPPLY * nextActiveTickPrice_)
// If the demand above clearing rounds up to the `nextActiveTickPrice`, we need to keep iterating over ticks
// This ensures that the `nextActiveTickPrice` is always the next initialized tick strictly above the clearing price
|| clearingPrice == nextActiveTickPrice_
) {
Tick storage $nextActiveTick = _getTick(nextActiveTickPrice_);
// Subtract the demand at the current nextActiveTick from the total demand
sumCurrencyDemandAboveClearingQ96_ -= $nextActiveTick.currencyDemandQ96;
// Save the previous next active tick price
minimumClearingPrice = nextActiveTickPrice_;
// Advance to the next tick
nextActiveTickPrice_ = $nextActiveTick.next;
clearingPrice = sumCurrencyDemandAboveClearingQ96_.divUp(TOTAL_SUPPLY);
updateStateVariables = true;
}
// Set the values into storage if we found a new next active tick price
if (updateStateVariables) {
$sumCurrencyDemandAboveClearingQ96 = sumCurrencyDemandAboveClearingQ96_;
$nextActiveTickPrice = nextActiveTickPrice_;
emit NextActiveTickUpdated(nextActiveTickPrice_);
}
// The minimum clearing price is either the floor price or the last tick we iterated over.
// With the exception of the first iteration, the minimum price is a lower bound on the clearing price
// because we already verified that we had enough demand to purchase all of the remaining supply at that price.
if (clearingPrice < minimumClearingPrice) {
return minimumClearingPrice;
}
// Otherwise, return the calculated clearing price
else {
return clearingPrice;
}
}
/// @notice Internal function for checkpointing at a specific block number
/// @dev This updates the state of the auction accounting for the bids placed after the last checkpoint
/// Checkpoints are created at the top of each block with a new bid and does NOT include that bid
/// Because of this, we need to calculate what the new state of the Auction should be before updating
/// purely on the supply we will sell to the potentially updated `sumCurrencyDemandAboveClearingQ96` value
/// @param blockNumber The block number to checkpoint at
function _checkpointAtBlock(uint64 blockNumber) internal returns (Checkpoint memory _checkpoint) {
uint64 lastCheckpointedBlock = $lastCheckpointedBlock;
if (blockNumber == lastCheckpointedBlock) return latestCheckpoint();
_checkpoint = latestCheckpoint();
uint256 clearingPrice = _iterateOverTicksAndFindClearingPrice(_checkpoint);
if (clearingPrice != _checkpoint.clearingPrice) {
// Set the new clearing price
_checkpoint.clearingPrice = clearingPrice;
// Reset the currencyRaisedAtClearingPrice to zero since the clearing price has changed
_checkpoint.currencyRaisedAtClearingPriceQ96_X7 = ValueX7.wrap(0);
emit ClearingPriceUpdated(blockNumber, clearingPrice);
}
// Calculate the percentage of the supply that has been sold since the last checkpoint and the start of the current step
(AuctionStep memory step, uint24 deltaMps) = _advanceToStartOfCurrentStep(blockNumber, lastCheckpointedBlock);
// `deltaMps` above is equal to the percentage of tokens sold up until the start of the current step.
// If the last checkpointed block is more recent than the start of the current step, account for the percentage
// sold since the last checkpointed block. Otherwise, add the percent sold since the start of the current step.
uint64 blockDelta = blockNumber - uint64(FixedPointMathLib.max(step.startBlock, lastCheckpointedBlock));
unchecked {
deltaMps += uint24(blockDelta * step.mps);
}
// Sell the percentage of outstanding tokens since the last checkpoint at the current clearing price
_checkpoint = _sellTokensAtClearingPrice(_checkpoint, deltaMps);
// Insert the checkpoint into storage, updating latest pointer and the linked list
_insertCheckpoint(_checkpoint, blockNumber);
emit CheckpointUpdated(blockNumber, _checkpoint.clearingPrice, _checkpoint.cumulativeMps);
}
/// @notice Return the final checkpoint of the auction
/// @dev Only called when the auction is over. Changes the current state of the `step` to the final step in the auction
/// any future calls to `step.mps` will return the mps of the last step in the auction
function _getFinalCheckpoint() internal returns (Checkpoint memory) {
return _checkpointAtBlock(END_BLOCK);
}
/// @notice Internal function for bid submission
/// @dev Validates `maxPrice`, calls the validation hook (if set) and updates global state variables
/// For gas efficiency, `prevTickPrice` should be the price of the tick immediately before `maxPrice`.
/// @dev Does not check that the actual value `amount` was received by the contract
/// @return bidId The id of the created bid
function _submitBid(uint256 maxPrice, uint128 amount, address owner, uint256 prevTickPrice, bytes calldata hookData)
internal
returns (uint256 bidId)
{
// Reject bids which would cause TOTAL_SUPPLY * maxPrice to overflow a uint256
if (maxPrice > MAX_BID_PRICE) revert InvalidBidPriceTooHigh(maxPrice, MAX_BID_PRICE);
// Get the latest checkpoint before validating the bid
Checkpoint memory _checkpoint = checkpoint();
// Revert if there are no more tokens to be sold
if (_checkpoint.remainingMpsInAuction() == 0) revert AuctionSoldOut();
// We don't allow bids to be submitted at or below the clearing price
if (maxPrice <= _checkpoint.clearingPrice) revert BidMustBeAboveClearingPrice();
// Initialize the tick if needed. This will no-op if the tick is already initialized.
_initializeTickIfNeeded(prevTickPrice, maxPrice);
// Call the validation hook and bubble up the revert reason if it reverts
VALIDATION_HOOK.handleValidate(maxPrice, amount, owner, msg.sender, hookData);
Bid memory bid;
uint256 amountQ96 = uint256(amount) << FixedPoint96.RESOLUTION;
(bid, bidId) = _createBid(amountQ96, owner, maxPrice, _checkpoint.cumulativeMps);
// Scale the amount according to the rest of the supply schedule, accounting for past blocks
// This is only used in demand related internal calculations
uint256 bidEffectiveAmountQ96 = bid.toEffectiveAmount();
// Update the tick demand with the bid's scaled amount
_updateTickDemand(maxPrice, bidEffectiveAmountQ96);
// Update the global sum of currency demand above the clearing price tracker
// Per the validation checks above this bid must be above the clearing price
$sumCurrencyDemandAboveClearingQ96 += bidEffectiveAmountQ96;
// If the sum of demand above clearing price becomes large enough to overflow a multiplication an X7 value,
// revert to prevent the bid from being submitted.
if ($sumCurrencyDemandAboveClearingQ96 >= ConstantsLib.X7_UPPER_BOUND) {
revert InvalidBidUnableToClear();
}
emit BidSubmitted(bidId, owner, maxPrice, amount);
}
/// @notice Internal function for processing the exit of a bid
/// @dev Given a bid, tokens filled and refund, process the transfers and refund
/// `exitedBlock` MUST be checked by the caller to prevent double spending
/// @param bidId The id of the bid to exit
/// @param tokensFilled The number of tokens filled
/// @param currencySpentQ96 The amount of currency the bid spent
function _processExit(uint256 bidId, uint256 tokensFilled, uint256 currencySpentQ96) internal {
Bid storage $bid = _getBid(bidId);
address _owner = $bid.owner;
uint256 refund = ($bid.amountQ96 - currencySpentQ96) >> FixedPoint96.RESOLUTION;
$bid.tokensFilled = tokensFilled;
$bid.exitedBlock = uint64(block.number);
if (refund > 0) {
CURRENCY.transfer(_owner, refund);
}
emit BidExited(bidId, _owner, tokensFilled, refund);
}
/// @inheritdoc IContinuousClearingAuction
function checkpoint() public onlyActiveAuction returns (Checkpoint memory) {
if (block.number > END_BLOCK) {
return _getFinalCheckpoint();
} else {
return _checkpointAtBlock(uint64(block.number));
}
}
/// @inheritdoc IContinuousClearingAuction
/// @dev Bids can be submitted anytime between the startBlock and the endBlock.
function submitBid(uint256 maxPrice, uint128 amount, address owner, uint256 prevTickPrice, bytes calldata hookData)
public
payable
onlyActiveAuction
returns (uint256)
{
// Bids cannot be submitted at the endBlock or after
if (block.number >= END_BLOCK) revert AuctionIsOver();
if (amount == 0) revert BidAmountTooSmall();
if (owner == address(0)) revert BidOwnerCannotBeZeroAddress();
if (CURRENCY.isAddressZero()) {
if (msg.value != amount) revert InvalidAmount();
} else {
if (msg.value != 0) revert CurrencyIsNotNative();
SafeTransferLib.permit2TransferFrom(Currency.unwrap(CURRENCY), msg.sender, address(this), amount);
}
return _submitBid(maxPrice, amount, owner, prevTickPrice, hookData);
}
/// @inheritdoc IContinuousClearingAuction
/// @dev The call to `submitBid` checks `onlyActiveAuction` so it's not required on this function
function submitBid(uint256 maxPrice, uint128 amount, address owner, bytes calldata hookData)
external
payable
returns (uint256)
{
return submitBid(maxPrice, amount, owner, FLOOR_PRICE, hookData);
}
/// @inheritdoc IContinuousClearingAuction
function exitBid(uint256 bidId) external onlyAfterAuctionIsOver {
Bid memory bid = _getBid(bidId);
if (bid.exitedBlock != 0) revert BidAlreadyExited();
Checkpoint memory finalCheckpoint = _getFinalCheckpoint();
if (!_isGraduated()) {
// In the case that the auction did not graduate, fully refund the bid
return _processExit(bidId, 0, 0);
}
// Only bids with a max price strictly above the final clearing price can be exited via `exitBid`
if (bid.maxPrice <= finalCheckpoint.clearingPrice) revert CannotExitBid();
// Account for the fully filled checkpoints
Checkpoint memory startCheckpoint = _getCheckpoint(bid.startBlock);
(uint256 tokensFilled, uint256 currencySpentQ96) =
_accountFullyFilledCheckpoints(finalCheckpoint, startCheckpoint, bid);
_processExit(bidId, tokensFilled, currencySpentQ96);
}
/// @inheritdoc IContinuousClearingAuction
function exitPartiallyFilledBid(uint256 bidId, uint64 lastFullyFilledCheckpointBlock, uint64 outbidBlock) external {
// Checkpoint before checking any of the hints because they could depend on the latest checkpoint
Checkpoint memory currentBlockCheckpoint = checkpoint();
Bid memory bid = _getBid(bidId);
if (bid.exitedBlock != 0) revert BidAlreadyExited();
// Prevent bids from being exited before graduation
if (!_isGraduated()) {
if (block.number >= END_BLOCK) {
// If the auction is over, fully refund the bid
return _processExit(bidId, 0, 0);
}
revert CannotPartiallyExitBidBeforeGraduation();
}
uint256 bidMaxPrice = bid.maxPrice;
uint64 bidStartBlock = bid.startBlock;
// Get the last fully filled checkpoint from the user's provided hint
Checkpoint memory lastFullyFilledCheckpoint = _getCheckpoint(lastFullyFilledCheckpointBlock);
// Since `lastFullyFilledCheckpointBlock` points to the last fully filled Checkpoint, it must be < bid.maxPrice
// The next Checkpoint after `lastFullyFilledCheckpoint` must be partially or fully filled (clearingPrice >= bid.maxPrice)
// `lastFullyFilledCheckpoint` also cannot be before the bid's startCheckpoint
if (
lastFullyFilledCheckpoint.clearingPrice >= bidMaxPrice
|| _getCheckpoint(lastFullyFilledCheckpoint.next).clearingPrice < bidMaxPrice
|| lastFullyFilledCheckpointBlock < bidStartBlock
) {
revert InvalidLastFullyFilledCheckpointHint();
}
// There is guaranteed to be a checkpoint at the bid's startBlock because we always checkpoint before bid submission
Checkpoint memory startCheckpoint = _getCheckpoint(bidStartBlock);
// Initialize the tokens filled and currency spent trackers
uint256 tokensFilled;
uint256 currencySpentQ96;
// If the lastFullyFilledCheckpoint is provided, account for the fully filled checkpoints
if (lastFullyFilledCheckpoint.clearingPrice > 0) {
// Assign the calculated tokens filled and currency spent to `tokensFilled` and `currencySpentQ96`
(tokensFilled, currencySpentQ96) =
_accountFullyFilledCheckpoints(lastFullyFilledCheckpoint, startCheckpoint, bid);
}
// Upper checkpoint is the last checkpoint where the bid is partially filled
Checkpoint memory upperCheckpoint;
// If outbidBlock is not zero, the bid was outbid and the bidder is requesting an early exit
// This can be done before the auction's endBlock
if (outbidBlock != 0) {
// If the provided hint is the current block, use the checkpoint returned by `checkpoint()` instead of getting it from storage
Checkpoint memory outbidCheckpoint;
if (outbidBlock == block.number) {
outbidCheckpoint = currentBlockCheckpoint;
} else {
outbidCheckpoint = _getCheckpoint(outbidBlock);
}
upperCheckpoint = _getCheckpoint(outbidCheckpoint.prev);
// We require that the outbid checkpoint is > bid max price AND the checkpoint before it is <= bid max price, revert if either of these conditions are not met
if (outbidCheckpoint.clearingPrice <= bidMaxPrice || upperCheckpoint.clearingPrice > bidMaxPrice) {
revert InvalidOutbidBlockCheckpointHint();
}
} else {
// The only other partially exitable case is if the auction ends with the clearing price equal to the bid's max price
// These bids can only be exited after the auction ends
if (block.number < END_BLOCK) revert CannotPartiallyExitBidBeforeEndBlock();
// Set the upper checkpoint to the checkpoint returned when we initially called `checkpoint()`
// This must be the final checkpoint because `checkpoint()` will return the final checkpoint after the auction is over
upperCheckpoint = currentBlockCheckpoint;
// Revert if the final checkpoint's clearing price is not equal to the bid's max price
if (upperCheckpoint.clearingPrice != bidMaxPrice) {
revert CannotExitBid();
}
}
// If there is an `upperCheckpoint` that means that the bid had a period where it was partially filled
// From the logic above, `upperCheckpoint` now points to the last checkpoint where the clearingPrice == bidMaxPrice.
// Because the clearing price can never decrease between checkpoints, and the fact that you cannot enter a bid
// at or below the current clearing price, the bid MUST have been active during the entire partial fill period.
// And `upperCheckpoint` tracks the cumulative currency raised at that clearing price since the first partially filled checkpoint.
if (upperCheckpoint.clearingPrice == bidMaxPrice) {
uint256 tickDemandQ96 = _getTick(bidMaxPrice).currencyDemandQ96;
(uint256 partialTokensFilled, uint256 partialCurrencySpentQ96) = _accountPartiallyFilledCheckpoints(
bid, tickDemandQ96, upperCheckpoint.currencyRaisedAtClearingPriceQ96_X7
);
// Add the tokensFilled and currencySpentQ96 from the partially filled checkpoints to the total
tokensFilled += partialTokensFilled;
currencySpentQ96 += partialCurrencySpentQ96;
}
_processExit(bidId, tokensFilled, currencySpentQ96);
}
/// @inheritdoc IContinuousClearingAuction
function claimTokens(uint256 _bidId) external onlyAfterClaimBlock ensureEndBlockIsCheckpointed {
// Tokens cannot be claimed if the auction did not graduate
if (!_isGraduated()) revert NotGraduated();
(address owner, uint256 tokensFilled) = _internalClaimTokens(_bidId);
if (tokensFilled > 0) {
Currency.wrap(address(TOKEN)).transfer(owner, tokensFilled);
emit TokensClaimed(_bidId, owner, tokensFilled);
}
}
/// @inheritdoc IContinuousClearingAuction
function claimTokensBatch(address _owner, uint256[] calldata _bidIds)
external
onlyAfterClaimBlock
ensureEndBlockIsCheckpointed
{
// Tokens cannot be claimed if the auction did not graduate
if (!_isGraduated()) revert NotGraduated();
uint256 tokensFilled = 0;
for (uint256 i = 0; i < _bidIds.length; i++) {
(address bidOwner, uint256 bidTokensFilled) = _internalClaimTokens(_bidIds[i]);
if (bidOwner != _owner) {
revert BatchClaimDifferentOwner(_owner, bidOwner);
}
tokensFilled += bidTokensFilled;
if (bidTokensFilled > 0) {
emit TokensClaimed(_bidIds[i], bidOwner, bidTokensFilled);
}
}
if (tokensFilled > 0) {
Currency.wrap(address(TOKEN)).transfer(_owner, tokensFilled);
}
}
/// @notice Internal function to claim tokens for a single bid
/// @param bidId The id of the bid
/// @return owner The owner of the bid
/// @return tokensFilled The amount of tokens filled
function _internalClaimTokens(uint256 bidId) internal returns (address owner, uint256 tokensFilled) {
Bid storage $bid = _getBid(bidId);
if ($bid.exitedBlock == 0) revert BidNotExited();
// Set return values
owner = $bid.owner;
tokensFilled = $bid.tokensFilled;
// Set the tokens filled to 0
$bid.tokensFilled = 0;
}
/// @inheritdoc IContinuousClearingAuction
function sweepCurrency() external onlyAfterAuctionIsOver ensureEndBlockIsCheckpointed {
// Cannot sweep if already swept
if (sweepCurrencyBlock != 0) revert CannotSweepCurrency();
// Cannot sweep currency if the auction has not graduated, as all of the Currency must be refunded
if (!_isGraduated()) revert NotGraduated();
_sweepCurrency(_currencyRaised());
}
/// @inheritdoc IContinuousClearingAuction
function sweepUnsoldTokens() external onlyAfterAuctionIsOver ensureEndBlockIsCheckpointed {
if (sweepUnsoldTokensBlock != 0) revert CannotSweepTokens();
uint256 unsoldTokens;
if (_isGraduated()) {
unsoldTokens = TOTAL_SUPPLY_Q96.scaleUpToX7().sub($totalClearedQ96_X7).divUint256(FixedPoint96.Q96)
.scaleDownToUint256();
} else {
unsoldTokens = TOTAL_SUPPLY;
}
_sweepUnsoldTokens(unsoldTokens);
}
// Getters
/// @inheritdoc IContinuousClearingAuction
function claimBlock() external view returns (uint64) {
return CLAIM_BLOCK;
}
/// @inheritdoc IContinuousClearingAuction
function validationHook() external view returns (IValidationHook) {
return VALIDATION_HOOK;
}
/// @inheritdoc IContinuousClearingAuction
function currencyRaisedQ96_X7() external view returns (ValueX7) {
return $currencyRaisedQ96_X7;
}
/// @inheritdoc IContinuousClearingAuction
function sumCurrencyDemandAboveClearingQ96() external view returns (uint256) {
return $sumCurrencyDemandAboveClearingQ96;
}
/// @inheritdoc IContinuousClearingAuction
function totalClearedQ96_X7() external view returns (ValueX7) {
return $totalClearedQ96_X7;
}
/// @inheritdoc IContinuousClearingAuction
function totalCleared() external view returns (uint256) {
return $totalClearedQ96_X7.divUint256(FixedPoint96.Q96).scaleDownToUint256();
}
}
IContinuousClearingAuctionFactory.sol 29 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IDistributionStrategy} from './external/IDistributionStrategy.sol';
/// @title IContinuousClearingAuctionFactory
interface IContinuousClearingAuctionFactory is IDistributionStrategy {
/// @notice Error thrown when the amount is invalid
error InvalidTokenAmount(uint256 amount);
/// @notice Emitted when an auction is created
/// @param auction The address of the auction contract
/// @param token The address of the token
/// @param amount The amount of tokens to sell
/// @param configData The configuration data for the auction
event AuctionCreated(address indexed auction, address indexed token, uint256 amount, bytes configData);
/// @notice Get the address of an auction contract
/// @param token The address of the token
/// @param amount The amount of tokens to sell
/// @param configData The configuration data for the auction
/// @param salt The salt to use for the deterministic deployment
/// @param sender The sender of the initializeDistribution transaction
/// @return The address of the auction contract
function getAuctionAddress(address token, uint256 amount, bytes calldata configData, bytes32 salt, address sender)
external
view
returns (address);
}
FullMath.sol 117 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
/// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
/// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
function mulDiv(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = a * b
// Compute the product mod 2**256 and mod 2**256 - 1
// then use the Chinese Remainder Theorem to reconstruct
// the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2**256 + prod0
uint256 prod0 = a * b; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly ("memory-safe") {
let mm := mulmod(a, b, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Make sure the result is less than 2**256.
// Also prevents denominator == 0
require(denominator > prod1);
// Handle non-overflow cases, 256 by 256 division
if (prod1 == 0) {
assembly ("memory-safe") {
result := div(prod0, denominator)
}
return result;
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0]
// Compute remainder using mulmod
uint256 remainder;
assembly ("memory-safe") {
remainder := mulmod(a, b, denominator)
}
// Subtract 256 bit number from 512 bit number
assembly ("memory-safe") {
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator
// Compute largest power of two divisor of denominator.
// Always >= 1.
uint256 twos = (0 - denominator) & denominator;
// Divide denominator by power of two
assembly ("memory-safe") {
denominator := div(denominator, twos)
}
// Divide [prod1 prod0] by the factors of two
assembly ("memory-safe") {
prod0 := div(prod0, twos)
}
// Shift in bits from prod1 into prod0. For this we need
// to flip `twos` such that it is 2**256 / twos.
// If twos is zero, then it becomes one
assembly ("memory-safe") {
twos := add(div(sub(0, twos), twos), 1)
}
prod0 |= prod1 * twos;
// Invert denominator mod 2**256
// Now that denominator is an odd number, it has an inverse
// modulo 2**256 such that denominator * inv = 1 mod 2**256.
// Compute the inverse by starting with a seed that is correct
// correct for four bits. That is, denominator * inv = 1 mod 2**4
uint256 inv = (3 * denominator) ^ 2;
// Now use Newton-Raphson iteration to improve the precision.
// Thanks to Hensel's lifting lemma, this also works in modular
// arithmetic, doubling the correct bits in each step.
inv *= 2 - denominator * inv; // inverse mod 2**8
inv *= 2 - denominator * inv; // inverse mod 2**16
inv *= 2 - denominator * inv; // inverse mod 2**32
inv *= 2 - denominator * inv; // inverse mod 2**64
inv *= 2 - denominator * inv; // inverse mod 2**128
inv *= 2 - denominator * inv; // inverse mod 2**256
// Because the division is now exact we can divide by multiplying
// with the modular inverse of denominator. This will give us the
// correct result modulo 2**256. Since the preconditions guarantee
// that the outcome is less than 2**256, this is the final result.
// We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inv;
return result;
}
}
/// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
function mulDivRoundingUp(uint256 a, uint256 b, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
result = mulDiv(a, b, denominator);
if (mulmod(a, b, denominator) != 0) {
require(++result > 0);
}
}
}
}
FixedPoint96.sol 10 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title FixedPoint96
/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format)
/// @dev Used in SqrtPriceMath.sol
library FixedPoint96 {
uint8 internal constant RESOLUTION = 96;
uint256 internal constant Q96 = 0x1000000000000000000000000;
}
Currency.sol 119 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IERC20Minimal} from "../interfaces/external/IERC20Minimal.sol";
import {CustomRevert} from "../libraries/CustomRevert.sol";
type Currency is address;
using {greaterThan as >, lessThan as <, greaterThanOrEqualTo as >=, equals as ==} for Currency global;
using CurrencyLibrary for Currency global;
function equals(Currency currency, Currency other) pure returns (bool) {
return Currency.unwrap(currency) == Currency.unwrap(other);
}
function greaterThan(Currency currency, Currency other) pure returns (bool) {
return Currency.unwrap(currency) > Currency.unwrap(other);
}
function lessThan(Currency currency, Currency other) pure returns (bool) {
return Currency.unwrap(currency) < Currency.unwrap(other);
}
function greaterThanOrEqualTo(Currency currency, Currency other) pure returns (bool) {
return Currency.unwrap(currency) >= Currency.unwrap(other);
}
/// @title CurrencyLibrary
/// @dev This library allows for transferring and holding native tokens and ERC20 tokens
library CurrencyLibrary {
/// @notice Additional context for ERC-7751 wrapped error when a native transfer fails
error NativeTransferFailed();
/// @notice Additional context for ERC-7751 wrapped error when an ERC20 transfer fails
error ERC20TransferFailed();
/// @notice A constant to represent the native currency
Currency public constant ADDRESS_ZERO = Currency.wrap(address(0));
function transfer(Currency currency, address to, uint256 amount) internal {
// altered from https://github.com/transmissions11/solmate/blob/44a9963d4c78111f77caa0e65d677b8b46d6f2e6/src/utils/SafeTransferLib.sol
// modified custom error selectors
bool success;
if (currency.isAddressZero()) {
assembly ("memory-safe") {
// Transfer the ETH and revert if it fails.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
// revert with NativeTransferFailed, containing the bubbled up error as an argument
if (!success) {
CustomRevert.bubbleUpAndRevertWith(to, bytes4(0), NativeTransferFailed.selector);
}
} else {
assembly ("memory-safe") {
// Get a pointer to some free memory.
let fmp := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(fmp, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(fmp, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(fmp, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success :=
and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), currency, 0, fmp, 68, 0, 32)
)
// Now clean the memory we used
mstore(fmp, 0) // 4 byte `selector` and 28 bytes of `to` were stored here
mstore(add(fmp, 0x20), 0) // 4 bytes of `to` and 28 bytes of `amount` were stored here
mstore(add(fmp, 0x40), 0) // 4 bytes of `amount` were stored here
}
// revert with ERC20TransferFailed, containing the bubbled up error as an argument
if (!success) {
CustomRevert.bubbleUpAndRevertWith(
Currency.unwrap(currency), IERC20Minimal.transfer.selector, ERC20TransferFailed.selector
);
}
}
}
function balanceOfSelf(Currency currency) internal view returns (uint256) {
if (currency.isAddressZero()) {
return address(this).balance;
} else {
return IERC20Minimal(Currency.unwrap(currency)).balanceOf(address(this));
}
}
function balanceOf(Currency currency, address owner) internal view returns (uint256) {
if (currency.isAddressZero()) {
return owner.balance;
} else {
return IERC20Minimal(Currency.unwrap(currency)).balanceOf(owner);
}
}
function isAddressZero(Currency currency) internal pure returns (bool) {
return Currency.unwrap(currency) == Currency.unwrap(ADDRESS_ZERO);
}
function toId(Currency currency) internal pure returns (uint256) {
return uint160(Currency.unwrap(currency));
}
// If the upper 12 bytes are non-zero, they will be zero-ed out
// Therefore, fromId() and toId() are not inverses of each other
function fromId(uint256 id) internal pure returns (Currency) {
return Currency.wrap(address(uint160(id)));
}
}
TickMath.sol 238 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {BitMath} from "./BitMath.sol";
import {CustomRevert} from "./CustomRevert.sol";
/// @title Math library for computing sqrt prices from ticks and vice versa
/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports
/// prices between 2**-128 and 2**128
library TickMath {
using CustomRevert for bytes4;
/// @notice Thrown when the tick passed to #getSqrtPriceAtTick is not between MIN_TICK and MAX_TICK
error InvalidTick(int24 tick);
/// @notice Thrown when the price passed to #getTickAtSqrtPrice does not correspond to a price between MIN_TICK and MAX_TICK
error InvalidSqrtPrice(uint160 sqrtPriceX96);
/// @dev The minimum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**-128
/// @dev If ever MIN_TICK and MAX_TICK are not centered around 0, the absTick logic in getSqrtPriceAtTick cannot be used
int24 internal constant MIN_TICK = -887272;
/// @dev The maximum tick that may be passed to #getSqrtPriceAtTick computed from log base 1.0001 of 2**128
/// @dev If ever MIN_TICK and MAX_TICK are not centered around 0, the absTick logic in getSqrtPriceAtTick cannot be used
int24 internal constant MAX_TICK = 887272;
/// @dev The minimum tick spacing value drawn from the range of type int16 that is greater than 0, i.e. min from the range [1, 32767]
int24 internal constant MIN_TICK_SPACING = 1;
/// @dev The maximum tick spacing value drawn from the range of type int16, i.e. max from the range [1, 32767]
int24 internal constant MAX_TICK_SPACING = type(int16).max;
/// @dev The minimum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MIN_TICK)
uint160 internal constant MIN_SQRT_PRICE = 4295128739;
/// @dev The maximum value that can be returned from #getSqrtPriceAtTick. Equivalent to getSqrtPriceAtTick(MAX_TICK)
uint160 internal constant MAX_SQRT_PRICE = 1461446703485210103287273052203988822378723970342;
/// @dev A threshold used for optimized bounds check, equals `MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1`
uint160 internal constant MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE =
1461446703485210103287273052203988822378723970342 - 4295128739 - 1;
/// @notice Given a tickSpacing, compute the maximum usable tick
function maxUsableTick(int24 tickSpacing) internal pure returns (int24) {
unchecked {
return (MAX_TICK / tickSpacing) * tickSpacing;
}
}
/// @notice Given a tickSpacing, compute the minimum usable tick
function minUsableTick(int24 tickSpacing) internal pure returns (int24) {
unchecked {
return (MIN_TICK / tickSpacing) * tickSpacing;
}
}
/// @notice Calculates sqrt(1.0001^tick) * 2^96
/// @dev Throws if |tick| > max tick
/// @param tick The input tick for the above formula
/// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the price of the two assets (currency1/currency0)
/// at the given tick
function getSqrtPriceAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) {
unchecked {
uint256 absTick;
assembly ("memory-safe") {
tick := signextend(2, tick)
// mask = 0 if tick >= 0 else -1 (all 1s)
let mask := sar(255, tick)
// if tick >= 0, |tick| = tick = 0 ^ tick
// if tick < 0, |tick| = ~~|tick| = ~(-|tick| - 1) = ~(tick - 1) = (-1) ^ (tick - 1)
// either way, |tick| = mask ^ (tick + mask)
absTick := xor(mask, add(mask, tick))
}
if (absTick > uint256(int256(MAX_TICK))) InvalidTick.selector.revertWith(tick);
// The tick is decomposed into bits, and for each bit with index i that is set, the product of 1/sqrt(1.0001^(2^i))
// is calculated (using Q128.128). The constants used for this calculation are rounded to the nearest integer
// Equivalent to:
// price = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
// or price = int(2**128 / sqrt(1.0001)) if (absTick & 0x1) else 1 << 128
uint256 price;
assembly ("memory-safe") {
price := xor(shl(128, 1), mul(xor(shl(128, 1), 0xfffcb933bd6fad37aa2d162d1a594001), and(absTick, 0x1)))
}
if (absTick & 0x2 != 0) price = (price * 0xfff97272373d413259a46990580e213a) >> 128;
if (absTick & 0x4 != 0) price = (price * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
if (absTick & 0x8 != 0) price = (price * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
if (absTick & 0x10 != 0) price = (price * 0xffcb9843d60f6159c9db58835c926644) >> 128;
if (absTick & 0x20 != 0) price = (price * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
if (absTick & 0x40 != 0) price = (price * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
if (absTick & 0x80 != 0) price = (price * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
if (absTick & 0x100 != 0) price = (price * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
if (absTick & 0x200 != 0) price = (price * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
if (absTick & 0x400 != 0) price = (price * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
if (absTick & 0x800 != 0) price = (price * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
if (absTick & 0x1000 != 0) price = (price * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
if (absTick & 0x2000 != 0) price = (price * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
if (absTick & 0x4000 != 0) price = (price * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
if (absTick & 0x8000 != 0) price = (price * 0x31be135f97d08fd981231505542fcfa6) >> 128;
if (absTick & 0x10000 != 0) price = (price * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
if (absTick & 0x20000 != 0) price = (price * 0x5d6af8dedb81196699c329225ee604) >> 128;
if (absTick & 0x40000 != 0) price = (price * 0x2216e584f5fa1ea926041bedfe98) >> 128;
if (absTick & 0x80000 != 0) price = (price * 0x48a170391f7dc42444e8fa2) >> 128;
assembly ("memory-safe") {
// if (tick > 0) price = type(uint256).max / price;
if sgt(tick, 0) { price := div(not(0), price) }
// this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96.
// we then downcast because we know the result always fits within 160 bits due to our tick input constraint
// we round up in the division so getTickAtSqrtPrice of the output price is always consistent
// `sub(shl(32, 1), 1)` is `type(uint32).max`
// `price + type(uint32).max` will not overflow because `price` fits in 192 bits
sqrtPriceX96 := shr(32, add(price, sub(shl(32, 1), 1)))
}
}
}
/// @notice Calculates the greatest tick value such that getSqrtPriceAtTick(tick) <= sqrtPriceX96
/// @dev Throws in case sqrtPriceX96 < MIN_SQRT_PRICE, as MIN_SQRT_PRICE is the lowest value getSqrtPriceAtTick may
/// ever return.
/// @param sqrtPriceX96 The sqrt price for which to compute the tick as a Q64.96
/// @return tick The greatest tick for which the getSqrtPriceAtTick(tick) is less than or equal to the input sqrtPriceX96
function getTickAtSqrtPrice(uint160 sqrtPriceX96) internal pure returns (int24 tick) {
unchecked {
// Equivalent: if (sqrtPriceX96 < MIN_SQRT_PRICE || sqrtPriceX96 >= MAX_SQRT_PRICE) revert InvalidSqrtPrice();
// second inequality must be >= because the price can never reach the price at the max tick
// if sqrtPriceX96 < MIN_SQRT_PRICE, the `sub` underflows and `gt` is true
// if sqrtPriceX96 >= MAX_SQRT_PRICE, sqrtPriceX96 - MIN_SQRT_PRICE > MAX_SQRT_PRICE - MIN_SQRT_PRICE - 1
if ((sqrtPriceX96 - MIN_SQRT_PRICE) > MAX_SQRT_PRICE_MINUS_MIN_SQRT_PRICE_MINUS_ONE) {
InvalidSqrtPrice.selector.revertWith(sqrtPriceX96);
}
uint256 price = uint256(sqrtPriceX96) << 32;
uint256 r = price;
uint256 msb = BitMath.mostSignificantBit(r);
if (msb >= 128) r = price >> (msb - 127);
else r = price << (127 - msb);
int256 log_2 = (int256(msb) - 128) << 64;
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(63, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(62, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(61, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(60, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(59, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(58, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(57, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(56, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(55, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(54, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(53, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(52, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(51, f))
r := shr(f, r)
}
assembly ("memory-safe") {
r := shr(127, mul(r, r))
let f := shr(128, r)
log_2 := or(log_2, shl(50, f))
}
int256 log_sqrt10001 = log_2 * 255738958999603826347141; // Q22.128 number
// Magic number represents the ceiling of the maximum value of the error when approximating log_sqrt10001(x)
int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128);
// Magic number represents the minimum value of the error when approximating log_sqrt10001(x), when
// sqrtPrice is from the range (2^-64, 2^64). This is safe as MIN_SQRT_PRICE is more than 2^-64. If MIN_SQRT_PRICE
// is changed, this may need to be changed too
int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128);
tick = tickLow == tickHi ? tickLow : getSqrtPriceAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow;
}
}
}
LPFeeLibrary.sol 79 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {CustomRevert} from "./CustomRevert.sol";
/// @notice Library of helper functions for a pools LP fee
library LPFeeLibrary {
using LPFeeLibrary for uint24;
using CustomRevert for bytes4;
/// @notice Thrown when the static or dynamic fee on a pool exceeds 100%.
error LPFeeTooLarge(uint24 fee);
/// @notice An lp fee of exactly 0b1000000... signals a dynamic fee pool. This isn't a valid static fee as it is > MAX_LP_FEE
uint24 public constant DYNAMIC_FEE_FLAG = 0x800000;
/// @notice the second bit of the fee returned by beforeSwap is used to signal if the stored LP fee should be overridden in this swap
// only dynamic-fee pools can return a fee via the beforeSwap hook
uint24 public constant OVERRIDE_FEE_FLAG = 0x400000;
/// @notice mask to remove the override fee flag from a fee returned by the beforeSwaphook
uint24 public constant REMOVE_OVERRIDE_MASK = 0xBFFFFF;
/// @notice the lp fee is represented in hundredths of a bip, so the max is 100%
uint24 public constant MAX_LP_FEE = 1000000;
/// @notice returns true if a pool's LP fee signals that the pool has a dynamic fee
/// @param self The fee to check
/// @return bool True of the fee is dynamic
function isDynamicFee(uint24 self) internal pure returns (bool) {
return self == DYNAMIC_FEE_FLAG;
}
/// @notice returns true if an LP fee is valid, aka not above the maximum permitted fee
/// @param self The fee to check
/// @return bool True of the fee is valid
function isValid(uint24 self) internal pure returns (bool) {
return self <= MAX_LP_FEE;
}
/// @notice validates whether an LP fee is larger than the maximum, and reverts if invalid
/// @param self The fee to validate
function validate(uint24 self) internal pure {
if (!self.isValid()) LPFeeTooLarge.selector.revertWith(self);
}
/// @notice gets and validates the initial LP fee for a pool. Dynamic fee pools have an initial fee of 0.
/// @dev if a dynamic fee pool wants a non-0 initial fee, it should call `updateDynamicLPFee` in the afterInitialize hook
/// @param self The fee to get the initial LP from
/// @return initialFee 0 if the fee is dynamic, otherwise the fee (if valid)
function getInitialLPFee(uint24 self) internal pure returns (uint24) {
// the initial fee for a dynamic fee pool is 0
if (self.isDynamicFee()) return 0;
self.validate();
return self;
}
/// @notice returns true if the fee has the override flag set (2nd highest bit of the uint24)
/// @param self The fee to check
/// @return bool True of the fee has the override flag set
function isOverride(uint24 self) internal pure returns (bool) {
return self & OVERRIDE_FEE_FLAG != 0;
}
/// @notice returns a fee with the override flag removed
/// @param self The fee to remove the override flag from
/// @return fee The fee without the override flag set
function removeOverrideFlag(uint24 self) internal pure returns (uint24) {
return self & REMOVE_OVERRIDE_MASK;
}
/// @notice Removes the override flag and validates the fee (reverts if the fee is too large)
/// @param self The fee to remove the override flag from, and then validate
/// @return fee The fee without the override flag set (if valid)
function removeOverrideFlagAndValidate(uint24 self) internal pure returns (uint24 fee) {
fee = self.removeOverrideFlag();
fee.validate();
}
}
ActionConstants.sol 20 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title Action Constants
/// @notice Common constants used in actions
/// @dev Constants are gas efficient alternatives to their literal values
library ActionConstants {
/// @notice used to signal that an action should use the input value of the open delta on the pool manager
/// or of the balance that the contract holds
uint128 internal constant OPEN_DELTA = 0;
/// @notice used to signal that an action should use the contract's entire balance of a currency
/// This value is equivalent to 1<<255, i.e. a singular 1 in the most significant bit.
uint256 internal constant CONTRACT_BALANCE = 0x8000000000000000000000000000000000000000000000000000000000000000;
/// @notice used to signal that the recipient of an action should be the msgSender
address internal constant MSG_SENDER = address(1);
/// @notice used to signal that the recipient of an action should be the address(this)
address internal constant ADDRESS_THIS = address(2);
}
LiquidityAmounts.sol 75 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol";
import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol";
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
/// @notice Provides functions for computing liquidity amounts from token amounts and prices
library LiquidityAmounts {
using SafeCast for uint256;
/// @notice Computes the amount of liquidity received for a given amount of token0 and price range
/// @dev Calculates amount0 * (sqrt(upper) * sqrt(lower)) / (sqrt(upper) - sqrt(lower))
/// @param sqrtPriceAX96 A sqrt price representing the first tick boundary
/// @param sqrtPriceBX96 A sqrt price representing the second tick boundary
/// @param amount0 The amount0 being sent in
/// @return liquidity The amount of returned liquidity
function getLiquidityForAmount0(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount0)
internal
pure
returns (uint128 liquidity)
{
unchecked {
if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
uint256 intermediate = FullMath.mulDiv(sqrtPriceAX96, sqrtPriceBX96, FixedPoint96.Q96);
return FullMath.mulDiv(amount0, intermediate, sqrtPriceBX96 - sqrtPriceAX96).toUint128();
}
}
/// @notice Computes the amount of liquidity received for a given amount of token1 and price range
/// @dev Calculates amount1 / (sqrt(upper) - sqrt(lower)).
/// @param sqrtPriceAX96 A sqrt price representing the first tick boundary
/// @param sqrtPriceBX96 A sqrt price representing the second tick boundary
/// @param amount1 The amount1 being sent in
/// @return liquidity The amount of returned liquidity
function getLiquidityForAmount1(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount1)
internal
pure
returns (uint128 liquidity)
{
unchecked {
if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
return FullMath.mulDiv(amount1, FixedPoint96.Q96, sqrtPriceBX96 - sqrtPriceAX96).toUint128();
}
}
/// @notice Computes the maximum amount of liquidity received for a given amount of token0, token1, the current
/// pool prices and the prices at the tick boundaries
/// @param sqrtPriceX96 A sqrt price representing the current pool prices
/// @param sqrtPriceAX96 A sqrt price representing the first tick boundary
/// @param sqrtPriceBX96 A sqrt price representing the second tick boundary
/// @param amount0 The amount of token0 being sent in
/// @param amount1 The amount of token1 being sent in
/// @return liquidity The maximum amount of liquidity received
function getLiquidityForAmounts(
uint160 sqrtPriceX96,
uint160 sqrtPriceAX96,
uint160 sqrtPriceBX96,
uint256 amount0,
uint256 amount1
) internal pure returns (uint128 liquidity) {
if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
if (sqrtPriceX96 <= sqrtPriceAX96) {
liquidity = getLiquidityForAmount0(sqrtPriceAX96, sqrtPriceBX96, amount0);
} else if (sqrtPriceX96 < sqrtPriceBX96) {
uint128 liquidity0 = getLiquidityForAmount0(sqrtPriceX96, sqrtPriceBX96, amount0);
uint128 liquidity1 = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceX96, amount1);
liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;
} else {
liquidity = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceBX96, amount1);
}
}
}
IERC20.sol 79 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC-20 standard as defined in the ERC.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
IDistributionContract.sol 17 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title IDistributionContract
/// @notice Interface for token distribution contracts.
interface IDistributionContract {
/// @notice Error thrown when the token address is invalid
error InvalidToken(address token);
/// @notice Error thrown when the amount received is invalid upon receiving tokens
/// @param expected The expected amount
/// @param received The received amount
error InvalidAmountReceived(uint256 expected, uint256 received);
/// @notice Notify a distribution contract that it has received the tokens to distribute
function onTokensReceived() external;
}
ILBPStrategyBasic.sol 124 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {IDistributionContract} from "./IDistributionContract.sol";
/// @title ILBPStrategyBasic
/// @notice Interface for the LBPStrategyBasic contract
interface ILBPStrategyBasic is IDistributionContract {
/// @notice Emitted when a v4 pool is created and the liquidity is migrated to it
/// @param key The key of the pool that was created
/// @param initialSqrtPriceX96 The initial sqrt price of the pool
event Migrated(PoolKey indexed key, uint160 initialSqrtPriceX96);
/// @notice Emitted when the auction is created
/// @param auction The address of the auction contract
event AuctionCreated(address indexed auction);
/// @notice Error thrown when migration to a v4 pool is not allowed yet
/// @param migrationBlock The block number at which migration is allowed
/// @param currentBlock The current block number
error MigrationNotAllowed(uint256 migrationBlock, uint256 currentBlock);
/// @notice Emitted when the tokens are swept
event TokensSwept(address indexed operator, uint256 amount);
/// @notice Emitted when the currency is swept
event CurrencySwept(address indexed operator, uint256 amount);
/// @notice Error thrown when the sweep block is before or at the migration block
error InvalidSweepBlock(uint256 sweepBlock, uint256 migrationBlock);
/// @notice Error thrown when the end block is at orafter the migration block
/// @param endBlock The invalid end block
/// @param migrationBlock The migration block
error InvalidEndBlock(uint256 endBlock, uint256 migrationBlock);
/// @notice Error thrown when the currency in the auction parameters is not the same as the currency in the migrator parameters
/// @param auctionCurrency The currency in the auction parameters
/// @param migratorCurrency The currency in the migrator parameters
error InvalidCurrency(address auctionCurrency, address migratorCurrency);
/// @notice Error thrown when the floor price is invalid
/// @param floorPrice The invalid floor price
error InvalidFloorPrice(uint256 floorPrice);
/// @notice Error thrown when the token split is too high
/// @param tokenSplit The invalid token split percentage
error TokenSplitTooHigh(uint24 tokenSplit, uint24 maxTokenSplit);
/// @notice Error thrown when the tick spacing is greater than the max tick spacing or less than the min tick spacing
/// @param tickSpacing The invalid tick spacing
error InvalidTickSpacing(int24 tickSpacing, int24 minTickSpacing, int24 maxTickSpacing);
/// @notice Error thrown when the fee is greater than the max fee
/// @param fee The invalid fee
error InvalidFee(uint24 fee, uint24 maxFee);
/// @notice Error thrown when the position recipient is the zero address, address(1), or address(2)
/// @param positionRecipient The invalid position recipient
error InvalidPositionRecipient(address positionRecipient);
/// @notice Error thrown when the funds recipient is not set to address(1)
/// @param invalidFundsRecipient The invalid funds recipient
/// @param expectedFundsRecipient The expected funds recipient (address(1))
error InvalidFundsRecipient(address invalidFundsRecipient, address expectedFundsRecipient);
/// @notice Error thrown when the reserve supply is too high
/// @param reserveSupply The invalid reserve supply
/// @param maxReserveSupply The maximum reserve supply (type(uint128).max)
error ReserveSupplyIsTooHigh(uint256 reserveSupply, uint256 maxReserveSupply);
/// @notice Error thrown when the liquidity is invalid
/// @param liquidity The invalid liquidity
/// @param maxLiquidity The max liquidity
error InvalidLiquidity(uint128 liquidity, uint128 maxLiquidity);
/// @notice Error thrown when the caller is not the auction
/// @param caller The caller that is not the auction
/// @param auction The auction that is not the caller
error NativeCurrencyTransferNotFromAuction(address caller, address auction);
/// @notice Error thrown when the caller is not the operator
error NotOperator(address caller, address operator);
/// @notice Error thrown when the sweep is not allowed yet
error SweepNotAllowed(uint256 sweepBlock, uint256 currentBlock);
/// @notice Error thrown when the token amount is invalid
/// @param tokenAmount The invalid token amount
/// @param reserveSupply The reserve supply
error InvalidTokenAmount(uint128 tokenAmount, uint128 reserveSupply);
/// @notice Error thrown when the auction supply is zero
error AuctionSupplyIsZero();
/// @notice Error thrown when the currency amount is greater than type(uint128).max
/// @param currencyAmount The invalid currency amount
/// @param maxCurrencyAmount The maximum currency amount (type(uint128).max)
error CurrencyAmountTooHigh(uint256 currencyAmount, uint256 maxCurrencyAmount);
/// @notice Error thrown when the currency amount is invalid
/// @param amountNeeded The currency amount needed
/// @param amountAvailable The balance of the currency in the contract
error InsufficientCurrency(uint256 amountNeeded, uint256 amountAvailable);
/// @notice Error thrown when no currency was raised
error NoCurrencyRaised();
/// @notice Error thrown when the token amount is too high
/// @param tokenAmount The invalid token amount
error AmountOverflow(uint256 tokenAmount);
/// @notice Migrates the raised funds and tokens to a v4 pool
function migrate() external;
/// @notice Allows the operator to sweep tokens from the contract
/// @dev Can only be called after sweepBlock by the operator
function sweepToken() external;
/// @notice Allows the operator to sweep currency from the contract
/// @dev Can only be called after sweepBlock by the operator
function sweepCurrency() external;
}
HookBasic.sol 47 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {BaseHook} from "@uniswap/v4-periphery/src/utils/BaseHook.sol";
/// @title HookBasic
/// @notice Hook contract that only allows itself to initialize the pool
abstract contract HookBasic is BaseHook {
/// @notice Error thrown when the caller of `initializePool` is not address(this)
/// @param caller The invalid address attempting to initialize the pool
/// @param expected address(this)
error InvalidInitializer(address caller, address expected);
constructor(IPoolManager _poolManager) BaseHook(_poolManager) {}
/// @inheritdoc BaseHook
function getHookPermissions() public pure virtual override returns (Hooks.Permissions memory) {
return Hooks.Permissions({
beforeInitialize: true,
beforeAddLiquidity: false,
beforeSwap: false,
beforeSwapReturnDelta: false,
afterSwap: false,
afterInitialize: false,
beforeRemoveLiquidity: false,
afterAddLiquidity: false,
afterRemoveLiquidity: false,
beforeDonate: false,
afterDonate: false,
afterSwapReturnDelta: false,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}
/// @inheritdoc BaseHook
function _beforeInitialize(address sender, PoolKey calldata, uint160) internal view override returns (bytes4) {
// This check is only hit when another address tries to initialize the pool, since hooks cannot call themselves.
// Therefore this will always revert, ensuring only this contract can initialize pools
if (sender != address(this)) revert InvalidInitializer(sender, address(this));
return IHooks.beforeInitialize.selector;
}
}
TokenPricing.sol 125 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol";
import {FixedPoint96} from "@uniswap/v4-core/src/libraries/FixedPoint96.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {Math} from "@openzeppelin-latest/contracts/utils/math/Math.sol";
/// @title TokenPricing
/// @notice Library for pricing operations including price conversions and token amount calculations
/// @dev Handles conversions between different price representations and calculates swap amounts
library TokenPricing {
/// @notice Thrown when price is invalid (0 or out of bounds)
/// @param price The invalid price in Q96 format in terms of currency1/currency0
error PriceIsZero(uint256 price);
/// @notice Thrown when price is too high
/// @param price The invalid price in Q96 format in terms of currency1/currency0
/// @param maxPrice The maximum price (type(uint160).max)
error PriceTooHigh(uint256 price, uint256 maxPrice);
/// @notice Thrown when price is out of bounds
/// @param sqrtPriceX96 The invalid sqrt price in Q96 format
/// @param minSqrtPriceX96 The minimum sqrt price (TickMath.MIN_SQRT_PRICE)
/// @param maxSqrtPriceX96 The maximum sqrt price (TickMath.MAX_SQRT_PRICE)
error SqrtPriceX96OutOfBounds(uint160 sqrtPriceX96, uint160 minSqrtPriceX96, uint160 maxSqrtPriceX96);
/// @notice Thrown when calculated amount exceeds uint128 max value
/// @param currencyAmount The invalid currency amount
error AmountOverflow(uint256 currencyAmount);
/// @notice Q192 format: 192-bit fixed-point number representation
/// @dev Used for intermediate calculations to maintain precision
uint256 public constant Q192 = 1 << 192;
/// @notice Converts a Q96 price to Uniswap v4 X192 format in terms of currency1/currency0
/// @dev Converts price from Q96 to X192 format
/// @param price The price in Q96 fixed-point format (96 bits of fractional precision)
/// @param currencyIsCurrency0 True if the currency is currency0 (lower address)
/// @return priceX192 The price in Q192 fixed-point format
function convertToPriceX192(uint256 price, bool currencyIsCurrency0) internal pure returns (uint256 priceX192) {
// Prevent division by zero
if (price == 0) {
revert PriceIsZero(price);
}
// If currency is currency0, we need to invert the price (price = currency1/currency0)
if (currencyIsCurrency0) {
// If the inverted price is greater than uint160.max it will revert in FullMath
// Catch it explicitly here and revert with PriceTooHigh
if ((Q192 / price) >> 160 != 0) {
revert PriceTooHigh(Q192 / price, type(uint160).max);
}
// Invert the Q96 price using FullMath with 512 bits of precision
// Equivalent to finding the inverse then shifting left 96 bits
priceX192 = FullMath.mulDiv(Q192, FixedPoint96.Q96, price);
} else {
// Otherwise, revert if the price exceeds uint160.max
if (price >> 160 != 0) {
revert PriceTooHigh(price, type(uint160).max);
}
priceX192 = price << FixedPoint96.RESOLUTION;
}
}
/// @notice Converts a Q192 price to Uniswap v4 sqrtPriceX96 format
/// @dev Converts price from Q192 to sqrtPriceX96 format
/// @param priceX192 The price in Q192 fixed-point format
/// @return sqrtPriceX96 The square root price in Q96 fixed-point format
function convertToSqrtPriceX96(uint256 priceX192) internal pure returns (uint160 sqrtPriceX96) {
// Calculate square root for Uniswap v4's sqrtPriceX96 format
// This will lose some precision and be rounded down
sqrtPriceX96 = uint160(Math.sqrt(priceX192));
if (sqrtPriceX96 < TickMath.MIN_SQRT_PRICE || sqrtPriceX96 > TickMath.MAX_SQRT_PRICE) {
revert SqrtPriceX96OutOfBounds(sqrtPriceX96, TickMath.MIN_SQRT_PRICE, TickMath.MAX_SQRT_PRICE);
}
return sqrtPriceX96;
}
/// @notice Calculates token amount based on currency amount and price
/// @dev Uses Q192 fixed-point arithmetic for precision
/// @param priceX192 The price in Q192 fixed-point format
/// @param currencyAmount The amount of currency to convert
/// @param currencyIsCurrency0 True if the currency is currency0 (lower address)
/// @param reserveSupply The reserve supply of the token
/// @return tokenAmount The calculated token amount
/// @return leftoverCurrency The leftover currency amount
/// @return correspondingCurrencyAmount The corresponding currency amount
function calculateAmounts(
uint256 priceX192,
uint128 currencyAmount,
bool currencyIsCurrency0,
uint128 reserveSupply
) internal pure returns (uint128 tokenAmount, uint128 leftoverCurrency, uint128 correspondingCurrencyAmount) {
// calculates corresponding token amount based on currency amount and price
uint256 tokenAmountUint256 = currencyIsCurrency0
? FullMath.mulDiv(priceX192, currencyAmount, Q192)
: FullMath.mulDiv(currencyAmount, Q192, priceX192);
// if token amount is greater than reserve supply, there is leftover currency. we need to find new currency amount based on reserve supply and price.
if (tokenAmountUint256 > reserveSupply) {
uint256 correspondingCurrencyAmountUint256 = currencyIsCurrency0
? FullMath.mulDiv(reserveSupply, Q192, priceX192)
: FullMath.mulDiv(priceX192, reserveSupply, Q192);
if (correspondingCurrencyAmountUint256 > type(uint128).max) {
revert AmountOverflow(correspondingCurrencyAmountUint256);
}
correspondingCurrencyAmount = uint128(correspondingCurrencyAmountUint256);
// currencyAmount is already validated to be less than or equal to type(uint128).max so leftoverCurrency is also less than or equal to type(uint128).max
leftoverCurrency = currencyAmount - correspondingCurrencyAmount;
tokenAmount = reserveSupply; // tokenAmount will never be greater than reserveSupply
} else {
correspondingCurrencyAmount = currencyAmount;
// tokenAmountUint256 is less than or equal to reserveSupply which is less than or equal to type(uint128).max
tokenAmount = uint128(tokenAmountUint256);
}
return (tokenAmount, leftoverCurrency, correspondingCurrencyAmount);
}
}
StrategyPlanner.sol 176 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {LiquidityAmounts} from "@uniswap/v4-periphery/src/libraries/LiquidityAmounts.sol";
import {BasePositionParams, FullRangeParams, OneSidedParams, TickBounds} from "../types/PositionTypes.sol";
import {ParamsBuilder} from "./ParamsBuilder.sol";
import {ActionsBuilder} from "./ActionsBuilder.sol";
import {TickCalculations} from "./TickCalculations.sol";
/// @title PositionPlanner
/// @notice Simplified library that orchestrates position planning using helper libraries
library StrategyPlanner {
using TickCalculations for int24;
using ParamsBuilder for *;
/// @notice Creates the actions and parameters needed to mint a full range position on the position manager
/// @param baseParams The base parameters for the position
/// @param fullRangeParams The amounts of currency and token that will be used to mint the position
/// @param paramsArraySize The size of the parameters array (either 5 if it's a standalone full range position,
/// or 8 if it's a full range position with one sided position)
/// @return actions The actions needed to mint a full range position on the position manager
/// @return params The parameters needed to mint a full range position on the position manager
function planFullRangePosition(
BasePositionParams memory baseParams,
FullRangeParams memory fullRangeParams,
uint256 paramsArraySize
) internal pure returns (bytes memory actions, bytes[] memory params) {
bool currencyIsCurrency0 = baseParams.currency < baseParams.poolToken;
// Get tick bounds for full range
TickBounds memory bounds = TickBounds({
lowerTick: TickMath.minUsableTick(baseParams.poolTickSpacing),
upperTick: TickMath.maxUsableTick(baseParams.poolTickSpacing)
});
PoolKey memory poolKey = PoolKey({
currency0: Currency.wrap(currencyIsCurrency0 ? baseParams.currency : baseParams.poolToken),
currency1: Currency.wrap(currencyIsCurrency0 ? baseParams.poolToken : baseParams.currency),
fee: baseParams.poolLPFee,
tickSpacing: baseParams.poolTickSpacing,
hooks: baseParams.hooks
});
actions = ActionsBuilder.buildFullRangeActions();
params = fullRangeParams.buildFullRangeParams(
poolKey, bounds, currencyIsCurrency0, paramsArraySize, baseParams.positionRecipient, baseParams.liquidity
);
// Build actions
return (actions, params);
}
/// @notice Creates the actions and parameters needed to mint a one-sided position on the position manager
/// @param baseParams The base parameters for the position
/// @param oneSidedParams The amounts of token that will be used to mint the position
/// @param existingActions The existing actions needed to mint a full range position on the position manager (Output of planFullRangePosition())
/// @param existingParams The existing parameters needed to mint a full range position on the position manager (Output of planFullRangePosition())
/// @return actions The actions needed to mint a full range position with one-sided position on the position manager
/// @return params The parameters needed to mint a full range position with one-sided position on the position manager
function planOneSidedPosition(
BasePositionParams memory baseParams,
OneSidedParams memory oneSidedParams,
bytes memory existingActions,
bytes[] memory existingParams
) internal pure returns (bytes memory actions, bytes[] memory params) {
bool currencyIsCurrency0 = baseParams.currency < baseParams.poolToken;
// Get tick bounds based on position side
TickBounds memory bounds = currencyIsCurrency0 == oneSidedParams.inToken
? getLeftSideBounds(baseParams.initialSqrtPriceX96, baseParams.poolTickSpacing)
: getRightSideBounds(baseParams.initialSqrtPriceX96, baseParams.poolTickSpacing);
// If the tick bounds are 0,0 (which means the current tick is too close to MIN_TICK or MAX_TICK), return the existing actions and parameters
// that will build a full range position
if (bounds.lowerTick == 0 && bounds.upperTick == 0) {
return (existingActions, existingParams.truncateParams());
}
// If this overflows, the transaction will revert and no position will be created
uint128 newLiquidity = LiquidityAmounts.getLiquidityForAmounts(
baseParams.initialSqrtPriceX96,
TickMath.getSqrtPriceAtTick(bounds.lowerTick),
TickMath.getSqrtPriceAtTick(bounds.upperTick),
currencyIsCurrency0 == oneSidedParams.inToken ? 0 : oneSidedParams.amount,
currencyIsCurrency0 == oneSidedParams.inToken ? oneSidedParams.amount : 0
);
if (
newLiquidity == 0
|| baseParams.liquidity + newLiquidity > baseParams.poolTickSpacing.tickSpacingToMaxLiquidityPerTick()
) {
return (existingActions, existingParams.truncateParams());
}
PoolKey memory poolKey = PoolKey({
currency0: Currency.wrap(currencyIsCurrency0 ? baseParams.currency : baseParams.poolToken),
currency1: Currency.wrap(currencyIsCurrency0 ? baseParams.poolToken : baseParams.currency),
fee: baseParams.poolLPFee,
tickSpacing: baseParams.poolTickSpacing,
hooks: baseParams.hooks
});
actions = ActionsBuilder.buildOneSidedActions(existingActions);
params = oneSidedParams.buildOneSidedParams(
poolKey, bounds, currencyIsCurrency0, existingParams, baseParams.positionRecipient, newLiquidity
);
return (actions, params);
}
function planFinalTakePair(
BasePositionParams memory baseParams,
bytes memory existingActions,
bytes[] memory existingParams
) internal view returns (bytes memory actions, bytes[] memory params) {
bool currencyIsCurrency0 = baseParams.currency < baseParams.poolToken;
actions = ActionsBuilder.buildFinalTakePairActions(existingActions);
params = ParamsBuilder.buildFinalTakePairParams(
currencyIsCurrency0 ? baseParams.currency : baseParams.poolToken,
currencyIsCurrency0 ? baseParams.poolToken : baseParams.currency,
existingParams
);
return (actions, params);
}
/// @notice Gets tick bounds for a left-side position (below current tick)
/// @param initialSqrtPriceX96 The initial sqrt price of the position
/// @param poolTickSpacing The tick spacing of the pool
/// @return bounds The tick bounds for the left-side position (returns 0,0 if the current tick is too close to MIN_TICK)
function getLeftSideBounds(uint160 initialSqrtPriceX96, int24 poolTickSpacing)
private
pure
returns (TickBounds memory bounds)
{
int24 initialTick = TickMath.getTickAtSqrtPrice(initialSqrtPriceX96);
// Check if position is too close to MIN_TICK. If so, return a lower tick and upper tick of 0
if (initialTick - TickMath.MIN_TICK < poolTickSpacing) {
return bounds;
}
bounds = TickBounds({
lowerTick: TickMath.minUsableTick(poolTickSpacing), // Rounds to the nearest multiple of tick spacing (rounds towards 0 since MIN_TICK is negative)
upperTick: initialTick.tickFloor(poolTickSpacing) // Rounds to the nearest multiple of tick spacing if needed (rounds toward -infinity)
});
return bounds;
}
/// @notice Gets tick bounds for a right-side position (above current tick)
/// @param initialSqrtPriceX96 The initial sqrt price of the position
/// @param poolTickSpacing The tick spacing of the pool
/// @return bounds The tick bounds for the right-side position (returns 0,0 if the current tick is too close to MAX_TICK)
function getRightSideBounds(uint160 initialSqrtPriceX96, int24 poolTickSpacing)
private
pure
returns (TickBounds memory bounds)
{
int24 initialTick = TickMath.getTickAtSqrtPrice(initialSqrtPriceX96);
// Check if position is too close to MAX_TICK. If so, return a lower tick and upper tick of 0
if (TickMath.MAX_TICK - initialTick <= poolTickSpacing) {
return bounds;
}
bounds = TickBounds({
lowerTick: initialTick.tickStrictCeil(poolTickSpacing), // Rounds toward +infinity to the nearest multiple of tick spacing
upperTick: TickMath.maxUsableTick(poolTickSpacing) // Rounds to the nearest multiple of tick spacing (rounds toward 0 since MAX_TICK is positive)
});
return bounds;
}
}
PositionTypes.sol 34 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
/// @notice Base parameters shared by all position types
struct BasePositionParams {
address currency;
address poolToken;
uint24 poolLPFee;
int24 poolTickSpacing;
uint160 initialSqrtPriceX96;
uint128 liquidity;
address positionRecipient;
IHooks hooks;
}
/// @notice Parameters specific to full-range positions
struct FullRangeParams {
uint128 tokenAmount;
uint128 currencyAmount;
}
/// @notice Parameters specific to one-sided positions
struct OneSidedParams {
uint128 amount;
bool inToken;
}
/// @notice Tick boundaries for a position
struct TickBounds {
int24 lowerTick;
int24 upperTick;
}
ParamsBuilder.sol 139 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {ActionConstants} from "@uniswap/v4-periphery/src/libraries/ActionConstants.sol";
import {FullRangeParams, OneSidedParams, TickBounds} from "../types/PositionTypes.sol";
/// @title ParamsBuilder
/// @notice Library for building position parameters
library ParamsBuilder {
error InvalidParamsLength(uint256 invalidLength);
/// @notice Empty bytes used as hook data when minting positions since no hook data is needed
bytes constant ZERO_BYTES = new bytes(0);
/// @notice Number of params needed for a standalone full-range position
/// (1. mint, 2. settle, 3. settle, 4. take pair)
uint256 public constant FULL_RANGE_SIZE = 4;
/// @notice Number of params needed for full-range + one-sided position
/// (1. mint, 2. settle, 3. settle, 4. mint, 5. take pair)
uint256 public constant FULL_RANGE_WITH_ONE_SIDED_SIZE = 5;
/// @notice Builds the parameters needed to mint a full range position using the position manager
/// @param fullRangeParams The amounts of currency and token that will be used to mint the position
/// @param poolKey The pool key
/// @param bounds The tick bounds for the full range position
/// @param currencyIsCurrency0 Whether the currency address is less than the token address
/// @param paramsArraySize The size of the parameters array (either 5 or 8)
/// @param positionRecipient The recipient of the position
/// @return params The parameters needed to mint a full range position using the position manager
function buildFullRangeParams(
FullRangeParams memory fullRangeParams,
PoolKey memory poolKey,
TickBounds memory bounds,
bool currencyIsCurrency0,
uint256 paramsArraySize,
address positionRecipient,
uint128 liquidity
) internal pure returns (bytes[] memory params) {
if (paramsArraySize != FULL_RANGE_SIZE && paramsArraySize != FULL_RANGE_WITH_ONE_SIDED_SIZE) {
revert InvalidParamsLength(paramsArraySize);
}
// Build parameters
params = new bytes[](paramsArraySize);
uint128 amount0 = currencyIsCurrency0 ? fullRangeParams.currencyAmount : fullRangeParams.tokenAmount;
uint128 amount1 = currencyIsCurrency0 ? fullRangeParams.tokenAmount : fullRangeParams.currencyAmount;
// Set up mint
params[0] = abi.encode(
poolKey, bounds.lowerTick, bounds.upperTick, liquidity, amount0, amount1, positionRecipient, ZERO_BYTES
);
// Send the position manager's full balance of both currencies to cover both positions
// This includes any pre-existing tokens in the position manager, which will be sent to the pool manager
// and ultimately transferred to the LBP contract at the end.
// Set up settlement for currency0
params[1] = abi.encode(poolKey.currency0, ActionConstants.CONTRACT_BALANCE, false); // payerIsUser is false because position manager will be the payer
// Set up settlement for currency1
params[2] = abi.encode(poolKey.currency1, ActionConstants.CONTRACT_BALANCE, false); // payerIsUser is false because position manager will be the payer
return params;
}
/// @notice Builds the parameters needed to mint a one-sided position using the position manager
/// @param oneSidedParams The data specific to creating the one-sided position
/// @param poolKey The pool key
/// @param bounds The tick bounds for the one-sided position
/// @param currencyIsCurrency0 Whether the currency address is less than the token address
/// @param existingParams Params to create a full range position (Output of buildFullRangeParams())
/// @param currencyIsCurrency0 Whether the currency address is less than the token address
/// @param existingParams Params to create a full range position (Output of buildFullRangeParams())
/// @param positionRecipient The recipient of the position
/// @return params The parameters needed to mint a one-sided position using the position manager
function buildOneSidedParams(
OneSidedParams memory oneSidedParams,
PoolKey memory poolKey,
TickBounds memory bounds,
bool currencyIsCurrency0,
bytes[] memory existingParams,
address positionRecipient,
uint128 liquidity
) internal pure returns (bytes[] memory) {
if (existingParams.length != FULL_RANGE_WITH_ONE_SIDED_SIZE) {
revert InvalidParamsLength(existingParams.length);
}
// Determine which currency (0 or 1) receives the one-sided liquidity amount
// XOR logic: position uses currency1 when:
// - currencyIsCurrency0=true AND inToken=true (currency is 0, position in token which is 1)
// - currencyIsCurrency0=false AND inToken=false (currency is 1, position in currency which is 1)
bool useAmountInCurrency1 = currencyIsCurrency0 == oneSidedParams.inToken;
// Set the amount to the appropriate currency slot
uint256 amount0 = useAmountInCurrency1 ? 0 : oneSidedParams.amount;
uint256 amount1 = useAmountInCurrency1 ? oneSidedParams.amount : 0;
// Set up mint for token
existingParams[FULL_RANGE_SIZE - 1] = abi.encode(
poolKey, bounds.lowerTick, bounds.upperTick, liquidity, amount0, amount1, positionRecipient, ZERO_BYTES
);
return existingParams;
}
/// @notice Builds the parameters needed to take the pair using the position manager
/// @param currency0 The currency0 address
/// @param currency1 The currency1 address
/// @param existingParams Params to create a full range position (Output of buildFullRangeParams() or buildOneSidedParams())
/// @return params The parameters needed to take the pair using the position manager
function buildFinalTakePairParams(address currency0, address currency1, bytes[] memory existingParams)
internal
view
returns (bytes[] memory)
{
if (existingParams.length != FULL_RANGE_SIZE && existingParams.length != FULL_RANGE_WITH_ONE_SIDED_SIZE) {
revert InvalidParamsLength(existingParams.length);
}
// Take any open deltas from the pool manager and send back to the lbp
existingParams[existingParams.length - 1] =
abi.encode(Currency.wrap(currency0), Currency.wrap(currency1), address(this));
return existingParams;
}
/// @notice Truncates parameters array to full-range only size (5 params)
/// @param params The parameters to truncate
/// @return truncated The truncated parameters only (5 params)
function truncateParams(bytes[] memory params) internal pure returns (bytes[] memory) {
assembly {
mstore(params, FULL_RANGE_SIZE)
}
return params;
}
}
MigrationData.sol 14 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title MigrationData
/// @notice Data for the migration of the pool
struct MigrationData {
uint160 sqrtPriceX96; // the initial sqrt price of the pool
uint128 initialTokenAmount; // the initial token amount for the full range position
uint128 leftoverCurrency; // the leftover currency (if any) after creating the full range position
uint128 initialCurrencyAmount; // the initial currency amount for the full range position
uint128 liquidity; // the liquidity for the full range position
bool shouldCreateOneSided; // whether to create a one sided position or not in either the token or the currency
bool hasOneSidedParams; // whether shouldCreateOneSided is true and the parameters for the one sided position are valid or not
}
TokenDistribution.sol 37 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title TokenDistribution
/// @notice Library for calculating token distribution splits between auction and reserves
/// @dev Handles the splitting of total token supply based on percentage allocations
library TokenDistribution {
/// @notice Maximum value for token split percentage (100% in basis points)
/// @dev 1e7 = 10,000,000 basis points = 100%
uint24 public constant MAX_TOKEN_SPLIT = 1e7;
/// @notice Calculates the auction supply based on the split ratio
/// @param totalSupply The total token supply
/// @param tokenSplitToAuction The percentage split to auction (in basis points, max 1e7)
/// @return auctionSupply The amount of tokens allocated to auction
function calculateAuctionSupply(uint128 totalSupply, uint24 tokenSplitToAuction)
internal
pure
returns (uint128 auctionSupply)
{
// Safe: totalSupply <= uint128.max and tokenSplitToAuction <= MAX_TOKEN_SPLIT (1e7)
// uint256(totalSupply) * tokenSplitToAuction will never overflow type(uint256).max
auctionSupply = uint128(uint256(totalSupply) * tokenSplitToAuction / MAX_TOKEN_SPLIT);
}
/// @notice Calculates the reserve supply (remainder after auction allocation)
/// @param totalSupply The total token supply
/// @param tokenSplitToAuction The percentage split to auction (in basis points, max 1e7)
/// @return reserveSupply The amount of tokens reserved for liquidity
function calculateReserveSupply(uint128 totalSupply, uint24 tokenSplitToAuction)
internal
pure
returns (uint128 reserveSupply)
{
reserveSupply = totalSupply - calculateAuctionSupply(totalSupply, tokenSplitToAuction);
}
}
IERC6909Claims.sol 66 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @notice Interface for claims over a contract balance, wrapped as a ERC6909
interface IERC6909Claims {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event OperatorSet(address indexed owner, address indexed operator, bool approved);
event Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount);
event Transfer(address caller, address indexed from, address indexed to, uint256 indexed id, uint256 amount);
/*//////////////////////////////////////////////////////////////
FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Owner balance of an id.
/// @param owner The address of the owner.
/// @param id The id of the token.
/// @return amount The balance of the token.
function balanceOf(address owner, uint256 id) external view returns (uint256 amount);
/// @notice Spender allowance of an id.
/// @param owner The address of the owner.
/// @param spender The address of the spender.
/// @param id The id of the token.
/// @return amount The allowance of the token.
function allowance(address owner, address spender, uint256 id) external view returns (uint256 amount);
/// @notice Checks if a spender is approved by an owner as an operator
/// @param owner The address of the owner.
/// @param spender The address of the spender.
/// @return approved The approval status.
function isOperator(address owner, address spender) external view returns (bool approved);
/// @notice Transfers an amount of an id from the caller to a receiver.
/// @param receiver The address of the receiver.
/// @param id The id of the token.
/// @param amount The amount of the token.
/// @return bool True, always, unless the function reverts
function transfer(address receiver, uint256 id, uint256 amount) external returns (bool);
/// @notice Transfers an amount of an id from a sender to a receiver.
/// @param sender The address of the sender.
/// @param receiver The address of the receiver.
/// @param id The id of the token.
/// @param amount The amount of the token.
/// @return bool True, always, unless the function reverts
function transferFrom(address sender, address receiver, uint256 id, uint256 amount) external returns (bool);
/// @notice Approves an amount of an id to a spender.
/// @param spender The address of the spender.
/// @param id The id of the token.
/// @param amount The amount of the token.
/// @return bool True, always
function approve(address spender, uint256 id, uint256 amount) external returns (bool);
/// @notice Sets or removes an operator for the caller.
/// @param operator The address of the operator.
/// @param approved The approval status.
/// @return bool True, always
function setOperator(address operator, bool approved) external returns (bool);
}
IProtocolFees.sol 52 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Currency} from "../types/Currency.sol";
import {PoolId} from "../types/PoolId.sol";
import {PoolKey} from "../types/PoolKey.sol";
/// @notice Interface for all protocol-fee related functions in the pool manager
interface IProtocolFees {
/// @notice Thrown when protocol fee is set too high
error ProtocolFeeTooLarge(uint24 fee);
/// @notice Thrown when collectProtocolFees or setProtocolFee is not called by the controller.
error InvalidCaller();
/// @notice Thrown when collectProtocolFees is attempted on a token that is synced.
error ProtocolFeeCurrencySynced();
/// @notice Emitted when the protocol fee controller address is updated in setProtocolFeeController.
event ProtocolFeeControllerUpdated(address indexed protocolFeeController);
/// @notice Emitted when the protocol fee is updated for a pool.
event ProtocolFeeUpdated(PoolId indexed id, uint24 protocolFee);
/// @notice Given a currency address, returns the protocol fees accrued in that currency
/// @param currency The currency to check
/// @return amount The amount of protocol fees accrued in the currency
function protocolFeesAccrued(Currency currency) external view returns (uint256 amount);
/// @notice Sets the protocol fee for the given pool
/// @param key The key of the pool to set a protocol fee for
/// @param newProtocolFee The fee to set
function setProtocolFee(PoolKey memory key, uint24 newProtocolFee) external;
/// @notice Sets the protocol fee controller
/// @param controller The new protocol fee controller
function setProtocolFeeController(address controller) external;
/// @notice Collects the protocol fees for a given recipient and currency, returning the amount collected
/// @dev This will revert if the contract is unlocked
/// @param recipient The address to receive the protocol fees
/// @param currency The currency to withdraw
/// @param amount The amount of currency to withdraw
/// @return amountCollected The amount of currency successfully withdrawn
function collectProtocolFees(address recipient, Currency currency, uint256 amount)
external
returns (uint256 amountCollected);
/// @notice Returns the current protocol fee controller address
/// @return address The current protocol fee controller address
function protocolFeeController() external view returns (address);
}
BalanceDelta.sol 72 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {SafeCast} from "../libraries/SafeCast.sol";
/// @dev Two `int128` values packed into a single `int256` where the upper 128 bits represent the amount0
/// and the lower 128 bits represent the amount1.
type BalanceDelta is int256;
using {add as +, sub as -, eq as ==, neq as !=} for BalanceDelta global;
using BalanceDeltaLibrary for BalanceDelta global;
using SafeCast for int256;
function toBalanceDelta(int128 _amount0, int128 _amount1) pure returns (BalanceDelta balanceDelta) {
assembly ("memory-safe") {
balanceDelta := or(shl(128, _amount0), and(sub(shl(128, 1), 1), _amount1))
}
}
function add(BalanceDelta a, BalanceDelta b) pure returns (BalanceDelta) {
int256 res0;
int256 res1;
assembly ("memory-safe") {
let a0 := sar(128, a)
let a1 := signextend(15, a)
let b0 := sar(128, b)
let b1 := signextend(15, b)
res0 := add(a0, b0)
res1 := add(a1, b1)
}
return toBalanceDelta(res0.toInt128(), res1.toInt128());
}
function sub(BalanceDelta a, BalanceDelta b) pure returns (BalanceDelta) {
int256 res0;
int256 res1;
assembly ("memory-safe") {
let a0 := sar(128, a)
let a1 := signextend(15, a)
let b0 := sar(128, b)
let b1 := signextend(15, b)
res0 := sub(a0, b0)
res1 := sub(a1, b1)
}
return toBalanceDelta(res0.toInt128(), res1.toInt128());
}
function eq(BalanceDelta a, BalanceDelta b) pure returns (bool) {
return BalanceDelta.unwrap(a) == BalanceDelta.unwrap(b);
}
function neq(BalanceDelta a, BalanceDelta b) pure returns (bool) {
return BalanceDelta.unwrap(a) != BalanceDelta.unwrap(b);
}
/// @notice Library for getting the amount0 and amount1 deltas from the BalanceDelta type
library BalanceDeltaLibrary {
/// @notice A BalanceDelta of 0
BalanceDelta public constant ZERO_DELTA = BalanceDelta.wrap(0);
function amount0(BalanceDelta balanceDelta) internal pure returns (int128 _amount0) {
assembly ("memory-safe") {
_amount0 := sar(128, balanceDelta)
}
}
function amount1(BalanceDelta balanceDelta) internal pure returns (int128 _amount1) {
assembly ("memory-safe") {
_amount1 := signextend(15, balanceDelta)
}
}
}
PoolId.sol 17 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "./PoolKey.sol";
type PoolId is bytes32;
/// @notice Library for computing the ID of a pool
library PoolIdLibrary {
/// @notice Returns value equal to keccak256(abi.encode(poolKey))
function toId(PoolKey memory poolKey) internal pure returns (PoolId poolId) {
assembly ("memory-safe") {
// 0xa0 represents the total size of the poolKey struct (5 slots of 32 bytes)
poolId := keccak256(poolKey, 0xa0)
}
}
}
IExtsload.sol 21 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @notice Interface for functions to access any storage slot in a contract
interface IExtsload {
/// @notice Called by external contracts to access granular pool state
/// @param slot Key of slot to sload
/// @return value The value of the slot as bytes32
function extsload(bytes32 slot) external view returns (bytes32 value);
/// @notice Called by external contracts to access granular pool state
/// @param startSlot Key of slot to start sloading from
/// @param nSlots Number of slots to load into return value
/// @return values List of loaded values.
function extsload(bytes32 startSlot, uint256 nSlots) external view returns (bytes32[] memory values);
/// @notice Called by external contracts to access sparse pool state
/// @param slots List of slots to SLOAD from.
/// @return values List of loaded values.
function extsload(bytes32[] calldata slots) external view returns (bytes32[] memory values);
}
IExttload.sol 15 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
/// @notice Interface for functions to access any transient storage slot in a contract
interface IExttload {
/// @notice Called by external contracts to access transient storage of the contract
/// @param slot Key of slot to tload
/// @return value The value of the slot as bytes32
function exttload(bytes32 slot) external view returns (bytes32 value);
/// @notice Called by external contracts to access sparse transient pool state
/// @param slots List of slots to tload
/// @return values List of loaded values
function exttload(bytes32[] calldata slots) external view returns (bytes32[] memory values);
}
PositionInfoLibrary.sol 105 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol";
/**
* @dev PositionInfo is a packed version of solidity structure.
* Using the packaged version saves gas and memory by not storing the structure fields in memory slots.
*
* Layout:
* 200 bits poolId | 24 bits tickUpper | 24 bits tickLower | 8 bits hasSubscriber
*
* Fields in the direction from the least significant bit:
*
* A flag to know if the tokenId is subscribed to an address
* uint8 hasSubscriber;
*
* The tickUpper of the position
* int24 tickUpper;
*
* The tickLower of the position
* int24 tickLower;
*
* The truncated poolId. Truncates a bytes32 value so the most signifcant (highest) 200 bits are used.
* bytes25 poolId;
*
* Note: If more bits are needed, hasSubscriber can be a single bit.
*
*/
type PositionInfo is uint256;
using PositionInfoLibrary for PositionInfo global;
library PositionInfoLibrary {
PositionInfo internal constant EMPTY_POSITION_INFO = PositionInfo.wrap(0);
uint256 internal constant MASK_UPPER_200_BITS = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000;
uint256 internal constant MASK_8_BITS = 0xFF;
uint24 internal constant MASK_24_BITS = 0xFFFFFF;
uint256 internal constant SET_UNSUBSCRIBE = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00;
uint256 internal constant SET_SUBSCRIBE = 0x01;
uint8 internal constant TICK_LOWER_OFFSET = 8;
uint8 internal constant TICK_UPPER_OFFSET = 32;
/// @dev This poolId is NOT compatible with the poolId used in UniswapV4 core. It is truncated to 25 bytes, and just used to lookup PoolKey in the poolKeys mapping.
function poolId(PositionInfo info) internal pure returns (bytes25 _poolId) {
assembly ("memory-safe") {
_poolId := and(MASK_UPPER_200_BITS, info)
}
}
function tickLower(PositionInfo info) internal pure returns (int24 _tickLower) {
assembly ("memory-safe") {
_tickLower := signextend(2, shr(TICK_LOWER_OFFSET, info))
}
}
function tickUpper(PositionInfo info) internal pure returns (int24 _tickUpper) {
assembly ("memory-safe") {
_tickUpper := signextend(2, shr(TICK_UPPER_OFFSET, info))
}
}
function hasSubscriber(PositionInfo info) internal pure returns (bool _hasSubscriber) {
assembly ("memory-safe") {
_hasSubscriber := and(MASK_8_BITS, info)
}
}
/// @dev this does not actually set any storage
function setSubscribe(PositionInfo info) internal pure returns (PositionInfo _info) {
assembly ("memory-safe") {
_info := or(info, SET_SUBSCRIBE)
}
}
/// @dev this does not actually set any storage
function setUnsubscribe(PositionInfo info) internal pure returns (PositionInfo _info) {
assembly ("memory-safe") {
_info := and(info, SET_UNSUBSCRIBE)
}
}
/// @notice Creates the default PositionInfo struct
/// @dev Called when minting a new position
/// @param _poolKey the pool key of the position
/// @param _tickLower the lower tick of the position
/// @param _tickUpper the upper tick of the position
/// @return info packed position info, with the truncated poolId and the hasSubscriber flag set to false
function initialize(PoolKey memory _poolKey, int24 _tickLower, int24 _tickUpper)
internal
pure
returns (PositionInfo info)
{
bytes25 _poolId = bytes25(PoolId.unwrap(_poolKey.toId()));
assembly {
info :=
or(
or(and(MASK_UPPER_200_BITS, _poolId), shl(TICK_UPPER_OFFSET, and(MASK_24_BITS, _tickUpper))),
shl(TICK_LOWER_OFFSET, and(MASK_24_BITS, _tickLower))
)
}
}
}
INotifier.sol 54 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {ISubscriber} from "./ISubscriber.sol";
/// @title INotifier
/// @notice Interface for the Notifier contract
interface INotifier {
/// @notice Thrown when unsubscribing without a subscriber
error NotSubscribed();
/// @notice Thrown when a subscriber does not have code
error NoCodeSubscriber();
/// @notice Thrown when a user specifies a gas limit too low to avoid valid unsubscribe notifications
error GasLimitTooLow();
/// @notice Wraps the revert message of the subscriber contract on a reverting subscription
error SubscriptionReverted(address subscriber, bytes reason);
/// @notice Wraps the revert message of the subscriber contract on a reverting modify liquidity notification
error ModifyLiquidityNotificationReverted(address subscriber, bytes reason);
/// @notice Wraps the revert message of the subscriber contract on a reverting burn notification
error BurnNotificationReverted(address subscriber, bytes reason);
/// @notice Thrown when a tokenId already has a subscriber
error AlreadySubscribed(uint256 tokenId, address subscriber);
/// @notice Emitted on a successful call to subscribe
event Subscription(uint256 indexed tokenId, address indexed subscriber);
/// @notice Emitted on a successful call to unsubscribe
event Unsubscription(uint256 indexed tokenId, address indexed subscriber);
/// @notice Returns the subscriber for a respective position
/// @param tokenId the ERC721 tokenId
/// @return subscriber the subscriber contract
function subscriber(uint256 tokenId) external view returns (ISubscriber subscriber);
/// @notice Enables the subscriber to receive notifications for a respective position
/// @param tokenId the ERC721 tokenId
/// @param newSubscriber the address of the subscriber contract
/// @param data caller-provided data that's forwarded to the subscriber contract
/// @dev Calling subscribe when a position is already subscribed will revert
/// @dev payable so it can be multicalled with NATIVE related actions
/// @dev will revert if pool manager is locked
function subscribe(uint256 tokenId, address newSubscriber, bytes calldata data) external payable;
/// @notice Removes the subscriber from receiving notifications for a respective position
/// @param tokenId the ERC721 tokenId
/// @dev Callers must specify a high gas limit (remaining gas should be higher than unsubscriberGasLimit) such that the subscriber can be notified
/// @dev payable so it can be multicalled with NATIVE related actions
/// @dev Must always allow a user to unsubscribe. In the case of a malicious subscriber, a user can always unsubscribe safely, ensuring liquidity is always modifiable.
/// @dev will revert if pool manager is locked
function unsubscribe(uint256 tokenId) external payable;
/// @notice Returns and determines the maximum allowable gas-used for notifying unsubscribe
/// @return uint256 the maximum gas limit when notifying a subscriber's `notifyUnsubscribe` function
function unsubscribeGasLimit() external view returns (uint256);
}
IImmutableState.sol 11 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
/// @title IImmutableState
/// @notice Interface for the ImmutableState contract
interface IImmutableState {
/// @notice The Uniswap v4 PoolManager contract
function poolManager() external view returns (IPoolManager);
}
IERC721Permit_v4.sol 38 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title IERC721Permit_v4
/// @notice Interface for the ERC721Permit_v4 contract
interface IERC721Permit_v4 {
error SignatureDeadlineExpired();
error NoSelfPermit();
error Unauthorized();
/// @notice Approve of a specific token ID for spending by spender via signature
/// @param spender The account that is being approved
/// @param tokenId The ID of the token that is being approved for spending
/// @param deadline The deadline timestamp by which the call must be mined for the approve to work
/// @param nonce a unique value, for an owner, to prevent replay attacks; an unordered nonce where the top 248 bits correspond to a word and the bottom 8 bits calculate the bit position of the word
/// @param signature Concatenated data from a valid secp256k1 signature from the holder, i.e. abi.encodePacked(r, s, v)
/// @dev payable so it can be multicalled with NATIVE related actions
function permit(address spender, uint256 tokenId, uint256 deadline, uint256 nonce, bytes calldata signature)
external
payable;
/// @notice Set an operator with full permission to an owner's tokens via signature
/// @param owner The address that is setting the operator
/// @param operator The address that will be set as an operator for the owner
/// @param approved The permission to set on the operator
/// @param deadline The deadline timestamp by which the call must be mined for the approve to work
/// @param nonce a unique value, for an owner, to prevent replay attacks; an unordered nonce where the top 248 bits correspond to a word and the bottom 8 bits calculate the bit position of the word
/// @param signature Concatenated data from a valid secp256k1 signature from the holder, i.e. abi.encodePacked(r, s, v)
/// @dev payable so it can be multicalled with NATIVE related actions
function permitForAll(
address owner,
address operator,
bool approved,
uint256 deadline,
uint256 nonce,
bytes calldata signature
) external payable;
}
IEIP712_v4.sol 10 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title IEIP712_v4
/// @notice Interface for the EIP712 contract
interface IEIP712_v4 {
/// @notice Returns the domain separator for the current chain.
/// @return bytes32 The domain separator
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
IMulticall_v4.sol 13 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title IMulticall_v4
/// @notice Interface for the Multicall_v4 contract
interface IMulticall_v4 {
/// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed
/// @dev The `msg.value` is passed onto all subcalls, even if a previous subcall has consumed the ether.
/// Subcalls can instead use `address(this).value` to see the available ETH, and consume it using {value: x}.
/// @param data The encoded function data for each of the calls to make to this contract
/// @return results The results from each of the calls passed in via data
function multicall(bytes[] calldata data) external payable returns (bytes[] memory results);
}
IPoolInitializer_v4.sol 15 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
/// @title IPoolInitializer_v4
/// @notice Interface for the PoolInitializer_v4 contract
interface IPoolInitializer_v4 {
/// @notice Initialize a Uniswap v4 Pool
/// @dev If the pool is already initialized, this function will not revert and just return type(int24).max
/// @param key The PoolKey of the pool to initialize
/// @param sqrtPriceX96 The initial starting price of the pool, expressed as a sqrtPriceX96
/// @return The current tick of the pool, or type(int24).max if the pool creation failed, or the pool already existed
function initializePool(PoolKey calldata key, uint160 sqrtPriceX96) external payable returns (int24);
}
IUnorderedNonce.sol 17 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title IUnorderedNonce
/// @notice Interface for the UnorderedNonce contract
interface IUnorderedNonce {
error NonceAlreadyUsed();
/// @notice mapping of nonces consumed by each address, where a nonce is a single bit on the 256-bit bitmap
/// @dev word is at most type(uint248).max
function nonces(address owner, uint256 word) external view returns (uint256);
/// @notice Revoke a nonce by spending it, preventing it from being used again
/// @dev Used in cases where a valid nonce has not been broadcasted onchain, and the owner wants to revoke the validity of the nonce
/// @dev payable so it can be multicalled with native-token related actions
function revokeNonce(uint256 nonce) external payable;
}
IPermit2Forwarder.sol 30 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
/// @title IPermit2Forwarder
/// @notice Interface for the Permit2Forwarder contract
interface IPermit2Forwarder {
/// @notice allows forwarding a single permit to permit2
/// @dev this function is payable to allow multicall with NATIVE based actions
/// @param owner the owner of the tokens
/// @param permitSingle the permit data
/// @param signature the signature of the permit; abi.encodePacked(r, s, v)
/// @return err the error returned by a reverting permit call, empty if successful
function permit(address owner, IAllowanceTransfer.PermitSingle calldata permitSingle, bytes calldata signature)
external
payable
returns (bytes memory err);
/// @notice allows forwarding batch permits to permit2
/// @dev this function is payable to allow multicall with NATIVE based actions
/// @param owner the owner of the tokens
/// @param _permitBatch a batch of approvals
/// @param signature the signature of the permit; abi.encodePacked(r, s, v)
/// @return err the error returned by a reverting permit call, empty if successful
function permitBatch(address owner, IAllowanceTransfer.PermitBatch calldata _permitBatch, bytes calldata signature)
external
payable
returns (bytes memory err);
}
SafeCast.sol 60 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {CustomRevert} from "./CustomRevert.sol";
/// @title Safe casting methods
/// @notice Contains methods for safely casting between types
library SafeCast {
using CustomRevert for bytes4;
error SafeCastOverflow();
/// @notice Cast a uint256 to a uint160, revert on overflow
/// @param x The uint256 to be downcasted
/// @return y The downcasted integer, now type uint160
function toUint160(uint256 x) internal pure returns (uint160 y) {
y = uint160(x);
if (y != x) SafeCastOverflow.selector.revertWith();
}
/// @notice Cast a uint256 to a uint128, revert on overflow
/// @param x The uint256 to be downcasted
/// @return y The downcasted integer, now type uint128
function toUint128(uint256 x) internal pure returns (uint128 y) {
y = uint128(x);
if (x != y) SafeCastOverflow.selector.revertWith();
}
/// @notice Cast a int128 to a uint128, revert on overflow or underflow
/// @param x The int128 to be casted
/// @return y The casted integer, now type uint128
function toUint128(int128 x) internal pure returns (uint128 y) {
if (x < 0) SafeCastOverflow.selector.revertWith();
y = uint128(x);
}
/// @notice Cast a int256 to a int128, revert on overflow or underflow
/// @param x The int256 to be downcasted
/// @return y The downcasted integer, now type int128
function toInt128(int256 x) internal pure returns (int128 y) {
y = int128(x);
if (y != x) SafeCastOverflow.selector.revertWith();
}
/// @notice Cast a uint256 to a int256, revert on overflow
/// @param x The uint256 to be casted
/// @return y The casted integer, now type int256
function toInt256(uint256 x) internal pure returns (int256 y) {
y = int256(x);
if (y < 0) SafeCastOverflow.selector.revertWith();
}
/// @notice Cast a uint256 to a int128, revert on overflow
/// @param x The uint256 to be downcasted
/// @return The downcasted integer, now type int128
function toInt128(uint256 x) internal pure returns (int128) {
if (x >= 1 << 127) SafeCastOverflow.selector.revertWith();
return int128(int256(x));
}
}
ParseBytes.sol 29 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @notice Parses bytes returned from hooks and the byte selector used to check return selectors from hooks.
/// @dev parseSelector also is used to parse the expected selector
/// For parsing hook returns, note that all hooks return either bytes4 or (bytes4, 32-byte-delta) or (bytes4, 32-byte-delta, uint24).
library ParseBytes {
function parseSelector(bytes memory result) internal pure returns (bytes4 selector) {
// equivalent: (selector,) = abi.decode(result, (bytes4, int256));
assembly ("memory-safe") {
selector := mload(add(result, 0x20))
}
}
function parseFee(bytes memory result) internal pure returns (uint24 lpFee) {
// equivalent: (,, lpFee) = abi.decode(result, (bytes4, int256, uint24));
assembly ("memory-safe") {
lpFee := mload(add(result, 0x60))
}
}
function parseReturnDelta(bytes memory result) internal pure returns (int256 hookReturn) {
// equivalent: (, hookReturnDelta) = abi.decode(result, (bytes4, int256));
assembly ("memory-safe") {
hookReturn := mload(add(result, 0x40))
}
}
}
CustomRevert.sol 120 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title Library for reverting with custom errors efficiently
/// @notice Contains functions for reverting with custom errors with different argument types efficiently
/// @dev To use this library, declare `using CustomRevert for bytes4;` and replace `revert CustomError()` with
/// `CustomError.selector.revertWith()`
/// @dev The functions may tamper with the free memory pointer but it is fine since the call context is exited immediately
library CustomRevert {
/// @dev ERC-7751 error for wrapping bubbled up reverts
error WrappedError(address target, bytes4 selector, bytes reason, bytes details);
/// @dev Reverts with the selector of a custom error in the scratch space
function revertWith(bytes4 selector) internal pure {
assembly ("memory-safe") {
mstore(0, selector)
revert(0, 0x04)
}
}
/// @dev Reverts with a custom error with an address argument in the scratch space
function revertWith(bytes4 selector, address addr) internal pure {
assembly ("memory-safe") {
mstore(0, selector)
mstore(0x04, and(addr, 0xffffffffffffffffffffffffffffffffffffffff))
revert(0, 0x24)
}
}
/// @dev Reverts with a custom error with an int24 argument in the scratch space
function revertWith(bytes4 selector, int24 value) internal pure {
assembly ("memory-safe") {
mstore(0, selector)
mstore(0x04, signextend(2, value))
revert(0, 0x24)
}
}
/// @dev Reverts with a custom error with a uint160 argument in the scratch space
function revertWith(bytes4 selector, uint160 value) internal pure {
assembly ("memory-safe") {
mstore(0, selector)
mstore(0x04, and(value, 0xffffffffffffffffffffffffffffffffffffffff))
revert(0, 0x24)
}
}
/// @dev Reverts with a custom error with two int24 arguments
function revertWith(bytes4 selector, int24 value1, int24 value2) internal pure {
assembly ("memory-safe") {
let fmp := mload(0x40)
mstore(fmp, selector)
mstore(add(fmp, 0x04), signextend(2, value1))
mstore(add(fmp, 0x24), signextend(2, value2))
revert(fmp, 0x44)
}
}
/// @dev Reverts with a custom error with two uint160 arguments
function revertWith(bytes4 selector, uint160 value1, uint160 value2) internal pure {
assembly ("memory-safe") {
let fmp := mload(0x40)
mstore(fmp, selector)
mstore(add(fmp, 0x04), and(value1, 0xffffffffffffffffffffffffffffffffffffffff))
mstore(add(fmp, 0x24), and(value2, 0xffffffffffffffffffffffffffffffffffffffff))
revert(fmp, 0x44)
}
}
/// @dev Reverts with a custom error with two address arguments
function revertWith(bytes4 selector, address value1, address value2) internal pure {
assembly ("memory-safe") {
let fmp := mload(0x40)
mstore(fmp, selector)
mstore(add(fmp, 0x04), and(value1, 0xffffffffffffffffffffffffffffffffffffffff))
mstore(add(fmp, 0x24), and(value2, 0xffffffffffffffffffffffffffffffffffffffff))
revert(fmp, 0x44)
}
}
/// @notice bubble up the revert message returned by a call and revert with a wrapped ERC-7751 error
/// @dev this method can be vulnerable to revert data bombs
function bubbleUpAndRevertWith(
address revertingContract,
bytes4 revertingFunctionSelector,
bytes4 additionalContext
) internal pure {
bytes4 wrappedErrorSelector = WrappedError.selector;
assembly ("memory-safe") {
// Ensure the size of the revert data is a multiple of 32 bytes
let encodedDataSize := mul(div(add(returndatasize(), 31), 32), 32)
let fmp := mload(0x40)
// Encode wrapped error selector, address, function selector, offset, additional context, size, revert reason
mstore(fmp, wrappedErrorSelector)
mstore(add(fmp, 0x04), and(revertingContract, 0xffffffffffffffffffffffffffffffffffffffff))
mstore(
add(fmp, 0x24),
and(revertingFunctionSelector, 0xffffffff00000000000000000000000000000000000000000000000000000000)
)
// offset revert reason
mstore(add(fmp, 0x44), 0x80)
// offset additional context
mstore(add(fmp, 0x64), add(0xa0, encodedDataSize))
// size revert reason
mstore(add(fmp, 0x84), returndatasize())
// revert reason
returndatacopy(add(fmp, 0xa4), 0, returndatasize())
// size additional context
mstore(add(fmp, add(0xa4, encodedDataSize)), 0x04)
// additional context
mstore(
add(fmp, add(0xc4, encodedDataSize)),
and(additionalContext, 0xffffffff00000000000000000000000000000000000000000000000000000000)
)
revert(fmp, add(0xe4, encodedDataSize))
}
}
}
CheckpointLib.sol 35 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {ConstantsLib} from './ConstantsLib.sol';
import {ValueX7} from './ValueX7Lib.sol';
struct Checkpoint {
uint256 clearingPrice; // The X96 price which the auction is currently clearing at
ValueX7 currencyRaisedAtClearingPriceQ96_X7; // The currency raised so far to this clearing price
uint256 cumulativeMpsPerPrice; // A running sum of the ratio between mps and price
uint24 cumulativeMps; // The number of mps sold in the auction so far (via the original supply schedule)
uint64 prev; // Block number of the previous checkpoint
uint64 next; // Block number of the next checkpoint
}
/// @title CheckpointLib
library CheckpointLib {
/// @notice Get the remaining mps in the auction at the given checkpoint
/// @param _checkpoint The checkpoint with `cumulativeMps` so far
/// @return The remaining mps in the auction
function remainingMpsInAuction(Checkpoint memory _checkpoint) internal pure returns (uint24) {
return ConstantsLib.MPS - _checkpoint.cumulativeMps;
}
/// @notice Calculate the supply to price ratio. Will return zero if `price` is zero
/// @dev This function returns a value in Q96 form
/// @param mps The number of supply mps sold
/// @param price The price they were sold at
/// @return the ratio
function getMpsPerPrice(uint24 mps, uint256 price) internal pure returns (uint256) {
if (price == 0) return 0;
// The bitshift cannot overflow because a uint24 shifted left FixedPoint96.RESOLUTION * 2 (192) bits will always be less than 2^256
return (uint256(mps) << 192) / price;
}
}
ValueX7Lib.sol 42 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {ConstantsLib} from './ConstantsLib.sol';
import {FixedPointMathLib} from 'solady/utils/FixedPointMathLib.sol';
/// @notice A ValueX7 is a uint256 value that has been multiplied by MPS
/// @dev X7 values are used for demand values to avoid intermediate division by MPS
type ValueX7 is uint256;
using {sub, divUint256} for ValueX7 global;
/// @notice Subtract two ValueX7 values
function sub(ValueX7 a, ValueX7 b) pure returns (ValueX7) {
return ValueX7.wrap(ValueX7.unwrap(a) - ValueX7.unwrap(b));
}
/// @notice Divide a ValueX7 value by a uint256
function divUint256(ValueX7 a, uint256 b) pure returns (ValueX7) {
return ValueX7.wrap(ValueX7.unwrap(a) / b);
}
/// @title ValueX7Lib
library ValueX7Lib {
using ValueX7Lib for ValueX7;
/// @notice The scaling factor for ValueX7 values (ConstantsLib.MPS)
uint256 public constant X7 = ConstantsLib.MPS;
/// @notice Multiply a uint256 value by MPS
/// @dev This ensures that future operations will not lose precision
/// @return The result as a ValueX7
function scaleUpToX7(uint256 value) internal pure returns (ValueX7) {
return ValueX7.wrap(value * X7);
}
/// @notice Divide a ValueX7 value by MPS
/// @return The result as a uint256
function scaleDownToUint256(ValueX7 value) internal pure returns (uint256) {
return ValueX7.unwrap(value) / X7;
}
}
IBidStorage.sol 20 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Bid} from '../libraries/BidLib.sol';
/// @notice Interface for bid storage operations
interface IBidStorage {
/// @notice Error thrown when doing an operation on a bid that does not exist
error BidIdDoesNotExist(uint256 bidId);
/// @notice Get the id of the next bid to be created
/// @return The id of the next bid to be created
function nextBidId() external view returns (uint256);
/// @notice Get a bid from storage
/// @dev Will revert if the bid does not exist
/// @param bidId The id of the bid to get
/// @return The bid
function bids(uint256 bidId) external view returns (Bid memory);
}
ICheckpointStorage.sol 32 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Checkpoint} from '../libraries/CheckpointLib.sol';
/// @notice Interface for checkpoint storage operations
interface ICheckpointStorage {
/// @notice Revert when attempting to insert a checkpoint at a block number not strictly greater than the last one
error CheckpointBlockNotIncreasing();
/// @notice Get the latest checkpoint at the last checkpointed block
/// @dev Be aware that the latest checkpoint may not be up to date, it is recommended
/// to always call `checkpoint()` before using getter functions
/// @return The latest checkpoint
function latestCheckpoint() external view returns (Checkpoint memory);
/// @notice Get the clearing price at the last checkpointed block
/// @dev Be aware that the latest checkpoint may not be up to date, it is recommended
/// to always call `checkpoint()` before using getter functions
/// @return The current clearing price in Q96 form
function clearingPrice() external view returns (uint256);
/// @notice Get the number of the last checkpointed block
/// @dev Be aware that the last checkpointed block may not be up to date, it is recommended
/// to always call `checkpoint()` before using getter functions
/// @return The block number of the last checkpoint
function lastCheckpointedBlock() external view returns (uint64);
/// @notice Get a checkpoint at a block number
/// @param blockNumber The block number to get the checkpoint for
function checkpoints(uint64 blockNumber) external view returns (Checkpoint memory);
}
IStepStorage.sol 44 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {AuctionStep} from '../libraries/StepLib.sol';
/// @notice Interface for managing auction step storage
interface IStepStorage {
/// @notice Error thrown when the end block is equal to or before the start block
error InvalidEndBlock();
/// @notice Error thrown when the auction is over
error AuctionIsOver();
/// @notice Error thrown when the auction data length is invalid
error InvalidAuctionDataLength();
/// @notice Error thrown when the block delta in a step is zero
error StepBlockDeltaCannotBeZero();
/// @notice Error thrown when the mps is invalid
/// @param actualMps The sum of the mps times the block delta
/// @param expectedMps The expected mps of the auction (ConstantsLib.MPS)
error InvalidStepDataMps(uint256 actualMps, uint256 expectedMps);
/// @notice Error thrown when the calculated end block is invalid
/// @param actualEndBlock The calculated end block from the step data
/// @param expectedEndBlock The expected end block from the constructor
error InvalidEndBlockGivenStepData(uint64 actualEndBlock, uint64 expectedEndBlock);
/// @notice The block at which the auction starts
/// @return The starting block number
function startBlock() external view returns (uint64);
/// @notice The block at which the auction ends
/// @return The ending block number
function endBlock() external view returns (uint64);
/// @notice The address pointer to the contract deployed by SSTORE2
/// @return The address pointer
function pointer() external view returns (address);
/// @notice Get the current active auction step
function step() external view returns (AuctionStep memory);
/// @notice Emitted when an auction step is recorded
/// @param startBlock The start block of the auction step
/// @param endBlock The end block of the auction step
/// @param mps The percentage of total tokens to sell per block during this auction step, represented in ten-millionths of the total supply (1e7 = 100%)
event AuctionStepRecorded(uint256 startBlock, uint256 endBlock, uint24 mps);
}
ITickStorage.sol 57 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @notice Each tick contains a pointer to the next price in the linked list
/// and the cumulative currency demand at the tick's price level
struct Tick {
uint256 next;
uint256 currencyDemandQ96;
}
/// @title ITickStorage
/// @notice Interface for the TickStorage contract
interface ITickStorage {
/// @notice Error thrown when the tick spacing is too small
error TickSpacingTooSmall();
/// @notice Error thrown when the floor price is zero
error FloorPriceIsZero();
/// @notice Error thrown when the floor price is below the minimum floor price
error FloorPriceTooLow();
/// @notice Error thrown when the previous price hint is invalid (higher than the new price)
error TickPreviousPriceInvalid();
/// @notice Error thrown when the tick price is not increasing
error TickPriceNotIncreasing();
/// @notice Error thrown when the price is not at a boundary designated by the tick spacing
error TickPriceNotAtBoundary();
/// @notice Error thrown when the tick price is invalid
error InvalidTickPrice();
/// @notice Error thrown when trying to update the demand of an uninitialized tick
error CannotUpdateUninitializedTick();
/// @notice Emitted when a tick is initialized
/// @param price The price of the tick
event TickInitialized(uint256 price);
/// @notice Emitted when the nextActiveTick is updated
/// @param price The price of the tick
event NextActiveTickUpdated(uint256 price);
/// @notice The price of the next initialized tick above the clearing price
/// @dev This will be equal to the clearingPrice if no ticks have been initialized yet
/// @return The price of the next active tick
function nextActiveTickPrice() external view returns (uint256);
/// @notice Get the floor price of the auction
/// @return The minimum price for bids
function floorPrice() external view returns (uint256);
/// @notice Get the tick spacing enforced for bid prices
/// @return The tick spacing value
function tickSpacing() external view returns (uint256);
/// @notice Get a tick at a price
/// @dev The returned tick is not guaranteed to be initialized
/// @param price The price of the tick, which must be at a boundary designated by the tick spacing
/// @return The tick at the given price
function ticks(uint256 price) external view returns (Tick memory);
}
ITokenCurrencyStorage.sol 52 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Currency} from '../libraries/CurrencyLibrary.sol';
import {IERC20Minimal} from './external/IERC20Minimal.sol';
/// @notice Interface for token and currency storage operations
interface ITokenCurrencyStorage {
/// @notice Error thrown when the token is the native currency
error TokenIsAddressZero();
/// @notice Error thrown when the token and currency are the same
error TokenAndCurrencyCannotBeTheSame();
/// @notice Error thrown when the total supply is zero
error TotalSupplyIsZero();
/// @notice Error thrown when the total supply is too large
error TotalSupplyIsTooLarge();
/// @notice Error thrown when the funds recipient is the zero address
error FundsRecipientIsZero();
/// @notice Error thrown when the tokens recipient is the zero address
error TokensRecipientIsZero();
/// @notice Error thrown when the currency cannot be swept
error CannotSweepCurrency();
/// @notice Error thrown when the tokens cannot be swept
error CannotSweepTokens();
/// @notice Error thrown when the auction has not graduated
error NotGraduated();
/// @notice Emitted when the tokens are swept
/// @param tokensRecipient The address of the tokens recipient
/// @param tokensAmount The amount of tokens swept
event TokensSwept(address indexed tokensRecipient, uint256 tokensAmount);
/// @notice Emitted when the currency is swept
/// @param fundsRecipient The address of the funds recipient
/// @param currencyAmount The amount of currency swept
event CurrencySwept(address indexed fundsRecipient, uint256 currencyAmount);
/// @notice The currency being raised in the auction
function currency() external view returns (Currency);
/// @notice The token being sold in the auction
function token() external view returns (IERC20Minimal);
/// @notice The total supply of tokens to sell
function totalSupply() external view returns (uint128);
/// @notice The recipient of any unsold tokens at the end of the auction
function tokensRecipient() external view returns (address);
/// @notice The recipient of the raised Currency from the auction
function fundsRecipient() external view returns (address);
}
IValidationHook.sol 14 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @notice Interface for custom bid validation logic
interface IValidationHook {
/// @notice Validate a bid
/// @dev MUST revert if the bid is invalid
/// @param maxPrice The maximum price the bidder is willing to pay
/// @param amount The amount of the bid
/// @param owner The owner of the bid
/// @param sender The sender of the bid
/// @param hookData Additional data to pass to the hook required for validation
function validate(uint256 maxPrice, uint128 amount, address owner, address sender, bytes calldata hookData) external;
}
IDistributionContract.sol 9 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title IDistributionContract
/// @notice Interface for token distribution contracts.
interface IDistributionContract {
/// @notice Notify a distribution contract that it has received the tokens to distribute
function onTokensReceived() external;
}
BidStorage.sol 58 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {IBidStorage} from './interfaces/IBidStorage.sol';
import {Bid} from './libraries/BidLib.sol';
/// @notice Abstract contract for managing bid storage
abstract contract BidStorage is IBidStorage {
/// @notice The id of the next bid to be created
uint256 private $_nextBidId;
/// @notice The mapping of bid ids to bids
mapping(uint256 bidId => Bid bid) private $_bids;
/// @notice Get a bid from storage
/// @param bidId The id of the bid to get
/// @return bid The bid
function _getBid(uint256 bidId) internal view returns (Bid storage) {
if (bidId >= $_nextBidId) revert BidIdDoesNotExist(bidId);
return $_bids[bidId];
}
/// @notice Create a new bid
/// @param amount The amount of the bid
/// @param owner The owner of the bid
/// @param maxPrice The maximum price for the bid
/// @param startCumulativeMps The cumulative mps at the start of the bid
/// @return bid The created bid
/// @return bidId The id of the created bid
function _createBid(uint256 amount, address owner, uint256 maxPrice, uint24 startCumulativeMps)
internal
returns (Bid memory bid, uint256 bidId)
{
bid = Bid({
startBlock: uint64(block.number),
startCumulativeMps: startCumulativeMps,
exitedBlock: 0,
maxPrice: maxPrice,
amountQ96: amount,
owner: owner,
tokensFilled: 0
});
bidId = $_nextBidId;
$_bids[bidId] = bid;
$_nextBidId++;
}
/// Getters
/// @inheritdoc IBidStorage
function nextBidId() external view returns (uint256) {
return $_nextBidId;
}
/// @inheritdoc IBidStorage
function bids(uint256 bidId) external view returns (Bid memory) {
return _getBid(bidId);
}
}
CheckpointStorage.sol 95 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {ICheckpointStorage} from './interfaces/ICheckpointStorage.sol';
import {Bid, BidLib} from './libraries/BidLib.sol';
import {CheckpointAccountingLib} from './libraries/CheckpointAccountingLib.sol';
import {Checkpoint, CheckpointLib} from './libraries/CheckpointLib.sol';
import {FixedPoint96} from './libraries/FixedPoint96.sol';
import {ValueX7, ValueX7Lib} from './libraries/ValueX7Lib.sol';
/// @title CheckpointStorage
/// @notice Abstract contract for managing auction checkpoints and bid fill calculations
abstract contract CheckpointStorage is ICheckpointStorage {
/// @notice Maximum block number value used as sentinel for last checkpoint
uint64 public constant MAX_BLOCK_NUMBER = type(uint64).max;
/// @notice Storage of checkpoints
mapping(uint64 blockNumber => Checkpoint) private $_checkpoints;
/// @notice The block number of the last checkpointed block
uint64 internal $lastCheckpointedBlock;
/// @inheritdoc ICheckpointStorage
function latestCheckpoint() public view returns (Checkpoint memory) {
return _getCheckpoint($lastCheckpointedBlock);
}
/// @inheritdoc ICheckpointStorage
function clearingPrice() external view returns (uint256) {
return _getCheckpoint($lastCheckpointedBlock).clearingPrice;
}
/// @notice Get a checkpoint from storage
function _getCheckpoint(uint64 blockNumber) internal view returns (Checkpoint memory) {
return $_checkpoints[blockNumber];
}
/// @notice Insert a checkpoint into storage
/// @dev This function updates the prev and next pointers of the latest checkpoint and the new checkpoint
function _insertCheckpoint(Checkpoint memory checkpoint, uint64 blockNumber) internal {
uint64 _lastCheckpointedBlock = $lastCheckpointedBlock;
// Enforce strictly increasing checkpoint block numbers
if (blockNumber <= _lastCheckpointedBlock) revert CheckpointBlockNotIncreasing();
// Link new checkpoint to the previous checkpoint
checkpoint.prev = _lastCheckpointedBlock;
checkpoint.next = MAX_BLOCK_NUMBER;
// Link previous checkpoint to the new checkpoint
$_checkpoints[_lastCheckpointedBlock].next = blockNumber;
// Write the new checkpoint
$_checkpoints[blockNumber] = checkpoint;
// Update the last checkpointed block
$lastCheckpointedBlock = blockNumber;
}
/// @notice Calculate the tokens sold and proportion of input used for a fully filled bid between two checkpoints
/// @dev This function MUST only be used for checkpoints where the bid's max price is strictly greater than the clearing price
/// because it uses lazy accounting to calculate the tokens filled
/// @param upper The upper checkpoint
/// @param startCheckpoint The start checkpoint of the bid
/// @param bid The bid
/// @return tokensFilled The tokens sold
/// @return currencySpentQ96 The amount of currency spent in Q96 form
function _accountFullyFilledCheckpoints(Checkpoint memory upper, Checkpoint memory startCheckpoint, Bid memory bid)
internal
pure
returns (uint256 tokensFilled, uint256 currencySpentQ96)
{
return CheckpointAccountingLib.accountFullyFilledCheckpoints(upper, startCheckpoint, bid);
}
/// @notice Calculate the tokens sold and currency spent for a partially filled bid
/// @param bid The bid
/// @param tickDemandQ96 The total demand at the tick
/// @param currencyRaisedAtClearingPriceQ96_X7 The cumulative supply sold to the clearing price
/// @return tokensFilled The tokens sold
/// @return currencySpentQ96 The amount of currency spent in Q96 form
function _accountPartiallyFilledCheckpoints(
Bid memory bid,
uint256 tickDemandQ96,
ValueX7 currencyRaisedAtClearingPriceQ96_X7
) internal pure returns (uint256 tokensFilled, uint256 currencySpentQ96) {
return CheckpointAccountingLib.accountPartiallyFilledCheckpoints(
bid, tickDemandQ96, currencyRaisedAtClearingPriceQ96_X7
);
}
/// @inheritdoc ICheckpointStorage
function lastCheckpointedBlock() external view returns (uint64) {
return $lastCheckpointedBlock;
}
/// @inheritdoc ICheckpointStorage
function checkpoints(uint64 blockNumber) external view returns (Checkpoint memory) {
return $_checkpoints[blockNumber];
}
}
StepStorage.sol 106 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {IStepStorage} from './interfaces/IStepStorage.sol';
import {ConstantsLib} from './libraries/ConstantsLib.sol';
import {AuctionStep, StepLib} from './libraries/StepLib.sol';
import {SSTORE2} from 'solady/utils/SSTORE2.sol';
/// @title StepStorage
/// @notice Abstract contract to store and read information about the auction issuance schedule
abstract contract StepStorage is IStepStorage {
using StepLib for *;
using SSTORE2 for *;
/// @notice The block at which the auction starts
uint64 internal immutable START_BLOCK;
/// @notice The block at which the auction ends
uint64 internal immutable END_BLOCK;
/// @notice Cached length of the auction steps data provided in the constructor
uint256 internal immutable _LENGTH;
/// @notice The address pointer to the contract deployed by SSTORE2
address private immutable $_pointer;
/// @notice The word offset of the last read step in `auctionStepsData` bytes
uint256 private $_offset;
/// @notice The current active auction step
AuctionStep internal $step;
constructor(bytes memory _auctionStepsData, uint64 _startBlock, uint64 _endBlock) {
if (_startBlock >= _endBlock) revert InvalidEndBlock();
START_BLOCK = _startBlock;
END_BLOCK = _endBlock;
_LENGTH = _auctionStepsData.length;
address _pointer = _auctionStepsData.write();
_validate(_pointer);
$_pointer = _pointer;
_advanceStep();
}
/// @notice Validate the data provided in the constructor
/// @dev Checks that the contract was correctly deployed by SSTORE2 and that the total mps and blocks are valid
function _validate(address _pointer) internal view {
bytes memory _auctionStepsData = _pointer.read();
if (
_auctionStepsData.length == 0 || _auctionStepsData.length % StepLib.UINT64_SIZE != 0
|| _auctionStepsData.length != _LENGTH
) revert InvalidAuctionDataLength();
// Loop through the auction steps data and check if the mps is valid
uint256 sumMps = 0;
uint64 sumBlockDelta = 0;
for (uint256 i = 0; i < _LENGTH; i += StepLib.UINT64_SIZE) {
(uint24 mps, uint40 blockDelta) = _auctionStepsData.get(i);
// Prevent the block delta from being set to zero
if (blockDelta == 0) revert StepBlockDeltaCannotBeZero();
sumMps += mps * blockDelta;
sumBlockDelta += blockDelta;
}
if (sumMps != ConstantsLib.MPS) revert InvalidStepDataMps(sumMps, ConstantsLib.MPS);
uint64 calculatedEndBlock = START_BLOCK + sumBlockDelta;
if (calculatedEndBlock != END_BLOCK) revert InvalidEndBlockGivenStepData(calculatedEndBlock, END_BLOCK);
}
/// @notice Advance the current auction step
/// @dev This function is called on every new bid if the current step is complete
function _advanceStep() internal returns (AuctionStep memory) {
if ($_offset >= _LENGTH) revert AuctionIsOver();
bytes8 _auctionStep = bytes8($_pointer.read($_offset, $_offset + StepLib.UINT64_SIZE));
(uint24 mps, uint40 blockDelta) = _auctionStep.parse();
uint64 _startBlock = $step.endBlock;
if (_startBlock == 0) _startBlock = START_BLOCK;
uint64 _endBlock = _startBlock + uint64(blockDelta);
$step = AuctionStep({startBlock: _startBlock, endBlock: _endBlock, mps: mps});
$_offset += StepLib.UINT64_SIZE;
emit AuctionStepRecorded(_startBlock, _endBlock, mps);
return $step;
}
/// @inheritdoc IStepStorage
function step() external view returns (AuctionStep memory) {
return $step;
}
// Getters
/// @inheritdoc IStepStorage
function startBlock() external view returns (uint64) {
return START_BLOCK;
}
/// @inheritdoc IStepStorage
function endBlock() external view returns (uint64) {
return END_BLOCK;
}
/// @inheritdoc IStepStorage
function pointer() external view returns (address) {
return $_pointer;
}
}
TickStorage.sol 115 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {ITickStorage, Tick} from './interfaces/ITickStorage.sol';
import {ConstantsLib} from './libraries/ConstantsLib.sol';
/// @title TickStorage
/// @notice Abstract contract for handling tick storage
abstract contract TickStorage is ITickStorage {
/// @notice Mapping of price levels to tick data
mapping(uint256 price => Tick) private $_ticks;
/// @notice The price of the next initialized tick above the clearing price
/// @dev This will be equal to the clearingPrice if no ticks have been initialized yet
uint256 internal $nextActiveTickPrice;
/// @notice The floor price of the auction
uint256 internal immutable FLOOR_PRICE;
/// @notice The tick spacing of the auction - bids must be placed at discrete tick intervals
uint256 internal immutable TICK_SPACING;
/// @notice Sentinel value for the next pointer of the highest tick in the book
uint256 public constant MAX_TICK_PTR = type(uint256).max;
constructor(uint256 _tickSpacing, uint256 _floorPrice) {
if (_tickSpacing < ConstantsLib.MIN_TICK_SPACING) revert TickSpacingTooSmall();
TICK_SPACING = _tickSpacing;
if (_floorPrice == 0) revert FloorPriceIsZero();
if (_floorPrice < ConstantsLib.MIN_FLOOR_PRICE) revert FloorPriceTooLow();
FLOOR_PRICE = _floorPrice;
// Initialize the floor price as the first tick
// _getTick will validate that it is also at a tick boundary
_getTick(FLOOR_PRICE).next = MAX_TICK_PTR;
$nextActiveTickPrice = MAX_TICK_PTR;
emit NextActiveTickUpdated(MAX_TICK_PTR);
emit TickInitialized(FLOOR_PRICE);
}
/// @notice Internal function to get a tick at a price
/// @dev The returned tick is not guaranteed to be initialized
function _getTick(uint256 price) internal view returns (Tick storage) {
// Validate `price` is at a boundary designated by the tick spacing
if (price % TICK_SPACING != 0) revert TickPriceNotAtBoundary();
return $_ticks[price];
}
/// @notice Initialize a tick at `price` if it does not exist already
/// @dev `prevPrice` MUST be the price of an initialized tick before the new price.
/// Ideally, it is the price of the tick immediately preceding the desired price. If not,
/// we will iterate through the ticks until we find the next price which requires more gas.
/// If `price` is < `nextActiveTickPrice`, then `price` will be set as the nextActiveTickPrice
/// @param prevPrice The price of the previous tick
/// @param price The price of the tick
function _initializeTickIfNeeded(uint256 prevPrice, uint256 price) internal {
if (price == MAX_TICK_PTR) revert InvalidTickPrice();
// _getTick will validate that `price` is at a boundary designated by the tick spacing
Tick storage $newTick = _getTick(price);
// Early return if the tick is already initialized
if ($newTick.next != 0) return;
// Otherwise, we need to iterate through the linked list to find the correct position for the new tick
// Require that `prevPrice` is less than `price` since we can only iterate forward
if (prevPrice >= price) revert TickPreviousPriceInvalid();
uint256 nextPrice = _getTick(prevPrice).next;
// Revert if the next price is 0 as that means the `prevPrice` hint was not an initialized tick
if (nextPrice == 0) revert TickPreviousPriceInvalid();
// Move the `prevPrice` pointer up until its next pointer is a tick greater than or equal to `price`
// If `price` would be the highest tick in the list, this will iterate until `nextPrice` == MAX_TICK_PTR,
// which will end the loop since we don't allow for ticks to be initialized at MAX_TICK_PTR.
// Iterating to find the tick right before `price` ensures that it is correctly positioned in the linked list.
while (nextPrice < price) {
prevPrice = nextPrice;
nextPrice = _getTick(nextPrice).next;
}
// Update linked list pointers
$newTick.next = nextPrice;
_getTick(prevPrice).next = price;
// If the next tick is the nextActiveTick, update nextActiveTick to the new tick
// In the base case, where next == 0 and nextActiveTickPrice == 0, this will set nextActiveTickPrice to price
if (nextPrice == $nextActiveTickPrice) {
$nextActiveTickPrice = price;
emit NextActiveTickUpdated(price);
}
emit TickInitialized(price);
}
/// @notice Internal function to add demand to a tick
/// @param price The price of the tick
/// @param currencyDemandQ96 The demand to add
function _updateTickDemand(uint256 price, uint256 currencyDemandQ96) internal {
Tick storage $tick = _getTick(price);
if ($tick.next == 0) revert CannotUpdateUninitializedTick();
$tick.currencyDemandQ96 += currencyDemandQ96;
}
// Getters
/// @inheritdoc ITickStorage
function floorPrice() external view returns (uint256) {
return FLOOR_PRICE;
}
/// @inheritdoc ITickStorage
function tickSpacing() external view returns (uint256) {
return TICK_SPACING;
}
/// @inheritdoc ITickStorage
function nextActiveTickPrice() external view returns (uint256) {
return $nextActiveTickPrice;
}
/// @inheritdoc ITickStorage
function ticks(uint256 price) external view returns (Tick memory) {
return _getTick(price);
}
}
TokenCurrencyStorage.sol 102 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {ITokenCurrencyStorage} from './interfaces/ITokenCurrencyStorage.sol';
import {IERC20Minimal} from './interfaces/external/IERC20Minimal.sol';
import {ConstantsLib} from './libraries/ConstantsLib.sol';
import {Currency, CurrencyLibrary} from './libraries/CurrencyLibrary.sol';
import {FixedPoint96} from './libraries/FixedPoint96.sol';
import {ValueX7, ValueX7Lib} from './libraries/ValueX7Lib.sol';
/// @title TokenCurrencyStorage
abstract contract TokenCurrencyStorage is ITokenCurrencyStorage {
using ValueX7Lib for *;
using CurrencyLibrary for Currency;
/// @notice The currency being raised in the auction
Currency internal immutable CURRENCY;
/// @notice The token being sold in the auction
IERC20Minimal internal immutable TOKEN;
/// @notice The total supply of tokens to sell
uint128 internal immutable TOTAL_SUPPLY;
/// @notice The total supply of tokens to sell in 160.96 form
uint256 internal immutable TOTAL_SUPPLY_Q96;
/// @notice The recipient of any unsold tokens at the end of the auction
address internal immutable TOKENS_RECIPIENT;
/// @notice The recipient of the raised Currency from the auction
address internal immutable FUNDS_RECIPIENT;
/// @notice The amount of currency required to be raised for the auction
/// to graduate in Q96 form, scaled up by X7
ValueX7 internal immutable REQUIRED_CURRENCY_RAISED_Q96_X7;
/// @notice The block at which the currency was swept
uint256 public sweepCurrencyBlock;
/// @notice The block at which the tokens were swept
uint256 public sweepUnsoldTokensBlock;
constructor(
address _token,
address _currency,
uint128 _totalSupply,
address _tokensRecipient,
address _fundsRecipient,
uint128 _requiredCurrencyRaised
) {
if (_token == address(0)) revert TokenIsAddressZero();
if (_token == _currency) revert TokenAndCurrencyCannotBeTheSame();
if (_totalSupply == 0) revert TotalSupplyIsZero();
if (_totalSupply > ConstantsLib.MAX_TOTAL_SUPPLY) revert TotalSupplyIsTooLarge();
if (_tokensRecipient == address(0)) revert TokensRecipientIsZero();
if (_fundsRecipient == address(0)) revert FundsRecipientIsZero();
TOKEN = IERC20Minimal(_token);
CURRENCY = Currency.wrap(_currency);
TOTAL_SUPPLY = _totalSupply;
TOTAL_SUPPLY_Q96 = uint256(_totalSupply) << FixedPoint96.RESOLUTION;
TOKENS_RECIPIENT = _tokensRecipient;
FUNDS_RECIPIENT = _fundsRecipient;
REQUIRED_CURRENCY_RAISED_Q96_X7 = (uint256(_requiredCurrencyRaised) << FixedPoint96.RESOLUTION).scaleUpToX7();
}
function _sweepCurrency(uint256 amount) internal {
sweepCurrencyBlock = block.number;
if (amount > 0) {
CURRENCY.transfer(FUNDS_RECIPIENT, amount);
}
emit CurrencySwept(FUNDS_RECIPIENT, amount);
}
function _sweepUnsoldTokens(uint256 amount) internal {
sweepUnsoldTokensBlock = block.number;
if (amount > 0) {
Currency.wrap(address(TOKEN)).transfer(TOKENS_RECIPIENT, amount);
}
emit TokensSwept(TOKENS_RECIPIENT, amount);
}
// Getters
/// @inheritdoc ITokenCurrencyStorage
function currency() external view returns (Currency) {
return CURRENCY;
}
/// @inheritdoc ITokenCurrencyStorage
function token() external view returns (IERC20Minimal) {
return TOKEN;
}
/// @inheritdoc ITokenCurrencyStorage
function totalSupply() external view returns (uint128) {
return TOTAL_SUPPLY;
}
/// @inheritdoc ITokenCurrencyStorage
function tokensRecipient() external view returns (address) {
return TOKENS_RECIPIENT;
}
/// @inheritdoc ITokenCurrencyStorage
function fundsRecipient() external view returns (address) {
return FUNDS_RECIPIENT;
}
}
BidLib.sol 42 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {ConstantsLib} from './ConstantsLib.sol';
struct Bid {
uint64 startBlock; // Block number when the bid was first made in
uint24 startCumulativeMps; // Cumulative mps at the start of the bid
uint64 exitedBlock; // Block number when the bid was exited
uint256 maxPrice; // The max price of the bid
address owner; // Who will receive the tokens filled and currency refunded
uint256 amountQ96; // User's currency amount in Q96 form
uint256 tokensFilled; // Amount of tokens filled
}
/// @title BidLib
library BidLib {
using BidLib for *;
/// @dev Error thrown when a bid is submitted with no remaining percentage of the auction
/// This is prevented by the auction contract as bids cannot be submitted when the auction is sold out,
/// but we catch it instead of reverting with division by zero.
error MpsRemainingIsZero();
/// @notice Calculate the number of mps remaining in the auction since the bid was submitted
/// @param bid The bid to calculate the remaining mps for
/// @return The number of mps remaining in the auction
function mpsRemainingInAuctionAfterSubmission(Bid memory bid) internal pure returns (uint24) {
return ConstantsLib.MPS - bid.startCumulativeMps;
}
/// @notice Scale a bid amount to its effective amount over the remaining percentage of the auction
/// This is an important normalization step to ensure that we can calculate the currencyRaised
/// when cumulative demand is less than supply using the original supply schedule.
/// @param bid The bid to scale
/// @return The scaled amount
function toEffectiveAmount(Bid memory bid) internal pure returns (uint256) {
uint24 mpsRemainingInAuction = bid.mpsRemainingInAuctionAfterSubmission();
if (mpsRemainingInAuction == 0) revert MpsRemainingIsZero();
return bid.amountQ96 * ConstantsLib.MPS / mpsRemainingInAuction;
}
}
ConstantsLib.sol 26 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title ConstantsLib
/// @notice Library containing protocol constants
library ConstantsLib {
/// @notice we use milli-bips, or one thousandth of a basis point
uint24 constant MPS = 1e7;
/// @notice The upper bound of a ValueX7 value
uint256 constant X7_UPPER_BOUND = type(uint256).max / 1e7;
/// @notice The maximum total supply of tokens that can be sold in the Auction
/// @dev This is set to 2^100 tokens, which is just above 1e30, or one trillion units of a token with 18 decimals.
/// This upper bound is chosen to prevent the Auction from being used with an extremely large token supply,
/// which would restrict the clearing price to be a very low price in the calculation below.
uint128 constant MAX_TOTAL_SUPPLY = 1 << 100;
/// @notice The minimum allowable floor price is type(uint32).max + 1
/// @dev This is the minimum price that fits in a uint160 after being inversed
uint256 constant MIN_FLOOR_PRICE = uint256(type(uint32).max) + 1;
/// @notice The minimum allowable tick spacing
/// @dev We don't support tick spacings of 1 to avoid edge cases where the rounding of the clearing price
/// would cause the price to move between initialized ticks.
uint256 constant MIN_TICK_SPACING = 2;
}
CurrencyLibrary.sol 81 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {IERC20Minimal} from '../interfaces/external/IERC20Minimal.sol';
type Currency is address;
using CurrencyLibrary for Currency global;
/// @title CurrencyLibrary
/// @dev This library allows for transferring and holding native tokens and ERC20 tokens
/// @dev Forked from https://github.com/Uniswap/v4-core/blob/main/src/types/Currency.sol but modified to not bubble up reverts
library CurrencyLibrary {
/// @notice Thrown when a native transfer fails
error NativeTransferFailed();
/// @notice Thrown when an ERC20 transfer fails
error ERC20TransferFailed();
/// @notice A constant to represent the native currency
Currency public constant ADDRESS_ZERO = Currency.wrap(address(0));
function transfer(Currency currency, address to, uint256 amount) internal {
// altered from https://github.com/transmissions11/solmate/blob/44a9963d4c78111f77caa0e65d677b8b46d6f2e6/src/utils/SafeTransferLib.sol
// modified custom error selectors
bool success;
if (currency.isAddressZero()) {
assembly ('memory-safe') {
// Transfer the ETH and revert if it fails.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
// revert with NativeTransferFailed
if (!success) {
revert NativeTransferFailed();
}
} else {
assembly ('memory-safe') {
// Get a pointer to some free memory.
let fmp := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(fmp, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(fmp, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument.
mstore(add(fmp, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), currency, 0, fmp, 68, 0, 32)
)
// Now clean the memory we used
mstore(fmp, 0) // 4 byte `selector` and 28 bytes of `to` were stored here
mstore(add(fmp, 0x20), 0) // 4 bytes of `to` and 28 bytes of `amount` were stored here
mstore(add(fmp, 0x40), 0) // 4 bytes of `amount` were stored here
}
// revert with ERC20TransferFailed
if (!success) {
revert ERC20TransferFailed();
}
}
}
function balanceOf(Currency currency, address owner) internal view returns (uint256) {
if (currency.isAddressZero()) {
return owner.balance;
} else {
return IERC20Minimal(Currency.unwrap(currency)).balanceOf(owner);
}
}
function isAddressZero(Currency currency) internal pure returns (bool) {
return Currency.unwrap(currency) == Currency.unwrap(ADDRESS_ZERO);
}
}
FixedPoint96.sol 10 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title FixedPoint96
/// @notice A library for handling binary fixed point numbers, see https://en.wikipedia.org/wiki/Q_(number_format)
/// @dev Copied from https://github.com/Uniswap/v4-core/blob/main/src/libraries/FixedPoint96.sol
library FixedPoint96 {
uint8 internal constant RESOLUTION = 96;
uint256 internal constant Q96 = 0x1000000000000000000000000;
}
MaxBidPriceLib.sol 123 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {FixedPointMathLib} from 'solady/utils/FixedPointMathLib.sol';
/// @title MaxBidPriceLib
/// @notice Library for calculating the maximum bid price for a given total supply
/// @dev The two are generally inversely correlated with certain constraints.
library MaxBidPriceLib {
/**
* @dev Given a total supply we want to find the maximum bid price such that both the
* token liquidity and currency liquidity at the end of the Auction are less than the
* maximum liquidity supported by Uniswap v4.
*
* The chart below shows the shaded area of valid (max bid price, total supply) value pairs such that
* both calculated liquidity values are less than the maximum liquidity supported by Uniswap v4.
* (x axis represents the max bid price in log form, and y is the total supply in log form)
*
* y ↑
* | : : :
* | : :
* 128 + : :
* | : :
* | : : :
* | : :
* | : :
* | : :
* | : : : (x=110, y=100)
* | : : : : : : : +#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+ : ::: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
* 96 + +############################################ :
* | ################################################# : :
* | +#################################################+# :
* | #####################################################+ :
* | +#######################################################:
* | ########################################################## : :
* | +#########################################################+# :
* | #############################################################+ :
* 64 + +###############################################################: (x=160, y=62)
* | ################################################################: :
* | +############################################################### : :
* | ################################################################: :
* | +############################################################### :
* | ################################################################: :
* | +############################################################### : :
* | ################################################################: :
* 32 + +############################################################### :
* | ################################################################: :
* | +############################################################### : :
* | ################################################################: :
* | +############################################################### :
* | ################################################################: :
* | +###############################################################+ + : : +
* +---------------+###############+###############+###############+###############+---------------+---------------+--------------- x (max price)
* 0 32 64 96 128 160 192 224 256
*
*
* Legend:
* x = max bid price in log form
* y = total supply in log form
* L_max = 2^107 (the lowest max liquidity per tick supported in Uniswap v4)
* p_sqrtMax = 1461446703485210103287273052203988822378723970342 (max sqrt price in Uniswap v4)
* p_sqrtMin = 4295128739 (min sqrt price in Uniswap v4)
* x < 160, x > 32; (minimum price of 2^32, maximum price of 2^160)
* y < 100; (minimum supply of 2^0 or 1, maximum supply of 2^100)
*
* Equations for liquidity amounts in Uniswap v4:
* 1) If currencyIsCurrency1, L_0 = (2^y * ((2^((x+96)/2) * 2^160) / 2^96)) / |2^((x+96)/2)-p_sqrtMax| < L_max
* 2) L_1 = (2^(x+y)) / |2^((x+96)/2)-p_sqrtMin| < L_max
* 3) if currencyIsCurrency0, L_0 = (2^y * p_sqrtMax * 2^((192-x+96)/2)) / (2^(192-x+96) * |p_sqrtMax-2^((192-x+96)/2)|) < L_max
* 4) L_1 = (2^(y+96)) / |2^((192-x+96)/2)-p_sqrtMin| < L_max
*/
/// @notice The maximum allowable price for a bid is type(uint160).max
/// @dev This is the maximum price that can be shifted left by 96 bits without overflowing a uint256
uint256 constant MAX_V4_PRICE = type(uint160).max;
/// @notice The total supply value below which the maximum bid price is capped at MAX_V4_PRICE
/// @dev Since the two are inversely correlated, generally lower total supply = higher max bid price
/// However, for very small total supply values we still can't exceed the max v4 price.
/// This is the intersection of `maxPriceKeepingCurrencyRaisedUnderInt128Max` and MAX_V4_PRICE,
/// meaning that because we can't support prices above uint160.max, all total supply values at or below
/// this threshold are capped at MAX_V4_PRICE.
uint256 constant LOWER_TOTAL_SUPPLY_THRESHOLD = 1 << 62;
/// @notice Calculates the maximum bid price for a given total supply
/// @dev Total supply values under the LOWER_TOTAL_SUPPLY_THRESHOLD are capped at MAX_V4_PRICE
function maxBidPrice(uint128 _totalSupply) internal pure returns (uint256) {
// Small total supply values would return a price which exceeds the max v4 price, so we cap it at MAX_V4_PRICE
if (_totalSupply <= LOWER_TOTAL_SUPPLY_THRESHOLD) return MAX_V4_PRICE;
/**
* Derivation: For a given total supply y (in log space), find the max bid price x (in log space)
* The equations in the chart are equivalent for both currency/token sort orders (intuitive given a full range position).
* Token1 liquidity is the limiting factor, so we use L_1 for simplicity:
* 2^(x+y) / |2^((x+96)/2)-p_sqrtMin| < L_max
* 2^(x+y) < L_max * |2^((x+96)/2)-p_sqrtMin|
* We substitute a larger number than p_sqrtMin such that |2^((x+96)/2)-p_sqrtMin| ~ 2^((x+96)/2 - 1)
* 2^(x+y) < L_max * 2^((x+96)/2 - 1)
* Using 2^107 for L_max, we get:
* 2^(x+y) < 2^107 * 2^((x+96)/2 - 1)
* Taking the log2 of both sides, we get:
* x + y < 107 + (x+96) / 2 - 1
* x + y < 107 + x/2 + 48 - 1
* Since we are given total supply (y), we can solve for x:
* x/2 = 107 + 47 - y
* x/2 = 154 - y
* x = 2 * (154 - y)
* We want to find 2^x, not `x` so we take both sides to the power of 2:
* 2^x = (2^154 / y) ** 2
*
* Because we return early if total supply is less than 2^62 the result of this will not overflow a uint256.
*/
uint256 maxPriceKeepingLiquidityUnderMax = uint256((1 << 154) / _totalSupply) ** 2;
// Additionally, we need to ensure that the currency raised is <= int128.max (2^127 - 1)
// since PoolManager will cast it to int128 when the position is created.
// The maxmimum currencyRaised in the auction is equal to totalSupply * maxBidPrice / Q96
// To be conservative, we ensure that it is under 2^126, and rearranging the equation we get:
// maxBidPrice < (2^126 * Q96) / totalSupply = 2^222 / totalSupply
uint256 maxPriceKeepingCurrencyRaisedUnderInt128Max = uint256(1 << 222) / _totalSupply;
// Take the minimum of the two to ensure that the (max bid price, total supply) pair is within the valid range.
return FixedPointMathLib.min(maxPriceKeepingLiquidityUnderMax, maxPriceKeepingCurrencyRaisedUnderInt128Max);
}
}
StepLib.sol 42 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
struct AuctionStep {
uint24 mps; // Mps to sell per block in the step
uint64 startBlock; // Start block of the step (inclusive)
uint64 endBlock; // Ending block of the step (exclusive)
}
/// @notice Library for auction step calculations and parsing
library StepLib {
using StepLib for *;
/// @notice The size of a uint64 in bytes
uint256 public constant UINT64_SIZE = 8;
/// @notice Error thrown when the offset is too large for the data length
error StepLib__InvalidOffsetTooLarge();
/// @notice Error thrown when the offset is not at a step boundary - a uint64 aligned offset
error StepLib__InvalidOffsetNotAtStepBoundary();
/// @notice Unpack the mps and block delta from the auction steps data
function parse(bytes8 data) internal pure returns (uint24 mps, uint40 blockDelta) {
mps = uint24(bytes3(data));
blockDelta = uint40(uint64(data));
}
/// @notice Load a word at `offset` from data and parse it into mps and blockDelta
function get(bytes memory data, uint256 offset) internal pure returns (uint24 mps, uint40 blockDelta) {
// Offset cannot be greater than the data length
if (offset >= data.length) revert StepLib__InvalidOffsetTooLarge();
// Offset must be a multiple of a step (uint64 - uint24|uint40)
if (offset % UINT64_SIZE != 0) revert StepLib__InvalidOffsetNotAtStepBoundary();
assembly {
let packedValue := mload(add(add(data, 0x20), offset))
packedValue := shr(192, packedValue)
mps := shr(40, packedValue)
blockDelta := and(packedValue, 0xFFFFFFFFFF)
}
}
}
ValidationHookLib.sol 29 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {IValidationHook} from '../interfaces/IValidationHook.sol';
/// @title ValidationHookLib
/// @notice Library for handling calls to validation hooks and bubbling up the revert reason
library ValidationHookLib {
/// @notice Error thrown when a validation hook call fails
/// @param reason The bubbled up revert reason
error ValidationHookCallFailed(bytes reason);
/// @notice Handles calling a validation hook and bubbling up the revert reason
function handleValidate(
IValidationHook hook,
uint256 maxPrice,
uint128 amount,
address owner,
address sender,
bytes calldata hookData
) internal {
if (address(hook) == address(0)) return;
try hook.validate(maxPrice, amount, owner, sender, hookData) {}
catch (bytes memory reason) {
revert ValidationHookCallFailed(reason);
}
}
}
FixedPointMathLib.sol 1312 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
library FixedPointMathLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The operation failed, as the output exceeds the maximum value of uint256.
error ExpOverflow();
/// @dev The operation failed, as the output exceeds the maximum value of uint256.
error FactorialOverflow();
/// @dev The operation failed, due to an overflow.
error RPowOverflow();
/// @dev The mantissa is too big to fit.
error MantissaOverflow();
/// @dev The operation failed, due to an multiplication overflow.
error MulWadFailed();
/// @dev The operation failed, due to an multiplication overflow.
error SMulWadFailed();
/// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
error DivWadFailed();
/// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
error SDivWadFailed();
/// @dev The operation failed, either due to a multiplication overflow, or a division by a zero.
error MulDivFailed();
/// @dev The division failed, as the denominator is zero.
error DivFailed();
/// @dev The full precision multiply-divide operation failed, either due
/// to the result being larger than 256 bits, or a division by a zero.
error FullMulDivFailed();
/// @dev The output is undefined, as the input is less-than-or-equal to zero.
error LnWadUndefined();
/// @dev The input outside the acceptable domain.
error OutOfDomain();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The scalar of ETH and most ERC20s.
uint256 internal constant WAD = 1e18;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* SIMPLIFIED FIXED POINT OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Equivalent to `(x * y) / WAD` rounded down.
function mulWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
if gt(x, div(not(0), y)) {
if y {
mstore(0x00, 0xbac65e5b) // `MulWadFailed()`.
revert(0x1c, 0x04)
}
}
z := div(mul(x, y), WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded down.
function sMulWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, y)
// Equivalent to `require((x == 0 || z / x == y) && !(x == -1 && y == type(int256).min))`.
if iszero(gt(or(iszero(x), eq(sdiv(z, x), y)), lt(not(x), eq(y, shl(255, 1))))) {
mstore(0x00, 0xedcd4dd4) // `SMulWadFailed()`.
revert(0x1c, 0x04)
}
z := sdiv(z, WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded down, but without overflow checks.
function rawMulWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := div(mul(x, y), WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded down, but without overflow checks.
function rawSMulWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := sdiv(mul(x, y), WAD)
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded up.
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, y)
// Equivalent to `require(y == 0 || x <= type(uint256).max / y)`.
if iszero(eq(div(z, y), x)) {
if y {
mstore(0x00, 0xbac65e5b) // `MulWadFailed()`.
revert(0x1c, 0x04)
}
}
z := add(iszero(iszero(mod(z, WAD))), div(z, WAD))
}
}
/// @dev Equivalent to `(x * y) / WAD` rounded up, but without overflow checks.
function rawMulWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := add(iszero(iszero(mod(mul(x, y), WAD))), div(mul(x, y), WAD))
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down.
function divWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y != 0 && x <= type(uint256).max / WAD)`.
if iszero(mul(y, lt(x, add(1, div(not(0), WAD))))) {
mstore(0x00, 0x7c5f487d) // `DivWadFailed()`.
revert(0x1c, 0x04)
}
z := div(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down.
function sDivWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, WAD)
// Equivalent to `require(y != 0 && ((x * WAD) / WAD == x))`.
if iszero(mul(y, eq(sdiv(z, WAD), x))) {
mstore(0x00, 0x5c43740d) // `SDivWadFailed()`.
revert(0x1c, 0x04)
}
z := sdiv(z, y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down, but without overflow and divide by zero checks.
function rawDivWad(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := div(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded down, but without overflow and divide by zero checks.
function rawSDivWad(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := sdiv(mul(x, WAD), y)
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded up.
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Equivalent to `require(y != 0 && x <= type(uint256).max / WAD)`.
if iszero(mul(y, lt(x, add(1, div(not(0), WAD))))) {
mstore(0x00, 0x7c5f487d) // `DivWadFailed()`.
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y))
}
}
/// @dev Equivalent to `(x * WAD) / y` rounded up, but without overflow and divide by zero checks.
function rawDivWadUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := add(iszero(iszero(mod(mul(x, WAD), y))), div(mul(x, WAD), y))
}
}
/// @dev Equivalent to `x` to the power of `y`.
/// because `x ** y = (e ** ln(x)) ** y = e ** (ln(x) * y)`.
/// Note: This function is an approximation.
function powWad(int256 x, int256 y) internal pure returns (int256) {
// Using `ln(x)` means `x` must be greater than 0.
return expWad((lnWad(x) * y) / int256(WAD));
}
/// @dev Returns `exp(x)`, denominated in `WAD`.
/// Credit to Remco Bloemen under MIT license: https://2π.com/22/exp-ln
/// Note: This function is an approximation. Monotonically increasing.
function expWad(int256 x) internal pure returns (int256 r) {
unchecked {
// When the result is less than 0.5 we return zero.
// This happens when `x <= (log(1e-18) * 1e18) ~ -4.15e19`.
if (x <= -41446531673892822313) return r;
/// @solidity memory-safe-assembly
assembly {
// When the result is greater than `(2**255 - 1) / 1e18` we can not represent it as
// an int. This happens when `x >= floor(log((2**255 - 1) / 1e18) * 1e18) ≈ 135`.
if iszero(slt(x, 135305999368893231589)) {
mstore(0x00, 0xa37bfec9) // `ExpOverflow()`.
revert(0x1c, 0x04)
}
}
// `x` is now in the range `(-42, 136) * 1e18`. Convert to `(-42, 136) * 2**96`
// for more intermediate precision and a binary basis. This base conversion
// is a multiplication by 1e18 / 2**96 = 5**18 / 2**78.
x = (x << 78) / 5 ** 18;
// Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers
// of two such that exp(x) = exp(x') * 2**k, where k is an integer.
// Solving this gives k = round(x / log(2)) and x' = x - k * log(2).
int256 k = ((x << 96) / 54916777467707473351141471128 + 2 ** 95) >> 96;
x = x - k * 54916777467707473351141471128;
// `k` is in the range `[-61, 195]`.
// Evaluate using a (6, 7)-term rational approximation.
// `p` is made monic, we'll multiply by a scale factor later.
int256 y = x + 1346386616545796478920950773328;
y = ((y * x) >> 96) + 57155421227552351082224309758442;
int256 p = y + x - 94201549194550492254356042504812;
p = ((p * y) >> 96) + 28719021644029726153956944680412240;
p = p * x + (4385272521454847904659076985693276 << 96);
// We leave `p` in `2**192` basis so we don't need to scale it back up for the division.
int256 q = x - 2855989394907223263936484059900;
q = ((q * x) >> 96) + 50020603652535783019961831881945;
q = ((q * x) >> 96) - 533845033583426703283633433725380;
q = ((q * x) >> 96) + 3604857256930695427073651918091429;
q = ((q * x) >> 96) - 14423608567350463180887372962807573;
q = ((q * x) >> 96) + 26449188498355588339934803723976023;
/// @solidity memory-safe-assembly
assembly {
// Div in assembly because solidity adds a zero check despite the unchecked.
// The q polynomial won't have zeros in the domain as all its roots are complex.
// No scaling is necessary because p is already `2**96` too large.
r := sdiv(p, q)
}
// r should be in the range `(0.09, 0.25) * 2**96`.
// We now need to multiply r by:
// - The scale factor `s ≈ 6.031367120`.
// - The `2**k` factor from the range reduction.
// - The `1e18 / 2**96` factor for base conversion.
// We do this all at once, with an intermediate result in `2**213`
// basis, so the final right shift is always by a positive amount.
r = int256(
(uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k)
);
}
}
/// @dev Returns `ln(x)`, denominated in `WAD`.
/// Credit to Remco Bloemen under MIT license: https://2π.com/22/exp-ln
/// Note: This function is an approximation. Monotonically increasing.
function lnWad(int256 x) internal pure returns (int256 r) {
/// @solidity memory-safe-assembly
assembly {
// We want to convert `x` from `10**18` fixed point to `2**96` fixed point.
// We do this by multiplying by `2**96 / 10**18`. But since
// `ln(x * C) = ln(x) + ln(C)`, we can simply do nothing here
// and add `ln(2**96 / 10**18)` at the end.
// Compute `k = log2(x) - 96`, `r = 159 - k = 255 - log2(x) = 255 ^ log2(x)`.
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
// We place the check here for more optimal stack operations.
if iszero(sgt(x, 0)) {
mstore(0x00, 0x1615e638) // `LnWadUndefined()`.
revert(0x1c, 0x04)
}
// forgefmt: disable-next-item
r := xor(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
0xf8f9f9faf9fdfafbf9fdfcfdfafbfcfef9fafdfafcfcfbfefafafcfbffffffff))
// Reduce range of x to (1, 2) * 2**96
// ln(2^k * x) = k * ln(2) + ln(x)
x := shr(159, shl(r, x))
// Evaluate using a (8, 8)-term rational approximation.
// `p` is made monic, we will multiply by a scale factor later.
// forgefmt: disable-next-item
let p := sub( // This heavily nested expression is to avoid stack-too-deep for via-ir.
sar(96, mul(add(43456485725739037958740375743393,
sar(96, mul(add(24828157081833163892658089445524,
sar(96, mul(add(3273285459638523848632254066296,
x), x))), x))), x)), 11111509109440967052023855526967)
p := sub(sar(96, mul(p, x)), 45023709667254063763336534515857)
p := sub(sar(96, mul(p, x)), 14706773417378608786704636184526)
p := sub(mul(p, x), shl(96, 795164235651350426258249787498))
// We leave `p` in `2**192` basis so we don't need to scale it back up for the division.
// `q` is monic by convention.
let q := add(5573035233440673466300451813936, x)
q := add(71694874799317883764090561454958, sar(96, mul(x, q)))
q := add(283447036172924575727196451306956, sar(96, mul(x, q)))
q := add(401686690394027663651624208769553, sar(96, mul(x, q)))
q := add(204048457590392012362485061816622, sar(96, mul(x, q)))
q := add(31853899698501571402653359427138, sar(96, mul(x, q)))
q := add(909429971244387300277376558375, sar(96, mul(x, q)))
// `p / q` is in the range `(0, 0.125) * 2**96`.
// Finalization, we need to:
// - Multiply by the scale factor `s = 5.549…`.
// - Add `ln(2**96 / 10**18)`.
// - Add `k * ln(2)`.
// - Multiply by `10**18 / 2**96 = 5**18 >> 78`.
// The q polynomial is known not to have zeros in the domain.
// No scaling required because p is already `2**96` too large.
p := sdiv(p, q)
// Multiply by the scaling factor: `s * 5**18 * 2**96`, base is now `5**18 * 2**192`.
p := mul(1677202110996718588342820967067443963516166, p)
// Add `ln(2) * k * 5**18 * 2**192`.
// forgefmt: disable-next-item
p := add(mul(16597577552685614221487285958193947469193820559219878177908093499208371, sub(159, r)), p)
// Add `ln(2**96 / 10**18) * 5**18 * 2**192`.
p := add(600920179829731861736702779321621459595472258049074101567377883020018308, p)
// Base conversion: mul `2**18 / 2**192`.
r := sar(174, p)
}
}
/// @dev Returns `W_0(x)`, denominated in `WAD`.
/// See: https://en.wikipedia.org/wiki/Lambert_W_function
/// a.k.a. Product log function. This is an approximation of the principal branch.
/// Note: This function is an approximation. Monotonically increasing.
function lambertW0Wad(int256 x) internal pure returns (int256 w) {
// forgefmt: disable-next-item
unchecked {
if ((w = x) <= -367879441171442322) revert OutOfDomain(); // `x` less than `-1/e`.
(int256 wad, int256 p) = (int256(WAD), x);
uint256 c; // Whether we need to avoid catastrophic cancellation.
uint256 i = 4; // Number of iterations.
if (w <= 0x1ffffffffffff) {
if (-0x4000000000000 <= w) {
i = 1; // Inputs near zero only take one step to converge.
} else if (w <= -0x3ffffffffffffff) {
i = 32; // Inputs near `-1/e` take very long to converge.
}
} else if (uint256(w >> 63) == uint256(0)) {
/// @solidity memory-safe-assembly
assembly {
// Inline log2 for more performance, since the range is small.
let v := shr(49, w)
let l := shl(3, lt(0xff, v))
l := add(or(l, byte(and(0x1f, shr(shr(l, v), 0x8421084210842108cc6318c6db6d54be)),
0x0706060506020504060203020504030106050205030304010505030400000000)), 49)
w := sdiv(shl(l, 7), byte(sub(l, 31), 0x0303030303030303040506080c13))
c := gt(l, 60)
i := add(2, add(gt(l, 53), c))
}
} else {
int256 ll = lnWad(w = lnWad(w));
/// @solidity memory-safe-assembly
assembly {
// `w = ln(x) - ln(ln(x)) + b * ln(ln(x)) / ln(x)`.
w := add(sdiv(mul(ll, 1023715080943847266), w), sub(w, ll))
i := add(3, iszero(shr(68, x)))
c := iszero(shr(143, x))
}
if (c == uint256(0)) {
do { // If `x` is big, use Newton's so that intermediate values won't overflow.
int256 e = expWad(w);
/// @solidity memory-safe-assembly
assembly {
let t := mul(w, div(e, wad))
w := sub(w, sdiv(sub(t, x), div(add(e, t), wad)))
}
if (p <= w) break;
p = w;
} while (--i != uint256(0));
/// @solidity memory-safe-assembly
assembly {
w := sub(w, sgt(w, 2))
}
return w;
}
}
do { // Otherwise, use Halley's for faster convergence.
int256 e = expWad(w);
/// @solidity memory-safe-assembly
assembly {
let t := add(w, wad)
let s := sub(mul(w, e), mul(x, wad))
w := sub(w, sdiv(mul(s, wad), sub(mul(e, t), sdiv(mul(add(t, wad), s), add(t, t)))))
}
if (p <= w) break;
p = w;
} while (--i != c);
/// @solidity memory-safe-assembly
assembly {
w := sub(w, sgt(w, 2))
}
// For certain ranges of `x`, we'll use the quadratic-rate recursive formula of
// R. Iacono and J.P. Boyd for the last iteration, to avoid catastrophic cancellation.
if (c == uint256(0)) return w;
int256 t = w | 1;
/// @solidity memory-safe-assembly
assembly {
x := sdiv(mul(x, wad), t)
}
x = (t * (wad + lnWad(x)));
/// @solidity memory-safe-assembly
assembly {
w := sdiv(x, add(wad, t))
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* GENERAL NUMBER UTILITIES */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns `a * b == x * y`, with full precision.
function fullMulEq(uint256 a, uint256 b, uint256 x, uint256 y)
internal
pure
returns (bool result)
{
/// @solidity memory-safe-assembly
assembly {
result := and(eq(mul(a, b), mul(x, y)), eq(mulmod(x, y, not(0)), mulmod(a, b, not(0))))
}
}
/// @dev Calculates `floor(x * y / d)` with full precision.
/// Throws if result overflows a uint256 or when `d` is zero.
/// Credit to Remco Bloemen under MIT license: https://2π.com/21/muldiv
function fullMulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// 512-bit multiply `[p1 p0] = x * y`.
// Compute the product mod `2**256` and mod `2**256 - 1`
// then use the Chinese Remainder Theorem to reconstruct
// the 512 bit result. The result is stored in two 256
// variables such that `product = p1 * 2**256 + p0`.
// Temporarily use `z` as `p0` to save gas.
z := mul(x, y) // Lower 256 bits of `x * y`.
for {} 1 {} {
// If overflows.
if iszero(mul(or(iszero(x), eq(div(z, x), y)), d)) {
let mm := mulmod(x, y, not(0))
let p1 := sub(mm, add(z, lt(mm, z))) // Upper 256 bits of `x * y`.
/*------------------- 512 by 256 division --------------------*/
// Make division exact by subtracting the remainder from `[p1 p0]`.
let r := mulmod(x, y, d) // Compute remainder using mulmod.
let t := and(d, sub(0, d)) // The least significant bit of `d`. `t >= 1`.
// Make sure `z` is less than `2**256`. Also prevents `d == 0`.
// Placing the check here seems to give more optimal stack operations.
if iszero(gt(d, p1)) {
mstore(0x00, 0xae47f702) // `FullMulDivFailed()`.
revert(0x1c, 0x04)
}
d := div(d, t) // Divide `d` by `t`, which is a power of two.
// Invert `d mod 2**256`
// Now that `d` is an odd number, it has an inverse
// modulo `2**256` such that `d * inv = 1 mod 2**256`.
// Compute the inverse by starting with a seed that is correct
// correct for four bits. That is, `d * inv = 1 mod 2**4`.
let inv := xor(2, mul(3, d))
// Now use Newton-Raphson iteration to improve the precision.
// Thanks to Hensel's lifting lemma, this also works in modular
// arithmetic, doubling the correct bits in each step.
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**8
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**16
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**32
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**64
inv := mul(inv, sub(2, mul(d, inv))) // inverse mod 2**128
z :=
mul(
// Divide [p1 p0] by the factors of two.
// Shift in bits from `p1` into `p0`. For this we need
// to flip `t` such that it is `2**256 / t`.
or(mul(sub(p1, gt(r, z)), add(div(sub(0, t), t), 1)), div(sub(z, r), t)),
mul(sub(2, mul(d, inv)), inv) // inverse mod 2**256
)
break
}
z := div(z, d)
break
}
}
}
/// @dev Calculates `floor(x * y / d)` with full precision.
/// Behavior is undefined if `d` is zero or the final result cannot fit in 256 bits.
/// Performs the full 512 bit calculation regardless.
function fullMulDivUnchecked(uint256 x, uint256 y, uint256 d)
internal
pure
returns (uint256 z)
{
/// @solidity memory-safe-assembly
assembly {
z := mul(x, y)
let mm := mulmod(x, y, not(0))
let p1 := sub(mm, add(z, lt(mm, z)))
let t := and(d, sub(0, d))
let r := mulmod(x, y, d)
d := div(d, t)
let inv := xor(2, mul(3, d))
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
inv := mul(inv, sub(2, mul(d, inv)))
z :=
mul(
or(mul(sub(p1, gt(r, z)), add(div(sub(0, t), t), 1)), div(sub(z, r), t)),
mul(sub(2, mul(d, inv)), inv)
)
}
}
/// @dev Calculates `floor(x * y / d)` with full precision, rounded up.
/// Throws if result overflows a uint256 or when `d` is zero.
/// Credit to Uniswap-v3-core under MIT license:
/// https://github.com/Uniswap/v3-core/blob/main/contracts/libraries/FullMath.sol
function fullMulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
z = fullMulDiv(x, y, d);
/// @solidity memory-safe-assembly
assembly {
if mulmod(x, y, d) {
z := add(z, 1)
if iszero(z) {
mstore(0x00, 0xae47f702) // `FullMulDivFailed()`.
revert(0x1c, 0x04)
}
}
}
}
/// @dev Calculates `floor(x * y / 2 ** n)` with full precision.
/// Throws if result overflows a uint256.
/// Credit to Philogy under MIT license:
/// https://github.com/SorellaLabs/angstrom/blob/main/contracts/src/libraries/X128MathLib.sol
function fullMulDivN(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// Temporarily use `z` as `p0` to save gas.
z := mul(x, y) // Lower 256 bits of `x * y`. We'll call this `z`.
for {} 1 {} {
if iszero(or(iszero(x), eq(div(z, x), y))) {
let k := and(n, 0xff) // `n`, cleaned.
let mm := mulmod(x, y, not(0))
let p1 := sub(mm, add(z, lt(mm, z))) // Upper 256 bits of `x * y`.
// | p1 | z |
// Before: | p1_0 ¦ p1_1 | z_0 ¦ z_1 |
// Final: | 0 ¦ p1_0 | p1_1 ¦ z_0 |
// Check that final `z` doesn't overflow by checking that p1_0 = 0.
if iszero(shr(k, p1)) {
z := add(shl(sub(256, k), p1), shr(k, z))
break
}
mstore(0x00, 0xae47f702) // `FullMulDivFailed()`.
revert(0x1c, 0x04)
}
z := shr(and(n, 0xff), z)
break
}
}
}
/// @dev Returns `floor(x * y / d)`.
/// Reverts if `x * y` overflows, or `d` is zero.
function mulDiv(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, y)
// Equivalent to `require(d != 0 && (y == 0 || x <= type(uint256).max / y))`.
if iszero(mul(or(iszero(x), eq(div(z, x), y)), d)) {
mstore(0x00, 0xad251c27) // `MulDivFailed()`.
revert(0x1c, 0x04)
}
z := div(z, d)
}
}
/// @dev Returns `ceil(x * y / d)`.
/// Reverts if `x * y` overflows, or `d` is zero.
function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(x, y)
// Equivalent to `require(d != 0 && (y == 0 || x <= type(uint256).max / y))`.
if iszero(mul(or(iszero(x), eq(div(z, x), y)), d)) {
mstore(0x00, 0xad251c27) // `MulDivFailed()`.
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(z, d))), div(z, d))
}
}
/// @dev Returns `x`, the modular multiplicative inverse of `a`, such that `(a * x) % n == 1`.
function invMod(uint256 a, uint256 n) internal pure returns (uint256 x) {
/// @solidity memory-safe-assembly
assembly {
let g := n
let r := mod(a, n)
for { let y := 1 } 1 {} {
let q := div(g, r)
let t := g
g := r
r := sub(t, mul(r, q))
let u := x
x := y
y := sub(u, mul(y, q))
if iszero(r) { break }
}
x := mul(eq(g, 1), add(x, mul(slt(x, 0), n)))
}
}
/// @dev Returns `ceil(x / d)`.
/// Reverts if `d` is zero.
function divUp(uint256 x, uint256 d) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
if iszero(d) {
mstore(0x00, 0x65244e4e) // `DivFailed()`.
revert(0x1c, 0x04)
}
z := add(iszero(iszero(mod(x, d))), div(x, d))
}
}
/// @dev Returns `max(0, x - y)`. Alias for `saturatingSub`.
function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(gt(x, y), sub(x, y))
}
}
/// @dev Returns `max(0, x - y)`.
function saturatingSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(gt(x, y), sub(x, y))
}
}
/// @dev Returns `min(2 ** 256 - 1, x + y)`.
function saturatingAdd(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := or(sub(0, lt(add(x, y), x)), add(x, y))
}
}
/// @dev Returns `min(2 ** 256 - 1, x * y)`.
function saturatingMul(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := or(sub(or(iszero(x), eq(div(mul(x, y), x), y)), 1), mul(x, y))
}
}
/// @dev Returns `condition ? x : y`, without branching.
function ternary(bool condition, uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), iszero(condition)))
}
}
/// @dev Returns `condition ? x : y`, without branching.
function ternary(bool condition, bytes32 x, bytes32 y) internal pure returns (bytes32 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), iszero(condition)))
}
}
/// @dev Returns `condition ? x : y`, without branching.
function ternary(bool condition, address x, address y) internal pure returns (address z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), iszero(condition)))
}
}
/// @dev Returns `x != 0 ? x : y`, without branching.
function coalesce(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := or(x, mul(y, iszero(x)))
}
}
/// @dev Returns `x != bytes32(0) ? x : y`, without branching.
function coalesce(bytes32 x, bytes32 y) internal pure returns (bytes32 z) {
/// @solidity memory-safe-assembly
assembly {
z := or(x, mul(y, iszero(x)))
}
}
/// @dev Returns `x != address(0) ? x : y`, without branching.
function coalesce(address x, address y) internal pure returns (address z) {
/// @solidity memory-safe-assembly
assembly {
z := or(x, mul(y, iszero(shl(96, x))))
}
}
/// @dev Exponentiate `x` to `y` by squaring, denominated in base `b`.
/// Reverts if the computation overflows.
function rpow(uint256 x, uint256 y, uint256 b) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := mul(b, iszero(y)) // `0 ** 0 = 1`. Otherwise, `0 ** n = 0`.
if x {
z := xor(b, mul(xor(b, x), and(y, 1))) // `z = isEven(y) ? scale : x`
let half := shr(1, b) // Divide `b` by 2.
// Divide `y` by 2 every iteration.
for { y := shr(1, y) } y { y := shr(1, y) } {
let xx := mul(x, x) // Store x squared.
let xxRound := add(xx, half) // Round to the nearest number.
// Revert if `xx + half` overflowed, or if `x ** 2` overflows.
if or(lt(xxRound, xx), shr(128, x)) {
mstore(0x00, 0x49f7642b) // `RPowOverflow()`.
revert(0x1c, 0x04)
}
x := div(xxRound, b) // Set `x` to scaled `xxRound`.
// If `y` is odd:
if and(y, 1) {
let zx := mul(z, x) // Compute `z * x`.
let zxRound := add(zx, half) // Round to the nearest number.
// If `z * x` overflowed or `zx + half` overflowed:
if or(xor(div(zx, x), z), lt(zxRound, zx)) {
// Revert if `x` is non-zero.
if x {
mstore(0x00, 0x49f7642b) // `RPowOverflow()`.
revert(0x1c, 0x04)
}
}
z := div(zxRound, b) // Return properly scaled `zxRound`.
}
}
}
}
}
/// @dev Returns the square root of `x`, rounded down.
function sqrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
// `floor(sqrt(2**15)) = 181`. `sqrt(2**15) - 181 = 2.84`.
z := 181 // The "correct" value is 1, but this saves a multiplication later.
// This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
// start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.
// Let `y = x / 2**r`. We check `y >= 2**(k + 8)`
// but shift right by `k` bits to ensure that if `x >= 256`, then `y >= 256`.
let r := shl(7, lt(0xffffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffffff, shr(r, x))))
z := shl(shr(1, r), z)
// Goal was to get `z*z*y` within a small factor of `x`. More iterations could
// get y in a tighter range. Currently, we will have y in `[256, 256*(2**16))`.
// We ensured `y >= 256` so that the relative difference between `y` and `y+1` is small.
// That's not possible if `x < 256` but we can just verify those cases exhaustively.
// Now, `z*z*y <= x < z*z*(y+1)`, and `y <= 2**(16+8)`, and either `y >= 256`, or `x < 256`.
// Correctness can be checked exhaustively for `x < 256`, so we assume `y >= 256`.
// Then `z*sqrt(y)` is within `sqrt(257)/sqrt(256)` of `sqrt(x)`, or about 20bps.
// For `s` in the range `[1/256, 256]`, the estimate `f(s) = (181/1024) * (s+1)`
// is in the range `(1/2.84 * sqrt(s), 2.84 * sqrt(s))`,
// with largest error when `s = 1` and when `s = 256` or `1/256`.
// Since `y` is in `[256, 256*(2**16))`, let `a = y/65536`, so that `a` is in `[1/256, 256)`.
// Then we can estimate `sqrt(y)` using
// `sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2**18`.
// There is no overflow risk here since `y < 2**136` after the first branch above.
z := shr(18, mul(z, add(shr(r, x), 65536))) // A `mul()` is saved from starting `z` at 181.
// Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
// If `x+1` is a perfect square, the Babylonian method cycles between
// `floor(sqrt(x))` and `ceil(sqrt(x))`. This statement ensures we return floor.
// See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
z := sub(z, lt(div(x, z), z))
}
}
/// @dev Returns the cube root of `x`, rounded down.
/// Credit to bout3fiddy and pcaversaccio under AGPLv3 license:
/// https://github.com/pcaversaccio/snekmate/blob/main/src/snekmate/utils/math.vy
/// Formally verified by xuwinnie:
/// https://github.com/vectorized/solady/blob/main/audits/xuwinnie-solady-cbrt-proof.pdf
function cbrt(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
let r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
// Makeshift lookup table to nudge the approximate log2 result.
z := div(shl(div(r, 3), shl(lt(0xf, shr(r, x)), 0xf)), xor(7, mod(r, 3)))
// Newton-Raphson's.
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
z := div(add(add(div(x, mul(z, z)), z), z), 3)
// Round down.
z := sub(z, lt(div(x, mul(z, z)), z))
}
}
/// @dev Returns the square root of `x`, denominated in `WAD`, rounded down.
function sqrtWad(uint256 x) internal pure returns (uint256 z) {
unchecked {
if (x <= type(uint256).max / 10 ** 18) return sqrt(x * 10 ** 18);
z = (1 + sqrt(x)) * 10 ** 9;
z = (fullMulDivUnchecked(x, 10 ** 18, z) + z) >> 1;
}
/// @solidity memory-safe-assembly
assembly {
z := sub(z, gt(999999999999999999, sub(mulmod(z, z, x), 1))) // Round down.
}
}
/// @dev Returns the cube root of `x`, denominated in `WAD`, rounded down.
/// Formally verified by xuwinnie:
/// https://github.com/vectorized/solady/blob/main/audits/xuwinnie-solady-cbrt-proof.pdf
function cbrtWad(uint256 x) internal pure returns (uint256 z) {
unchecked {
if (x <= type(uint256).max / 10 ** 36) return cbrt(x * 10 ** 36);
z = (1 + cbrt(x)) * 10 ** 12;
z = (fullMulDivUnchecked(x, 10 ** 36, z * z) + z + z) / 3;
}
/// @solidity memory-safe-assembly
assembly {
let p := x
for {} 1 {} {
if iszero(shr(229, p)) {
if iszero(shr(199, p)) {
p := mul(p, 100000000000000000) // 10 ** 17.
break
}
p := mul(p, 100000000) // 10 ** 8.
break
}
if iszero(shr(249, p)) { p := mul(p, 100) }
break
}
let t := mulmod(mul(z, z), z, p)
z := sub(z, gt(lt(t, shr(1, p)), iszero(t))) // Round down.
}
}
/// @dev Returns `sqrt(x * y)`. Also called the geometric mean.
function mulSqrt(uint256 x, uint256 y) internal pure returns (uint256 z) {
if (x == y) return x;
uint256 p = rawMul(x, y);
if (y == rawDiv(p, x)) return sqrt(p);
for (z = saturatingMul(rawAdd(sqrt(x), 1), rawAdd(sqrt(y), 1));; z = avg(z, p)) {
if ((p = fullMulDivUnchecked(x, y, z)) >= z) break;
}
}
/// @dev Returns the factorial of `x`.
function factorial(uint256 x) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := 1
if iszero(lt(x, 58)) {
mstore(0x00, 0xaba0f2a2) // `FactorialOverflow()`.
revert(0x1c, 0x04)
}
for {} x { x := sub(x, 1) } { z := mul(z, x) }
}
}
/// @dev Returns the log2 of `x`.
/// Equivalent to computing the index of the most significant bit (MSB) of `x`.
/// Returns 0 if `x` is zero.
function log2(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
// forgefmt: disable-next-item
r := or(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
0x0706060506020504060203020504030106050205030304010505030400000000))
}
}
/// @dev Returns the log2 of `x`, rounded up.
/// Returns 0 if `x` is zero.
function log2Up(uint256 x) internal pure returns (uint256 r) {
r = log2(x);
/// @solidity memory-safe-assembly
assembly {
r := add(r, lt(shl(r, 1), x))
}
}
/// @dev Returns the log10 of `x`.
/// Returns 0 if `x` is zero.
function log10(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
if iszero(lt(x, 100000000000000000000000000000000000000)) {
x := div(x, 100000000000000000000000000000000000000)
r := 38
}
if iszero(lt(x, 100000000000000000000)) {
x := div(x, 100000000000000000000)
r := add(r, 20)
}
if iszero(lt(x, 10000000000)) {
x := div(x, 10000000000)
r := add(r, 10)
}
if iszero(lt(x, 100000)) {
x := div(x, 100000)
r := add(r, 5)
}
r := add(r, add(gt(x, 9), add(gt(x, 99), add(gt(x, 999), gt(x, 9999)))))
}
}
/// @dev Returns the log10 of `x`, rounded up.
/// Returns 0 if `x` is zero.
function log10Up(uint256 x) internal pure returns (uint256 r) {
r = log10(x);
/// @solidity memory-safe-assembly
assembly {
r := add(r, lt(exp(10, r), x))
}
}
/// @dev Returns the log256 of `x`.
/// Returns 0 if `x` is zero.
function log256(uint256 x) internal pure returns (uint256 r) {
/// @solidity memory-safe-assembly
assembly {
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(shr(3, r), lt(0xff, shr(r, x)))
}
}
/// @dev Returns the log256 of `x`, rounded up.
/// Returns 0 if `x` is zero.
function log256Up(uint256 x) internal pure returns (uint256 r) {
r = log256(x);
/// @solidity memory-safe-assembly
assembly {
r := add(r, lt(shl(shl(3, r), 1), x))
}
}
/// @dev Returns the scientific notation format `mantissa * 10 ** exponent` of `x`.
/// Useful for compressing prices (e.g. using 25 bit mantissa and 7 bit exponent).
function sci(uint256 x) internal pure returns (uint256 mantissa, uint256 exponent) {
/// @solidity memory-safe-assembly
assembly {
mantissa := x
if mantissa {
if iszero(mod(mantissa, 1000000000000000000000000000000000)) {
mantissa := div(mantissa, 1000000000000000000000000000000000)
exponent := 33
}
if iszero(mod(mantissa, 10000000000000000000)) {
mantissa := div(mantissa, 10000000000000000000)
exponent := add(exponent, 19)
}
if iszero(mod(mantissa, 1000000000000)) {
mantissa := div(mantissa, 1000000000000)
exponent := add(exponent, 12)
}
if iszero(mod(mantissa, 1000000)) {
mantissa := div(mantissa, 1000000)
exponent := add(exponent, 6)
}
if iszero(mod(mantissa, 10000)) {
mantissa := div(mantissa, 10000)
exponent := add(exponent, 4)
}
if iszero(mod(mantissa, 100)) {
mantissa := div(mantissa, 100)
exponent := add(exponent, 2)
}
if iszero(mod(mantissa, 10)) {
mantissa := div(mantissa, 10)
exponent := add(exponent, 1)
}
}
}
}
/// @dev Convenience function for packing `x` into a smaller number using `sci`.
/// The `mantissa` will be in bits [7..255] (the upper 249 bits).
/// The `exponent` will be in bits [0..6] (the lower 7 bits).
/// Use `SafeCastLib` to safely ensure that the `packed` number is small
/// enough to fit in the desired unsigned integer type:
/// ```
/// uint32 packed = SafeCastLib.toUint32(FixedPointMathLib.packSci(777 ether));
/// ```
function packSci(uint256 x) internal pure returns (uint256 packed) {
(x, packed) = sci(x); // Reuse for `mantissa` and `exponent`.
/// @solidity memory-safe-assembly
assembly {
if shr(249, x) {
mstore(0x00, 0xce30380c) // `MantissaOverflow()`.
revert(0x1c, 0x04)
}
packed := or(shl(7, x), packed)
}
}
/// @dev Convenience function for unpacking a packed number from `packSci`.
function unpackSci(uint256 packed) internal pure returns (uint256 unpacked) {
unchecked {
unpacked = (packed >> 7) * 10 ** (packed & 0x7f);
}
}
/// @dev Returns the average of `x` and `y`. Rounds towards zero.
function avg(uint256 x, uint256 y) internal pure returns (uint256 z) {
unchecked {
z = (x & y) + ((x ^ y) >> 1);
}
}
/// @dev Returns the average of `x` and `y`. Rounds towards negative infinity.
function avg(int256 x, int256 y) internal pure returns (int256 z) {
unchecked {
z = (x >> 1) + (y >> 1) + (x & y & 1);
}
}
/// @dev Returns the absolute value of `x`.
function abs(int256 x) internal pure returns (uint256 z) {
unchecked {
z = (uint256(x) + uint256(x >> 255)) ^ uint256(x >> 255);
}
}
/// @dev Returns the absolute distance between `x` and `y`.
function dist(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := add(xor(sub(0, gt(x, y)), sub(y, x)), gt(x, y))
}
}
/// @dev Returns the absolute distance between `x` and `y`.
function dist(int256 x, int256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := add(xor(sub(0, sgt(x, y)), sub(y, x)), sgt(x, y))
}
}
/// @dev Returns the minimum of `x` and `y`.
function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), lt(y, x)))
}
}
/// @dev Returns the minimum of `x` and `y`.
function min(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), slt(y, x)))
}
}
/// @dev Returns the maximum of `x` and `y`.
function max(uint256 x, uint256 y) internal pure returns (uint256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), gt(y, x)))
}
}
/// @dev Returns the maximum of `x` and `y`.
function max(int256 x, int256 y) internal pure returns (int256 z) {
/// @solidity memory-safe-assembly
assembly {
z := xor(x, mul(xor(x, y), sgt(y, x)))
}
}
/// @dev Returns `x`, bounded to `minValue` and `maxValue`.
function clamp(uint256 x, uint256 minValue, uint256 maxValue)
inte...
// [truncated — 55692 bytes total]
SafeTransferLib.sol 680 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @author Permit2 operations from (https://github.com/Uniswap/permit2/blob/main/src/libraries/Permit2Lib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
library SafeTransferLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ETH transfer has failed.
error ETHTransferFailed();
/// @dev The ERC20 `transferFrom` has failed.
error TransferFromFailed();
/// @dev The ERC20 `transfer` has failed.
error TransferFailed();
/// @dev The ERC20 `approve` has failed.
error ApproveFailed();
/// @dev The ERC20 `totalSupply` query has failed.
error TotalSupplyQueryFailed();
/// @dev The Permit2 operation has failed.
error Permit2Failed();
/// @dev The Permit2 amount must be less than `2**160 - 1`.
error Permit2AmountOverflow();
/// @dev The Permit2 approve operation has failed.
error Permit2ApproveFailed();
/// @dev The Permit2 lockdown operation has failed.
error Permit2LockdownFailed();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;
/// @dev Suggested gas stipend for contract receiving ETH to perform a few
/// storage reads and writes, but low enough to prevent griefing.
uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;
/// @dev The unique EIP-712 domain separator for the DAI token contract.
bytes32 internal constant DAI_DOMAIN_SEPARATOR =
0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7;
/// @dev The address for the WETH9 contract on Ethereum mainnet.
address internal constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
/// @dev The canonical Permit2 address.
/// [Github](https://github.com/Uniswap/permit2)
/// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
address internal constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
/// @dev The canonical address of the `SELFDESTRUCT` ETH mover.
/// See: https://gist.github.com/Vectorized/1cb8ad4cf393b1378e08f23f79bd99fa
/// [Etherscan](https://etherscan.io/address/0x00000000000073c48c8055bD43D1A53799176f0D)
address internal constant ETH_MOVER = 0x00000000000073c48c8055bD43D1A53799176f0D;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ETH OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
//
// The regular variants:
// - Forwards all remaining gas to the target.
// - Reverts if the target reverts.
// - Reverts if the current contract has insufficient balance.
//
// The force variants:
// - Forwards with an optional gas stipend
// (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
// - If the target reverts, or if the gas stipend is exhausted,
// creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
// Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
// - Reverts if the current contract has insufficient balance.
//
// The try variants:
// - Forwards with a mandatory gas stipend.
// - Instead of reverting, returns whether the transfer succeeded.
/// @dev Sends `amount` (in wei) ETH to `to`.
function safeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Sends all the ETH in the current contract to `to`.
function safeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// Transfer all the ETH and check if it succeeded or not.
if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// forgefmt: disable-next-item
if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
}
}
/// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
function trySafeTransferAllETH(address to, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
}
}
/// @dev Force transfers ETH to `to`, without triggering the fallback (if any).
/// This method attempts to use a separate contract to send via `SELFDESTRUCT`,
/// and upon failure, deploys a minimal vault to accrue the ETH.
function safeMoveETH(address to, uint256 amount) internal returns (address vault) {
/// @solidity memory-safe-assembly
assembly {
to := shr(96, shl(96, to)) // Clean upper 96 bits.
for { let mover := ETH_MOVER } iszero(eq(to, address())) {} {
let selfBalanceBefore := selfbalance()
if or(lt(selfBalanceBefore, amount), eq(to, mover)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if extcodesize(mover) {
let balanceBefore := balance(to) // Check via delta, in case `SELFDESTRUCT` is bricked.
pop(call(gas(), mover, amount, codesize(), 0x00, codesize(), 0x00))
if iszero(lt(add(amount, balance(to)), balanceBefore)) { break }
if lt(selfBalanceBefore, selfbalance()) { invalid() } // Just in case.
}
let m := mload(0x40)
// If the mover is missing or bricked, deploy a minimal vault
// that withdraws all ETH to `to` when being called only by `to`.
// forgefmt: disable-next-item
mstore(add(m, 0x20), 0x33146025575b600160005260206000f35b3d3d3d3d47335af1601a5760003dfd)
mstore(m, or(to, shl(160, 0x6035600b3d3960353df3fe73)))
// Compute and store the bytecode hash.
mstore8(0x00, 0xff) // Write the prefix.
mstore(0x35, keccak256(m, 0x40))
mstore(0x01, shl(96, address())) // Deployer.
mstore(0x15, 0) // Salt.
vault := keccak256(0x00, 0x55)
pop(call(gas(), vault, amount, codesize(), 0x00, codesize(), 0x00))
// The vault returns a single word on success. Failure reverts with empty data.
if iszero(returndatasize()) {
if iszero(create2(0, m, 0x40, 0)) { revert(codesize(), codesize()) } // For gas estimation.
}
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for
/// the current contract to manage.
function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
///
/// The `from` account must have at least `amount` approved for the current contract to manage.
function trySafeTransferFrom(address token, address from, address to, uint256 amount)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
success := lt(or(iszero(extcodesize(token)), returndatasize()), success)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends all of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have their entire balance approved for the current contract to manage.
function safeTransferAllFrom(address token, address from, address to)
internal
returns (uint256 amount)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
// Perform the transfer, reverting upon failure.
let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransfer(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sends all of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransferAll(address token, address to) internal returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
mstore(0x20, address()) // Store the address of the current contract.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x14, to) // Store the `to` argument.
amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// Reverts upon failure.
function safeApprove(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
/// then retries the approval again (some tokens, e.g. USDT, requires this).
/// Reverts upon failure.
function safeApproveWithRetry(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, retrying upon failure.
let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x34, 0) // Store 0 for the `amount`.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
mstore(0x34, amount) // Store back the original `amount`.
// Retry the approval, reverting upon failure.
success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
// Check the `extcodesize` again just in case the token selfdestructs lol.
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
}
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Returns the amount of ERC20 `token` owned by `account`.
/// Returns zero if the `token` does not exist.
function balanceOf(address token, address account) internal view returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, account) // Store the `account` argument.
mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
amount :=
mul( // The arguments of `mul` are evaluated from right to left.
mload(0x20),
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
)
)
}
}
/// @dev Performs a `token.balanceOf(account)` check.
/// `implemented` denotes whether the `token` does not implement `balanceOf`.
/// `amount` is zero if the `token` does not implement `balanceOf`.
function checkBalanceOf(address token, address account)
internal
view
returns (bool implemented, uint256 amount)
{
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, account) // Store the `account` argument.
mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
implemented :=
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
)
amount := mul(mload(0x20), implemented)
}
}
/// @dev Returns the total supply of the `token`.
/// Reverts if the token does not exist or does not implement `totalSupply()`.
function totalSupply(address token) internal view returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x18160ddd) // `totalSupply()`.
if iszero(
and(gt(returndatasize(), 0x1f), staticcall(gas(), token, 0x1c, 0x04, 0x00, 0x20))
) {
mstore(0x00, 0x54cd9435) // `TotalSupplyQueryFailed()`.
revert(0x1c, 0x04)
}
result := mload(0x00)
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// If the initial attempt fails, try to use Permit2 to transfer the token.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for the current contract to manage.
function safeTransferFrom2(address token, address from, address to, uint256 amount) internal {
if (!trySafeTransferFrom(token, from, to, amount)) {
permit2TransferFrom(token, from, to, amount);
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to` via Permit2.
/// Reverts upon failure.
function permit2TransferFrom(address token, address from, address to, uint256 amount)
internal
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(add(m, 0x74), shr(96, shl(96, token)))
mstore(add(m, 0x54), amount)
mstore(add(m, 0x34), to)
mstore(add(m, 0x20), shl(96, from))
// `transferFrom(address,address,uint160,address)`.
mstore(m, 0x36c78516000000000000000000000000)
let p := PERMIT2
let exists := eq(chainid(), 1)
if iszero(exists) { exists := iszero(iszero(extcodesize(p))) }
if iszero(
and(
call(gas(), p, 0, add(m, 0x10), 0x84, codesize(), 0x00),
lt(iszero(extcodesize(token)), exists) // Token has code and Permit2 exists.
)
) {
mstore(0x00, 0x7939f4248757f0fd) // `TransferFromFailed()` or `Permit2AmountOverflow()`.
revert(add(0x18, shl(2, iszero(iszero(shr(160, amount))))), 0x04)
}
}
}
/// @dev Permit a user to spend a given amount of
/// another user's tokens via native EIP-2612 permit if possible, falling
/// back to Permit2 if native permit fails or is not implemented on the token.
function permit2(
address token,
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
for {} shl(96, xor(token, WETH9)) {} {
mstore(0x00, 0x3644e515) // `DOMAIN_SEPARATOR()`.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
lt(iszero(mload(0x00)), eq(returndatasize(), 0x20)), // Returns 1 non-zero word.
// Gas stipend to limit gas burn for tokens that don't refund gas when
// an non-existing function is called. 5K should be enough for a SLOAD.
staticcall(5000, token, 0x1c, 0x04, 0x00, 0x20)
)
) { break }
// After here, we can be sure that token is a contract.
let m := mload(0x40)
mstore(add(m, 0x34), spender)
mstore(add(m, 0x20), shl(96, owner))
mstore(add(m, 0x74), deadline)
if eq(mload(0x00), DAI_DOMAIN_SEPARATOR) {
mstore(0x14, owner)
mstore(0x00, 0x7ecebe00000000000000000000000000) // `nonces(address)`.
mstore(
add(m, 0x94),
lt(iszero(amount), staticcall(gas(), token, 0x10, 0x24, add(m, 0x54), 0x20))
)
mstore(m, 0x8fcbaf0c000000000000000000000000) // `IDAIPermit.permit`.
// `nonces` is already at `add(m, 0x54)`.
// `amount != 0` is already stored at `add(m, 0x94)`.
mstore(add(m, 0xb4), and(0xff, v))
mstore(add(m, 0xd4), r)
mstore(add(m, 0xf4), s)
success := call(gas(), token, 0, add(m, 0x10), 0x104, codesize(), 0x00)
break
}
mstore(m, 0xd505accf000000000000000000000000) // `IERC20Permit.permit`.
mstore(add(m, 0x54), amount)
mstore(add(m, 0x94), and(0xff, v))
mstore(add(m, 0xb4), r)
mstore(add(m, 0xd4), s)
success := call(gas(), token, 0, add(m, 0x10), 0xe4, codesize(), 0x00)
break
}
}
if (!success) simplePermit2(token, owner, spender, amount, deadline, v, r, s);
}
/// @dev Simple permit on the Permit2 contract.
function simplePermit2(
address token,
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, 0x927da105) // `allowance(address,address,address)`.
{
let addressMask := shr(96, not(0))
mstore(add(m, 0x20), and(addressMask, owner))
mstore(add(m, 0x40), and(addressMask, token))
mstore(add(m, 0x60), and(addressMask, spender))
mstore(add(m, 0xc0), and(addressMask, spender))
}
let p := mul(PERMIT2, iszero(shr(160, amount)))
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x5f), // Returns 3 words: `amount`, `expiration`, `nonce`.
staticcall(gas(), p, add(m, 0x1c), 0x64, add(m, 0x60), 0x60)
)
) {
mstore(0x00, 0x6b836e6b8757f0fd) // `Permit2Failed()` or `Permit2AmountOverflow()`.
revert(add(0x18, shl(2, iszero(p))), 0x04)
}
mstore(m, 0x2b67b570) // `Permit2.permit` (PermitSingle variant).
// `owner` is already `add(m, 0x20)`.
// `token` is already at `add(m, 0x40)`.
mstore(add(m, 0x60), amount)
mstore(add(m, 0x80), 0xffffffffffff) // `expiration = type(uint48).max`.
// `nonce` is already at `add(m, 0xa0)`.
// `spender` is already at `add(m, 0xc0)`.
mstore(add(m, 0xe0), deadline)
mstore(add(m, 0x100), 0x100) // `signature` offset.
mstore(add(m, 0x120), 0x41) // `signature` length.
mstore(add(m, 0x140), r)
mstore(add(m, 0x160), s)
mstore(add(m, 0x180), shl(248, v))
if iszero( // Revert if token does not have code, or if the call fails.
mul(extcodesize(token), call(gas(), p, 0, add(m, 0x1c), 0x184, codesize(), 0x00))) {
mstore(0x00, 0x6b836e6b) // `Permit2Failed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Approves `spender` to spend `amount` of `token` for `address(this)`.
function permit2Approve(address token, address spender, uint160 amount, uint48 expiration)
internal
{
/// @solidity memory-safe-assembly
assembly {
let addressMask := shr(96, not(0))
let m := mload(0x40)
mstore(m, 0x87517c45) // `approve(address,address,uint160,uint48)`.
mstore(add(m, 0x20), and(addressMask, token))
mstore(add(m, 0x40), and(addressMask, spender))
mstore(add(m, 0x60), and(addressMask, amount))
mstore(add(m, 0x80), and(0xffffffffffff, expiration))
if iszero(call(gas(), PERMIT2, 0, add(m, 0x1c), 0xa0, codesize(), 0x00)) {
mstore(0x00, 0x324f14ae) // `Permit2ApproveFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Revokes an approval for `token` and `spender` for `address(this)`.
function permit2Lockdown(address token, address spender) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, 0xcc53287f) // `Permit2.lockdown`.
mstore(add(m, 0x20), 0x20) // Offset of the `approvals`.
mstore(add(m, 0x40), 1) // `approvals.length`.
mstore(add(m, 0x60), shr(96, shl(96, token)))
mstore(add(m, 0x80), shr(96, shl(96, spender)))
if iszero(call(gas(), PERMIT2, 0, add(m, 0x1c), 0xa0, codesize(), 0x00)) {
mstore(0x00, 0x96b3de23) // `Permit2LockdownFailed()`.
revert(0x1c, 0x04)
}
}
}
}
IDistributionStrategy.sol 22 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IDistributionContract} from './IDistributionContract.sol';
/// @title IDistributionStrategy
/// @notice Interface for token distribution strategies.
interface IDistributionStrategy {
/// @notice Initialize a distribution of tokens under this strategy.
/// @dev Contracts can choose to deploy an instance with a factory-model or handle all distributions within the
/// implementing contract. For some strategies this function will handle the entire distribution, for others it
/// could merely set up initial state and provide additional entrypoints to handle the distribution logic.
/// @param token The address of the token to be distributed.
/// @param amount The amount of tokens intended for distribution.
/// @param configData Arbitrary, strategy-specific parameters.
/// @param salt The salt to use for the deterministic deployment.
/// @return distributionContract The contract that will handle or manage the distribution.
/// (Could be `address(this)` if the strategy is handled in-place, or a newly deployed instance).
function initializeDistribution(address token, uint256 amount, bytes calldata configData, bytes32 salt)
external
returns (IDistributionContract distributionContract);
}
IERC20Minimal.sol 48 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title Minimal ERC20 interface for Uniswap
/// @notice Contains a subset of the full ERC20 interface that is used in Uniswap V3
interface IERC20Minimal {
/// @notice Returns an account's balance in the token
/// @param account The account for which to look up the number of tokens it has, i.e. its balance
/// @return The number of tokens held by the account
function balanceOf(address account) external view returns (uint256);
/// @notice Transfers the amount of token from the `msg.sender` to the recipient
/// @param recipient The account that will receive the amount transferred
/// @param amount The number of tokens to send from the sender to the recipient
/// @return Returns true for a successful transfer, false for an unsuccessful transfer
function transfer(address recipient, uint256 amount) external returns (bool);
/// @notice Returns the current allowance given to a spender by an owner
/// @param owner The account of the token owner
/// @param spender The account of the token spender
/// @return The current allowance granted by `owner` to `spender`
function allowance(address owner, address spender) external view returns (uint256);
/// @notice Sets the allowance of a spender from the `msg.sender` to the value `amount`
/// @param spender The account which will be allowed to spend a given amount of the owners tokens
/// @param amount The amount of tokens allowed to be used by `spender`
/// @return Returns true for a successful approval, false for unsuccessful
function approve(address spender, uint256 amount) external returns (bool);
/// @notice Transfers `amount` tokens from `sender` to `recipient` up to the allowance given to the `msg.sender`
/// @param sender The account from which the transfer will be initiated
/// @param recipient The recipient of the transfer
/// @param amount The amount of the transfer
/// @return Returns true for a successful transfer, false for unsuccessful
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
/// @notice Event emitted when tokens are transferred from one address to another, either via `#transfer` or `#transferFrom`.
/// @param from The account from which the tokens were sent, i.e. the balance decreased
/// @param to The account to which the tokens were sent, i.e. the balance increased
/// @param value The amount of tokens that were transferred
event Transfer(address indexed from, address indexed to, uint256 value);
/// @notice Event emitted when the approval amount for the spender of a given owner's tokens changes.
/// @param owner The account that approved spending of its tokens
/// @param spender The account for which the spending allowance was modified
/// @param value The new allowance from the owner to the spender
event Approval(address indexed owner, address indexed spender, uint256 value);
}
BitMath.sol 49 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title BitMath
/// @dev This library provides functionality for computing bit properties of an unsigned integer
/// @author Solady (https://github.com/Vectorized/solady/blob/8200a70e8dc2a77ecb074fc2e99a2a0d36547522/src/utils/LibBit.sol)
library BitMath {
/// @notice Returns the index of the most significant bit of the number,
/// where the least significant bit is at index 0 and the most significant bit is at index 255
/// @param x the value for which to compute the most significant bit, must be greater than 0
/// @return r the index of the most significant bit
function mostSignificantBit(uint256 x) internal pure returns (uint8 r) {
require(x > 0);
assembly ("memory-safe") {
r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x))
r := or(r, shl(6, lt(0xffffffffffffffff, shr(r, x))))
r := or(r, shl(5, lt(0xffffffff, shr(r, x))))
r := or(r, shl(4, lt(0xffff, shr(r, x))))
r := or(r, shl(3, lt(0xff, shr(r, x))))
// forgefmt: disable-next-item
r := or(r, byte(and(0x1f, shr(shr(r, x), 0x8421084210842108cc6318c6db6d54be)),
0x0706060506020500060203020504000106050205030304010505030400000000))
}
}
/// @notice Returns the index of the least significant bit of the number,
/// where the least significant bit is at index 0 and the most significant bit is at index 255
/// @param x the value for which to compute the least significant bit, must be greater than 0
/// @return r the index of the least significant bit
function leastSignificantBit(uint256 x) internal pure returns (uint8 r) {
require(x > 0);
assembly ("memory-safe") {
// Isolate the least significant bit.
x := and(x, sub(0, x))
// For the upper 3 bits of the result, use a De Bruijn-like lookup.
// Credit to adhusson: https://blog.adhusson.com/cheap-find-first-set-evm/
// forgefmt: disable-next-item
r := shl(5, shr(252, shl(shl(2, shr(250, mul(x,
0xb6db6db6ddddddddd34d34d349249249210842108c6318c639ce739cffffffff))),
0x8040405543005266443200005020610674053026020000107506200176117077)))
// For the lower 5 bits of the result, use a De Bruijn lookup.
// forgefmt: disable-next-item
r := or(r, byte(and(div(0xd76453e0, shr(r, x)), 0x1f),
0x001f0d1e100c1d070f090b19131c1706010e11080a1a141802121b1503160405))
}
}
}
BaseHook.sol 216 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {BeforeSwapDelta} from "@uniswap/v4-core/src/types/BeforeSwapDelta.sol";
import {ImmutableState} from "../base/ImmutableState.sol";
import {ModifyLiquidityParams, SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
/// @title Base Hook
/// @notice abstract contract for hook implementations
abstract contract BaseHook is IHooks, ImmutableState {
error HookNotImplemented();
constructor(IPoolManager _manager) ImmutableState(_manager) {
validateHookAddress(this);
}
/// @notice Returns a struct of permissions to signal which hook functions are to be implemented
/// @dev Used at deployment to validate the address correctly represents the expected permissions
/// @return Permissions struct
function getHookPermissions() public pure virtual returns (Hooks.Permissions memory);
/// @notice Validates the deployed hook address agrees with the expected permissions of the hook
/// @dev this function is virtual so that we can override it during testing,
/// which allows us to deploy an implementation to any address
/// and then etch the bytecode into the correct address
function validateHookAddress(BaseHook _this) internal pure virtual {
Hooks.validateHookPermissions(_this, getHookPermissions());
}
/// @inheritdoc IHooks
function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96)
external
onlyPoolManager
returns (bytes4)
{
return _beforeInitialize(sender, key, sqrtPriceX96);
}
function _beforeInitialize(address, PoolKey calldata, uint160) internal virtual returns (bytes4) {
revert HookNotImplemented();
}
/// @inheritdoc IHooks
function afterInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96, int24 tick)
external
onlyPoolManager
returns (bytes4)
{
return _afterInitialize(sender, key, sqrtPriceX96, tick);
}
function _afterInitialize(address, PoolKey calldata, uint160, int24) internal virtual returns (bytes4) {
revert HookNotImplemented();
}
/// @inheritdoc IHooks
function beforeAddLiquidity(
address sender,
PoolKey calldata key,
ModifyLiquidityParams calldata params,
bytes calldata hookData
) external onlyPoolManager returns (bytes4) {
return _beforeAddLiquidity(sender, key, params, hookData);
}
function _beforeAddLiquidity(address, PoolKey calldata, ModifyLiquidityParams calldata, bytes calldata)
internal
virtual
returns (bytes4)
{
revert HookNotImplemented();
}
/// @inheritdoc IHooks
function beforeRemoveLiquidity(
address sender,
PoolKey calldata key,
ModifyLiquidityParams calldata params,
bytes calldata hookData
) external onlyPoolManager returns (bytes4) {
return _beforeRemoveLiquidity(sender, key, params, hookData);
}
function _beforeRemoveLiquidity(address, PoolKey calldata, ModifyLiquidityParams calldata, bytes calldata)
internal
virtual
returns (bytes4)
{
revert HookNotImplemented();
}
/// @inheritdoc IHooks
function afterAddLiquidity(
address sender,
PoolKey calldata key,
ModifyLiquidityParams calldata params,
BalanceDelta delta,
BalanceDelta feesAccrued,
bytes calldata hookData
) external onlyPoolManager returns (bytes4, BalanceDelta) {
return _afterAddLiquidity(sender, key, params, delta, feesAccrued, hookData);
}
function _afterAddLiquidity(
address,
PoolKey calldata,
ModifyLiquidityParams calldata,
BalanceDelta,
BalanceDelta,
bytes calldata
) internal virtual returns (bytes4, BalanceDelta) {
revert HookNotImplemented();
}
/// @inheritdoc IHooks
function afterRemoveLiquidity(
address sender,
PoolKey calldata key,
ModifyLiquidityParams calldata params,
BalanceDelta delta,
BalanceDelta feesAccrued,
bytes calldata hookData
) external onlyPoolManager returns (bytes4, BalanceDelta) {
return _afterRemoveLiquidity(sender, key, params, delta, feesAccrued, hookData);
}
function _afterRemoveLiquidity(
address,
PoolKey calldata,
ModifyLiquidityParams calldata,
BalanceDelta,
BalanceDelta,
bytes calldata
) internal virtual returns (bytes4, BalanceDelta) {
revert HookNotImplemented();
}
/// @inheritdoc IHooks
function beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata hookData)
external
onlyPoolManager
returns (bytes4, BeforeSwapDelta, uint24)
{
return _beforeSwap(sender, key, params, hookData);
}
function _beforeSwap(address, PoolKey calldata, SwapParams calldata, bytes calldata)
internal
virtual
returns (bytes4, BeforeSwapDelta, uint24)
{
revert HookNotImplemented();
}
/// @inheritdoc IHooks
function afterSwap(
address sender,
PoolKey calldata key,
SwapParams calldata params,
BalanceDelta delta,
bytes calldata hookData
) external onlyPoolManager returns (bytes4, int128) {
return _afterSwap(sender, key, params, delta, hookData);
}
function _afterSwap(address, PoolKey calldata, SwapParams calldata, BalanceDelta, bytes calldata)
internal
virtual
returns (bytes4, int128)
{
revert HookNotImplemented();
}
/// @inheritdoc IHooks
function beforeDonate(
address sender,
PoolKey calldata key,
uint256 amount0,
uint256 amount1,
bytes calldata hookData
) external onlyPoolManager returns (bytes4) {
return _beforeDonate(sender, key, amount0, amount1, hookData);
}
function _beforeDonate(address, PoolKey calldata, uint256, uint256, bytes calldata)
internal
virtual
returns (bytes4)
{
revert HookNotImplemented();
}
/// @inheritdoc IHooks
function afterDonate(
address sender,
PoolKey calldata key,
uint256 amount0,
uint256 amount1,
bytes calldata hookData
) external onlyPoolManager returns (bytes4) {
return _afterDonate(sender, key, amount0, amount1, hookData);
}
function _afterDonate(address, PoolKey calldata, uint256, uint256, bytes calldata)
internal
virtual
returns (bytes4)
{
revert HookNotImplemented();
}
}
Math.sol 749 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
import {Panic} from "../Panic.sol";
import {SafeCast} from "./SafeCast.sol";
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Return the 512-bit addition of two uint256.
*
* The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low.
*/
function add512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
assembly ("memory-safe") {
low := add(a, b)
high := lt(low, a)
}
}
/**
* @dev Return the 512-bit multiplication of two uint256.
*
* The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low.
*/
function mul512(uint256 a, uint256 b) internal pure returns (uint256 high, uint256 low) {
// 512-bit multiply [high low] = x * y. Compute the product mod 2²⁵⁶ and mod 2²⁵⁶ - 1, then use
// the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = high * 2²⁵⁶ + low.
assembly ("memory-safe") {
let mm := mulmod(a, b, not(0))
low := mul(a, b)
high := sub(sub(mm, low), lt(mm, low))
}
}
/**
* @dev Returns the addition of two unsigned integers, with a success flag (no overflow).
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
uint256 c = a + b;
success = c >= a;
result = c * SafeCast.toUint(success);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with a success flag (no overflow).
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
uint256 c = a - b;
success = c <= a;
result = c * SafeCast.toUint(success);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with a success flag (no overflow).
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
uint256 c = a * b;
assembly ("memory-safe") {
// Only true when the multiplication doesn't overflow
// (c / a == b) || (a == 0)
success := or(eq(div(c, a), b), iszero(a))
}
// equivalent to: success ? c : 0
result = c * SafeCast.toUint(success);
}
}
/**
* @dev Returns the division of two unsigned integers, with a success flag (no division by zero).
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
success = b > 0;
assembly ("memory-safe") {
// The `DIV` opcode returns zero when the denominator is 0.
result := div(a, b)
}
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero).
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool success, uint256 result) {
unchecked {
success = b > 0;
assembly ("memory-safe") {
// The `MOD` opcode returns zero when the denominator is 0.
result := mod(a, b)
}
}
}
/**
* @dev Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing.
*/
function saturatingAdd(uint256 a, uint256 b) internal pure returns (uint256) {
(bool success, uint256 result) = tryAdd(a, b);
return ternary(success, result, type(uint256).max);
}
/**
* @dev Unsigned saturating subtraction, bounds to zero instead of overflowing.
*/
function saturatingSub(uint256 a, uint256 b) internal pure returns (uint256) {
(, uint256 result) = trySub(a, b);
return result;
}
/**
* @dev Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing.
*/
function saturatingMul(uint256 a, uint256 b) internal pure returns (uint256) {
(bool success, uint256 result) = tryMul(a, b);
return ternary(success, result, type(uint256).max);
}
/**
* @dev Branchless ternary evaluation for `a ? b : c`. Gas costs are constant.
*
* IMPORTANT: This function may reduce bytecode size and consume less gas when used standalone.
* However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute
* one branch when needed, making this function more expensive.
*/
function ternary(bool condition, uint256 a, uint256 b) internal pure returns (uint256) {
unchecked {
// branchless ternary works because:
// b ^ (a ^ b) == a
// b ^ 0 == b
return b ^ ((a ^ b) * SafeCast.toUint(condition));
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return ternary(a > b, a, b);
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return ternary(a < b, a, b);
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
Panic.panic(Panic.DIVISION_BY_ZERO);
}
// The following calculation ensures accurate ceiling division without overflow.
// Since a is non-zero, (a - 1) / b will not overflow.
// The largest possible result occurs when (a - 1) / b is type(uint256).max,
// but the largest value we can obtain is type(uint256).max - 1, which happens
// when a = type(uint256).max and b = 1.
unchecked {
return SafeCast.toUint(a > 0) * ((a - 1) / b + 1);
}
}
/**
* @dev Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
*
* Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
(uint256 high, uint256 low) = mul512(x, y);
// Handle non-overflow cases, 256 by 256 division.
if (high == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return low / denominator;
}
// Make sure the result is less than 2²⁵⁶. Also prevents denominator == 0.
if (denominator <= high) {
Panic.panic(ternary(denominator == 0, Panic.DIVISION_BY_ZERO, Panic.UNDER_OVERFLOW));
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [high low].
uint256 remainder;
assembly ("memory-safe") {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
high := sub(high, gt(remainder, low))
low := sub(low, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly ("memory-safe") {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [high low] by twos.
low := div(low, twos)
// Flip twos such that it is 2²⁵⁶ / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from high into low.
low |= high * twos;
// Invert denominator mod 2²⁵⁶. Now that denominator is an odd number, it has an inverse modulo 2²⁵⁶ such
// that denominator * inv ≡ 1 mod 2²⁵⁶. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv ≡ 1 mod 2⁴.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
// works in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2⁸
inverse *= 2 - denominator * inverse; // inverse mod 2¹⁶
inverse *= 2 - denominator * inverse; // inverse mod 2³²
inverse *= 2 - denominator * inverse; // inverse mod 2⁶⁴
inverse *= 2 - denominator * inverse; // inverse mod 2¹²⁸
inverse *= 2 - denominator * inverse; // inverse mod 2²⁵⁶
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2²⁵⁶. Since the preconditions guarantee that the outcome is
// less than 2²⁵⁶, this is the final result. We don't need to compute the high bits of the result and high
// is no longer required.
result = low * inverse;
return result;
}
}
/**
* @dev Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
return mulDiv(x, y, denominator) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0);
}
/**
* @dev Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256.
*/
function mulShr(uint256 x, uint256 y, uint8 n) internal pure returns (uint256 result) {
unchecked {
(uint256 high, uint256 low) = mul512(x, y);
if (high >= 1 << n) {
Panic.panic(Panic.UNDER_OVERFLOW);
}
return (high << (256 - n)) | (low >> n);
}
}
/**
* @dev Calculates x * y >> n with full precision, following the selected rounding direction.
*/
function mulShr(uint256 x, uint256 y, uint8 n, Rounding rounding) internal pure returns (uint256) {
return mulShr(x, y, n) + SafeCast.toUint(unsignedRoundsUp(rounding) && mulmod(x, y, 1 << n) > 0);
}
/**
* @dev Calculate the modular multiplicative inverse of a number in Z/nZ.
*
* If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0.
* If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible.
*
* If the input value is not inversible, 0 is returned.
*
* NOTE: If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the
* inverse using `Math.modExp(a, n - 2, n)`. See {invModPrime}.
*/
function invMod(uint256 a, uint256 n) internal pure returns (uint256) {
unchecked {
if (n == 0) return 0;
// The inverse modulo is calculated using the Extended Euclidean Algorithm (iterative version)
// Used to compute integers x and y such that: ax + ny = gcd(a, n).
// When the gcd is 1, then the inverse of a modulo n exists and it's x.
// ax + ny = 1
// ax = 1 + (-y)n
// ax ≡ 1 (mod n) # x is the inverse of a modulo n
// If the remainder is 0 the gcd is n right away.
uint256 remainder = a % n;
uint256 gcd = n;
// Therefore the initial coefficients are:
// ax + ny = gcd(a, n) = n
// 0a + 1n = n
int256 x = 0;
int256 y = 1;
while (remainder != 0) {
uint256 quotient = gcd / remainder;
(gcd, remainder) = (
// The old remainder is the next gcd to try.
remainder,
// Compute the next remainder.
// Can't overflow given that (a % gcd) * (gcd // (a % gcd)) <= gcd
// where gcd is at most n (capped to type(uint256).max)
gcd - remainder * quotient
);
(x, y) = (
// Increment the coefficient of a.
y,
// Decrement the coefficient of n.
// Can overflow, but the result is casted to uint256 so that the
// next value of y is "wrapped around" to a value between 0 and n - 1.
x - y * int256(quotient)
);
}
if (gcd != 1) return 0; // No inverse exists.
return ternary(x < 0, n - uint256(-x), uint256(x)); // Wrap the result if it's negative.
}
}
/**
* @dev Variant of {invMod}. More efficient, but only works if `p` is known to be a prime greater than `2`.
*
* From https://en.wikipedia.org/wiki/Fermat%27s_little_theorem[Fermat's little theorem], we know that if p is
* prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that
* `a**(p-2)` is the modular multiplicative inverse of a in Fp.
*
* NOTE: this function does NOT check that `p` is a prime greater than `2`.
*/
function invModPrime(uint256 a, uint256 p) internal view returns (uint256) {
unchecked {
return Math.modExp(a, p - 2, p);
}
}
/**
* @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m)
*
* Requirements:
* - modulus can't be zero
* - underlying staticcall to precompile must succeed
*
* IMPORTANT: The result is only valid if the underlying call succeeds. When using this function, make
* sure the chain you're using it on supports the precompiled contract for modular exponentiation
* at address 0x05 as specified in https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise,
* the underlying function will succeed given the lack of a revert, but the result may be incorrectly
* interpreted as 0.
*/
function modExp(uint256 b, uint256 e, uint256 m) internal view returns (uint256) {
(bool success, uint256 result) = tryModExp(b, e, m);
if (!success) {
Panic.panic(Panic.DIVISION_BY_ZERO);
}
return result;
}
/**
* @dev Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m).
* It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying
* to operate modulo 0 or if the underlying precompile reverted.
*
* IMPORTANT: The result is only valid if the success flag is true. When using this function, make sure the chain
* you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in
* https://eips.ethereum.org/EIPS/eip-198[EIP-198]. Otherwise, the underlying function will succeed given the lack
* of a revert, but the result may be incorrectly interpreted as 0.
*/
function tryModExp(uint256 b, uint256 e, uint256 m) internal view returns (bool success, uint256 result) {
if (m == 0) return (false, 0);
assembly ("memory-safe") {
let ptr := mload(0x40)
// | Offset | Content | Content (Hex) |
// |-----------|------------|--------------------------------------------------------------------|
// | 0x00:0x1f | size of b | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x20:0x3f | size of e | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x40:0x5f | size of m | 0x0000000000000000000000000000000000000000000000000000000000000020 |
// | 0x60:0x7f | value of b | 0x<.............................................................b> |
// | 0x80:0x9f | value of e | 0x<.............................................................e> |
// | 0xa0:0xbf | value of m | 0x<.............................................................m> |
mstore(ptr, 0x20)
mstore(add(ptr, 0x20), 0x20)
mstore(add(ptr, 0x40), 0x20)
mstore(add(ptr, 0x60), b)
mstore(add(ptr, 0x80), e)
mstore(add(ptr, 0xa0), m)
// Given the result < m, it's guaranteed to fit in 32 bytes,
// so we can use the memory scratch space located at offset 0.
success := staticcall(gas(), 0x05, ptr, 0xc0, 0x00, 0x20)
result := mload(0x00)
}
}
/**
* @dev Variant of {modExp} that supports inputs of arbitrary length.
*/
function modExp(bytes memory b, bytes memory e, bytes memory m) internal view returns (bytes memory) {
(bool success, bytes memory result) = tryModExp(b, e, m);
if (!success) {
Panic.panic(Panic.DIVISION_BY_ZERO);
}
return result;
}
/**
* @dev Variant of {tryModExp} that supports inputs of arbitrary length.
*/
function tryModExp(
bytes memory b,
bytes memory e,
bytes memory m
) internal view returns (bool success, bytes memory result) {
if (_zeroBytes(m)) return (false, new bytes(0));
uint256 mLen = m.length;
// Encode call args in result and move the free memory pointer
result = abi.encodePacked(b.length, e.length, mLen, b, e, m);
assembly ("memory-safe") {
let dataPtr := add(result, 0x20)
// Write result on top of args to avoid allocating extra memory.
success := staticcall(gas(), 0x05, dataPtr, mload(result), dataPtr, mLen)
// Overwrite the length.
// result.length > returndatasize() is guaranteed because returndatasize() == m.length
mstore(result, mLen)
// Set the memory pointer after the returned data.
mstore(0x40, add(dataPtr, mLen))
}
}
/**
* @dev Returns whether the provided byte array is zero.
*/
function _zeroBytes(bytes memory byteArray) private pure returns (bool) {
for (uint256 i = 0; i < byteArray.length; ++i) {
if (byteArray[i] != 0) {
return false;
}
}
return true;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* This method is based on Newton's method for computing square roots; the algorithm is restricted to only
* using integer operations.
*/
function sqrt(uint256 a) internal pure returns (uint256) {
unchecked {
// Take care of easy edge cases when a == 0 or a == 1
if (a <= 1) {
return a;
}
// In this function, we use Newton's method to get a root of `f(x) := x² - a`. It involves building a
// sequence x_n that converges toward sqrt(a). For each iteration x_n, we also define the error between
// the current value as `ε_n = | x_n - sqrt(a) |`.
//
// For our first estimation, we consider `e` the smallest power of 2 which is bigger than the square root
// of the target. (i.e. `2**(e-1) ≤ sqrt(a) < 2**e`). We know that `e ≤ 128` because `(2¹²⁸)² = 2²⁵⁶` is
// bigger than any uint256.
//
// By noticing that
// `2**(e-1) ≤ sqrt(a) < 2**e → (2**(e-1))² ≤ a < (2**e)² → 2**(2*e-2) ≤ a < 2**(2*e)`
// we can deduce that `e - 1` is `log2(a) / 2`. We can thus compute `x_n = 2**(e-1)` using a method similar
// to the msb function.
uint256 aa = a;
uint256 xn = 1;
if (aa >= (1 << 128)) {
aa >>= 128;
xn <<= 64;
}
if (aa >= (1 << 64)) {
aa >>= 64;
xn <<= 32;
}
if (aa >= (1 << 32)) {
aa >>= 32;
xn <<= 16;
}
if (aa >= (1 << 16)) {
aa >>= 16;
xn <<= 8;
}
if (aa >= (1 << 8)) {
aa >>= 8;
xn <<= 4;
}
if (aa >= (1 << 4)) {
aa >>= 4;
xn <<= 2;
}
if (aa >= (1 << 2)) {
xn <<= 1;
}
// We now have x_n such that `x_n = 2**(e-1) ≤ sqrt(a) < 2**e = 2 * x_n`. This implies ε_n ≤ 2**(e-1).
//
// We can refine our estimation by noticing that the middle of that interval minimizes the error.
// If we move x_n to equal 2**(e-1) + 2**(e-2), then we reduce the error to ε_n ≤ 2**(e-2).
// This is going to be our x_0 (and ε_0)
xn = (3 * xn) >> 1; // ε_0 := | x_0 - sqrt(a) | ≤ 2**(e-2)
// From here, Newton's method give us:
// x_{n+1} = (x_n + a / x_n) / 2
//
// One should note that:
// x_{n+1}² - a = ((x_n + a / x_n) / 2)² - a
// = ((x_n² + a) / (2 * x_n))² - a
// = (x_n⁴ + 2 * a * x_n² + a²) / (4 * x_n²) - a
// = (x_n⁴ + 2 * a * x_n² + a² - 4 * a * x_n²) / (4 * x_n²)
// = (x_n⁴ - 2 * a * x_n² + a²) / (4 * x_n²)
// = (x_n² - a)² / (2 * x_n)²
// = ((x_n² - a) / (2 * x_n))²
// ≥ 0
// Which proves that for all n ≥ 1, sqrt(a) ≤ x_n
//
// This gives us the proof of quadratic convergence of the sequence:
// ε_{n+1} = | x_{n+1} - sqrt(a) |
// = | (x_n + a / x_n) / 2 - sqrt(a) |
// = | (x_n² + a - 2*x_n*sqrt(a)) / (2 * x_n) |
// = | (x_n - sqrt(a))² / (2 * x_n) |
// = | ε_n² / (2 * x_n) |
// = ε_n² / | (2 * x_n) |
//
// For the first iteration, we have a special case where x_0 is known:
// ε_1 = ε_0² / | (2 * x_0) |
// ≤ (2**(e-2))² / (2 * (2**(e-1) + 2**(e-2)))
// ≤ 2**(2*e-4) / (3 * 2**(e-1))
// ≤ 2**(e-3) / 3
// ≤ 2**(e-3-log2(3))
// ≤ 2**(e-4.5)
//
// For the following iterations, we use the fact that, 2**(e-1) ≤ sqrt(a) ≤ x_n:
// ε_{n+1} = ε_n² / | (2 * x_n) |
// ≤ (2**(e-k))² / (2 * 2**(e-1))
// ≤ 2**(2*e-2*k) / 2**e
// ≤ 2**(e-2*k)
xn = (xn + a / xn) >> 1; // ε_1 := | x_1 - sqrt(a) | ≤ 2**(e-4.5) -- special case, see above
xn = (xn + a / xn) >> 1; // ε_2 := | x_2 - sqrt(a) | ≤ 2**(e-9) -- general case with k = 4.5
xn = (xn + a / xn) >> 1; // ε_3 := | x_3 - sqrt(a) | ≤ 2**(e-18) -- general case with k = 9
xn = (xn + a / xn) >> 1; // ε_4 := | x_4 - sqrt(a) | ≤ 2**(e-36) -- general case with k = 18
xn = (xn + a / xn) >> 1; // ε_5 := | x_5 - sqrt(a) | ≤ 2**(e-72) -- general case with k = 36
xn = (xn + a / xn) >> 1; // ε_6 := | x_6 - sqrt(a) | ≤ 2**(e-144) -- general case with k = 72
// Because e ≤ 128 (as discussed during the first estimation phase), we know have reached a precision
// ε_6 ≤ 2**(e-144) < 1. Given we're operating on integers, then we can ensure that xn is now either
// sqrt(a) or sqrt(a) + 1.
return xn - SafeCast.toUint(xn > a / xn);
}
}
/**
* @dev Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && result * result < a);
}
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 x) internal pure returns (uint256 r) {
// If value has upper 128 bits set, log2 result is at least 128
r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
// If upper 64 bits of 128-bit half set, add 64 to result
r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
// If upper 32 bits of 64-bit half set, add 32 to result
r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
// If upper 16 bits of 32-bit half set, add 16 to result
r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
// If upper 8 bits of 16-bit half set, add 8 to result
r |= SafeCast.toUint((x >> r) > 0xff) << 3;
// If upper 4 bits of 8-bit half set, add 4 to result
r |= SafeCast.toUint((x >> r) > 0xf) << 2;
// Shifts value right by the current result and use it as an index into this lookup table:
//
// | x (4 bits) | index | table[index] = MSB position |
// |------------|---------|-----------------------------|
// | 0000 | 0 | table[0] = 0 |
// | 0001 | 1 | table[1] = 0 |
// | 0010 | 2 | table[2] = 1 |
// | 0011 | 3 | table[3] = 1 |
// | 0100 | 4 | table[4] = 2 |
// | 0101 | 5 | table[5] = 2 |
// | 0110 | 6 | table[6] = 2 |
// | 0111 | 7 | table[7] = 2 |
// | 1000 | 8 | table[8] = 3 |
// | 1001 | 9 | table[9] = 3 |
// | 1010 | 10 | table[10] = 3 |
// | 1011 | 11 | table[11] = 3 |
// | 1100 | 12 | table[12] = 3 |
// | 1101 | 13 | table[13] = 3 |
// | 1110 | 14 | table[14] = 3 |
// | 1111 | 15 | table[15] = 3 |
//
// The lookup table is represented as a 32-byte value with the MSB positions for 0-15 in the last 16 bytes.
assembly ("memory-safe") {
r := or(r, byte(shr(r, x), 0x0000010102020202030303030303030300000000000000000000000000000000))
}
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << result < value);
}
}
/**
* @dev Return the log in base 10 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 10 ** result < value);
}
}
/**
* @dev Return the log in base 256 of a positive value rounded towards zero.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 x) internal pure returns (uint256 r) {
// If value has upper 128 bits set, log2 result is at least 128
r = SafeCast.toUint(x > 0xffffffffffffffffffffffffffffffff) << 7;
// If upper 64 bits of 128-bit half set, add 64 to result
r |= SafeCast.toUint((x >> r) > 0xffffffffffffffff) << 6;
// If upper 32 bits of 64-bit half set, add 32 to result
r |= SafeCast.toUint((x >> r) > 0xffffffff) << 5;
// If upper 16 bits of 32-bit half set, add 16 to result
r |= SafeCast.toUint((x >> r) > 0xffff) << 4;
// Add 1 if upper 8 bits of 16-bit half set, and divide accumulated result by 8
return (r >> 3) | SafeCast.toUint((x >> r) > 0xff);
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + SafeCast.toUint(unsignedRoundsUp(rounding) && 1 << (result << 3) < value);
}
}
/**
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
*/
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
}
ActionsBuilder.sol 30 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
import {ParamsBuilder} from "./ParamsBuilder.sol";
/// @title ActionsBuilder
/// @notice Library for building position actions and parameters
library ActionsBuilder {
error InvalidActionsLength(uint256 invalidLength);
/// @notice Builds full range position actions without final take pair action
function buildFullRangeActions() internal pure returns (bytes memory) {
return abi.encodePacked(uint8(Actions.MINT_POSITION), uint8(Actions.SETTLE), uint8(Actions.SETTLE));
}
/// @notice Builds one-sided position actions to append without final take pair action
function buildOneSidedActions(bytes memory existingActions) internal pure returns (bytes memory) {
if (existingActions.length != ParamsBuilder.FULL_RANGE_SIZE - 1) {
revert InvalidActionsLength(existingActions.length);
}
return abi.encodePacked(existingActions, uint8(Actions.MINT_POSITION));
}
/// @notice Builds final take pair action
function buildFinalTakePairActions(bytes memory existingActions) internal pure returns (bytes memory) {
return abi.encodePacked(existingActions, uint8(Actions.TAKE_PAIR));
}
}
TickCalculations.sol 47 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
/// @title TickCalculations
/// @notice Library for tick calculations
library TickCalculations {
/// @notice Derives max liquidity per tick from given tick spacing
/// @dev Taken directly from Pool.sol
/// @param tickSpacing The amount of required tick separation, realized in multiples of `tickSpacing` (cannot be 0)
/// e.g., a tickSpacing of 3 requires ticks to be initialized every 3rd tick i.e., ..., -6, -3, 0, 3, 6, ...
/// @return result The max liquidity per tick
function tickSpacingToMaxLiquidityPerTick(int24 tickSpacing) internal pure returns (uint128 result) {
int24 MAX_TICK = TickMath.MAX_TICK;
int24 MIN_TICK = TickMath.MIN_TICK;
assembly ("memory-safe") {
tickSpacing := signextend(2, tickSpacing)
let minTick := sub(sdiv(MIN_TICK, tickSpacing), slt(smod(MIN_TICK, tickSpacing), 0))
let maxTick := sdiv(MAX_TICK, tickSpacing)
let numTicks := add(sub(maxTick, minTick), 1)
result := div(sub(shl(128, 1), 1), numTicks)
}
}
/// @notice Rounds down to the nearest tick spacing if needed
/// @param tick The tick to round down
/// @param tickSpacing The tick spacing to round down to (cannot be 0)
/// @return The rounded down tick
function tickFloor(int24 tick, int24 tickSpacing) internal pure returns (int24) {
unchecked {
int24 remainder = tick % tickSpacing;
return remainder >= 0 ? tick - remainder : tick - remainder - tickSpacing;
}
}
/// @notice Rounds up to the next tick spacing
/// @param tick The tick to round up
/// @param tickSpacing The tick spacing to round up to (cannot be 0)
/// @return The rounded up tick
function tickStrictCeil(int24 tick, int24 tickSpacing) internal pure returns (int24) {
unchecked {
int24 remainder = tick % tickSpacing;
return remainder >= 0 ? tick + tickSpacing - remainder : tick - remainder;
}
}
}
ISubscriber.sol 38 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {PositionInfo} from "../libraries/PositionInfoLibrary.sol";
/// @title ISubscriber
/// @notice Interface that a Subscriber contract should implement to receive updates from the v4 position manager
interface ISubscriber {
/// @notice Called when a position subscribes to this subscriber contract
/// @param tokenId the token ID of the position
/// @param data additional data passed in by the caller
function notifySubscribe(uint256 tokenId, bytes memory data) external;
/// @notice Called when a position unsubscribes from the subscriber
/// @dev This call's gas is capped at `unsubscribeGasLimit` (set at deployment)
/// @dev Because of EIP-150, solidity may only allocate 63/64 of gasleft()
/// @param tokenId the token ID of the position
function notifyUnsubscribe(uint256 tokenId) external;
/// @notice Called when a position is burned
/// @param tokenId the token ID of the position
/// @param owner the current owner of the tokenId
/// @param info information about the position
/// @param liquidity the amount of liquidity decreased in the position, may be 0
/// @param feesAccrued the fees accrued by the position if liquidity was decreased
function notifyBurn(uint256 tokenId, address owner, PositionInfo info, uint256 liquidity, BalanceDelta feesAccrued)
external;
/// @notice Called when a position modifies its liquidity or collects fees
/// @param tokenId the token ID of the position
/// @param liquidityChange the change in liquidity on the underlying position
/// @param feesAccrued the fees to be collected from the position as a result of the modifyLiquidity call
/// @dev Note that feesAccrued can be artificially inflated by a malicious user
/// Pools with a single liquidity position can inflate feeGrowthGlobal (and consequently feesAccrued) by donating to themselves;
/// atomically donating and collecting fees within the same unlockCallback may further inflate feeGrowthGlobal/feesAccrued
function notifyModifyLiquidity(uint256 tokenId, int256 liquidityChange, BalanceDelta feesAccrued) external;
}
IAllowanceTransfer.sol 165 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IEIP712} from "./IEIP712.sol";
/// @title AllowanceTransfer
/// @notice Handles ERC20 token permissions through signature based allowance setting and ERC20 token transfers by checking allowed amounts
/// @dev Requires user's token approval on the Permit2 contract
interface IAllowanceTransfer is IEIP712 {
/// @notice Thrown when an allowance on a token has expired.
/// @param deadline The timestamp at which the allowed amount is no longer valid
error AllowanceExpired(uint256 deadline);
/// @notice Thrown when an allowance on a token has been depleted.
/// @param amount The maximum amount allowed
error InsufficientAllowance(uint256 amount);
/// @notice Thrown when too many nonces are invalidated.
error ExcessiveInvalidation();
/// @notice Emits an event when the owner successfully invalidates an ordered nonce.
event NonceInvalidation(
address indexed owner, address indexed token, address indexed spender, uint48 newNonce, uint48 oldNonce
);
/// @notice Emits an event when the owner successfully sets permissions on a token for the spender.
event Approval(
address indexed owner, address indexed token, address indexed spender, uint160 amount, uint48 expiration
);
/// @notice Emits an event when the owner successfully sets permissions using a permit signature on a token for the spender.
event Permit(
address indexed owner,
address indexed token,
address indexed spender,
uint160 amount,
uint48 expiration,
uint48 nonce
);
/// @notice Emits an event when the owner sets the allowance back to 0 with the lockdown function.
event Lockdown(address indexed owner, address token, address spender);
/// @notice The permit data for a token
struct PermitDetails {
// ERC20 token address
address token;
// the maximum amount allowed to spend
uint160 amount;
// timestamp at which a spender's token allowances become invalid
uint48 expiration;
// an incrementing value indexed per owner,token,and spender for each signature
uint48 nonce;
}
/// @notice The permit message signed for a single token allowance
struct PermitSingle {
// the permit data for a single token alownce
PermitDetails details;
// address permissioned on the allowed tokens
address spender;
// deadline on the permit signature
uint256 sigDeadline;
}
/// @notice The permit message signed for multiple token allowances
struct PermitBatch {
// the permit data for multiple token allowances
PermitDetails[] details;
// address permissioned on the allowed tokens
address spender;
// deadline on the permit signature
uint256 sigDeadline;
}
/// @notice The saved permissions
/// @dev This info is saved per owner, per token, per spender and all signed over in the permit message
/// @dev Setting amount to type(uint160).max sets an unlimited approval
struct PackedAllowance {
// amount allowed
uint160 amount;
// permission expiry
uint48 expiration;
// an incrementing value indexed per owner,token,and spender for each signature
uint48 nonce;
}
/// @notice A token spender pair.
struct TokenSpenderPair {
// the token the spender is approved
address token;
// the spender address
address spender;
}
/// @notice Details for a token transfer.
struct AllowanceTransferDetails {
// the owner of the token
address from;
// the recipient of the token
address to;
// the amount of the token
uint160 amount;
// the token to be transferred
address token;
}
/// @notice A mapping from owner address to token address to spender address to PackedAllowance struct, which contains details and conditions of the approval.
/// @notice The mapping is indexed in the above order see: allowance[ownerAddress][tokenAddress][spenderAddress]
/// @dev The packed slot holds the allowed amount, expiration at which the allowed amount is no longer valid, and current nonce thats updated on any signature based approvals.
function allowance(address user, address token, address spender)
external
view
returns (uint160 amount, uint48 expiration, uint48 nonce);
/// @notice Approves the spender to use up to amount of the specified token up until the expiration
/// @param token The token to approve
/// @param spender The spender address to approve
/// @param amount The approved amount of the token
/// @param expiration The timestamp at which the approval is no longer valid
/// @dev The packed allowance also holds a nonce, which will stay unchanged in approve
/// @dev Setting amount to type(uint160).max sets an unlimited approval
function approve(address token, address spender, uint160 amount, uint48 expiration) external;
/// @notice Permit a spender to a given amount of the owners token via the owner's EIP-712 signature
/// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce
/// @param owner The owner of the tokens being approved
/// @param permitSingle Data signed over by the owner specifying the terms of approval
/// @param signature The owner's signature over the permit data
function permit(address owner, PermitSingle memory permitSingle, bytes calldata signature) external;
/// @notice Permit a spender to the signed amounts of the owners tokens via the owner's EIP-712 signature
/// @dev May fail if the owner's nonce was invalidated in-flight by invalidateNonce
/// @param owner The owner of the tokens being approved
/// @param permitBatch Data signed over by the owner specifying the terms of approval
/// @param signature The owner's signature over the permit data
function permit(address owner, PermitBatch memory permitBatch, bytes calldata signature) external;
/// @notice Transfer approved tokens from one address to another
/// @param from The address to transfer from
/// @param to The address of the recipient
/// @param amount The amount of the token to transfer
/// @param token The token address to transfer
/// @dev Requires the from address to have approved at least the desired amount
/// of tokens to msg.sender.
function transferFrom(address from, address to, uint160 amount, address token) external;
/// @notice Transfer approved tokens in a batch
/// @param transferDetails Array of owners, recipients, amounts, and tokens for the transfers
/// @dev Requires the from addresses to have approved at least the desired amount
/// of tokens to msg.sender.
function transferFrom(AllowanceTransferDetails[] calldata transferDetails) external;
/// @notice Enables performing a "lockdown" of the sender's Permit2 identity
/// by batch revoking approvals
/// @param approvals Array of approvals to revoke.
function lockdown(TokenSpenderPair[] calldata approvals) external;
/// @notice Invalidate nonces for a given (token, spender) pair
/// @param token The token to invalidate nonces for
/// @param spender The spender to invalidate nonces for
/// @param newNonce The new nonce to set. Invalidates all nonces less than it.
/// @dev Can't invalidate more than 2**16 nonces per transaction.
function invalidateNonces(address token, address spender, uint48 newNonce) external;
}
IERC20Minimal.sol 22 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @notice Minimal ERC20 interface
interface IERC20Minimal {
/// @notice Returns an account's balance in the token
/// @param account The account for which to look up the number of tokens it has, i.e. its balance
/// @return The number of tokens held by the account
function balanceOf(address account) external view returns (uint256);
/// @notice Transfers the amount of token from the `msg.sender` to the recipient
/// @param recipient The account that will receive the amount transferred
/// @param amount The number of tokens to send from the sender to the recipient
/// @return Returns true for a successful transfer, false for an unsuccessful transfer
function transfer(address recipient, uint256 amount) external returns (bool);
/// @notice Approves the spender to spend the amount of tokens from the `msg.sender`
/// @param spender The account that will be allowed to spend the amount
/// @param amount The number of tokens to allow the spender to spend
/// @return Returns true for a successful approval, false for an unsuccessful approval
function approve(address spender, uint256 amount) external returns (bool);
}
CheckpointAccountingLib.sol 87 lines
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {Bid, BidLib} from '../libraries/BidLib.sol';
import {Checkpoint} from '../libraries/CheckpointLib.sol';
import {FixedPoint96} from '../libraries/FixedPoint96.sol';
import {ValueX7} from '../libraries/ValueX7Lib.sol';
import {FixedPointMathLib} from 'solady/utils/FixedPointMathLib.sol';
/// @title CheckpointAccountingLib
/// @notice Pure accounting helpers for computing fills and currency spent across checkpoints
library CheckpointAccountingLib {
using FixedPointMathLib for *;
using BidLib for *;
/// @notice Calculate the tokens sold and proportion of input used for a fully filled bid between two checkpoints
/// @dev MUST only be used for checkpoints where the bid's max price is strictly greater than the clearing price
/// because it uses lazy accounting to calculate the tokens filled
/// @param upper The upper checkpoint
/// @param startCheckpoint The start checkpoint of the bid
/// @param bid The bid
/// @return tokensFilled The tokens sold
/// @return currencySpentQ96 The amount of currency spent in Q96 form
function accountFullyFilledCheckpoints(Checkpoint memory upper, Checkpoint memory startCheckpoint, Bid memory bid)
internal
pure
returns (uint256 tokensFilled, uint256 currencySpentQ96)
{
(tokensFilled, currencySpentQ96) = calculateFill(
bid,
upper.cumulativeMpsPerPrice - startCheckpoint.cumulativeMpsPerPrice,
upper.cumulativeMps - startCheckpoint.cumulativeMps
);
}
/// @notice Calculate the tokens sold and currency spent for a partially filled bid
/// @param bid The bid
/// @param tickDemandQ96 The total demand at the tick
/// @param currencyRaisedAtClearingPriceQ96_X7 The cumulative supply sold to the clearing price
/// @return tokensFilled The tokens sold
/// @return currencySpentQ96 The amount of currency spent in Q96 form
function accountPartiallyFilledCheckpoints(
Bid memory bid,
uint256 tickDemandQ96,
ValueX7 currencyRaisedAtClearingPriceQ96_X7
) internal pure returns (uint256 tokensFilled, uint256 currencySpentQ96) {
if (tickDemandQ96 == 0) return (0, 0);
// Apply the ratio between bid demand and tick demand to the currencyRaisedAtClearingPriceQ96_X7 value
// If currency spent is calculated to have a remainder, we round up.
// In the case where the result would have been 0, we will return 1 wei.
uint256 denominator = tickDemandQ96 * bid.mpsRemainingInAuctionAfterSubmission();
currencySpentQ96 = bid.amountQ96.fullMulDivUp(ValueX7.unwrap(currencyRaisedAtClearingPriceQ96_X7), denominator);
// We derive tokens filled from the currency spent by dividing it by the max price.
// If the currency spent is 0, tokens filled will be 0 as well.
tokensFilled =
bid.amountQ96.fullMulDiv(ValueX7.unwrap(currencyRaisedAtClearingPriceQ96_X7), denominator) / bid.maxPrice;
}
/// @notice Calculate the tokens filled and currency spent for a bid
/// @dev Uses lazy accounting to efficiently calculate fills across time periods without iterating blocks.
/// MUST only be used when the bid's max price is strictly greater than the clearing price throughout.
/// @param bid the bid to evaluate
/// @param cumulativeMpsPerPriceDelta the cumulative sum of supply to price ratio
/// @param cumulativeMpsDelta the cumulative sum of mps values across the block range
/// @return tokensFilled the amount of tokens filled for this bid
/// @return currencySpentQ96 the amount of currency spent by this bid in Q96 form
function calculateFill(Bid memory bid, uint256 cumulativeMpsPerPriceDelta, uint24 cumulativeMpsDelta)
internal
pure
returns (uint256 tokensFilled, uint256 currencySpentQ96)
{
uint24 mpsRemainingInAuctionAfterSubmission = bid.mpsRemainingInAuctionAfterSubmission();
// Currency spent is original currency amount multiplied by percentage fully filled over percentage allocated
currencySpentQ96 = bid.amountQ96.fullMulDivUp(cumulativeMpsDelta, mpsRemainingInAuctionAfterSubmission);
// Tokens filled are calculated from the effective amount over the allocation
tokensFilled = bid.amountQ96
.fullMulDiv(
cumulativeMpsPerPriceDelta,
(FixedPoint96.Q96 << FixedPoint96.RESOLUTION) * mpsRemainingInAuctionAfterSubmission
);
}
}
SSTORE2.sol 259 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Read and write to persistent storage at a fraction of the cost.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SSTORE2.sol)
/// @author Saw-mon-and-Natalie (https://github.com/Saw-mon-and-Natalie)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol)
/// @author Modified from 0xSequence (https://github.com/0xSequence/sstore2/blob/master/contracts/SSTORE2.sol)
/// @author Modified from SSTORE3 (https://github.com/Philogy/sstore3)
library SSTORE2 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The proxy initialization code.
uint256 private constant _CREATE3_PROXY_INITCODE = 0x67363d3d37363d34f03d5260086018f3;
/// @dev Hash of the `_CREATE3_PROXY_INITCODE`.
/// Equivalent to `keccak256(abi.encodePacked(hex"67363d3d37363d34f03d5260086018f3"))`.
bytes32 internal constant CREATE3_PROXY_INITCODE_HASH =
0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Unable to deploy the storage contract.
error DeploymentFailed();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* WRITE LOGIC */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Writes `data` into the bytecode of a storage contract and returns its address.
function write(bytes memory data) internal returns (address pointer) {
/// @solidity memory-safe-assembly
assembly {
let n := mload(data) // Let `l` be `n + 1`. +1 as we prefix a STOP opcode.
/**
* ---------------------------------------------------+
* Opcode | Mnemonic | Stack | Memory |
* ---------------------------------------------------|
* 61 l | PUSH2 l | l | |
* 80 | DUP1 | l l | |
* 60 0xa | PUSH1 0xa | 0xa l l | |
* 3D | RETURNDATASIZE | 0 0xa l l | |
* 39 | CODECOPY | l | [0..l): code |
* 3D | RETURNDATASIZE | 0 l | [0..l): code |
* F3 | RETURN | | [0..l): code |
* 00 | STOP | | |
* ---------------------------------------------------+
* @dev Prefix the bytecode with a STOP opcode to ensure it cannot be called.
* Also PUSH2 is used since max contract size cap is 24,576 bytes which is less than 2 ** 16.
*/
// Do a out-of-gas revert if `n + 1` is more than 2 bytes.
mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
// Deploy a new contract with the generated creation code.
pointer := create(0, add(data, 0x15), add(n, 0xb))
if iszero(pointer) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(data, n) // Restore the length of `data`.
}
}
/// @dev Writes `data` into the bytecode of a storage contract with `salt`
/// and returns its normal CREATE2 deterministic address.
function writeCounterfactual(bytes memory data, bytes32 salt)
internal
returns (address pointer)
{
/// @solidity memory-safe-assembly
assembly {
let n := mload(data)
// Do a out-of-gas revert if `n + 1` is more than 2 bytes.
mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
// Deploy a new contract with the generated creation code.
pointer := create2(0, add(data, 0x15), add(n, 0xb), salt)
if iszero(pointer) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(data, n) // Restore the length of `data`.
}
}
/// @dev Writes `data` into the bytecode of a storage contract and returns its address.
/// This uses the so-called "CREATE3" workflow,
/// which means that `pointer` is agnostic to `data, and only depends on `salt`.
function writeDeterministic(bytes memory data, bytes32 salt)
internal
returns (address pointer)
{
/// @solidity memory-safe-assembly
assembly {
let n := mload(data)
mstore(0x00, _CREATE3_PROXY_INITCODE) // Store the `_PROXY_INITCODE`.
let proxy := create2(0, 0x10, 0x10, salt)
if iszero(proxy) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(0x14, proxy) // Store the proxy's address.
// 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01).
// 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex).
mstore(0x00, 0xd694)
mstore8(0x34, 0x01) // Nonce of the proxy contract (1).
pointer := keccak256(0x1e, 0x17)
// Do a out-of-gas revert if `n + 1` is more than 2 bytes.
mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
if iszero(
mul( // The arguments of `mul` are evaluated last to first.
extcodesize(pointer),
call(gas(), proxy, 0, add(data, 0x15), add(n, 0xb), codesize(), 0x00)
)
) {
mstore(0x00, 0x30116425) // `DeploymentFailed()`.
revert(0x1c, 0x04)
}
mstore(data, n) // Restore the length of `data`.
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ADDRESS CALCULATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the initialization code hash of the storage contract for `data`.
/// Used for mining vanity addresses with create2crunch.
function initCodeHash(bytes memory data) internal pure returns (bytes32 hash) {
/// @solidity memory-safe-assembly
assembly {
let n := mload(data)
// Do a out-of-gas revert if `n + 1` is more than 2 bytes.
returndatacopy(returndatasize(), returndatasize(), gt(n, 0xfffe))
mstore(data, add(0x61000180600a3d393df300, shl(0x40, n)))
hash := keccak256(add(data, 0x15), add(n, 0xb))
mstore(data, n) // Restore the length of `data`.
}
}
/// @dev Equivalent to `predictCounterfactualAddress(data, salt, address(this))`
function predictCounterfactualAddress(bytes memory data, bytes32 salt)
internal
view
returns (address pointer)
{
pointer = predictCounterfactualAddress(data, salt, address(this));
}
/// @dev Returns the CREATE2 address of the storage contract for `data`
/// deployed with `salt` by `deployer`.
/// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly.
function predictCounterfactualAddress(bytes memory data, bytes32 salt, address deployer)
internal
pure
returns (address predicted)
{
bytes32 hash = initCodeHash(data);
/// @solidity memory-safe-assembly
assembly {
// Compute and store the bytecode hash.
mstore8(0x00, 0xff) // Write the prefix.
mstore(0x35, hash)
mstore(0x01, shl(96, deployer))
mstore(0x15, salt)
predicted := keccak256(0x00, 0x55)
// Restore the part of the free memory pointer that has been overwritten.
mstore(0x35, 0)
}
}
/// @dev Equivalent to `predictDeterministicAddress(salt, address(this))`.
function predictDeterministicAddress(bytes32 salt) internal view returns (address pointer) {
pointer = predictDeterministicAddress(salt, address(this));
}
/// @dev Returns the "CREATE3" deterministic address for `salt` with `deployer`.
function predictDeterministicAddress(bytes32 salt, address deployer)
internal
pure
returns (address pointer)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x00, deployer) // Store `deployer`.
mstore8(0x0b, 0xff) // Store the prefix.
mstore(0x20, salt) // Store the salt.
mstore(0x40, CREATE3_PROXY_INITCODE_HASH) // Store the bytecode hash.
mstore(0x14, keccak256(0x0b, 0x55)) // Store the proxy's address.
mstore(0x40, m) // Restore the free memory pointer.
// 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01).
// 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex).
mstore(0x00, 0xd694)
mstore8(0x34, 0x01) // Nonce of the proxy contract (1).
pointer := keccak256(0x1e, 0x17)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* READ LOGIC */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Equivalent to `read(pointer, 0, 2 ** 256 - 1)`.
function read(address pointer) internal view returns (bytes memory data) {
/// @solidity memory-safe-assembly
assembly {
data := mload(0x40)
let n := and(0xffffffffff, sub(extcodesize(pointer), 0x01))
extcodecopy(pointer, add(data, 0x1f), 0x00, add(n, 0x21))
mstore(data, n) // Store the length.
mstore(0x40, add(n, add(data, 0x40))) // Allocate memory.
}
}
/// @dev Equivalent to `read(pointer, start, 2 ** 256 - 1)`.
function read(address pointer, uint256 start) internal view returns (bytes memory data) {
/// @solidity memory-safe-assembly
assembly {
data := mload(0x40)
let n := and(0xffffffffff, sub(extcodesize(pointer), 0x01))
let l := sub(n, and(0xffffff, mul(lt(start, n), start)))
extcodecopy(pointer, add(data, 0x1f), start, add(l, 0x21))
mstore(data, mul(sub(n, start), lt(start, n))) // Store the length.
mstore(0x40, add(data, add(0x40, mload(data)))) // Allocate memory.
}
}
/// @dev Returns a slice of the data on `pointer` from `start` to `end`.
/// `start` and `end` will be clamped to the range `[0, args.length]`.
/// The `pointer` MUST be deployed via the SSTORE2 write functions.
/// Otherwise, the behavior is undefined.
/// Out-of-gas reverts if `pointer` does not have any code.
function read(address pointer, uint256 start, uint256 end)
internal
view
returns (bytes memory data)
{
/// @solidity memory-safe-assembly
assembly {
data := mload(0x40)
if iszero(lt(end, 0xffff)) { end := 0xffff }
let d := mul(sub(end, start), lt(start, end))
extcodecopy(pointer, add(data, 0x1f), start, add(d, 0x01))
if iszero(and(0xff, mload(add(data, d)))) {
let n := sub(extcodesize(pointer), 0x01)
returndatacopy(returndatasize(), returndatasize(), shr(40, n))
d := mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n))))
}
mstore(data, d) // Store the length.
mstore(add(add(data, 0x20), d), 0) // Zeroize the slot after the bytes.
mstore(0x40, add(add(data, 0x40), d)) // Allocate memory.
}
}
}
ImmutableState.sol 25 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {IImmutableState} from "../interfaces/IImmutableState.sol";
/// @title Immutable State
/// @notice A collection of immutable state variables, commonly used across multiple contracts
contract ImmutableState is IImmutableState {
/// @inheritdoc IImmutableState
IPoolManager public immutable poolManager;
/// @notice Thrown when the caller is not PoolManager
error NotPoolManager();
/// @notice Only allow calls from the PoolManager contract
modifier onlyPoolManager() {
if (msg.sender != address(poolManager)) revert NotPoolManager();
_;
}
constructor(IPoolManager _poolManager) {
poolManager = _poolManager;
}
}
Panic.sol 57 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/Panic.sol)
pragma solidity ^0.8.20;
/**
* @dev Helper library for emitting standardized panic codes.
*
* ```solidity
* contract Example {
* using Panic for uint256;
*
* // Use any of the declared internal constants
* function foo() { Panic.GENERIC.panic(); }
*
* // Alternatively
* function foo() { Panic.panic(Panic.GENERIC); }
* }
* ```
*
* Follows the list from https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h[libsolutil].
*
* _Available since v5.1._
*/
// slither-disable-next-line unused-state
library Panic {
/// @dev generic / unspecified error
uint256 internal constant GENERIC = 0x00;
/// @dev used by the assert() builtin
uint256 internal constant ASSERT = 0x01;
/// @dev arithmetic underflow or overflow
uint256 internal constant UNDER_OVERFLOW = 0x11;
/// @dev division or modulo by zero
uint256 internal constant DIVISION_BY_ZERO = 0x12;
/// @dev enum conversion error
uint256 internal constant ENUM_CONVERSION_ERROR = 0x21;
/// @dev invalid encoding in storage
uint256 internal constant STORAGE_ENCODING_ERROR = 0x22;
/// @dev empty array pop
uint256 internal constant EMPTY_ARRAY_POP = 0x31;
/// @dev array out of bounds access
uint256 internal constant ARRAY_OUT_OF_BOUNDS = 0x32;
/// @dev resource error (too large allocation or too large array)
uint256 internal constant RESOURCE_ERROR = 0x41;
/// @dev calling invalid internal function
uint256 internal constant INVALID_INTERNAL_FUNCTION = 0x51;
/// @dev Reverts with a panic code. Recommended to use with
/// the internal constants with predefined codes.
function panic(uint256 code) internal pure {
assembly ("memory-safe") {
mstore(0x00, 0x4e487b71)
mstore(0x20, code)
revert(0x1c, 0x24)
}
}
}
SafeCast.sol 1162 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/math/SafeCast.sol)
// This file was procedurally generated from scripts/generate/templates/SafeCast.js.
pragma solidity ^0.8.20;
/**
* @dev Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow
* checks.
*
* Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
* easily result in undesired exploitation or bugs, since developers usually
* assume that overflows raise errors. `SafeCast` restores this intuition by
* reverting the transaction when such an operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeCast {
/**
* @dev Value doesn't fit in an uint of `bits` size.
*/
error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);
/**
* @dev An int value doesn't fit in an uint of `bits` size.
*/
error SafeCastOverflowedIntToUint(int256 value);
/**
* @dev Value doesn't fit in an int of `bits` size.
*/
error SafeCastOverflowedIntDowncast(uint8 bits, int256 value);
/**
* @dev An uint value doesn't fit in an int of `bits` size.
*/
error SafeCastOverflowedUintToInt(uint256 value);
/**
* @dev Returns the downcasted uint248 from uint256, reverting on
* overflow (when the input is greater than largest uint248).
*
* Counterpart to Solidity's `uint248` operator.
*
* Requirements:
*
* - input must fit into 248 bits
*/
function toUint248(uint256 value) internal pure returns (uint248) {
if (value > type(uint248).max) {
revert SafeCastOverflowedUintDowncast(248, value);
}
return uint248(value);
}
/**
* @dev Returns the downcasted uint240 from uint256, reverting on
* overflow (when the input is greater than largest uint240).
*
* Counterpart to Solidity's `uint240` operator.
*
* Requirements:
*
* - input must fit into 240 bits
*/
function toUint240(uint256 value) internal pure returns (uint240) {
if (value > type(uint240).max) {
revert SafeCastOverflowedUintDowncast(240, value);
}
return uint240(value);
}
/**
* @dev Returns the downcasted uint232 from uint256, reverting on
* overflow (when the input is greater than largest uint232).
*
* Counterpart to Solidity's `uint232` operator.
*
* Requirements:
*
* - input must fit into 232 bits
*/
function toUint232(uint256 value) internal pure returns (uint232) {
if (value > type(uint232).max) {
revert SafeCastOverflowedUintDowncast(232, value);
}
return uint232(value);
}
/**
* @dev Returns the downcasted uint224 from uint256, reverting on
* overflow (when the input is greater than largest uint224).
*
* Counterpart to Solidity's `uint224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*/
function toUint224(uint256 value) internal pure returns (uint224) {
if (value > type(uint224).max) {
revert SafeCastOverflowedUintDowncast(224, value);
}
return uint224(value);
}
/**
* @dev Returns the downcasted uint216 from uint256, reverting on
* overflow (when the input is greater than largest uint216).
*
* Counterpart to Solidity's `uint216` operator.
*
* Requirements:
*
* - input must fit into 216 bits
*/
function toUint216(uint256 value) internal pure returns (uint216) {
if (value > type(uint216).max) {
revert SafeCastOverflowedUintDowncast(216, value);
}
return uint216(value);
}
/**
* @dev Returns the downcasted uint208 from uint256, reverting on
* overflow (when the input is greater than largest uint208).
*
* Counterpart to Solidity's `uint208` operator.
*
* Requirements:
*
* - input must fit into 208 bits
*/
function toUint208(uint256 value) internal pure returns (uint208) {
if (value > type(uint208).max) {
revert SafeCastOverflowedUintDowncast(208, value);
}
return uint208(value);
}
/**
* @dev Returns the downcasted uint200 from uint256, reverting on
* overflow (when the input is greater than largest uint200).
*
* Counterpart to Solidity's `uint200` operator.
*
* Requirements:
*
* - input must fit into 200 bits
*/
function toUint200(uint256 value) internal pure returns (uint200) {
if (value > type(uint200).max) {
revert SafeCastOverflowedUintDowncast(200, value);
}
return uint200(value);
}
/**
* @dev Returns the downcasted uint192 from uint256, reverting on
* overflow (when the input is greater than largest uint192).
*
* Counterpart to Solidity's `uint192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*/
function toUint192(uint256 value) internal pure returns (uint192) {
if (value > type(uint192).max) {
revert SafeCastOverflowedUintDowncast(192, value);
}
return uint192(value);
}
/**
* @dev Returns the downcasted uint184 from uint256, reverting on
* overflow (when the input is greater than largest uint184).
*
* Counterpart to Solidity's `uint184` operator.
*
* Requirements:
*
* - input must fit into 184 bits
*/
function toUint184(uint256 value) internal pure returns (uint184) {
if (value > type(uint184).max) {
revert SafeCastOverflowedUintDowncast(184, value);
}
return uint184(value);
}
/**
* @dev Returns the downcasted uint176 from uint256, reverting on
* overflow (when the input is greater than largest uint176).
*
* Counterpart to Solidity's `uint176` operator.
*
* Requirements:
*
* - input must fit into 176 bits
*/
function toUint176(uint256 value) internal pure returns (uint176) {
if (value > type(uint176).max) {
revert SafeCastOverflowedUintDowncast(176, value);
}
return uint176(value);
}
/**
* @dev Returns the downcasted uint168 from uint256, reverting on
* overflow (when the input is greater than largest uint168).
*
* Counterpart to Solidity's `uint168` operator.
*
* Requirements:
*
* - input must fit into 168 bits
*/
function toUint168(uint256 value) internal pure returns (uint168) {
if (value > type(uint168).max) {
revert SafeCastOverflowedUintDowncast(168, value);
}
return uint168(value);
}
/**
* @dev Returns the downcasted uint160 from uint256, reverting on
* overflow (when the input is greater than largest uint160).
*
* Counterpart to Solidity's `uint160` operator.
*
* Requirements:
*
* - input must fit into 160 bits
*/
function toUint160(uint256 value) internal pure returns (uint160) {
if (value > type(uint160).max) {
revert SafeCastOverflowedUintDowncast(160, value);
}
return uint160(value);
}
/**
* @dev Returns the downcasted uint152 from uint256, reverting on
* overflow (when the input is greater than largest uint152).
*
* Counterpart to Solidity's `uint152` operator.
*
* Requirements:
*
* - input must fit into 152 bits
*/
function toUint152(uint256 value) internal pure returns (uint152) {
if (value > type(uint152).max) {
revert SafeCastOverflowedUintDowncast(152, value);
}
return uint152(value);
}
/**
* @dev Returns the downcasted uint144 from uint256, reverting on
* overflow (when the input is greater than largest uint144).
*
* Counterpart to Solidity's `uint144` operator.
*
* Requirements:
*
* - input must fit into 144 bits
*/
function toUint144(uint256 value) internal pure returns (uint144) {
if (value > type(uint144).max) {
revert SafeCastOverflowedUintDowncast(144, value);
}
return uint144(value);
}
/**
* @dev Returns the downcasted uint136 from uint256, reverting on
* overflow (when the input is greater than largest uint136).
*
* Counterpart to Solidity's `uint136` operator.
*
* Requirements:
*
* - input must fit into 136 bits
*/
function toUint136(uint256 value) internal pure returns (uint136) {
if (value > type(uint136).max) {
revert SafeCastOverflowedUintDowncast(136, value);
}
return uint136(value);
}
/**
* @dev Returns the downcasted uint128 from uint256, reverting on
* overflow (when the input is greater than largest uint128).
*
* Counterpart to Solidity's `uint128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*/
function toUint128(uint256 value) internal pure returns (uint128) {
if (value > type(uint128).max) {
revert SafeCastOverflowedUintDowncast(128, value);
}
return uint128(value);
}
/**
* @dev Returns the downcasted uint120 from uint256, reverting on
* overflow (when the input is greater than largest uint120).
*
* Counterpart to Solidity's `uint120` operator.
*
* Requirements:
*
* - input must fit into 120 bits
*/
function toUint120(uint256 value) internal pure returns (uint120) {
if (value > type(uint120).max) {
revert SafeCastOverflowedUintDowncast(120, value);
}
return uint120(value);
}
/**
* @dev Returns the downcasted uint112 from uint256, reverting on
* overflow (when the input is greater than largest uint112).
*
* Counterpart to Solidity's `uint112` operator.
*
* Requirements:
*
* - input must fit into 112 bits
*/
function toUint112(uint256 value) internal pure returns (uint112) {
if (value > type(uint112).max) {
revert SafeCastOverflowedUintDowncast(112, value);
}
return uint112(value);
}
/**
* @dev Returns the downcasted uint104 from uint256, reverting on
* overflow (when the input is greater than largest uint104).
*
* Counterpart to Solidity's `uint104` operator.
*
* Requirements:
*
* - input must fit into 104 bits
*/
function toUint104(uint256 value) internal pure returns (uint104) {
if (value > type(uint104).max) {
revert SafeCastOverflowedUintDowncast(104, value);
}
return uint104(value);
}
/**
* @dev Returns the downcasted uint96 from uint256, reverting on
* overflow (when the input is greater than largest uint96).
*
* Counterpart to Solidity's `uint96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*/
function toUint96(uint256 value) internal pure returns (uint96) {
if (value > type(uint96).max) {
revert SafeCastOverflowedUintDowncast(96, value);
}
return uint96(value);
}
/**
* @dev Returns the downcasted uint88 from uint256, reverting on
* overflow (when the input is greater than largest uint88).
*
* Counterpart to Solidity's `uint88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*/
function toUint88(uint256 value) internal pure returns (uint88) {
if (value > type(uint88).max) {
revert SafeCastOverflowedUintDowncast(88, value);
}
return uint88(value);
}
/**
* @dev Returns the downcasted uint80 from uint256, reverting on
* overflow (when the input is greater than largest uint80).
*
* Counterpart to Solidity's `uint80` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*/
function toUint80(uint256 value) internal pure returns (uint80) {
if (value > type(uint80).max) {
revert SafeCastOverflowedUintDowncast(80, value);
}
return uint80(value);
}
/**
* @dev Returns the downcasted uint72 from uint256, reverting on
* overflow (when the input is greater than largest uint72).
*
* Counterpart to Solidity's `uint72` operator.
*
* Requirements:
*
* - input must fit into 72 bits
*/
function toUint72(uint256 value) internal pure returns (uint72) {
if (value > type(uint72).max) {
revert SafeCastOverflowedUintDowncast(72, value);
}
return uint72(value);
}
/**
* @dev Returns the downcasted uint64 from uint256, reverting on
* overflow (when the input is greater than largest uint64).
*
* Counterpart to Solidity's `uint64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*/
function toUint64(uint256 value) internal pure returns (uint64) {
if (value > type(uint64).max) {
revert SafeCastOverflowedUintDowncast(64, value);
}
return uint64(value);
}
/**
* @dev Returns the downcasted uint56 from uint256, reverting on
* overflow (when the input is greater than largest uint56).
*
* Counterpart to Solidity's `uint56` operator.
*
* Requirements:
*
* - input must fit into 56 bits
*/
function toUint56(uint256 value) internal pure returns (uint56) {
if (value > type(uint56).max) {
revert SafeCastOverflowedUintDowncast(56, value);
}
return uint56(value);
}
/**
* @dev Returns the downcasted uint48 from uint256, reverting on
* overflow (when the input is greater than largest uint48).
*
* Counterpart to Solidity's `uint48` operator.
*
* Requirements:
*
* - input must fit into 48 bits
*/
function toUint48(uint256 value) internal pure returns (uint48) {
if (value > type(uint48).max) {
revert SafeCastOverflowedUintDowncast(48, value);
}
return uint48(value);
}
/**
* @dev Returns the downcasted uint40 from uint256, reverting on
* overflow (when the input is greater than largest uint40).
*
* Counterpart to Solidity's `uint40` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*/
function toUint40(uint256 value) internal pure returns (uint40) {
if (value > type(uint40).max) {
revert SafeCastOverflowedUintDowncast(40, value);
}
return uint40(value);
}
/**
* @dev Returns the downcasted uint32 from uint256, reverting on
* overflow (when the input is greater than largest uint32).
*
* Counterpart to Solidity's `uint32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*/
function toUint32(uint256 value) internal pure returns (uint32) {
if (value > type(uint32).max) {
revert SafeCastOverflowedUintDowncast(32, value);
}
return uint32(value);
}
/**
* @dev Returns the downcasted uint24 from uint256, reverting on
* overflow (when the input is greater than largest uint24).
*
* Counterpart to Solidity's `uint24` operator.
*
* Requirements:
*
* - input must fit into 24 bits
*/
function toUint24(uint256 value) internal pure returns (uint24) {
if (value > type(uint24).max) {
revert SafeCastOverflowedUintDowncast(24, value);
}
return uint24(value);
}
/**
* @dev Returns the downcasted uint16 from uint256, reverting on
* overflow (when the input is greater than largest uint16).
*
* Counterpart to Solidity's `uint16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*/
function toUint16(uint256 value) internal pure returns (uint16) {
if (value > type(uint16).max) {
revert SafeCastOverflowedUintDowncast(16, value);
}
return uint16(value);
}
/**
* @dev Returns the downcasted uint8 from uint256, reverting on
* overflow (when the input is greater than largest uint8).
*
* Counterpart to Solidity's `uint8` operator.
*
* Requirements:
*
* - input must fit into 8 bits
*/
function toUint8(uint256 value) internal pure returns (uint8) {
if (value > type(uint8).max) {
revert SafeCastOverflowedUintDowncast(8, value);
}
return uint8(value);
}
/**
* @dev Converts a signed int256 into an unsigned uint256.
*
* Requirements:
*
* - input must be greater than or equal to 0.
*/
function toUint256(int256 value) internal pure returns (uint256) {
if (value < 0) {
revert SafeCastOverflowedIntToUint(value);
}
return uint256(value);
}
/**
* @dev Returns the downcasted int248 from int256, reverting on
* overflow (when the input is less than smallest int248 or
* greater than largest int248).
*
* Counterpart to Solidity's `int248` operator.
*
* Requirements:
*
* - input must fit into 248 bits
*/
function toInt248(int256 value) internal pure returns (int248 downcasted) {
downcasted = int248(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(248, value);
}
}
/**
* @dev Returns the downcasted int240 from int256, reverting on
* overflow (when the input is less than smallest int240 or
* greater than largest int240).
*
* Counterpart to Solidity's `int240` operator.
*
* Requirements:
*
* - input must fit into 240 bits
*/
function toInt240(int256 value) internal pure returns (int240 downcasted) {
downcasted = int240(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(240, value);
}
}
/**
* @dev Returns the downcasted int232 from int256, reverting on
* overflow (when the input is less than smallest int232 or
* greater than largest int232).
*
* Counterpart to Solidity's `int232` operator.
*
* Requirements:
*
* - input must fit into 232 bits
*/
function toInt232(int256 value) internal pure returns (int232 downcasted) {
downcasted = int232(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(232, value);
}
}
/**
* @dev Returns the downcasted int224 from int256, reverting on
* overflow (when the input is less than smallest int224 or
* greater than largest int224).
*
* Counterpart to Solidity's `int224` operator.
*
* Requirements:
*
* - input must fit into 224 bits
*/
function toInt224(int256 value) internal pure returns (int224 downcasted) {
downcasted = int224(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(224, value);
}
}
/**
* @dev Returns the downcasted int216 from int256, reverting on
* overflow (when the input is less than smallest int216 or
* greater than largest int216).
*
* Counterpart to Solidity's `int216` operator.
*
* Requirements:
*
* - input must fit into 216 bits
*/
function toInt216(int256 value) internal pure returns (int216 downcasted) {
downcasted = int216(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(216, value);
}
}
/**
* @dev Returns the downcasted int208 from int256, reverting on
* overflow (when the input is less than smallest int208 or
* greater than largest int208).
*
* Counterpart to Solidity's `int208` operator.
*
* Requirements:
*
* - input must fit into 208 bits
*/
function toInt208(int256 value) internal pure returns (int208 downcasted) {
downcasted = int208(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(208, value);
}
}
/**
* @dev Returns the downcasted int200 from int256, reverting on
* overflow (when the input is less than smallest int200 or
* greater than largest int200).
*
* Counterpart to Solidity's `int200` operator.
*
* Requirements:
*
* - input must fit into 200 bits
*/
function toInt200(int256 value) internal pure returns (int200 downcasted) {
downcasted = int200(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(200, value);
}
}
/**
* @dev Returns the downcasted int192 from int256, reverting on
* overflow (when the input is less than smallest int192 or
* greater than largest int192).
*
* Counterpart to Solidity's `int192` operator.
*
* Requirements:
*
* - input must fit into 192 bits
*/
function toInt192(int256 value) internal pure returns (int192 downcasted) {
downcasted = int192(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(192, value);
}
}
/**
* @dev Returns the downcasted int184 from int256, reverting on
* overflow (when the input is less than smallest int184 or
* greater than largest int184).
*
* Counterpart to Solidity's `int184` operator.
*
* Requirements:
*
* - input must fit into 184 bits
*/
function toInt184(int256 value) internal pure returns (int184 downcasted) {
downcasted = int184(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(184, value);
}
}
/**
* @dev Returns the downcasted int176 from int256, reverting on
* overflow (when the input is less than smallest int176 or
* greater than largest int176).
*
* Counterpart to Solidity's `int176` operator.
*
* Requirements:
*
* - input must fit into 176 bits
*/
function toInt176(int256 value) internal pure returns (int176 downcasted) {
downcasted = int176(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(176, value);
}
}
/**
* @dev Returns the downcasted int168 from int256, reverting on
* overflow (when the input is less than smallest int168 or
* greater than largest int168).
*
* Counterpart to Solidity's `int168` operator.
*
* Requirements:
*
* - input must fit into 168 bits
*/
function toInt168(int256 value) internal pure returns (int168 downcasted) {
downcasted = int168(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(168, value);
}
}
/**
* @dev Returns the downcasted int160 from int256, reverting on
* overflow (when the input is less than smallest int160 or
* greater than largest int160).
*
* Counterpart to Solidity's `int160` operator.
*
* Requirements:
*
* - input must fit into 160 bits
*/
function toInt160(int256 value) internal pure returns (int160 downcasted) {
downcasted = int160(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(160, value);
}
}
/**
* @dev Returns the downcasted int152 from int256, reverting on
* overflow (when the input is less than smallest int152 or
* greater than largest int152).
*
* Counterpart to Solidity's `int152` operator.
*
* Requirements:
*
* - input must fit into 152 bits
*/
function toInt152(int256 value) internal pure returns (int152 downcasted) {
downcasted = int152(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(152, value);
}
}
/**
* @dev Returns the downcasted int144 from int256, reverting on
* overflow (when the input is less than smallest int144 or
* greater than largest int144).
*
* Counterpart to Solidity's `int144` operator.
*
* Requirements:
*
* - input must fit into 144 bits
*/
function toInt144(int256 value) internal pure returns (int144 downcasted) {
downcasted = int144(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(144, value);
}
}
/**
* @dev Returns the downcasted int136 from int256, reverting on
* overflow (when the input is less than smallest int136 or
* greater than largest int136).
*
* Counterpart to Solidity's `int136` operator.
*
* Requirements:
*
* - input must fit into 136 bits
*/
function toInt136(int256 value) internal pure returns (int136 downcasted) {
downcasted = int136(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(136, value);
}
}
/**
* @dev Returns the downcasted int128 from int256, reverting on
* overflow (when the input is less than smallest int128 or
* greater than largest int128).
*
* Counterpart to Solidity's `int128` operator.
*
* Requirements:
*
* - input must fit into 128 bits
*/
function toInt128(int256 value) internal pure returns (int128 downcasted) {
downcasted = int128(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(128, value);
}
}
/**
* @dev Returns the downcasted int120 from int256, reverting on
* overflow (when the input is less than smallest int120 or
* greater than largest int120).
*
* Counterpart to Solidity's `int120` operator.
*
* Requirements:
*
* - input must fit into 120 bits
*/
function toInt120(int256 value) internal pure returns (int120 downcasted) {
downcasted = int120(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(120, value);
}
}
/**
* @dev Returns the downcasted int112 from int256, reverting on
* overflow (when the input is less than smallest int112 or
* greater than largest int112).
*
* Counterpart to Solidity's `int112` operator.
*
* Requirements:
*
* - input must fit into 112 bits
*/
function toInt112(int256 value) internal pure returns (int112 downcasted) {
downcasted = int112(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(112, value);
}
}
/**
* @dev Returns the downcasted int104 from int256, reverting on
* overflow (when the input is less than smallest int104 or
* greater than largest int104).
*
* Counterpart to Solidity's `int104` operator.
*
* Requirements:
*
* - input must fit into 104 bits
*/
function toInt104(int256 value) internal pure returns (int104 downcasted) {
downcasted = int104(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(104, value);
}
}
/**
* @dev Returns the downcasted int96 from int256, reverting on
* overflow (when the input is less than smallest int96 or
* greater than largest int96).
*
* Counterpart to Solidity's `int96` operator.
*
* Requirements:
*
* - input must fit into 96 bits
*/
function toInt96(int256 value) internal pure returns (int96 downcasted) {
downcasted = int96(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(96, value);
}
}
/**
* @dev Returns the downcasted int88 from int256, reverting on
* overflow (when the input is less than smallest int88 or
* greater than largest int88).
*
* Counterpart to Solidity's `int88` operator.
*
* Requirements:
*
* - input must fit into 88 bits
*/
function toInt88(int256 value) internal pure returns (int88 downcasted) {
downcasted = int88(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(88, value);
}
}
/**
* @dev Returns the downcasted int80 from int256, reverting on
* overflow (when the input is less than smallest int80 or
* greater than largest int80).
*
* Counterpart to Solidity's `int80` operator.
*
* Requirements:
*
* - input must fit into 80 bits
*/
function toInt80(int256 value) internal pure returns (int80 downcasted) {
downcasted = int80(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(80, value);
}
}
/**
* @dev Returns the downcasted int72 from int256, reverting on
* overflow (when the input is less than smallest int72 or
* greater than largest int72).
*
* Counterpart to Solidity's `int72` operator.
*
* Requirements:
*
* - input must fit into 72 bits
*/
function toInt72(int256 value) internal pure returns (int72 downcasted) {
downcasted = int72(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(72, value);
}
}
/**
* @dev Returns the downcasted int64 from int256, reverting on
* overflow (when the input is less than smallest int64 or
* greater than largest int64).
*
* Counterpart to Solidity's `int64` operator.
*
* Requirements:
*
* - input must fit into 64 bits
*/
function toInt64(int256 value) internal pure returns (int64 downcasted) {
downcasted = int64(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(64, value);
}
}
/**
* @dev Returns the downcasted int56 from int256, reverting on
* overflow (when the input is less than smallest int56 or
* greater than largest int56).
*
* Counterpart to Solidity's `int56` operator.
*
* Requirements:
*
* - input must fit into 56 bits
*/
function toInt56(int256 value) internal pure returns (int56 downcasted) {
downcasted = int56(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(56, value);
}
}
/**
* @dev Returns the downcasted int48 from int256, reverting on
* overflow (when the input is less than smallest int48 or
* greater than largest int48).
*
* Counterpart to Solidity's `int48` operator.
*
* Requirements:
*
* - input must fit into 48 bits
*/
function toInt48(int256 value) internal pure returns (int48 downcasted) {
downcasted = int48(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(48, value);
}
}
/**
* @dev Returns the downcasted int40 from int256, reverting on
* overflow (when the input is less than smallest int40 or
* greater than largest int40).
*
* Counterpart to Solidity's `int40` operator.
*
* Requirements:
*
* - input must fit into 40 bits
*/
function toInt40(int256 value) internal pure returns (int40 downcasted) {
downcasted = int40(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(40, value);
}
}
/**
* @dev Returns the downcasted int32 from int256, reverting on
* overflow (when the input is less than smallest int32 or
* greater than largest int32).
*
* Counterpart to Solidity's `int32` operator.
*
* Requirements:
*
* - input must fit into 32 bits
*/
function toInt32(int256 value) internal pure returns (int32 downcasted) {
downcasted = int32(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(32, value);
}
}
/**
* @dev Returns the downcasted int24 from int256, reverting on
* overflow (when the input is less than smallest int24 or
* greater than largest int24).
*
* Counterpart to Solidity's `int24` operator.
*
* Requirements:
*
* - input must fit into 24 bits
*/
function toInt24(int256 value) internal pure returns (int24 downcasted) {
downcasted = int24(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(24, value);
}
}
/**
* @dev Returns the downcasted int16 from int256, reverting on
* overflow (when the input is less than smallest int16 or
* greater than largest int16).
*
* Counterpart to Solidity's `int16` operator.
*
* Requirements:
*
* - input must fit into 16 bits
*/
function toInt16(int256 value) internal pure returns (int16 downcasted) {
downcasted = int16(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(16, value);
}
}
/**
* @dev Returns the downcasted int8 from int256, reverting on
* overflow (when the input is less than smallest int8 or
* greater than largest int8).
*
* Counterpart to Solidity's `int8` operator.
*
* Requirements:
*
* - input must fit into 8 bits
*/
function toInt8(int256 value) internal pure returns (int8 downcasted) {
downcasted = int8(value);
if (downcasted != value) {
revert SafeCastOverflowedIntDowncast(8, value);
}
}
/**
* @dev Converts an unsigned uint256 into a signed int256.
*
* Requirements:
*
* - input must be less than or equal to maxInt256.
*/
function toInt256(uint256 value) internal pure returns (int256) {
// Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
if (value > uint256(type(int256).max)) {
revert SafeCastOverflowedUintToInt(value);
}
return int256(value);
}
/**
* @dev Cast a boolean (false or true) to a uint256 (0 or 1) with no jump.
*/
function toUint(bool b) internal pure returns (uint256 u) {
assembly ("memory-safe") {
u := iszero(iszero(b))
}
}
}
Actions.sol 49 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @notice Library to define different pool actions.
/// @dev These are suggested common commands, however additional commands should be defined as required
/// Some of these actions are not supported in the Router contracts or Position Manager contracts, but are left as they may be helpful commands for other peripheral contracts.
library Actions {
// pool actions
// liquidity actions
uint256 internal constant INCREASE_LIQUIDITY = 0x00;
uint256 internal constant DECREASE_LIQUIDITY = 0x01;
uint256 internal constant MINT_POSITION = 0x02;
uint256 internal constant BURN_POSITION = 0x03;
uint256 internal constant INCREASE_LIQUIDITY_FROM_DELTAS = 0x04;
uint256 internal constant MINT_POSITION_FROM_DELTAS = 0x05;
// swapping
uint256 internal constant SWAP_EXACT_IN_SINGLE = 0x06;
uint256 internal constant SWAP_EXACT_IN = 0x07;
uint256 internal constant SWAP_EXACT_OUT_SINGLE = 0x08;
uint256 internal constant SWAP_EXACT_OUT = 0x09;
// donate
// note this is not supported in the position manager or router
uint256 internal constant DONATE = 0x0a;
// closing deltas on the pool manager
// settling
uint256 internal constant SETTLE = 0x0b;
uint256 internal constant SETTLE_ALL = 0x0c;
uint256 internal constant SETTLE_PAIR = 0x0d;
// taking
uint256 internal constant TAKE = 0x0e;
uint256 internal constant TAKE_ALL = 0x0f;
uint256 internal constant TAKE_PORTION = 0x10;
uint256 internal constant TAKE_PAIR = 0x11;
uint256 internal constant CLOSE_CURRENCY = 0x12;
uint256 internal constant CLEAR_OR_TAKE = 0x13;
uint256 internal constant SWEEP = 0x14;
uint256 internal constant WRAP = 0x15;
uint256 internal constant UNWRAP = 0x16;
// minting/burning 6909s to close deltas
// note this is not supported in the position manager or router
uint256 internal constant MINT_6909 = 0x17;
uint256 internal constant BURN_6909 = 0x18;
}
IEIP712.sol 6 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IEIP712 {
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
Read Contract
GOVERNANCE 0x14627834 → address
UNDERLYING_TOKEN 0x29db1be6 → address
auction 0x7d9f6db5 → address
auctionFactory 0xabf4fde4 → address
auctionParameters 0x2c9dc6ab → bytes
createOneSidedCurrencyPosition 0xecaf76af → bool
createOneSidedTokenPosition 0x6f0d5e68 → bool
currency 0xe5a6b10f → address
getHookPermissions 0xc4e833ce → tuple
isMigrationApproved 0xf6dbee1e → bool
migrationBlock 0x42162044 → uint64
operator 0x570ca735 → address
poolLPFee 0x4b9bf62b → uint24
poolManager 0xdc4c90d3 → address
poolTickSpacing 0x7164cf9b → int24
positionManager 0x791b98bc → address
positionRecipient 0xaf7a40ca → address
reserveSupply 0x03d41eb6 → uint128
sweepBlock 0xd3decc68 → uint64
token 0xfc0c546a → address
totalSupply 0x18160ddd → uint128
Write Contract 15 functions
These functions modify contract state and require a wallet transaction to execute.
afterAddLiquidity 0xbc29bafc
address sender
tuple key
tuple params
int256 delta
int256 feesAccrued
bytes hookData
returns: bytes4, int256
afterDonate 0x1f29cf9d
address sender
tuple key
uint256 amount0
uint256 amount1
bytes hookData
returns: bytes4
afterInitialize 0xb6d4944a
address sender
tuple key
uint160 sqrtPriceX96
int24 tick
returns: bytes4
afterRemoveLiquidity 0x606e8192
address sender
tuple key
tuple params
int256 delta
int256 feesAccrued
bytes hookData
returns: bytes4, int256
afterSwap 0xc13d1c69
address sender
tuple key
tuple params
int256 delta
bytes hookData
returns: bytes4, int128
approveMigration 0x325564ec
No parameters
beforeAddLiquidity 0xb772b8cc
address sender
tuple key
tuple params
bytes hookData
returns: bytes4
beforeDonate 0xd950bd74
address sender
tuple key
uint256 amount0
uint256 amount1
bytes hookData
returns: bytes4
beforeInitialize 0xebe1cdaf
address sender
tuple key
uint160 sqrtPriceX96
returns: bytes4
beforeRemoveLiquidity 0x5cb32d10
address sender
tuple key
tuple params
bytes hookData
returns: bytes4
beforeSwap 0x468ead2c
address sender
tuple key
tuple params
bytes hookData
returns: bytes4, int256, uint24
migrate 0x8fd3ab80
No parameters
onTokensReceived 0x331f2f65
No parameters
sweepCurrency 0x7c121574
No parameters
sweepToken 0x532cce18
No parameters
Recent Transactions
Transaction index is loading. Only unfinalized transactions are shown while the index starts up.